| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 3 | //
 | 
        
           |  |  | 4 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 5 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 6 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 7 | // (at your option) any later version.
 | 
        
           |  |  | 8 | //
 | 
        
           |  |  | 9 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 12 | // GNU General Public License for more details.
 | 
        
           |  |  | 13 | //
 | 
        
           |  |  | 14 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 15 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | use qtype_ordering\question_hint_ordering;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | /**
 | 
        
           |  |  | 20 |  * The ordering question type.
 | 
        
           |  |  | 21 |  *
 | 
        
           |  |  | 22 |  * @package    qtype_ordering
 | 
        
           |  |  | 23 |  * @copyright  2013 Gordon Bateson (gordon.bateson@gmail.com)
 | 
        
           |  |  | 24 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 25 |  */
 | 
        
           |  |  | 26 | class qtype_ordering extends question_type {
 | 
        
           |  |  | 27 |   | 
        
           |  |  | 28 |     /** @var int Number of hints default. */
 | 
        
           |  |  | 29 |     const DEFAULT_NUM_HINTS = 2;
 | 
        
           |  |  | 30 |   | 
        
           | 11 | efrain | 31 |     /**
 | 
        
           |  |  | 32 |      * Determine if the question type can have HTML answers.
 | 
        
           |  |  | 33 |      *
 | 
        
           |  |  | 34 |      * @return bool
 | 
        
           |  |  | 35 |      * @codeCoverageIgnore
 | 
        
           |  |  | 36 |      */
 | 
        
           | 1 | efrain | 37 |     public function has_html_answers(): bool {
 | 
        
           |  |  | 38 |         return true;
 | 
        
           |  |  | 39 |     }
 | 
        
           |  |  | 40 |   | 
        
           |  |  | 41 |     public function extra_question_fields(): array {
 | 
        
           |  |  | 42 |         return [
 | 
        
           |  |  | 43 |             'qtype_ordering_options',
 | 
        
           |  |  | 44 |             'layouttype', 'selecttype', 'selectcount',
 | 
        
           |  |  | 45 |             'gradingtype', 'showgrading', 'numberingstyle',
 | 
        
           |  |  | 46 |         ];
 | 
        
           |  |  | 47 |     }
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 |     protected function initialise_question_instance(question_definition $question, $questiondata): void {
 | 
        
           |  |  | 50 |         global $CFG;
 | 
        
           |  |  | 51 |   | 
        
           |  |  | 52 |         parent::initialise_question_instance($question, $questiondata);
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 |         $question->answers = $questiondata->options->answers;
 | 
        
           |  |  | 55 |         foreach ($question->answers as $answerid => $answer) {
 | 
        
           |  |  | 56 |             $question->answers[$answerid]->md5key = 'ordering_item_' . md5(($CFG->passwordsaltmain ?? '') . $answer->answer);
 | 
        
           |  |  | 57 |         }
 | 
        
           |  |  | 58 |   | 
        
           |  |  | 59 |         $this->initialise_combined_feedback($question, $questiondata, true);
 | 
        
           |  |  | 60 |     }
 | 
        
           |  |  | 61 |   | 
        
           |  |  | 62 |     public function save_defaults_for_new_questions(stdClass $fromform): void {
 | 
        
           |  |  | 63 |         parent::save_defaults_for_new_questions($fromform);
 | 
        
           |  |  | 64 |         $this->set_default_value('layouttype', $fromform->layouttype);
 | 
        
           |  |  | 65 |         $this->set_default_value('selecttype', $fromform->selecttype);
 | 
        
           |  |  | 66 |         $this->set_default_value('selectcount', $fromform->selectcount);
 | 
        
           |  |  | 67 |         $this->set_default_value('gradingtype', $fromform->gradingtype);
 | 
        
           |  |  | 68 |         $this->set_default_value('showgrading', $fromform->showgrading);
 | 
        
           |  |  | 69 |         $this->set_default_value('numberingstyle', $fromform->numberingstyle);
 | 
        
           |  |  | 70 |     }
 | 
        
           |  |  | 71 |   | 
        
           |  |  | 72 |     public function save_question_options($question): bool|stdClass {
 | 
        
           |  |  | 73 |         global $DB;
 | 
        
           |  |  | 74 |   | 
        
           |  |  | 75 |         $result = new stdClass();
 | 
        
           |  |  | 76 |         $context = $question->context;
 | 
        
           |  |  | 77 |   | 
        
           |  |  | 78 |         // Remove empty answers.
 | 
        
           |  |  | 79 |         $question->answer = array_filter($question->answer, [$this, 'is_not_blank']);
 | 
        
           |  |  | 80 |         $question->answer = array_values($question->answer); // Make keys sequential.
 | 
        
           |  |  | 81 |   | 
        
           |  |  | 82 |         // Count how many answers we have.
 | 
        
           |  |  | 83 |         $countanswers = count($question->answer);
 | 
        
           |  |  | 84 |   | 
        
           |  |  | 85 |         // Search/replace strings to reduce simple <p>...</p> to plain text.
 | 
        
           |  |  | 86 |         $psearch = '/^\s*<p>\s*(.*?)(\s*<br\s*\/?>)*\s*<\/p>\s*$/';
 | 
        
           |  |  | 87 |         $preplace = '$1';
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |         // Search/replace strings to standardize vertical align of <img> tags.
 | 
        
           |  |  | 90 |         $imgsearch = '/(<img[^>]*)\bvertical-align:\s*[a-zA-Z0-9_-]+([^>]*>)/';
 | 
        
           |  |  | 91 |         $imgreplace = '$1'.'vertical-align:text-top'.'$2';
 | 
        
           |  |  | 92 |   | 
        
           |  |  | 93 |         // Check at least two answers exist.
 | 
        
           |  |  | 94 |         if ($countanswers < 2) {
 | 
        
           |  |  | 95 |             $result->notice = get_string('notenoughanswers', 'qtype_ordering', '2');
 | 
        
           |  |  | 96 |             return $result;
 | 
        
           |  |  | 97 |         }
 | 
        
           |  |  | 98 |   | 
        
           |  |  | 99 |         $question->feedback = range(1, $countanswers);
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |         if ($answerids = $DB->get_records('question_answers', ['question' => $question->id], 'id ASC', 'id,question')) {
 | 
        
           |  |  | 102 |             $answerids = array_keys($answerids);
 | 
        
           |  |  | 103 |         } else {
 | 
        
           |  |  | 104 |             $answerids = [];
 | 
        
           |  |  | 105 |         }
 | 
        
           |  |  | 106 |   | 
        
           |  |  | 107 |         // Insert all the new answers.
 | 
        
           |  |  | 108 |         foreach ($question->answer as $i => $answer) {
 | 
        
           |  |  | 109 |             $answertext = '';
 | 
        
           |  |  | 110 |             $answerformat = 0;
 | 
        
           |  |  | 111 |             $answeritemid = null;
 | 
        
           |  |  | 112 |   | 
        
           |  |  | 113 |             // Extract $answer fields.
 | 
        
           |  |  | 114 |             if (is_string($answer)) {
 | 
        
           |  |  | 115 |                 // Import from file.
 | 
        
           |  |  | 116 |                 $answertext = $answer;
 | 
        
           |  |  | 117 |             } else if (is_array($answer)) {
 | 
        
           |  |  | 118 |                 // Input from browser.
 | 
        
           |  |  | 119 |                 if (isset($answer['text'])) {
 | 
        
           |  |  | 120 |                     $answertext = $answer['text'];
 | 
        
           |  |  | 121 |                 }
 | 
        
           |  |  | 122 |                 if (isset($answer['format'])) {
 | 
        
           |  |  | 123 |                     $answerformat = $answer['format'];
 | 
        
           |  |  | 124 |                 }
 | 
        
           |  |  | 125 |                 if (isset($answer['itemid'])) {
 | 
        
           |  |  | 126 |                     $answeritemid = $answer['itemid'];
 | 
        
           |  |  | 127 |                 }
 | 
        
           |  |  | 128 |             }
 | 
        
           |  |  | 129 |   | 
        
           |  |  | 130 |             // Reduce simple <p>...</p> to plain text.
 | 
        
           |  |  | 131 |             if (substr_count($answertext, '<p>') == 1) {
 | 
        
           |  |  | 132 |                 $answertext = preg_replace($psearch, $preplace, $answertext);
 | 
        
           |  |  | 133 |             }
 | 
        
           |  |  | 134 |             $answertext = trim($answertext);
 | 
        
           |  |  | 135 |   | 
        
           |  |  | 136 |             // Skip empty answers.
 | 
        
           |  |  | 137 |             if ($answertext == '') {
 | 
        
           |  |  | 138 |                 continue;
 | 
        
           |  |  | 139 |             }
 | 
        
           |  |  | 140 |   | 
        
           |  |  | 141 |             // Standardize vertical align of img tags.
 | 
        
           |  |  | 142 |             $answertext = preg_replace($imgsearch, $imgreplace, $answertext);
 | 
        
           |  |  | 143 |   | 
        
           |  |  | 144 |             // Prepare the $answer object.
 | 
        
           |  |  | 145 |             $answer = (object) [
 | 
        
           |  |  | 146 |                 'question' => $question->id,
 | 
        
           |  |  | 147 |                 'fraction' => ($i + 1), // Start at 1.
 | 
        
           |  |  | 148 |                 'answer' => $answertext,
 | 
        
           |  |  | 149 |                 'answerformat' => $answerformat,
 | 
        
           |  |  | 150 |                 'feedback' => '',
 | 
        
           |  |  | 151 |                 'feedbackformat' => FORMAT_MOODLE,
 | 
        
           |  |  | 152 |             ];
 | 
        
           |  |  | 153 |   | 
        
           |  |  | 154 |             // Add/insert $answer into the database.
 | 
        
           |  |  | 155 |             if ($answer->id = array_shift($answerids)) {
 | 
        
           |  |  | 156 |                 if (!$DB->update_record('question_answers', $answer)) {
 | 
        
           |  |  | 157 |                     $result->error = get_string('cannotupdaterecord', 'error', 'question_answers (id='.$answer->id.')');
 | 
        
           |  |  | 158 |                     return $result;
 | 
        
           |  |  | 159 |                 }
 | 
        
           |  |  | 160 |             } else {
 | 
        
           |  |  | 161 |                 unset($answer->id);
 | 
        
           |  |  | 162 |                 if (!$answer->id = $DB->insert_record('question_answers', $answer)) {
 | 
        
           |  |  | 163 |                     $result->error = get_string('cannotinsertrecord', 'error', 'question_answers');
 | 
        
           |  |  | 164 |                     return $result;
 | 
        
           |  |  | 165 |                 }
 | 
        
           |  |  | 166 |             }
 | 
        
           |  |  | 167 |   | 
        
           |  |  | 168 |             // Copy files across from draft files area.
 | 
        
           |  |  | 169 |             // Note: we must do this AFTER inserting the answer record
 | 
        
           |  |  | 170 |             // because the answer id is used as the file's "itemid".
 | 
        
           |  |  | 171 |             if ($answeritemid) {
 | 
        
           |  |  | 172 |                 $answertext = file_save_draft_area_files($answeritemid, $context->id, 'question', 'answer', $answer->id,
 | 
        
           |  |  | 173 |                     $this->fileoptions, $answertext);
 | 
        
           |  |  | 174 |                 $DB->set_field('question_answers', 'answer', $answertext, ['id' => $answer->id]);
 | 
        
           |  |  | 175 |             }
 | 
        
           |  |  | 176 |         }
 | 
        
           |  |  | 177 |         // Create $options for this ordering question.
 | 
        
           |  |  | 178 |         $options = (object) [
 | 
        
           |  |  | 179 |             'questionid' => $question->id,
 | 
        
           |  |  | 180 |             'layouttype' => $question->layouttype,
 | 
        
           |  |  | 181 |             'selecttype' => $question->selecttype,
 | 
        
           |  |  | 182 |             'selectcount' => $question->selectcount,
 | 
        
           |  |  | 183 |             'gradingtype' => $question->gradingtype,
 | 
        
           |  |  | 184 |             'showgrading' => $question->showgrading,
 | 
        
           |  |  | 185 |             'numberingstyle' => $question->numberingstyle,
 | 
        
           |  |  | 186 |         ];
 | 
        
           |  |  | 187 |         $options = $this->save_combined_feedback_helper($options, $question, $context, true);
 | 
        
           |  |  | 188 |         $this->save_hints($question, true);
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |         // Add/update $options for this ordering question.
 | 
        
           |  |  | 191 |         if ($options->id = $DB->get_field('qtype_ordering_options', 'id', ['questionid' => $question->id])) {
 | 
        
           |  |  | 192 |             if (!$DB->update_record('qtype_ordering_options', $options)) {
 | 
        
           |  |  | 193 |                 $result->error = get_string('cannotupdaterecord', 'error', 'qtype_ordering_options (id='.$options->id.')');
 | 
        
           |  |  | 194 |                 return $result;
 | 
        
           |  |  | 195 |             }
 | 
        
           |  |  | 196 |         } else {
 | 
        
           |  |  | 197 |             unset($options->id);
 | 
        
           |  |  | 198 |             if (!$options->id = $DB->insert_record('qtype_ordering_options', $options)) {
 | 
        
           |  |  | 199 |                 $result->error = get_string('cannotinsertrecord', 'error', 'qtype_ordering_options');
 | 
        
           |  |  | 200 |                 return $result;
 | 
        
           |  |  | 201 |             }
 | 
        
           |  |  | 202 |         }
 | 
        
           |  |  | 203 |   | 
        
           |  |  | 204 |         // Delete old answer records, if any.
 | 
        
           |  |  | 205 |         if (count($answerids)) {
 | 
        
           |  |  | 206 |             $fs = get_file_storage();
 | 
        
           |  |  | 207 |             foreach ($answerids as $answerid) {
 | 
        
           |  |  | 208 |                 $fs->delete_area_files($context->id, 'question', 'answer', $answerid);
 | 
        
           |  |  | 209 |                 $DB->delete_records('question_answers', ['id' => $answerid]);
 | 
        
           |  |  | 210 |             }
 | 
        
           |  |  | 211 |         }
 | 
        
           |  |  | 212 |   | 
        
           |  |  | 213 |         return true;
 | 
        
           |  |  | 214 |     }
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |     protected function count_hints_on_form($formdata, $withparts): int {
 | 
        
           |  |  | 217 |         $numhints = parent::count_hints_on_form($formdata, $withparts);
 | 
        
           |  |  | 218 |   | 
        
           |  |  | 219 |         if (!empty($formdata->hintoptions)) {
 | 
        
           |  |  | 220 |             $numhints = max($numhints, max(array_keys($formdata->hintoptions)) + 1);
 | 
        
           |  |  | 221 |         }
 | 
        
           |  |  | 222 |   | 
        
           |  |  | 223 |         return $numhints;
 | 
        
           |  |  | 224 |     }
 | 
        
           |  |  | 225 |   | 
        
           |  |  | 226 |     protected function is_hint_empty_in_form_data($formdata, $number, $withparts): bool {
 | 
        
           |  |  | 227 |         return parent::is_hint_empty_in_form_data($formdata, $number, $withparts) &&
 | 
        
           |  |  | 228 |             empty($formdata->hintoptions[$number]);
 | 
        
           |  |  | 229 |     }
 | 
        
           |  |  | 230 |   | 
        
           |  |  | 231 |     protected function save_hint_options($formdata, $number, $withparts): bool {
 | 
        
           |  |  | 232 |         return !empty($formdata->hintoptions[$number]);
 | 
        
           |  |  | 233 |     }
 | 
        
           |  |  | 234 |   | 
        
           |  |  | 235 |     protected function make_hint($hint): question_hint_ordering {
 | 
        
           |  |  | 236 |         return question_hint_ordering::load_from_record($hint);
 | 
        
           |  |  | 237 |     }
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 |     public function get_possible_responses($questiondata): array {
 | 
        
           |  |  | 240 |         $responseclasses = [];
 | 
        
           |  |  | 241 |         $itemcount = count($questiondata->options->answers);
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |         $position = 0;
 | 
        
           |  |  | 244 |         foreach ($questiondata->options->answers as $answer) {
 | 
        
           |  |  | 245 |             $position += 1;
 | 
        
           |  |  | 246 |             $classes = [];
 | 
        
           |  |  | 247 |             for ($i = 1; $i <= $itemcount; $i++) {
 | 
        
           |  |  | 248 |                 $classes[$i] = new question_possible_response(
 | 
        
           |  |  | 249 |                     get_string('positionx', 'qtype_ordering', $i),
 | 
        
           |  |  | 250 |                     ($i === $position) / $itemcount);
 | 
        
           |  |  | 251 |             }
 | 
        
           |  |  | 252 |   | 
        
           |  |  | 253 |             $subqid = question_utils::to_plain_text($answer->answer, $answer->answerformat);
 | 
        
           |  |  | 254 |             $subqid = core_text::substr($subqid, 0, 100); // Ensure not more than 100 chars.
 | 
        
           |  |  | 255 |             $responseclasses[$subqid] = $classes;
 | 
        
           |  |  | 256 |         }
 | 
        
           |  |  | 257 |   | 
        
           |  |  | 258 |         return $responseclasses;
 | 
        
           |  |  | 259 |     }
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 |     /**
 | 
        
           |  |  | 262 |      * Callback function for filtering answers with array_filter
 | 
        
           |  |  | 263 |      *
 | 
        
           |  |  | 264 |      * @param mixed $value
 | 
        
           |  |  | 265 |      * @return bool If true, this item should be saved.
 | 
        
           |  |  | 266 |      */
 | 
        
           |  |  | 267 |     public function is_not_blank(mixed $value): bool {
 | 
        
           |  |  | 268 |         if (is_array($value)) {
 | 
        
           |  |  | 269 |             $value = $value['text'];
 | 
        
           |  |  | 270 |         }
 | 
        
           |  |  | 271 |         $value = trim($value);
 | 
        
           |  |  | 272 |         return ($value || $value === '0');
 | 
        
           |  |  | 273 |     }
 | 
        
           |  |  | 274 |   | 
        
           |  |  | 275 |     public function get_question_options($question): bool {
 | 
        
           |  |  | 276 |         global $DB, $OUTPUT;
 | 
        
           |  |  | 277 |   | 
        
           |  |  | 278 |         // Load the options.
 | 
        
           |  |  | 279 |         if (!$question->options = $DB->get_record('qtype_ordering_options', ['questionid' => $question->id])) {
 | 
        
           |  |  | 280 |             echo $OUTPUT->notification('Error: Missing question options!');
 | 
        
           |  |  | 281 |             return false;
 | 
        
           |  |  | 282 |         }
 | 
        
           |  |  | 283 |   | 
        
           |  |  | 284 |         // Load the answers - "fraction" is used to signify the order of the answers,
 | 
        
           |  |  | 285 |         // with id as a tie-break which should not be required.
 | 
        
           |  |  | 286 |         if (!$question->options->answers = $DB->get_records('question_answers',
 | 
        
           |  |  | 287 |                 ['question' => $question->id], 'fraction, id')) {
 | 
        
           |  |  | 288 |             echo $OUTPUT->notification('Error: Missing question answers for ordering question ' . $question->id . '!');
 | 
        
           |  |  | 289 |             return false;
 | 
        
           |  |  | 290 |         }
 | 
        
           |  |  | 291 |   | 
        
           |  |  | 292 |         parent::get_question_options($question);
 | 
        
           |  |  | 293 |         return true;
 | 
        
           |  |  | 294 |     }
 | 
        
           |  |  | 295 |   | 
        
           |  |  | 296 |     public function delete_question($questionid, $contextid): void {
 | 
        
           |  |  | 297 |         global $DB;
 | 
        
           |  |  | 298 |         $DB->delete_records('qtype_ordering_options', ['questionid' => $questionid]);
 | 
        
           |  |  | 299 |         parent::delete_question($questionid, $contextid);
 | 
        
           |  |  | 300 |     }
 | 
        
           |  |  | 301 |   | 
        
           |  |  | 302 |     /**
 | 
        
           |  |  | 303 |      * Import question from GIFT format
 | 
        
           |  |  | 304 |      *
 | 
        
           |  |  | 305 |      * @param array $lines
 | 
        
           |  |  | 306 |      * @param stdClass|null $question
 | 
        
           |  |  | 307 |      * @param qformat_gift $format
 | 
        
           |  |  | 308 |      * @param string|null $extra (optional, default=null)
 | 
        
           |  |  | 309 |      * @return stdClass|bool Question instance
 | 
        
           |  |  | 310 |      */
 | 
        
           | 1441 | ariadna | 311 |     public function import_from_gift(array $lines, ?stdClass $question, qformat_gift $format, ?string $extra = null): bool|stdClass {
 | 
        
           | 1 | efrain | 312 |         global $CFG;
 | 
        
           |  |  | 313 |         require_once($CFG->dirroot.'/question/type/ordering/question.php');
 | 
        
           |  |  | 314 |   | 
        
           |  |  | 315 |         // Extract question info from GIFT file $lines.
 | 
        
           |  |  | 316 |         $selectcount = '\d+';
 | 
        
           |  |  | 317 |         $selecttype  = '(?:ALL|EXACT|'.
 | 
        
           |  |  | 318 |             'RANDOM|REL|'.
 | 
        
           |  |  | 319 |             'CONTIGUOUS|CONTIG)?';
 | 
        
           |  |  | 320 |         $layouttype  = '(?:HORIZONTAL|HORI|H|1|'.
 | 
        
           |  |  | 321 |             'VERTICAL|VERT|V|0)?';
 | 
        
           |  |  | 322 |         $gradingtype = '(?:ALL_OR_NOTHING|'.
 | 
        
           |  |  | 323 |             'ABSOLUTE_POSITION|'.
 | 
        
           |  |  | 324 |             'ABSOLUTE|ABS|'.
 | 
        
           |  |  | 325 |             'RELATIVE_NEXT_EXCLUDE_LAST|'.
 | 
        
           |  |  | 326 |             'RELATIVE_NEXT_INCLUDE_LAST|'.
 | 
        
           |  |  | 327 |             'RELATIVE_ONE_PREVIOUS_AND_NEXT|'.
 | 
        
           |  |  | 328 |             'RELATIVE_ALL_PREVIOUS_AND_NEXT|'.
 | 
        
           |  |  | 329 |             'RELATIVE_TO_CORRECT|'.
 | 
        
           |  |  | 330 |             'RELATIVE|REL'.
 | 
        
           |  |  | 331 |             'LONGEST_ORDERED_SUBSET|'.
 | 
        
           |  |  | 332 |             'LONGEST_CONTIGUOUS_SUBSET)?';
 | 
        
           |  |  | 333 |         $showgrading = '(?:SHOW|TRUE|YES|1|HIDE|FALSE|NO|0)?';
 | 
        
           |  |  | 334 |         $numberingstyle = '(?:none|123|abc|ABCD|iii|IIII)?';
 | 
        
           |  |  | 335 |         $search = '/^\s*>\s*('.$selectcount.')\s*'.
 | 
        
           |  |  | 336 |             '('.$selecttype.')\s*'.
 | 
        
           |  |  | 337 |             '('.$layouttype.')\s*'.
 | 
        
           |  |  | 338 |             '('.$gradingtype.')\s*'.
 | 
        
           |  |  | 339 |             '('.$showgrading.')\s*'.
 | 
        
           |  |  | 340 |             '('.$numberingstyle.')\s*'.
 | 
        
           |  |  | 341 |             '(.*?)\s*$/s';
 | 
        
           |  |  | 342 |         // Item $1 the number of items to be shown.
 | 
        
           |  |  | 343 |         // Item $2 the extraction/grading type.
 | 
        
           |  |  | 344 |         // Item $3 the layout type.
 | 
        
           |  |  | 345 |         // Item $4 the grading type.
 | 
        
           |  |  | 346 |         // Item $5 show the grading details (SHOW/HIDE).
 | 
        
           |  |  | 347 |         // Item $6 the numbering style (none/123/abc/...).
 | 
        
           |  |  | 348 |         // Item $7 the lines of items to be ordered.
 | 
        
           |  |  | 349 |         if (!$extra) {
 | 
        
           |  |  | 350 |             return false; // Format not recognized.
 | 
        
           |  |  | 351 |         }
 | 
        
           |  |  | 352 |         if (!preg_match($search, $extra, $matches)) {
 | 
        
           |  |  | 353 |             return false; // Format not recognized.
 | 
        
           |  |  | 354 |         }
 | 
        
           |  |  | 355 |   | 
        
           |  |  | 356 |         $selectcount = trim($matches[1]);
 | 
        
           |  |  | 357 |         $selecttype = trim($matches[2]);
 | 
        
           |  |  | 358 |         $layouttype = trim($matches[3]);
 | 
        
           |  |  | 359 |         $gradingtype = trim($matches[4]);
 | 
        
           |  |  | 360 |         $showgrading = trim($matches[5]);
 | 
        
           |  |  | 361 |         $numberingstyle = trim($matches[6]);
 | 
        
           |  |  | 362 |   | 
        
           |  |  | 363 |         $answers = preg_split('/[\r\n]+/', $matches[7]);
 | 
        
           |  |  | 364 |         $answers = array_filter($answers);
 | 
        
           |  |  | 365 |   | 
        
           |  |  | 366 |         if (empty($question)) {
 | 
        
           |  |  | 367 |             $text = implode(PHP_EOL, $lines);
 | 
        
           |  |  | 368 |             $text = trim($text);
 | 
        
           |  |  | 369 |             if ($pos = strpos($text, '{')) {
 | 
        
           |  |  | 370 |                 $text = substr($text, 0, $pos);
 | 
        
           |  |  | 371 |             }
 | 
        
           |  |  | 372 |   | 
        
           |  |  | 373 |             // Extract name.
 | 
        
           |  |  | 374 |             $name = false;
 | 
        
           |  |  | 375 |             if (str_starts_with($text, '::')) {
 | 
        
           |  |  | 376 |                 $text = substr($text, 2);
 | 
        
           |  |  | 377 |                 $pos = strpos($text, '::');
 | 
        
           |  |  | 378 |                 if (is_numeric($pos)) {
 | 
        
           |  |  | 379 |                     $name = substr($text, 0, $pos);
 | 
        
           |  |  | 380 |                     $name = $format->clean_question_name($name);
 | 
        
           |  |  | 381 |                     $text = trim(substr($text, $pos + 2));
 | 
        
           |  |  | 382 |                 }
 | 
        
           |  |  | 383 |             }
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |             // Extract question text format.
 | 
        
           |  |  | 386 |             $format = FORMAT_MOODLE;
 | 
        
           |  |  | 387 |             if (str_starts_with($text, '[')) {
 | 
        
           |  |  | 388 |                 $text = substr($text, 1);
 | 
        
           |  |  | 389 |                 $pos = strpos($text, ']');
 | 
        
           |  |  | 390 |                 if (is_numeric($pos)) {
 | 
        
           |  |  | 391 |                     $format = substr($text, 0, $pos);
 | 
        
           |  |  | 392 |                     switch ($format) {
 | 
        
           |  |  | 393 |                         case 'html':
 | 
        
           |  |  | 394 |                             $format = FORMAT_HTML;
 | 
        
           |  |  | 395 |                             break;
 | 
        
           |  |  | 396 |                         case 'plain':
 | 
        
           |  |  | 397 |                             $format = FORMAT_PLAIN;
 | 
        
           |  |  | 398 |                             break;
 | 
        
           |  |  | 399 |                         case 'markdown':
 | 
        
           |  |  | 400 |                             $format = FORMAT_MARKDOWN;
 | 
        
           |  |  | 401 |                             break;
 | 
        
           |  |  | 402 |                         case 'moodle':
 | 
        
           |  |  | 403 |                             $format = FORMAT_MOODLE;
 | 
        
           |  |  | 404 |                             break;
 | 
        
           |  |  | 405 |                     }
 | 
        
           |  |  | 406 |                     $text = trim(substr($text, $pos + 1)); // Remove name from text.
 | 
        
           |  |  | 407 |                 }
 | 
        
           |  |  | 408 |             }
 | 
        
           |  |  | 409 |   | 
        
           |  |  | 410 |             $question = new stdClass();
 | 
        
           |  |  | 411 |             $question->name = $name;
 | 
        
           |  |  | 412 |             $question->questiontext = $text;
 | 
        
           |  |  | 413 |             $question->questiontextformat = $format;
 | 
        
           |  |  | 414 |             $question->generalfeedback = '';
 | 
        
           |  |  | 415 |             $question->generalfeedbackformat = FORMAT_MOODLE;
 | 
        
           |  |  | 416 |         }
 | 
        
           |  |  | 417 |   | 
        
           |  |  | 418 |         $question->qtype = 'ordering';
 | 
        
           |  |  | 419 |   | 
        
           |  |  | 420 |         // Set "selectcount" field from $selectcount.
 | 
        
           |  |  | 421 |         if (is_numeric($selectcount) && $selectcount >= qtype_ordering_question::MIN_SUBSET_ITEMS &&
 | 
        
           |  |  | 422 |                 $selectcount <= count($answers)) {
 | 
        
           |  |  | 423 |             $selectcount = intval($selectcount);
 | 
        
           |  |  | 424 |         } else {
 | 
        
           |  |  | 425 |             $selectcount = min(6, count($answers));
 | 
        
           |  |  | 426 |         }
 | 
        
           |  |  | 427 |         $this->set_options_for_import($question, $layouttype, $selecttype, $selectcount,
 | 
        
           |  |  | 428 |             $gradingtype, $showgrading, $numberingstyle);
 | 
        
           |  |  | 429 |   | 
        
           |  |  | 430 |         // Remove blank items.
 | 
        
           |  |  | 431 |         $answers = array_map('trim', $answers);
 | 
        
           |  |  | 432 |         $answers = array_filter($answers); // Remove blanks.
 | 
        
           |  |  | 433 |   | 
        
           |  |  | 434 |         // Set up answer arrays.
 | 
        
           |  |  | 435 |         $question->answer = [];
 | 
        
           |  |  | 436 |         $question->answerformat = [];
 | 
        
           |  |  | 437 |         $question->fraction = [];
 | 
        
           |  |  | 438 |         $question->feedback = [];
 | 
        
           |  |  | 439 |         $question->feedbackformat = [];
 | 
        
           |  |  | 440 |   | 
        
           |  |  | 441 |         // Note that "fraction" field is used to denote sort order
 | 
        
           |  |  | 442 |         // "fraction" fields will be set to correct values later
 | 
        
           |  |  | 443 |         // in the save_question_options() method of this class.
 | 
        
           |  |  | 444 |   | 
        
           |  |  | 445 |         foreach ($answers as $i => $answer) {
 | 
        
           |  |  | 446 |             $question->answer[$i] = $answer;
 | 
        
           |  |  | 447 |             $question->answerformat[$i] = FORMAT_MOODLE;
 | 
        
           |  |  | 448 |             $question->fraction[$i] = 1; // Will be reset later in save_question_options().
 | 
        
           |  |  | 449 |             $question->feedback[$i] = '';
 | 
        
           |  |  | 450 |             $question->feedbackformat[$i] = FORMAT_MOODLE;
 | 
        
           |  |  | 451 |         }
 | 
        
           |  |  | 452 |   | 
        
           |  |  | 453 |         return $question;
 | 
        
           |  |  | 454 |     }
 | 
        
           |  |  | 455 |   | 
        
           |  |  | 456 |     /**
 | 
        
           |  |  | 457 |      * Given question object, returns array with array layouttype, selecttype, selectcount, gradingtype, showgrading
 | 
        
           |  |  | 458 |      * where layouttype, selecttype, gradingtype and showgrading are string representations.
 | 
        
           |  |  | 459 |      *
 | 
        
           |  |  | 460 |      * @param stdClass $question
 | 
        
           |  |  | 461 |      * @return array(layouttype, selecttype, selectcount, gradingtype, $showgrading, $numberingstyle)
 | 
        
           |  |  | 462 |      */
 | 
        
           |  |  | 463 |     public function extract_options_for_export(stdClass $question): array {
 | 
        
           |  |  | 464 |   | 
        
           | 1441 | ariadna | 465 |         $layouttype = match (intval($question->options->layouttype)) {
 | 
        
           | 1 | efrain | 466 |             qtype_ordering_question::LAYOUT_VERTICAL => 'VERTICAL',
 | 
        
           |  |  | 467 |             qtype_ordering_question::LAYOUT_HORIZONTAL => 'HORIZONTAL',
 | 
        
           |  |  | 468 |             default => '', // Shouldn't happen !!
 | 
        
           |  |  | 469 |         };
 | 
        
           |  |  | 470 |   | 
        
           | 1441 | ariadna | 471 |         $selecttype = match (intval($question->options->selecttype)) {
 | 
        
           | 1 | efrain | 472 |             qtype_ordering_question::SELECT_ALL => 'ALL',
 | 
        
           |  |  | 473 |             qtype_ordering_question::SELECT_RANDOM => 'RANDOM',
 | 
        
           |  |  | 474 |             qtype_ordering_question::SELECT_CONTIGUOUS => 'CONTIGUOUS',
 | 
        
           |  |  | 475 |             default => '', // Shouldn't happen !!
 | 
        
           |  |  | 476 |         };
 | 
        
           |  |  | 477 |   | 
        
           | 1441 | ariadna | 478 |         $gradingtype = match (intval($question->options->gradingtype)) {
 | 
        
           | 1 | efrain | 479 |             qtype_ordering_question::GRADING_ALL_OR_NOTHING => 'ALL_OR_NOTHING',
 | 
        
           |  |  | 480 |             qtype_ordering_question::GRADING_ABSOLUTE_POSITION => 'ABSOLUTE_POSITION',
 | 
        
           |  |  | 481 |             qtype_ordering_question::GRADING_RELATIVE_NEXT_EXCLUDE_LAST => 'RELATIVE_NEXT_EXCLUDE_LAST',
 | 
        
           |  |  | 482 |             qtype_ordering_question::GRADING_RELATIVE_NEXT_INCLUDE_LAST => 'RELATIVE_NEXT_INCLUDE_LAST',
 | 
        
           |  |  | 483 |             qtype_ordering_question::GRADING_RELATIVE_ONE_PREVIOUS_AND_NEXT => 'RELATIVE_ONE_PREVIOUS_AND_NEXT',
 | 
        
           |  |  | 484 |             qtype_ordering_question::GRADING_RELATIVE_ALL_PREVIOUS_AND_NEXT => 'RELATIVE_ALL_PREVIOUS_AND_NEXT',
 | 
        
           |  |  | 485 |             qtype_ordering_question::GRADING_LONGEST_ORDERED_SUBSET => 'LONGEST_ORDERED_SUBSET',
 | 
        
           |  |  | 486 |             qtype_ordering_question::GRADING_LONGEST_CONTIGUOUS_SUBSET => 'LONGEST_CONTIGUOUS_SUBSET',
 | 
        
           |  |  | 487 |             qtype_ordering_question::GRADING_RELATIVE_TO_CORRECT => 'RELATIVE_TO_CORRECT',
 | 
        
           |  |  | 488 |             default => '', // Shouldn't happen !!
 | 
        
           |  |  | 489 |         };
 | 
        
           |  |  | 490 |   | 
        
           | 1441 | ariadna | 491 |         $showgrading = match (intval($question->options->showgrading)) {
 | 
        
           | 1 | efrain | 492 |   | 
        
           |  |  | 493 |             1 => 'SHOW',
 | 
        
           |  |  | 494 |             default => '', // Shouldn't happen !!
 | 
        
           |  |  | 495 |         };
 | 
        
           |  |  | 496 |   | 
        
           |  |  | 497 |         if (empty($question->options->numberingstyle)) {
 | 
        
           |  |  | 498 |             $numberingstyle = qtype_ordering_question::NUMBERING_STYLE_DEFAULT;
 | 
        
           |  |  | 499 |         } else {
 | 
        
           |  |  | 500 |             $numberingstyle = $question->options->numberingstyle;
 | 
        
           |  |  | 501 |         }
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 |         // Note: this used to be (selectcount + 2).
 | 
        
           |  |  | 504 |         $selectcount = $question->options->selectcount;
 | 
        
           |  |  | 505 |   | 
        
           |  |  | 506 |         return [$layouttype, $selecttype, $selectcount, $gradingtype, $showgrading, $numberingstyle];
 | 
        
           |  |  | 507 |     }
 | 
        
           |  |  | 508 |   | 
        
           |  |  | 509 |     /**
 | 
        
           |  |  | 510 |      * Exports question to GIFT format
 | 
        
           |  |  | 511 |      *
 | 
        
           |  |  | 512 |      * @param stdClass $question
 | 
        
           |  |  | 513 |      * @param qformat_gift $format
 | 
        
           |  |  | 514 |      * @param string|null $extra (optional, default=null)
 | 
        
           |  |  | 515 |      * @return string GIFT representation of question
 | 
        
           |  |  | 516 |      */
 | 
        
           | 1441 | ariadna | 517 |     public function export_to_gift(stdClass $question, qformat_gift $format, ?string $extra = null): string {
 | 
        
           | 1 | efrain | 518 |         global $CFG;
 | 
        
           |  |  | 519 |         require_once($CFG->dirroot.'/question/type/ordering/question.php');
 | 
        
           |  |  | 520 |   | 
        
           |  |  | 521 |         $output = '';
 | 
        
           |  |  | 522 |   | 
        
           |  |  | 523 |         if ($question->name) {
 | 
        
           |  |  | 524 |             $output .= '::'.$question->name.'::';
 | 
        
           |  |  | 525 |         }
 | 
        
           |  |  | 526 |   | 
        
           |  |  | 527 |         $output .= match ($question->questiontextformat) {
 | 
        
           |  |  | 528 |             FORMAT_HTML => '[html]',
 | 
        
           |  |  | 529 |             FORMAT_PLAIN => '[plain]',
 | 
        
           |  |  | 530 |             FORMAT_MARKDOWN => '[markdown]',
 | 
        
           |  |  | 531 |             FORMAT_MOODLE => '[moodle]',
 | 
        
           |  |  | 532 |             default => '',
 | 
        
           |  |  | 533 |         };
 | 
        
           |  |  | 534 |   | 
        
           |  |  | 535 |         $output .= $question->questiontext.'{';
 | 
        
           |  |  | 536 |   | 
        
           |  |  | 537 |         list($layouttype, $selecttype, $selectcount, $gradingtype, $showgrading, $numberingstyle) =
 | 
        
           |  |  | 538 |             $this->extract_options_for_export($question);
 | 
        
           |  |  | 539 |         $output .= ">$selectcount $selecttype $layouttype $gradingtype $showgrading $numberingstyle".PHP_EOL;
 | 
        
           |  |  | 540 |   | 
        
           |  |  | 541 |         foreach ($question->options->answers as $answer) {
 | 
        
           |  |  | 542 |             $output .= $answer->answer.PHP_EOL;
 | 
        
           |  |  | 543 |         }
 | 
        
           |  |  | 544 |   | 
        
           |  |  | 545 |         $output .= '}';
 | 
        
           |  |  | 546 |         return $output;
 | 
        
           |  |  | 547 |     }
 | 
        
           |  |  | 548 |   | 
        
           |  |  | 549 |     public function export_to_xml($question, qformat_xml $format, $extra = null): string {
 | 
        
           |  |  | 550 |         global $CFG;
 | 
        
           |  |  | 551 |         require_once($CFG->dirroot.'/question/type/ordering/question.php');
 | 
        
           |  |  | 552 |   | 
        
           |  |  | 553 |         list($layouttype, $selecttype, $selectcount, $gradingtype, $showgrading, $numberingstyle) =
 | 
        
           |  |  | 554 |             $this->extract_options_for_export($question);
 | 
        
           |  |  | 555 |   | 
        
           |  |  | 556 |         $output = '';
 | 
        
           |  |  | 557 |         $output .= "    <layouttype>$layouttype</layouttype>\n";
 | 
        
           |  |  | 558 |         $output .= "    <selecttype>$selecttype</selecttype>\n";
 | 
        
           |  |  | 559 |         $output .= "    <selectcount>$selectcount</selectcount>\n";
 | 
        
           |  |  | 560 |         $output .= "    <gradingtype>$gradingtype</gradingtype>\n";
 | 
        
           |  |  | 561 |         $output .= "    <showgrading>$showgrading</showgrading>\n";
 | 
        
           |  |  | 562 |         $output .= "    <numberingstyle>$numberingstyle</numberingstyle>\n";
 | 
        
           |  |  | 563 |         $output .= $format->write_combined_feedback($question->options, $question->id, $question->contextid);
 | 
        
           |  |  | 564 |   | 
        
           |  |  | 565 |         $shownumcorrect = $question->options->shownumcorrect;
 | 
        
           |  |  | 566 |         if (!empty($question->options->shownumcorrect)) {
 | 
        
           |  |  | 567 |             $output = str_replace("    <shownumcorrect/>\n", "", $output);
 | 
        
           |  |  | 568 |         }
 | 
        
           |  |  | 569 |         $output .= "    <shownumcorrect>$shownumcorrect</shownumcorrect>\n";
 | 
        
           |  |  | 570 |   | 
        
           |  |  | 571 |         foreach ($question->options->answers as $answer) {
 | 
        
           |  |  | 572 |             $output .= '    <answer fraction="'.$answer->fraction.'" '.$format->format($answer->answerformat).">\n";
 | 
        
           |  |  | 573 |             $output .= $format->writetext($answer->answer, 3);
 | 
        
           |  |  | 574 |             if (trim($answer->feedback)) { // Usually there is no feedback.
 | 
        
           |  |  | 575 |                 $output .= '      <feedback '.$format->format($answer->feedbackformat).">\n";
 | 
        
           |  |  | 576 |                 $output .= $format->writetext($answer->feedback, 4);
 | 
        
           |  |  | 577 |                 $output .= $format->write_files($answer->feedbackfiles);
 | 
        
           |  |  | 578 |                 $output .= "      </feedback>\n";
 | 
        
           |  |  | 579 |             }
 | 
        
           |  |  | 580 |             $output .= "    </answer>\n";
 | 
        
           |  |  | 581 |         }
 | 
        
           |  |  | 582 |   | 
        
           |  |  | 583 |         return $output;
 | 
        
           |  |  | 584 |     }
 | 
        
           |  |  | 585 |   | 
        
           |  |  | 586 |     public function import_from_xml($data, $question, qformat_xml $format, $extra = null): object|bool {
 | 
        
           |  |  | 587 |         global $CFG;
 | 
        
           |  |  | 588 |         require_once($CFG->dirroot.'/question/type/ordering/question.php');
 | 
        
           |  |  | 589 |   | 
        
           |  |  | 590 |         $questiontype = $format->getpath($data, ['@', 'type'], '');
 | 
        
           |  |  | 591 |   | 
        
           |  |  | 592 |         if ($questiontype != 'ordering') {
 | 
        
           |  |  | 593 |             return false;
 | 
        
           |  |  | 594 |         }
 | 
        
           |  |  | 595 |   | 
        
           |  |  | 596 |         $newquestion = $format->import_headers($data);
 | 
        
           |  |  | 597 |         $newquestion->qtype = $questiontype;
 | 
        
           |  |  | 598 |   | 
        
           |  |  | 599 |         // Extra fields - "selecttype" and "selectcount"
 | 
        
           |  |  | 600 |         // (these fields used to be called "logical" and "studentsee").
 | 
        
           |  |  | 601 |         if (isset($data['#']['selecttype'])) {
 | 
        
           |  |  | 602 |             $selecttype = 'selecttype';
 | 
        
           |  |  | 603 |             $selectcount = 'selectcount';
 | 
        
           |  |  | 604 |         } else {
 | 
        
           |  |  | 605 |             $selecttype = 'logical';
 | 
        
           |  |  | 606 |             $selectcount = 'studentsee';
 | 
        
           |  |  | 607 |         }
 | 
        
           |  |  | 608 |         $layouttype = $format->getpath($data, ['#', 'layouttype', 0, '#'], 'VERTICAL');
 | 
        
           |  |  | 609 |         $selecttype = $format->getpath($data, ['#', $selecttype, 0, '#'], 'RANDOM');
 | 
        
           |  |  | 610 |         $selectcount = $format->getpath($data, ['#', $selectcount, 0, '#'], 6);
 | 
        
           |  |  | 611 |         $gradingtype = $format->getpath($data, ['#', 'gradingtype', 0, '#'], 'RELATIVE');
 | 
        
           |  |  | 612 |         $showgrading = $format->getpath($data, ['#', 'showgrading', 0, '#'], '1');
 | 
        
           |  |  | 613 |         $numberingstyle = $format->getpath($data, ['#', 'numberingstyle', 0, '#'], '1');
 | 
        
           |  |  | 614 |         $this->set_options_for_import($newquestion, $layouttype, $selecttype, $selectcount,
 | 
        
           |  |  | 615 |             $gradingtype, $showgrading, $numberingstyle);
 | 
        
           |  |  | 616 |   | 
        
           |  |  | 617 |         $newquestion->answer = [];
 | 
        
           |  |  | 618 |         $newquestion->answerformat = [];
 | 
        
           |  |  | 619 |         $newquestion->fraction = [];
 | 
        
           |  |  | 620 |         $newquestion->feedback = [];
 | 
        
           |  |  | 621 |         $newquestion->feedbackformat = [];
 | 
        
           |  |  | 622 |   | 
        
           |  |  | 623 |         $i = 0;
 | 
        
           |  |  | 624 |         while ($answer = $format->getpath($data, ['#', 'answer', $i], '')) {
 | 
        
           |  |  | 625 |             $ans = $format->import_answer($answer, true, $format->get_format($newquestion->questiontextformat));
 | 
        
           |  |  | 626 |             $newquestion->answer[$i] = $ans->answer;
 | 
        
           |  |  | 627 |             $newquestion->fraction[$i] = 1; // Will be reset later in save_question_options().
 | 
        
           |  |  | 628 |             $newquestion->feedback[$i] = $ans->feedback;
 | 
        
           |  |  | 629 |             $i++;
 | 
        
           |  |  | 630 |         }
 | 
        
           |  |  | 631 |   | 
        
           |  |  | 632 |         $format->import_combined_feedback($newquestion, $data);
 | 
        
           |  |  | 633 |         $newquestion->shownumcorrect = $format->getpath($data, ['#', 'shownumcorrect', 0, '#'], null);
 | 
        
           |  |  | 634 |   | 
        
           |  |  | 635 |         $format->import_hints($newquestion, $data, true, true);
 | 
        
           |  |  | 636 |   | 
        
           |  |  | 637 |         if (!isset($newquestion->shownumcorrect)) {
 | 
        
           |  |  | 638 |             $newquestion->shownumcorrect = 1;
 | 
        
           |  |  | 639 |             $counthintshownumcorrect = self::DEFAULT_NUM_HINTS;
 | 
        
           |  |  | 640 |             $counthintoptions = self::DEFAULT_NUM_HINTS;
 | 
        
           |  |  | 641 |   | 
        
           |  |  | 642 |             if (isset($newquestion->hintshownumcorrect)) {
 | 
        
           |  |  | 643 |                 $counthintshownumcorrect = max(self::DEFAULT_NUM_HINTS, count($newquestion->hintshownumcorrect));
 | 
        
           |  |  | 644 |             }
 | 
        
           |  |  | 645 |   | 
        
           |  |  | 646 |             if (isset($newquestion->hintoptions)) {
 | 
        
           |  |  | 647 |                 $counthintoptions = max(self::DEFAULT_NUM_HINTS, count($newquestion->hintoptions));
 | 
        
           |  |  | 648 |             }
 | 
        
           |  |  | 649 |   | 
        
           |  |  | 650 |             $newquestion->hintshownumcorrect  = array_fill(0, $counthintshownumcorrect, 1);
 | 
        
           |  |  | 651 |             $newquestion->hintoptions  = array_fill(0, $counthintoptions, 1);
 | 
        
           |  |  | 652 |         }
 | 
        
           |  |  | 653 |   | 
        
           |  |  | 654 |         return $newquestion;
 | 
        
           |  |  | 655 |     }
 | 
        
           |  |  | 656 |   | 
        
           |  |  | 657 |     /**
 | 
        
           |  |  | 658 |      * Set layouttype, selecttype, selectcount, gradingtype, showgrading based on their textual representation
 | 
        
           |  |  | 659 |      *
 | 
        
           |  |  | 660 |      * @param stdClass $question the question object
 | 
        
           |  |  | 661 |      * @param string $layouttype the layout type
 | 
        
           |  |  | 662 |      * @param string $selecttype the select type
 | 
        
           |  |  | 663 |      * @param string $selectcount the number of items to display
 | 
        
           |  |  | 664 |      * @param string $gradingtype the grading type
 | 
        
           |  |  | 665 |      * @param string $showgrading the grading details or not
 | 
        
           |  |  | 666 |      * @param string $numberingstyle the numbering style
 | 
        
           |  |  | 667 |      */
 | 
        
           |  |  | 668 |     public function set_options_for_import(stdClass $question, string $layouttype, string $selecttype, string $selectcount,
 | 
        
           |  |  | 669 |             string $gradingtype, string $showgrading, string $numberingstyle): void {
 | 
        
           |  |  | 670 |   | 
        
           |  |  | 671 |         // Set "layouttype" option.
 | 
        
           |  |  | 672 |         $question->layouttype = match (strtoupper($layouttype)) {
 | 
        
           |  |  | 673 |             'HORIZONTAL', 'HORI', 'H', '1' => qtype_ordering_question::LAYOUT_HORIZONTAL,
 | 
        
           |  |  | 674 |             default => qtype_ordering_question::LAYOUT_VERTICAL,
 | 
        
           |  |  | 675 |         };
 | 
        
           |  |  | 676 |   | 
        
           |  |  | 677 |         // Set "selecttype" option.
 | 
        
           |  |  | 678 |         $question->selecttype = match (strtoupper($selecttype)) {
 | 
        
           |  |  | 679 |             'ALL', 'EXACT' => qtype_ordering_question::SELECT_ALL,
 | 
        
           |  |  | 680 |             'CONTIGUOUS', 'CONTIG' => qtype_ordering_question::SELECT_CONTIGUOUS,
 | 
        
           |  |  | 681 |             default => qtype_ordering_question::SELECT_RANDOM,
 | 
        
           |  |  | 682 |         };
 | 
        
           |  |  | 683 |   | 
        
           |  |  | 684 |         // Set "selectcount" option - this used to be ($count - 2).
 | 
        
           |  |  | 685 |         if (is_numeric($selectcount) && $selectcount >= qtype_ordering_question::MIN_SUBSET_ITEMS) {
 | 
        
           |  |  | 686 |             $question->selectcount = intval($selectcount);
 | 
        
           |  |  | 687 |         } else {
 | 
        
           |  |  | 688 |             $question->selectcount = qtype_ordering_question::MIN_SUBSET_ITEMS; // Default!
 | 
        
           |  |  | 689 |         }
 | 
        
           |  |  | 690 |   | 
        
           |  |  | 691 |         // Set "gradingtype" option.
 | 
        
           |  |  | 692 |         $question->gradingtype = match (strtoupper($gradingtype)) {
 | 
        
           |  |  | 693 |             'ALL_OR_NOTHING' => qtype_ordering_question::GRADING_ALL_OR_NOTHING,
 | 
        
           |  |  | 694 |             'ABS', 'ABSOLUTE', 'ABSOLUTE_POSITION' => qtype_ordering_question::GRADING_ABSOLUTE_POSITION,
 | 
        
           |  |  | 695 |             'RELATIVE_NEXT_INCLUDE_LAST' => qtype_ordering_question::GRADING_RELATIVE_NEXT_INCLUDE_LAST,
 | 
        
           |  |  | 696 |             'RELATIVE_ONE_PREVIOUS_AND_NEXT' => qtype_ordering_question::GRADING_RELATIVE_ONE_PREVIOUS_AND_NEXT,
 | 
        
           |  |  | 697 |             'RELATIVE_ALL_PREVIOUS_AND_NEXT' => qtype_ordering_question::GRADING_RELATIVE_ALL_PREVIOUS_AND_NEXT,
 | 
        
           |  |  | 698 |             'LONGEST_ORDERED_SUBSET' => qtype_ordering_question::GRADING_LONGEST_ORDERED_SUBSET,
 | 
        
           |  |  | 699 |             'LONGEST_CONTIGUOUS_SUBSET' => qtype_ordering_question::GRADING_LONGEST_CONTIGUOUS_SUBSET,
 | 
        
           |  |  | 700 |             'RELATIVE_TO_CORRECT' => qtype_ordering_question::GRADING_RELATIVE_TO_CORRECT,
 | 
        
           |  |  | 701 |             default => qtype_ordering_question::GRADING_RELATIVE_NEXT_EXCLUDE_LAST,
 | 
        
           |  |  | 702 |         };
 | 
        
           |  |  | 703 |   | 
        
           |  |  | 704 |         // Set "showgrading" option.
 | 
        
           |  |  | 705 |         $question->showgrading = match (strtoupper($showgrading)) {
 | 
        
           |  |  | 706 |             'HIDE', 'FALSE', 'NO' => 0,
 | 
        
           |  |  | 707 |             default => 1,
 | 
        
           |  |  | 708 |         };
 | 
        
           |  |  | 709 |   | 
        
           |  |  | 710 |         // Set "numberingstyle" option.
 | 
        
           |  |  | 711 |         $question->numberingstyle = match ($numberingstyle) {
 | 
        
           |  |  | 712 |             'none', '123', 'abc', 'ABCD', 'iii', 'IIII' => $numberingstyle,
 | 
        
           |  |  | 713 |             default => qtype_ordering_question::NUMBERING_STYLE_DEFAULT,
 | 
        
           |  |  | 714 |         };
 | 
        
           |  |  | 715 |     }
 | 
        
           |  |  | 716 |   | 
        
           |  |  | 717 |     /**
 | 
        
           |  |  | 718 |      * Return the answer numbering style.
 | 
        
           |  |  | 719 |      * This method is used by "tests/questiontype_test.php".
 | 
        
           |  |  | 720 |      *
 | 
        
           |  |  | 721 |      * @param stdClass $questiondata
 | 
        
           |  |  | 722 |      * @return string
 | 
        
           |  |  | 723 |      */
 | 
        
           |  |  | 724 |     public function get_numberingstyle(stdClass $questiondata): string {
 | 
        
           |  |  | 725 |         return $questiondata->options->numberingstyle;
 | 
        
           |  |  | 726 |     }
 | 
        
           |  |  | 727 | }
 |