';
+ $errormessage = get_string('formerror_disallowedtags', 'qtype_ddimageortext', s($allowedtags));
} else {
$allowedtags = '';
$errormessage = get_string('formerror_noallowedtags', 'qtype_ddimageortext');
diff --git a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php
index 5acf026ce7f..4143d01f86d 100644
--- a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php
+++ b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php
@@ -37,7 +37,7 @@ $string['draggableword'] = 'Draggable text';
$string['dropbackground'] = 'Background image for dragging markers onto';
$string['dropzone'] = 'Drop zone {$a}';
$string['dropzoneheader'] = 'Drop zones';
-$string['formerror_disallowedtags'] = 'Sorry, HTML tags are not allowed in draggable text.';
+$string['formerror_disallowedtags'] = 'Only "{$a}" tags are allowed in this draggable text.';
$string['formerror_noallowedtags'] = 'HTML tags are not allowed in this text which is the alt text for a draggable image.';
$string['formerror_noytop'] = 'You must provide a value for the y coordinate for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.';
$string['formerror_noxleft'] = 'You must provide a value for the x coordinate for the top left corner of this drop area. You can drag and drop the drop area above to set the coordinates or enter them manually here.';
diff --git a/question/type/ddimageortext/tests/edit_form_test.php b/question/type/ddimageortext/tests/edit_form_test.php
new file mode 100644
index 00000000000..4e7b2245611
--- /dev/null
+++ b/question/type/ddimageortext/tests/edit_form_test.php
@@ -0,0 +1,108 @@
+.
+
+/**
+ * Unit tests for the drag-and-drop onto image edit form.
+ *
+ * @package qtype_ddimageortext
+ * @copyright 2019 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+require_once($CFG->dirroot . '/question/type/edit_question_form.php');
+require_once($CFG->dirroot . '/question/type/ddimageortext/edit_ddimageortext_form.php');
+
+/**
+ * Unit tests for the drag-and-drop onto image edit form.
+ *
+ * @copyright 2019 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddimageortext_edit_form_test extends advanced_testcase {
+ /**
+ * Helper method.
+ *
+ * @return array with two elements:
+ * question_edit_form great a question form instance that can be tested.
+ * stdClass the question category.
+ */
+ protected function get_form() {
+ $this->setAdminUser();
+ $this->resetAfterTest();
+
+ $syscontext = context_system::instance();
+ $category = question_make_default_categories(array($syscontext));
+ $fakequestion = new stdClass();
+ $fakequestion->qtype = 'ddimageortext';
+ $fakequestion->contextid = $syscontext->id;
+ $fakequestion->createdby = 2;
+ $fakequestion->category = $category->id;
+ $fakequestion->questiontext = 'Test question';
+ $fakequestion->options = new stdClass();
+ $fakequestion->options->answers = array();
+ $fakequestion->formoptions = new stdClass();
+ $fakequestion->formoptions->movecontext = null;
+ $fakequestion->formoptions->repeatelements = true;
+ $fakequestion->inputs = null;
+
+ $form = new qtype_ddimageortext_edit_form(new moodle_url('/'), $fakequestion, $category,
+ new question_edit_contexts($syscontext));
+
+ return [$form, $category];
+ }
+
+ /**
+ * Test the form correctly validates the HTML allowed in items.
+ */
+ public function test_item_validation() {
+ list($form, $category) = $this->get_form();
+
+ $submitteddata = [
+ 'category' => $category->id,
+ 'bgimage' => '',
+ 'nodropzone' => 0,
+ 'noitems' => 5,
+ 'drags' => [
+ ['dragitemtype' => 'image'],
+ ['dragitemtype' => 'image'],
+ ['dragitemtype' => 'word'],
+ ['dragitemtype' => 'word'],
+ ['dragitemtype' => 'word'],
+ ],
+ 'draglabel' => [
+ 'frog',
+ 'toad',
+ 'cat',
+ 'chien',
+ '',
+ ],
+ ];
+
+ $errors = $form->validation($submitteddata, []);
+
+ $this->assertArrayNotHasKey('drags[0]', $errors);
+ $this->assertEquals('HTML tags are not allowed in this text which is the alt text for a draggable image.',
+ $errors['drags[1]']);
+ $this->assertArrayNotHasKey('drags[2]', $errors);
+ $this->assertArrayNotHasKey('drags[3]', $errors);
+ $this->assertEquals('Only "<br><sub><sup><b><i><strong><em><span>" ' .
+ 'tags are allowed in this draggable text.', $errors['drags[4]']);
+ }
+}
diff --git a/question/type/ddimageortext/tests/helper.php b/question/type/ddimageortext/tests/helper.php
index 62d00d5b0cc..718469f67f7 100644
--- a/question/type/ddimageortext/tests/helper.php
+++ b/question/type/ddimageortext/tests/helper.php
@@ -34,7 +34,7 @@ defined('MOODLE_INTERNAL') || die();
*/
class qtype_ddimageortext_test_helper extends question_test_helper {
public function get_test_questions() {
- return array('fox', 'maths', 'xsection');
+ return array('fox', 'maths', 'xsection', 'mixedlang');
}
/**
@@ -248,4 +248,39 @@ class qtype_ddimageortext_test_helper extends question_test_helper {
return $fromform;
}
+
+ /**
+ * Make a test question where the drag items are a different language than the main question text.
+ *
+ * @return qtype_ddimageortext_question
+ */
+ public function make_ddimageortext_question_mixedlang() {
+ question_bank::load_question_definition_classes('ddimageortext');
+ $dd = new qtype_ddimageortext_question();
+
+ test_question_maker::initialise_a_question($dd);
+
+ $dd->name = 'Question about French in English.';
+ $dd->questiontext = 'Complete the blanks in this sentence.
' .
+ 'J\'ai perdu [[1]] plume de [[2]] tante - l\'avez-vous vue?
';
+ $dd->generalfeedback = 'This sentence uses each letter of the alphabet.';
+ $dd->qtype = question_bank::get_qtype('ddimageortext');
+
+ $dd->shufflechoices = true;
+
+ test_question_maker::set_standard_combined_feedback_fields($dd);
+
+ $dd->choices = $this->make_choice_structure(array(
+ new qtype_ddimageortext_drag_item('la', 1, 1),
+ new qtype_ddimageortext_drag_item('ma', 2, 1),
+ ));
+
+ $dd->places = $this->make_place_structure(array(
+ new qtype_ddimageortext_drop_zone('', 1, 1),
+ new qtype_ddimageortext_drop_zone('', 2, 1)
+ ));
+ $dd->rightchoices = array(1 => 1, 2 => 2);
+
+ return $dd;
+ }
}
diff --git a/question/type/ddimageortext/tests/walkthrough_test.php b/question/type/ddimageortext/tests/walkthrough_test.php
index e9cad591782..b71e4afb4f9 100644
--- a/question/type/ddimageortext/tests/walkthrough_test.php
+++ b/question/type/ddimageortext/tests/walkthrough_test.php
@@ -854,4 +854,20 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
$this->check_current_state(question_state::$gradedright);
$this->check_current_mark(3);
}
+
+ public function test_mixed_lang_rendering() {
+
+ // Create a mixe drag-and-drop question.
+ $dd = test_question_maker::make_question('ddimageortext', 'mixedlang');
+ $dd->shufflechoices = false;
+ $this->start_attempt_at_question($dd, 'interactive', 1);
+
+ // Check the initial state.
+ $this->check_current_state(question_state::$todo);
+ $this->check_current_mark(null);
+ $this->check_current_output(
+ new question_pattern_expectation('~la
~'),
+ new question_pattern_expectation('~ma
~')
+ );
+ }
}
diff --git a/question/type/ddmarker/edit_ddmarker_form.php b/question/type/ddmarker/edit_ddmarker_form.php
index fede3d401b6..f42bd857786 100644
--- a/question/type/ddmarker/edit_ddmarker_form.php
+++ b/question/type/ddmarker/edit_ddmarker_form.php
@@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/question/type/ddimageortext/edit_ddtoimage_form_base.php');
require_once($CFG->dirroot.'/question/type/ddmarker/shapes.php');
-define('QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER', '
');
+define('QTYPE_DDMARKER_ALLOWED_TAGS_IN_MARKER', '
');
/**
diff --git a/question/type/ddmarker/tests/edit_form_test.php b/question/type/ddmarker/tests/edit_form_test.php
new file mode 100644
index 00000000000..45032c54887
--- /dev/null
+++ b/question/type/ddmarker/tests/edit_form_test.php
@@ -0,0 +1,99 @@
+.
+
+/**
+ * Unit tests for the drag-and-drop markers edit form.
+ *
+ * @package qtype_ddmarker
+ * @copyright 2019 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+require_once($CFG->dirroot . '/question/type/edit_question_form.php');
+require_once($CFG->dirroot . '/question/type/ddmarker/edit_ddmarker_form.php');
+
+/**
+ * Unit tests for the drag-and-drop markers edit form.
+ *
+ * @copyright 2019 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_ddmarker_edit_form_test extends advanced_testcase {
+ /**
+ * Helper method.
+ *
+ * @return array with two elements:
+ * question_edit_form great a question form instance that can be tested.
+ * stdClass the question category.
+ */
+ protected function get_form() {
+ $this->setAdminUser();
+ $this->resetAfterTest();
+
+ $syscontext = context_system::instance();
+ $category = question_make_default_categories(array($syscontext));
+ $fakequestion = new stdClass();
+ $fakequestion->qtype = 'ddmarker';
+ $fakequestion->contextid = $syscontext->id;
+ $fakequestion->createdby = 2;
+ $fakequestion->category = $category->id;
+ $fakequestion->questiontext = 'Test question';
+ $fakequestion->options = new stdClass();
+ $fakequestion->options->answers = array();
+ $fakequestion->formoptions = new stdClass();
+ $fakequestion->formoptions->movecontext = null;
+ $fakequestion->formoptions->repeatelements = true;
+ $fakequestion->inputs = null;
+
+ $form = new qtype_ddmarker_edit_form(new moodle_url('/'), $fakequestion, $category,
+ new question_edit_contexts($syscontext));
+
+ return [$form, $category];
+ }
+
+ /**
+ * Test the form correctly validates the HTML allowed in items.
+ */
+ public function test_item_validation() {
+ list($form, $category) = $this->get_form();
+
+ $submitteddata = [
+ 'category' => $category->id,
+ 'bgimage' => 0,
+ 'nodropzone' => 0,
+ 'noitems' => 4,
+ 'drags' => [
+ ['label' => 'frog'],
+ ['label' => 'toad'],
+ ['label' => 'chien'],
+ ['label' => ''],
+ ],
+ ];
+
+ $errors = $form->validation($submitteddata, []);
+
+ $this->assertArrayNotHasKey('drags[0]', $errors);
+ $this->assertArrayNotHasKey('drags[1]', $errors);
+ $this->assertArrayNotHasKey('drags[2]', $errors);
+ $this->assertEquals('Only "<br><i><em><b><strong>' .
+ '<sup><sub><u><span>" tags are allowed in the label for a marker.',
+ $errors['drags[3]']);
+ }
+}
diff --git a/question/type/ddwtos/tests/edit_form_test.php b/question/type/ddwtos/tests/edit_form_test.php
index 0e462aa9b5f..e33fb7ae9ed 100644
--- a/question/type/ddwtos/tests/edit_form_test.php
+++ b/question/type/ddwtos/tests/edit_form_test.php
@@ -41,7 +41,9 @@ class qtype_ddwtos_edit_form_test extends advanced_testcase {
*
* @param string $classname the question form class to instantiate.
*
- * @return question_edit_form great a question form instance that can be tested.
+ * @return array with two elements:
+ * question_edit_form great a question form instance that can be tested.
+ * stdClass the question category.
*/
protected function get_form($classname) {
$this->setAdminUser();
@@ -50,7 +52,7 @@ class qtype_ddwtos_edit_form_test extends advanced_testcase {
$syscontext = context_system::instance();
$category = question_make_default_categories(array($syscontext));
$fakequestion = new stdClass();
- $fakequestion->qtype = 'stack';
+ $fakequestion->qtype = 'ddwtos'; // Does not actually matter if this is wrong.
$fakequestion->contextid = $syscontext->id;
$fakequestion->createdby = 2;
$fakequestion->category = $category->id;
@@ -61,15 +63,18 @@ class qtype_ddwtos_edit_form_test extends advanced_testcase {
$fakequestion->formoptions->movecontext = null;
$fakequestion->formoptions->repeatelements = true;
$fakequestion->inputs = null;
- return new $classname(new moodle_url('/'), $fakequestion, $category,
+
+ $form = new $classname(new moodle_url('/'), $fakequestion, $category,
new question_edit_contexts($syscontext));
+
+ return [$form, $category];
}
/**
* Test the form shows the right number of groups of choices.
*/
public function test_number_of_choice_groups() {
- $form = $this->get_form('qtype_ddwtos_edit_form');
+ list($form) = $this->get_form('qtype_ddwtos_edit_form');
// Use reflection to get the protected property we need.
$property = new ReflectionProperty('qtype_ddwtos_edit_form', '_form');
$property->setAccessible(true);
@@ -78,4 +83,32 @@ class qtype_ddwtos_edit_form_test extends advanced_testcase {
$groupoptions = $choices->_elements[1];
$this->assertCount(8, $groupoptions->_options);
}
+
+ /**
+ * Test the form correctly validates the HTML allowed in choices.
+ */
+ public function test_choices_validation() {
+ list($form, $category) = $this->get_form('qtype_ddwtos_edit_form');
+
+ $submitteddata = [
+ 'category' => $category->id,
+ 'questiontext' => ['text' => 'Test [[1]] question [[2]]', 'format' => FORMAT_HTML],
+ 'choices' => [
+ ['answer' => 'frog'],
+ ['answer' => 'toad'],
+ ['answer' => 'chien'],
+ ['answer' => ''],
+ ],
+ ];
+
+ $errors = $form->validation($submitteddata, []);
+
+ $this->assertArrayNotHasKey('choices[0]', $errors);
+ $this->assertArrayNotHasKey('choices[1]', $errors);
+ $this->assertArrayNotHasKey('choices[2]', $errors);
+ $this->assertEquals('<textarea> is not allowed. ' .
+ '(Only <sub>, <sup>, <b>, <i>, ' .
+ '<em>, <strong>, <span> are permitted.)',
+ $errors['choices[3]']);
+ }
}
diff --git a/question/type/gapselect/edit_form_base.php b/question/type/gapselect/edit_form_base.php
index fb861d5306c..fa99533ef47 100644
--- a/question/type/gapselect/edit_form_base.php
+++ b/question/type/gapselect/edit_form_base.php
@@ -40,7 +40,8 @@ class qtype_gapselect_edit_form_base extends question_edit_form {
'b',
'i',
'em',
- 'strong'
+ 'strong',
+ 'span',
);
/** @var string regex to match HTML open tags. */
@@ -55,7 +56,7 @@ class qtype_gapselect_edit_form_base extends question_edit_form {
/**
* Vaidate some input to make sure it does not contain any tags other than
* $this->allowedhtmltags.
- * @param unknown_type $text the input to validate.
+ * @param string $text the input to validate.
* @return string any validation errors.
*/
protected function get_illegal_tag_error($text) {
diff --git a/question/type/gapselect/tests/edit_form_test.php b/question/type/gapselect/tests/edit_form_test.php
index 6c427355d2c..dee9bd51043 100644
--- a/question/type/gapselect/tests/edit_form_test.php
+++ b/question/type/gapselect/tests/edit_form_test.php
@@ -65,7 +65,10 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
*
* @param string $classname the question form class to instantiate.
*
- * @return question_edit_form great a question form instance that can be tested.
+ *
+ * @return array with two elements:
+ * question_edit_form great a question form instance that can be tested.
+ * stdClass the question category.
*/
protected function get_form($classname) {
$this->setAdminUser();
@@ -74,7 +77,7 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
$syscontext = context_system::instance();
$category = question_make_default_categories(array($syscontext));
$fakequestion = new stdClass();
- $fakequestion->qtype = 'stack';
+ $fakequestion->qtype = 'gapselect'; // Does not actually matter if this is wrong.
$fakequestion->contextid = $syscontext->id;
$fakequestion->createdby = 2;
$fakequestion->category = $category->id;
@@ -85,19 +88,22 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
$fakequestion->formoptions->movecontext = null;
$fakequestion->formoptions->repeatelements = true;
$fakequestion->inputs = null;
- return new $classname(new moodle_url('/'), $fakequestion, $category,
+
+ $form = new $classname(new moodle_url('/'), $fakequestion, $category,
new question_edit_contexts($syscontext));
+
+ return [$form, $category];
}
public function test_get_illegal_tag_error() {
- $form = $this->get_form('qtype_gapselect_edit_form_base_testable');
+ list($form) = $this->get_form('qtype_gapselect_edit_form_base_testable');
$this->assertEquals('', $form->get_illegal_tag_error('frog'));
$this->assertEquals('', $form->get_illegal_tag_error('toad'));
$a = new stdClass();
$a->tag = '<ijk>';
- $a->allowed = '<sub>, <sup>, <b>, <i>, <em>, <strong>';
+ $a->allowed = '<sub>, <sup>, <b>, <i>, <em>, <strong>, <span>';
$this->assertEquals(get_string('tagsnotallowed', 'qtype_gapselect', $a), $form->get_illegal_tag_error(''));
$a->tag = '</cat>';
@@ -131,7 +137,7 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
* Test the form shows the right number of groups of choices.
*/
public function test_number_of_choice_groups() {
- $form = $this->get_form('qtype_gapselect_edit_form');
+ list($form) = $this->get_form('qtype_gapselect_edit_form');
// Use reflection to get the protected property we need.
$property = new ReflectionProperty('qtype_gapselect_edit_form', '_form');
$property->setAccessible(true);
@@ -140,4 +146,26 @@ class qtype_gapselect_edit_form_test extends advanced_testcase {
$groupoptions = $choices->_elements[1];
$this->assertCount(20, $groupoptions->_options);
}
+
+ /**
+ * Test the form correctly validates the HTML allowed in choices.
+ */
+ public function test_choices_validation() {
+ list($form, $category) = $this->get_form('qtype_gapselect_edit_form');
+
+ $submitteddata = [
+ 'category' => $category->id,
+ 'questiontext' => ['text' => 'Test [[1]] question [[2]]', 'format' => FORMAT_HTML],
+ 'choices' => [
+ ['answer' => 'frog'],
+ ['answer' => 'toad'],
+ ],
+ ];
+
+ $errors = $form->validation($submitteddata, []);
+
+ $this->assertArrayNotHasKey('choices[0]', $errors);
+ $this->assertEquals('<b> is not allowed. (No HTML is allowed here.)',
+ $errors['choices[1]']);
+ }
}