diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 144fcf6..b162197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: services: postgres: - image: postgres:13 + image: postgres:14 env: POSTGRES_USER: 'postgres' POSTGRES_HOST_AUTH_METHOD: 'trust' diff --git a/backup/moodle2/backup_qtype_crossword_plugin.class.php b/backup/moodle2/backup_qtype_crossword_plugin.class.php index 7eb2146..f59f119 100644 --- a/backup/moodle2/backup_qtype_crossword_plugin.class.php +++ b/backup/moodle2/backup_qtype_crossword_plugin.class.php @@ -40,7 +40,7 @@ protected function define_question_plugin_structure(): backup_plugin_element { // Now create the qtype own structures. // phpcs:disable NormalizedArrays.Arrays.CommaAfterLast.MissingMultiLine $crossword = new backup_nested_element('crossword', ['id'], ['correctfeedback', - 'correctfeedbackformat', 'numrows', 'numcolumns', 'accentgradingtype', 'accentpenalty', + 'correctfeedbackformat', 'numrows', 'numcolumns', 'accentgradingtype', 'accentpenalty', 'quotematching', 'partiallycorrectfeedback', 'partiallycorrectfeedbackformat', 'incorrectfeedback', 'incorrectfeedbackformat', 'shownumcorrect']); // phpcs:enable @@ -60,6 +60,7 @@ protected function define_question_plugin_structure(): backup_plugin_element { return $plugin; } + #[\Override] public static function get_qtype_fileareas() { return [ 'clue' => 'qtype_crossword_words', diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php index 5b6c4e6..f26f435 100644 --- a/classes/privacy/provider.php +++ b/classes/privacy/provider.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Privacy Subsystem implementation for qtype_crossword. * diff --git a/classes/util.php b/classes/util.php index 67ae068..bc7f16c 100644 --- a/classes/util.php +++ b/classes/util.php @@ -52,7 +52,7 @@ public static function safe_normalize(string $string, int $normalizeform = Norma /** * Remove the work-break characters '-' and ' ' from an answer. * - * @param string Full answer. + * @param string $text Full answer. * @return string Answer with just the letters remaining. */ public static function remove_break_characters(string $text): string { diff --git a/db/install.xml b/db/install.xml index b87bdba..73934ab 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -19,6 +19,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index 1a8828b..a6c997b 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -134,5 +134,20 @@ function xmldb_qtype_crossword_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2023032901, 'qtype', 'crossword'); } + if ($oldversion < 2025010700) { + + // Define field quotematching to be added to qtype_crossword_options. + $table = new xmldb_table('qtype_crossword_options'); + $field = new xmldb_field('quotematching', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'shownumcorrect'); + + // Conditionally launch add field quotematching. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Crossword savepoint reached. + upgrade_plugin_savepoint(true, 2025010700, 'qtype', 'crossword'); + } + return true; } diff --git a/edit_crossword_form.php b/edit_crossword_form.php index 3d3f088..455dd6f 100644 --- a/edit_crossword_form.php +++ b/edit_crossword_form.php @@ -39,6 +39,7 @@ class qtype_crossword_edit_form extends question_edit_form { /** @var array The grid options. */ protected $gridoptions; + #[\Override] protected function definition_inner($mform): void { // Set grid options. $this->gridoptions = range(3, 30); @@ -69,6 +70,7 @@ protected function definition_inner($mform): void { $this->add_interactive_settings(true, true); } + #[\Override] protected function get_per_answer_fields($mform, $label, $gradeoptions, &$repeatedoptions, &$wordsoptions): array { $repeated = []; @@ -110,6 +112,7 @@ protected function get_per_answer_fields($mform, $label, $gradeoptions, return $repeated; } + #[\Override] protected function add_per_answer_fields(&$mform, $label, $gradeoptions, $minoptions = QUESTION_NUMANS_START, $addoptions = QUESTION_NUMANS_ADD) { $mform->addElement('header', 'words', @@ -131,6 +134,7 @@ protected function add_per_answer_fields(&$mform, $label, $gradeoptions, $this->get_more_choices_string(), true); } + #[\Override] protected function get_more_choices_string() { return get_string('addmorewordblanks', 'qtype_crossword'); } @@ -189,6 +193,13 @@ protected function add_question_section(MoodleQuickForm $mform): void { $optionsaccented); $mform->setDefault('accentgradingtype', $this->get_default_value('accentgradingtype', qtype_crossword::ACCENT_GRADING_STRICT)); + + $mform->addElement('select', 'quotematching', get_string('smart_straight_quote_matching', 'qtype_crossword'), [ + get_string('smart_straight_quote_matching_relaxed', 'qtype_crossword'), + get_string('smart_straight_quote_matching_strict', 'qtype_crossword'), + ]); + $mform->setDefault('quotematching', 0); + $penaltyoptions = question_bank::fraction_options(); // Remove None and 100%. unset($penaltyoptions['0.0']); @@ -234,6 +245,7 @@ protected function add_coordinates_input(MoodleQuickForm $mform): array { return $repeated; } + #[\Override] protected function data_preprocessing($question): stdClass { $question = parent::data_preprocessing($question); $question = $this->data_preprocessing_combined_feedback($question, true); @@ -313,6 +325,7 @@ protected function data_preprocessing_words(stdClass $question): stdClass { return $question; } + #[\Override] public function validation($data, $files): array { $errors = parent::validation($data, $files); $answercount = 0; diff --git a/lang/en/qtype_crossword.php b/lang/en/qtype_crossword.php index e381478..4231174 100644 --- a/lang/en/qtype_crossword.php +++ b/lang/en/qtype_crossword.php @@ -54,6 +54,9 @@ $string['preview'] = 'Preview'; $string['privacy:metadata'] = 'The Crossword plugin does not store any personal data.'; $string['refresh'] = 'Refresh preview'; +$string['smart_straight_quote_matching'] = 'Smart and straight quote matching'; +$string['smart_straight_quote_matching_relaxed'] = 'Relaxed: ` \' and \' are interchangeable (default)'; +$string['smart_straight_quote_matching_strict'] = 'Strict: ` \' and \' all different'; $string['startcolumn'] = 'Column index'; $string['startrow'] = 'Row index'; $string['updateform'] = 'Update the form'; @@ -61,8 +64,8 @@

Add your words and clues using the text fields. If you want a specific word fixed on the grid, tick \'Fix word on grid\' and specify its orientation and placement.

Most characters are supported in this question type, from A-Z, 0-9, diacritics and currency symbols etc. Any curly quotation marks or apostrophes will be converted or interpreted as \'straight\' versions for ease of input and auto-marking.

Add more words by selecting the \'Blanks for 3 more words\' button. Any blank words will be removed when the question is saved.

'; -$string['wordno'] = 'Word {$a}'; $string['wordlabel'] = 'W{$a->number}{$a->orientation}'; +$string['wordno'] = 'Word {$a}'; $string['words'] = 'Words'; $string['words_help'] = 'Please set at least one word and its matching clue, and define its direction and start position. Remember that the words are numbered in the grid according to their order in this section.'; $string['wrongadjacentcharacter'] = 'Two or more consecutive new word breaks detected. Please use a maximum of one between individual words. Note that this does not limit the number of new words in the answer itself.'; diff --git a/question.php b/question.php index ecf572b..ae7baf1 100644 --- a/question.php +++ b/question.php @@ -66,6 +66,9 @@ class qtype_crossword_question extends question_graded_automatically { /** @var int format of $incorrectfeedback. */ public $incorrectfeedbackformat; + /** @var bool Whether smart and straight quotes are matched strictly or relaxed. */ + public $quotematching; + /** * Answer field name. * @@ -76,6 +79,7 @@ protected function field(int $key): string { return 'sub' . $key; } + #[\Override] public function get_expected_data(): array { $response = []; for ($i = 0; $i < count($this->answers); $i++) { @@ -84,6 +88,7 @@ public function get_expected_data(): array { return $response; } + #[\Override] public function get_correct_response(): ?array { $response = []; foreach ($this->answers as $key => $answer) { @@ -92,6 +97,7 @@ public function get_correct_response(): ?array { return $response; } + #[\Override] public function summarise_response(array $response): ?string { $responsewords = []; foreach ($this->answers as $key => $answer) { @@ -110,16 +116,19 @@ public function summarise_response(array $response): ?string { return implode('; ', $responsewords); } + #[\Override] public function is_complete_response(array $response): bool { $filteredresponse = $this->remove_blank_words_from_response($response); return count($this->answers) === count($filteredresponse); } + #[\Override] public function is_gradable_response(array $response): bool { $filteredresponse = $this->remove_blank_words_from_response($response); return count($filteredresponse) > 0; } + #[\Override] public function get_validation_error(array $response): string { if ($this->is_complete_response($response)) { return ''; @@ -127,6 +136,7 @@ public function get_validation_error(array $response): string { return get_string('pleaseananswerallparts', 'qtype_crossword'); } + #[\Override] public function is_same_response(array $prevresponse, array $newresponse): bool { foreach ($this->answers as $key => $notused) { $fieldname = $this->field($key); @@ -138,6 +148,7 @@ public function is_same_response(array $prevresponse, array $newresponse): bool return true; } + #[\Override] public function grade_response(array $response): array { // Retrieve a number of right answers and total answers. [$numrightparts, $total] = $this->get_num_parts_right($response); @@ -150,6 +161,7 @@ public function grade_response(array $response): array { return [$fraction, question_state::graded_state_for_fraction($fraction)]; } + #[\Override] public function get_num_parts_right(array $response): array { $numright = 0; foreach ($this->answers as $key => $answer) { @@ -177,6 +189,7 @@ public function get_num_parts_partial(array $response): int { return $numpartial; } + #[\Override] public function clear_wrong_from_response(array $response): array { foreach ($this->answers as $key => $answer) { if (isset($response[$this->field($key)]) && !$this->is_full_fraction($answer, $response[$this->field($key)])) { @@ -249,6 +262,7 @@ private function remove_blank_words_from_response(array $response): array { }); } + #[\Override] public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) { diff --git a/questiontype.php b/questiontype.php index a5c9b06..4bb74cf 100644 --- a/questiontype.php +++ b/questiontype.php @@ -50,6 +50,7 @@ class qtype_crossword extends question_type { /** @var string Accents errors are allowed and the points will not be deducted. */ const ACCENT_GRADING_IGNORE = 'ignore'; + #[\Override] public function get_question_options($question): bool { global $DB; parent::get_question_options($question); @@ -85,6 +86,7 @@ protected function create_default_options($question): object { $options->incorrectfeedback = get_string('incorrectfeedbackdefault', 'question'); $options->incorrectfeedbackformat = FORMAT_HTML; $options->shownumcorrect = 1; + $options->quotematching = 0; $options->numrows = 10; $options->numcolumns = 10; $options->accentgradingtype = self::ACCENT_GRADING_STRICT; @@ -92,6 +94,32 @@ protected function create_default_options($question): object { return $options; } + /** + * Convert smart quotes to straight quotes, handling recursion for arrays. + * + * @param mixed $input Form input data can be a string / number / array. + * @return mixed + */ + public function convert_quote_to_straight_quote(mixed $input): mixed { + if (is_array($input)) { + // If input is an array, process each element recursively. + foreach ($input as $key => $subvalue) { + $input[$key] = $this->convert_quote_to_straight_quote($subvalue); + } + } else if (is_string($input)) { + // If input is a string, convert quotes. + // Replace smart quotes with straight quotes. + $input = str_replace( + ['‘', '’', '“', '”', '‘', '’', '“', '”'], // HTML entities and smart quotes. + ["'", "'", '"', '"', "'", "'", '"', '"'], // Corresponding straight quotes. + $input + ); + } + + return $input; + } + + #[\Override] public function save_question($question, $form) { // For MVP version, default mark will be set automatically. $marks = 0; @@ -102,9 +130,17 @@ public function save_question($question, $form) { $marks++; } $form->defaultmark = $marks; + if (!$form->quotematching) { + foreach ($form as $property => $value) { + if (isset($value)) { + $form->{$property} = $this->convert_quote_to_straight_quote($value); + } + } + } return parent::save_question($question, $form); } + #[\Override] public function save_question_options($question) { global $DB; $context = $question->context; @@ -183,6 +219,7 @@ public function save_question_options($question) { $options->correctfeedback = ''; $options->partiallycorrectfeedback = ''; $options->incorrectfeedback = ''; + $options->quotematching = 0; $options->numrows = 10; $options->numcolumns = 10; $options->accentgradingtype = self::ACCENT_GRADING_STRICT; @@ -190,6 +227,7 @@ public function save_question_options($question) { $options->id = $DB->insert_record('qtype_crossword_options', $options); } + $options->quotematching = $question->quotematching; $options->numrows = $question->numrows; $options->numcolumns = $question->numcolumns; $options->accentgradingtype = $question->accentgradingtype; @@ -199,6 +237,7 @@ public function save_question_options($question) { $this->save_hints($question, true); } + #[\Override] public function delete_question($questionid, $contextid) { global $DB; $DB->delete_records('qtype_crossword_options', ['questionid' => $questionid]); @@ -206,10 +245,12 @@ public function delete_question($questionid, $contextid) { parent::delete_question($questionid, $contextid); } + #[\Override] protected function make_hint($hint) { return question_hint_with_parts::load_from_record($hint); } + #[\Override] protected function initialise_question_instance($question, $questiondata) { parent::initialise_question_instance($question, $questiondata); $this->initialise_combined_feedback($question, $questiondata, true); @@ -233,12 +274,14 @@ protected function initialise_question_instance($question, $questiondata) { // Based on the given list of answers, we will create list of answer objects, // each containing an 'answer number'. $question->answers = util::update_answer_list($answers); + $question->quotematching = $questiondata->options->quotematching; $question->numrows = (int) $questiondata->options->numrows; $question->numcolumns = (int) $questiondata->options->numcolumns; $question->accentgradingtype = $questiondata->options->accentgradingtype; $question->accentpenalty = (float) $questiondata->options->accentpenalty; } + #[\Override] public function export_to_xml($question, qformat_xml $format, $extra = null): string { $expout = parent::export_to_xml($question, $format, $extra); $expout .= ' ' . $format->xml_escape($question->options->numrows) . "\n"; @@ -247,6 +290,8 @@ public function export_to_xml($question, qformat_xml $format, $extra = null): st . "\n"; $expout .= ' ' . $format->xml_escape($question->options->accentpenalty) . "\n"; + $expout .= ' ' . $format->xml_escape($question->options->quotematching) + . "\n"; $fs = get_file_storage(); foreach ($question->options->words as $word => $value) { $expout .= " \n"; @@ -275,6 +320,7 @@ public function export_to_xml($question, qformat_xml $format, $extra = null): st return $expout; } + #[\Override] public function import_from_xml($data, $question, qformat_xml $format, $extra = null): ?object { if (!isset($data['#']['word'])) { return null; @@ -285,6 +331,7 @@ public function import_from_xml($data, $question, qformat_xml $format, $extra = $question->numcolumns = $format->getpath($data, ['#', 'numcolumns', 0, '#'], '', true); $question->accentgradingtype = $format->getpath($data, ['#', 'accentgradingtype', 0, '#'], '', true); $question->accentpenalty = $format->getpath($data, ['#', 'accentpenalty', 0, '#'], '', true); + $question->quotematching = $format->getpath($data, ['#', 'quotematching', 0, '#'], '', true); foreach ($data['#']['word'] as $word) { foreach (self::WORD_FIELDS as $field) { if ($field === 'clue' || $field === 'feedback') { @@ -302,6 +349,7 @@ public function import_from_xml($data, $question, qformat_xml $format, $extra = return $question; } + #[\Override] public function move_files($questionid, $oldcontextid, $newcontextid) { global $DB; $fs = get_file_storage(); @@ -321,6 +369,7 @@ public function move_files($questionid, $oldcontextid, $newcontextid) { $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid); } + #[\Override] protected function delete_files($questionid, $contextid) { global $DB; $fs = get_file_storage(); diff --git a/renderer.php b/renderer.php index 8445c8f..14e22d6 100644 --- a/renderer.php +++ b/renderer.php @@ -31,6 +31,7 @@ */ class qtype_crossword_renderer extends qtype_with_combined_feedback_renderer { + #[\Override] public function formulation_and_controls(question_attempt $qa, question_display_options $options): string { /** @var qtype_crossword_question $question */ @@ -125,10 +126,12 @@ public function formulation_and_controls(question_attempt $qa, return $result; } + #[\Override] public function specific_feedback(question_attempt $qa): string { return $this->combined_feedback($qa); } + #[\Override] public function correct_response(question_attempt $qa): string { /** @var qtype_crossword_question $question */ $question = $qa->get_question(); @@ -136,6 +139,7 @@ public function correct_response(question_attempt $qa): string { $question->summarise_response($question->get_correct_response())); } + #[\Override] protected function num_parts_correct(question_attempt $qa): ?string { $a = new stdClass(); [$a->num, $a->outof] = $qa->get_question()->get_num_parts_right($qa->get_last_qt_data()); diff --git a/tests/backup_test.php b/tests/backup_test.php index 306cb33..ec4bde7 100644 --- a/tests/backup_test.php +++ b/tests/backup_test.php @@ -42,6 +42,7 @@ final class backup_test extends \restore_date_testcase { public static function setUpBeforeClass(): void { global $CFG; require_once("{$CFG->dirroot}/backup/util/includes/restore_includes.php"); + parent::setUpBeforeClass(); } /** diff --git a/tests/behat/edit.feature b/tests/behat/edit.feature index f081c8d..f22ee85 100644 --- a/tests/behat/edit.feature +++ b/tests/behat/edit.feature @@ -44,13 +44,19 @@ Feature: Test editing a Crossword question Scenario: Editing Crossword question will change the accented setting to accept wrong accent but subtracts points. When I am on the "crossword-001" "core_question > edit" page logged in as teacher And I set the following fields to these values: - | Accented letters | penalty | - | Grade for answers with incorrect accents | 0.5 | + | Accented letters | penalty | + | Grade for answers with incorrect accents | 0.5 | + | Smart and straight quote matching | Relaxed: ` ' and ' are interchangeable (default) | + | For any correct response | “Correct feedback” | + | For any partially correct response | ‘Partially correct feedback.’ | And I press "id_submitbutton" And I choose "Edit question" action for "crossword-001" in the question bank Then the following fields match these values: - | Accented letters | penalty | - | Grade for answers with incorrect accents | 0.5 | + | Accented letters | penalty | + | Grade for answers with incorrect accents | 0.5 | + | Smart and straight quote matching | Relaxed: ` ' and ' are interchangeable (default) | + | For any correct response | "Correct feedback" | + | For any partially correct response | 'Partially correct feedback.' | Scenario: Editing Crossword question will change the accented setting to accept wrong accent. When I am on the "crossword-001" "core_question > edit" page logged in as teacher diff --git a/tests/fixtures/testquestion.moodle.xml b/tests/fixtures/testquestion.moodle.xml index f14050b..b9b19b1 100644 --- a/tests/fixtures/testquestion.moodle.xml +++ b/tests/fixtures/testquestion.moodle.xml @@ -19,6 +19,7 @@ 0 penalty 0.2 + 0 AAA diff --git a/tests/form_test.php b/tests/form_test.php index cb39b16..118bc64 100644 --- a/tests/form_test.php +++ b/tests/form_test.php @@ -230,6 +230,7 @@ public function test_form_validation(array $sampledata, array $expectederror): v 'format' => 1, ], 'shownumcorrect' => 1, + 'quotematching' => 0, 'incorrectfeedback' => [ 'text' => 'Your answer is incorrect.', 'format' => 1, diff --git a/tests/helper.php b/tests/helper.php index 2e5156f..dbcff5c 100644 --- a/tests/helper.php +++ b/tests/helper.php @@ -77,9 +77,11 @@ public function make_crossword_question_normal() { $cw->numrows = 5; $cw->numcolumns = 7; $cw->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; + $cw->quotematching = 0; $cw->accentpenalty = 0; $cw->qtype = question_bank::get_qtype('crossword'); $cw->generalfeedback = ''; + $cw->quotematching = 0; $answerslist = [ (object) [ 'id' => 1, @@ -173,6 +175,8 @@ public function get_crossword_question_form_data_normal() { $fromform->numcolumns = 7; $fromform->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $fromform->accentpenalty = 0; + $fromform->questiontext = 0; + $fromform->quotematching = 0; return $fromform; } @@ -231,6 +235,7 @@ public function make_crossword_question_unicode() { $cw->numcolumns = 4; $cw->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $cw->accentpenalty = 0; + $cw->quotematching = 0; $cw->qtype = question_bank::get_qtype('crossword'); $answerslist = [ (object) [ @@ -325,6 +330,7 @@ public function get_crossword_question_form_data_unicode() { $fromform->numcolumns = 4; $fromform->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $fromform->accentpenalty = 0; + $fromform->quotematching = 0; return $fromform; } @@ -346,6 +352,7 @@ public function make_crossword_question_different_codepoint() { $cw->numrows = 6; $cw->numcolumns = 6; $cw->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; + $cw->quotematching = 0; $cw->accentpenalty = 0; $cw->qtype = question_bank::get_qtype('crossword'); $answerslist = [ @@ -406,6 +413,7 @@ public function get_crossword_question_form_data_different_codepoint() { $fromform->incorrectfeedback = ['text' => 'Incorrect feedback.', 'format' => FORMAT_HTML]; $fromform->penalty = 1; $fromform->defaultmark = 1; + $fromform->quotematching = 0; $fromform->answer = ['Amélie', 'Amélie']; $fromform->clue = [ [ @@ -496,6 +504,7 @@ public function make_crossword_question_normal_with_hyphen_and_space() { $cw->numcolumns = 17; $cw->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $cw->accentpenalty = 0; + $cw->quotematching = 0; $cw->qtype = question_bank::get_qtype('crossword'); $answerslist = [ (object) [ @@ -590,6 +599,7 @@ public function get_crossword_question_form_data_normal_with_hyphen_and_space() $fromform->numcolumns = 17; $fromform->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $fromform->accentpenalty = 0; + $fromform->quotematching = 0; return $fromform; } @@ -623,6 +633,7 @@ public function make_crossword_question_not_accept_wrong_accents() { $cw->numcolumns = 4; $cw->accentgradingtype = qtype_crossword::ACCENT_GRADING_STRICT; $cw->accentpenalty = 0; + $cw->quotematching = 0; $cw->qtype = question_bank::get_qtype('crossword'); $answerslist = [ (object) [ @@ -693,6 +704,7 @@ public function get_crossword_question_form_data_not_accept_wrong_accents() { 'format' => FORMAT_HTML, ], ]; + $fromform->quotematching = 0; $fromform->orientation = [0, 1]; $fromform->startrow = [0, 0]; $fromform->startcolumn = [0, 2]; diff --git a/tests/question_type_test.php b/tests/question_type_test.php index 5948df6..a7cd834 100644 --- a/tests/question_type_test.php +++ b/tests/question_type_test.php @@ -36,12 +36,15 @@ * @package qtype_crossword * @copyright 2022 The Open University * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * + * @covers \qtype_crossword */ final class question_type_test extends \question_testcase { /** @var \qtype_crossword instance of the question type class to test. */ protected $qtype; protected function setUp(): void { + parent::setUp(); $this->qtype = \question_bank::get_qtype('crossword'); } @@ -88,6 +91,7 @@ public function test_export_to_xml(): void { 'accentgradingtype' => 'penalty', 'accentpenalty' => 0.2, 'shownumcorrect' => 1, + 'quotematching' => 0, 'words' => [ (object)[ 'id' => 1, @@ -170,6 +174,7 @@ public function test_export_to_xml(): void { 0 penalty 0.2 + 0 AAA @@ -239,4 +244,14 @@ public function test_export_to_xml(): void { $this->assertEquals($expectedxml, $xml); } + + public function test_convert_quote_to_straight_quote(): void { + $data = [ + 'arrayelement' => ['hasrecursion' => '‘ single smart quote ’ and “ double smart quote ”'], + 'test' => '‘ HTML entities single quote ’ and “ HTML entities double quote ”', + ]; + $result = $this->qtype->convert_quote_to_straight_quote($data); + $this->assertEquals($result['arrayelement']['hasrecursion'], "' single smart quote ' and " . '" double smart quote "'); + $this->assertEquals($result['test'], "' HTML entities single quote ' and " . '" HTML entities double quote "'); + } } diff --git a/version.php b/version.php index 1008d32..9c9da72 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_crossword'; -$plugin->version = 2023051200; +$plugin->version = 2025010700; $plugin->requires = 2021051700; $plugin->release = 'v1.0.0 for Moodle 3.11+'; $plugin->maturity = MATURITY_STABLE;