Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

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