diff --git a/lang/en/qtype_stack.php b/lang/en/qtype_stack.php index 620113ddb95..5f107e9007c 100644 --- a/lang/en/qtype_stack.php +++ b/lang/en/qtype_stack.php @@ -1128,6 +1128,7 @@ $string['stackBlock_ascii_answer_format'] = 'The answer attribute of the ASCII block must be in the format \"[ans1,extractor,filter],[ans2,extractor,filter],...\".'; $string['stackBlock_ascii_unknown_param'] = 'Unknown parameter \'{$a}\' for the ASCII block.'; $string['stackBlock_ascii_param'] = 'Valid parameters are: {$a->param}.'; +$string['stackBlock_ascii_incorrect_alignment'] = 'The output must be aligned left or right.'; $string['stackBlock_extractor_targetinput_required'] = 'You must define the targetinput for the extractor block to send the answer to.'; $string['stackBlock_extractor_type_required'] = 'You must define the type of extractor you want to use.'; diff --git a/stack/cas/castext2/blocks/ascii.block.php b/stack/cas/castext2/blocks/ascii.block.php index 9c6fb418ca7..3704af64dd7 100644 --- a/stack/cas/castext2/blocks/ascii.block.php +++ b/stack/cas/castext2/blocks/ascii.block.php @@ -108,6 +108,13 @@ public function compile($format, $options): ?MP_Node { $height = $existsuserheight ? $xpars['height'] : "400px"; $xpars['width'] = $width; $xpars['height'] = $height; + $direction = stack_get_system_direction(); + if (($xpars['align'] ?? null) === 'left') { + $direction = 'ltr'; + } else if (($xpars['align'] ?? null) === 'right') { + $direction = 'rtl'; + } + $xpars['stack-ascii-direction'] = $direction; // Set a title. $xpars['title'] = 'STACK ASCII ///ASCII_COUNT///'; @@ -287,6 +294,12 @@ public function validate( $valid = false; $err[] = stack_string('stackBlock_ascii_underdefined_dimension'); } + if ( + array_key_exists('align', $this->params) && !in_array($this->params['align'], ['left', 'right']) + ) { + $valid = false; + $err[] = stack_string('stackBlock_ascii_incorrect_alignment'); + } // Check that only valid parameters are passed to block header. $valids = null; @@ -294,7 +307,8 @@ public function validate( if ( $key !== 'width' && $key !== 'height' && - $key !== 'aspect-ratio' & + $key !== 'aspect-ratio' && + $key !== 'align' && $key !== 'input' && $key !== 'hidden' ) { @@ -302,7 +316,7 @@ public function validate( $valid = false; if ($valids === null) { $valids = [ - 'width', 'height', 'aspect-ratio', 'input', 'hidden', + 'width', 'height', 'aspect-ratio', 'align', 'input', 'hidden', ]; $err[] = stack_string('stackBlock_ascii_param', [ 'param' => implode(', ', $valids), diff --git a/stack/cas/castext2/blocks/iframe.block.php b/stack/cas/castext2/blocks/iframe.block.php index 2c74bbf7ec8..3e6f0fb5950 100644 --- a/stack/cas/castext2/blocks/iframe.block.php +++ b/stack/cas/castext2/blocks/iframe.block.php @@ -184,8 +184,15 @@ public function postprocess( $code = '' . "\n"; $code .= '' . "\n"; + $directionattribute = ''; + if (isset($parameters['stack-ascii-direction'])) { + $direction = $parameters['stack-ascii-direction']; + if ($direction === 'ltr' || $direction === 'rtl') { + $directionattribute = ' dir="' . $direction . '"'; + } + } $code .= ''; + stack_get_system_language() . '"' . $directionattribute . '>'; // Include a title to help JS debugging. $code .= '' . $title . ''; $code .= $style; diff --git a/styles.css b/styles.css index 83395be659e..893aa396d75 100644 --- a/styles.css +++ b/styles.css @@ -1247,7 +1247,6 @@ table.HELM_table th:last-child { .que.stack .equivinput, .que.stack .varmatrixinput, .que.stack .matrixtable, -.que.stack .freetextinput, /* Question authoring form */ #page-question-type-stack textarea#id_questionvariables, #page-question-type-stack input#id_variantsselectionseed, diff --git a/tests/ascii_block_test.php b/tests/ascii_block_test.php index 04fdb072b0f..880b61b5d7b 100644 --- a/tests/ascii_block_test.php +++ b/tests/ascii_block_test.php @@ -23,6 +23,7 @@ namespace qtype_stack; +use api\util\StackIframeHolder; use castext2_evaluatable; use qtype_stack_testcase; use stack_cas_session2; @@ -60,6 +61,30 @@ private function get_string_items(\MP_List $compiled): array { return $strings; } + /** + * Render an ASCII block and return the generated iframe document. + * @param string $raw + * @return string + */ + private function render_ascii_iframe_content(string $raw): string { + stack_cas_castext2_iframe::register_counter('///IFRAME_COUNT///'); + StackIframeHolder::$islibrary = true; + StackIframeHolder::$iframes = []; + + try { + $at1 = castext2_evaluatable::make_from_source($raw, 'test-case'); + $session = new stack_cas_session2([$at1]); + $session->instantiate(); + $at1->apply_placeholder_holder($at1->get_rendered()); + + $this->assertCount(1, StackIframeHolder::$iframes); + return StackIframeHolder::$iframes[0][1]; + } finally { + StackIframeHolder::$islibrary = false; + StackIframeHolder::$iframes = []; + } + } + public function test_basic_ascii_block(): void { stack_cas_castext2_iframe::register_counter('///IFRAME_COUNT///'); @@ -73,6 +98,32 @@ public function test_basic_ascii_block(): void { $this->assertEquals($expected, $at1->apply_placeholder_holder($at1->get_rendered())); } + public function test_ascii_iframe_document_uses_system_direction(): void { + $iframecontent = $this->render_ascii_iframe_content('[[ascii input="ans1"]][[/ascii]]'); + + $this->assertStringContainsString( + '', + $iframecontent + ); + $this->assertStringContainsString('
assertStringNotContainsString('id="asciiContainerRow" dir=', $iframecontent); + } + + public function test_ascii_align_parameter_overrides_iframe_document_direction(): void { + $leftcontent = $this->render_ascii_iframe_content('[[ascii input="ans1" align="left"]][[/ascii]]'); + $rightcontent = $this->render_ascii_iframe_content('[[ascii input="ans1" align="right"]][[/ascii]]'); + + $this->assertStringContainsString( + '', + $leftcontent + ); + $this->assertStringContainsString( + '', + $rightcontent + ); + } + public function test_ascii_block_with_filter_and_extractor_children(): void { stack_cas_castext2_iframe::register_counter('///IFRAME_COUNT///'); @@ -102,6 +153,7 @@ public function test_ascii_compile_adds_default_filter_and_input_request(): void $xpars = json_decode($compiled->items[1]->value, true); $this->assertEquals('100%', $xpars['width']); $this->assertEquals('400px', $xpars['height']); + $this->assertEquals(stack_get_system_direction(), $xpars['stack-ascii-direction']); $this->assertStringContainsString('STACK ASCII', $xpars['title']); $strings = $this->get_string_items($compiled); @@ -131,6 +183,35 @@ public function test_ascii_compile_without_input_parameter_uses_empty_input_requ $this->assertStringContainsString($expectedlinkcode, $joined); } + public function test_ascii_align_parameter_sets_document_direction_only(): void { + $blockright = new \stack_cas_castext2_ascii(['align' => 'right'], []); + $compiledright = $blockright->compile(null, []); + + $this->assertInstanceOf(\MP_List::class, $compiledright); + + $xparsright = json_decode($compiledright->items[1]->value, true); + $this->assertEquals('rtl', $xparsright['stack-ascii-direction']); + $strings = $this->get_string_items($compiledright); + $joined = implode("\n", $strings); + $this->assertStringContainsString( + '
assertStringNotContainsString('algebraic-right', $joined); + + $blockleft = new \stack_cas_castext2_ascii(['align' => 'left'], []); + $compiledleft = $blockleft->compile(null, []); + $this->assertInstanceOf(\MP_List::class, $compiledleft); + $xparsleft = json_decode($compiledleft->items[1]->value, true); + $this->assertEquals('ltr', $xparsleft['stack-ascii-direction']); + $joinedleft = implode("\n", $this->get_string_items($compiledleft)); + $this->assertStringContainsString( + '
assertStringNotContainsString('algebraic-right', $joinedleft); + } + public function test_ascii_compile_uses_child_filter_and_extractor_operations(): void { $filter = new \stack_cas_castext2_filter([ 'type' => 'markdown', @@ -254,6 +335,22 @@ public function test_ascii_aspect_ratio_dimension_rules(): void { $this->assertEquals(stack_string('stackBlock_ascii_underdefined_dimension'), $atunder->get_errors()); } + public function test_ascii_validate_alignment_parameter(): void { + $validleft = '[[ascii input="ans1" align="left"]][[/ascii]]'; + $validright = '[[ascii input="ans1" align="right"]][[/ascii]]'; + $invalid = '[[ascii input="ans1" align="center"]][[/ascii]]'; + + $atleft = castext2_evaluatable::make_from_source($validleft, 'test-case'); + $this->assertTrue($atleft->get_valid()); + + $atright = castext2_evaluatable::make_from_source($validright, 'test-case'); + $this->assertTrue($atright->get_valid()); + + $atinvalid = castext2_evaluatable::make_from_source($invalid, 'test-case'); + $this->assertFalse($atinvalid->get_valid()); + $this->assertEquals(stack_string('stackBlock_ascii_incorrect_alignment'), $atinvalid->get_errors()); + } + public function test_ascii_unknown_param_rejected(): void { $raw = '[[ascii input="ans1" bad_param="x"]][[/ascii]]'; @@ -262,7 +359,7 @@ public function test_ascii_unknown_param_rejected(): void { $this->assertFalse($at1->get_valid()); $this->assertStringContainsString(stack_string('stackBlock_ascii_unknown_param', 'bad_param'), $at1->get_errors()); $this->assertStringContainsString(stack_string('stackBlock_ascii_param', [ - 'param' => 'width, height, aspect-ratio, input, hidden', + 'param' => 'width, height, aspect-ratio, align, input, hidden', ]), $at1->get_errors()); } } diff --git a/vle_specific.php b/vle_specific.php index 5d725437320..e9c31e519a6 100644 --- a/vle_specific.php +++ b/vle_specific.php @@ -370,3 +370,10 @@ function stack_fetch_included_content(string $url) { function stack_get_system_language(): string { return current_language(); } + +/** + * Fetches the direction of the current VLE UI language. + */ +function stack_get_system_direction(): string { + return function_exists('right_to_left') && right_to_left() ? 'rtl' : 'ltr'; +}