Passed
Push — 1.11.x ( 056f3f...b39662 )
by
unknown
10:47 queued 14s
created

QuizExport::getAnswersForQuestion()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 10
c 2
b 0
f 0
dl 0
loc 18
rs 9.6111
cc 5
nc 5
nop 1
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
namespace moodleexport;
6
7
use Exception;
8
use FillBlanks;
9
10
/**
11
 * Class QuizExport.
12
 *
13
 * Handles the export of quizzes within a course.
14
 */
15
class QuizExport extends ActivityExport
16
{
17
    /**
18
     * Export a quiz to the specified directory.
19
     *
20
     * @param int    $activityId The ID of the quiz.
21
     * @param string $exportDir  The directory where the quiz will be exported.
22
     * @param int    $moduleId   The ID of the module.
23
     * @param int    $sectionId  The ID of the section.
24
     */
25
    public function export($activityId, $exportDir, $moduleId, $sectionId): void
26
    {
27
        // Prepare the directory where the quiz export will be saved
28
        $quizDir = $this->prepareActivityDirectory($exportDir, 'quiz', $moduleId);
29
30
        // Retrieve quiz data
31
        $quizData = $this->getData($activityId, $sectionId);
32
33
        // Generate XML files
34
        $this->createQuizXml($quizData, $quizDir);
35
        $this->createModuleXml($quizData, $quizDir);
36
        $this->createGradesXml($quizData, $quizDir);
37
        $this->createCompletionXml($quizData, $quizDir);
38
        $this->createCommentsXml($quizData, $quizDir);
39
        $this->createCompetenciesXml($quizData, $quizDir);
40
        $this->createFiltersXml($quizData, $quizDir);
41
        $this->createGradeHistoryXml($quizData, $quizDir);
42
        $this->createInforefXml($quizData, $quizDir);
43
        $this->createRolesXml($quizData, $quizDir);
44
        $this->createCalendarXml($quizData, $quizDir);
45
    }
46
47
    /**
48
     * Retrieves the quiz data.
49
     */
50
    public function getData(int $quizId, int $sectionId): array
51
    {
52
        $quizResources = $this->course->resources[RESOURCE_QUIZ];
53
54
        foreach ($quizResources as $quiz) {
55
            if ($quiz->obj->iid == -1) {
56
                continue;
57
            }
58
59
            if ($quiz->obj->iid == $quizId) {
60
                $contextid = $quiz->obj->c_id;
61
62
                return [
63
                    'id' => $quiz->obj->iid,
64
                    'name' => $quiz->obj->title,
65
                    'intro' => $quiz->obj->description,
66
                    'timeopen' => $quiz->obj->start_time ?? 0,
67
                    'timeclose' => $quiz->obj->end_time ?? 0,
68
                    'timelimit' => $quiz->obj->timelimit ?? 0,
69
                    'grademethod' => $quiz->obj->grademethod ?? 1,
70
                    'decimalpoints' => $quiz->obj->decimalpoints ?? 2,
71
                    'sumgrades' => $quiz->obj->sumgrades ?? 0,
72
                    'grade' => $quiz->obj->grade ?? 0,
73
                    'questionsperpage' => $quiz->obj->questionsperpage ?? 1,
74
                    'preferredbehaviour' => $quiz->obj->preferredbehaviour ?? 'deferredfeedback',
75
                    'shuffleanswers' => $quiz->obj->shuffleanswers ?? 1,
76
                    'questions' => $this->getQuestionsForQuiz($quizId),
77
                    'feedbacks' => $this->getFeedbacksForQuiz($quizId),
78
                    'sectionid' => $sectionId,
79
                    'moduleid' => $quiz->obj->iid ?? 0,
80
                    'modulename' => 'quiz',
81
                    'contextid' => $contextid,
82
                    'overduehandling' => $quiz->obj->overduehandling ?? 'autosubmit',
83
                    'graceperiod' => $quiz->obj->graceperiod ?? 0,
84
                    'canredoquestions' => $quiz->obj->canredoquestions ?? 0,
85
                    'attempts_number' => $quiz->obj->attempts_number ?? 0,
86
                    'attemptonlast' => $quiz->obj->attemptonlast ?? 0,
87
                    'questiondecimalpoints' => $quiz->obj->questiondecimalpoints ?? 2,
88
                    'reviewattempt' => $quiz->obj->reviewattempt ?? 0,
89
                    'reviewcorrectness' => $quiz->obj->reviewcorrectness ?? 0,
90
                    'reviewmarks' => $quiz->obj->reviewmarks ?? 0,
91
                    'reviewspecificfeedback' => $quiz->obj->reviewspecificfeedback ?? 0,
92
                    'reviewgeneralfeedback' => $quiz->obj->reviewgeneralfeedback ?? 0,
93
                    'reviewrightanswer' => $quiz->obj->reviewrightanswer ?? 0,
94
                    'reviewoverallfeedback' => $quiz->obj->reviewoverallfeedback ?? 0,
95
                    'timecreated' => $quiz->obj->insert_date ?? time(),
96
                    'timemodified' => $quiz->obj->lastedit_date ?? time(),
97
                    'password' => $quiz->obj->password ?? '',
98
                    'subnet' => $quiz->obj->subnet ?? '',
99
                    'browsersecurity' => $quiz->obj->browsersecurity ?? '-',
100
                    'delay1' => $quiz->obj->delay1 ?? 0,
101
                    'delay2' => $quiz->obj->delay2 ?? 0,
102
                    'showuserpicture' => $quiz->obj->showuserpicture ?? 0,
103
                    'showblocks' => $quiz->obj->showblocks ?? 0,
104
                    'completionattemptsexhausted' => $quiz->obj->completionattemptsexhausted ?? 0,
105
                    'completionpass' => $quiz->obj->completionpass ?? 0,
106
                    'completionminattempts' => $quiz->obj->completionminattempts ?? 0,
107
                    'allowofflineattempts' => $quiz->obj->allowofflineattempts ?? 0,
108
                    'users' => [],
109
                    'files' => [],
110
                ];
111
            }
112
        }
113
114
        return [];
115
    }
116
117
    /**
118
     * Exports a question in XML format.
119
     */
120
    public function exportQuestion(array $question): string
121
    {
122
        $xmlContent = '      <question id="'.($question['id'] ?? '0').'">'.PHP_EOL;
123
        $xmlContent .= '        <parent>0</parent>'.PHP_EOL;
124
        $xmlContent .= '        <name>'.htmlspecialchars($question['questiontext'] ?? 'No question text').'</name>'.PHP_EOL;
125
        $xmlContent .= '        <questiontext>'.htmlspecialchars($question['questiontext'] ?? 'No question text').'</questiontext>'.PHP_EOL;
126
        $xmlContent .= '        <questiontextformat>1</questiontextformat>'.PHP_EOL;
127
        $xmlContent .= '        <generalfeedback></generalfeedback>'.PHP_EOL;
128
        $xmlContent .= '        <generalfeedbackformat>1</generalfeedbackformat>'.PHP_EOL;
129
        $xmlContent .= '        <defaultmark>'.($question['maxmark'] ?? '0').'</defaultmark>'.PHP_EOL;
130
        $xmlContent .= '        <penalty>0.3333333</penalty>'.PHP_EOL;
131
        $xmlContent .= '        <qtype>'.htmlspecialchars(str_replace('_nosingle', '', $question['qtype']) ?? 'unknown').'</qtype>'.PHP_EOL;
132
        $xmlContent .= '        <length>1</length>'.PHP_EOL;
133
        $xmlContent .= '        <stamp>moodle+'.time().'+QUESTIONSTAMP</stamp>'.PHP_EOL;
134
        $xmlContent .= '        <version>moodle+'.time().'+VERSIONSTAMP</version>'.PHP_EOL;
135
        $xmlContent .= '        <hidden>0</hidden>'.PHP_EOL;
136
        $xmlContent .= '        <timecreated>'.time().'</timecreated>'.PHP_EOL;
137
        $xmlContent .= '        <timemodified>'.time().'</timemodified>'.PHP_EOL;
138
        $xmlContent .= '        <createdby>2</createdby>'.PHP_EOL;
139
        $xmlContent .= '        <modifiedby>2</modifiedby>'.PHP_EOL;
140
141
        // Add question type-specific content
142
        switch ($question['qtype']) {
143
            case 'multichoice':
144
                $xmlContent .= $this->exportMultichoiceQuestion($question);
145
                break;
146
            case 'multichoice_nosingle':
147
                $xmlContent .= $this->exportMultichoiceNosingleQuestion($question);
148
                break;
149
            case 'truefalse':
150
                $xmlContent .= $this->exportTrueFalseQuestion($question);
151
                break;
152
            case 'shortanswer':
153
                $xmlContent .= $this->exportShortAnswerQuestion($question);
154
                break;
155
            case 'match':
156
                $xmlContent .= $this->exportMatchQuestion($question);
157
                break;
158
        }
159
160
        $xmlContent .= '      </question>'.PHP_EOL;
161
162
        return $xmlContent;
163
    }
164
165
    /**
166
     * Retrieves the questions for a specific quiz.
167
     */
168
    private function getQuestionsForQuiz(int $quizId): array
169
    {
170
        $questions = [];
171
        $quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
172
173
        foreach ($quizResources as $questionId => $questionData) {
174
            if (in_array($questionId, $this->course->resources[RESOURCE_QUIZ][$quizId]->obj->question_ids)) {
175
                $categoryId = $questionData->question_category ?? 0;
176
                $categoryId = $categoryId > 0 ? $categoryId : $this->getDefaultCategoryId();
177
                $questions[] = [
178
                    'id' => $questionData->source_id,
179
                    'questiontext' => $questionData->question,
180
                    'qtype' => $this->mapQuestionType($questionData->quiz_type),
181
                    'questioncategoryid' =>  $categoryId,
182
                    'answers' => $this->getAnswersForQuestion($questionData->source_id),
183
                    'maxmark' => $questionData->ponderation ?? 1,
184
                ];
185
            }
186
        }
187
188
        return $questions;
189
    }
190
191
    /**
192
     * Maps the quiz type code to a descriptive string.
193
     */
194
    private function mapQuestionType(string $quizType): string
195
    {
196
        switch ($quizType) {
197
            case UNIQUE_ANSWER: return 'multichoice';
198
            case MULTIPLE_ANSWER: return 'multichoice_nosingle';
199
            case FILL_IN_BLANKS: return 'match';
200
            case FREE_ANSWER: return 'shortanswer';
201
            case CALCULATED_ANSWER: return 'calculated';
202
            case UPLOAD_ANSWER: return 'fileupload';
203
            default: return 'unknown';
204
        }
205
    }
206
207
    /**
208
     * Retrieves the answers for a specific question ID.
209
     */
210
    private function getAnswersForQuestion(int $questionId): array
211
    {
212
        $answers = [];
213
        $quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
214
215
        foreach ($quizResources as $questionData) {
216
            if ($questionData->source_id == $questionId) {
217
                foreach ($questionData->answers as $answer) {
218
                    $answers[] = [
219
                        'text' => $answer['answer'],
220
                        'fraction' => $answer['correct'] == '1' ? 100 : 0,
221
                        'feedback' => $answer['comment'],
222
                    ];
223
                }
224
            }
225
        }
226
227
        return $answers;
228
    }
229
230
    /**
231
     * Retrieves feedbacks for a specific quiz.
232
     */
233
    private function getFeedbacksForQuiz(int $quizId): array
234
    {
235
        $feedbacks = [];
236
        $quizResources = $this->course->resources[RESOURCE_QUIZ] ?? [];
237
238
        foreach ($quizResources as $quiz) {
239
            if ($quiz->obj->iid == $quizId) {
240
                $feedbacks[] = [
241
                    'feedbacktext' => $quiz->obj->description ?? '',
242
                    'mingrade' => 0.00000,
243
                    'maxgrade' => $quiz->obj->grade ?? 10.00000,
244
                ];
245
            }
246
        }
247
248
        return $feedbacks;
249
    }
250
251
    private function getDefaultCategoryId(): int
252
    {
253
        return 1;
254
    }
255
256
    /**
257
     * Creates the quiz.xml file.
258
     */
259
    private function createQuizXml(array $quizData, string $destinationDir): void
260
    {
261
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
262
        $xmlContent .= '<activity id="'.$quizData['id'].'" moduleid="'.$quizData['moduleid'].'" modulename="quiz" contextid="'.$quizData['contextid'].'">'.PHP_EOL;
263
        $xmlContent .= '  <quiz id="'.$quizData['id'].'">'.PHP_EOL;
264
        $xmlContent .= '    <name>'.htmlspecialchars($quizData['name']).'</name>'.PHP_EOL;
265
        $xmlContent .= '    <intro>'.htmlspecialchars($quizData['intro']).'</intro>'.PHP_EOL;
266
        $xmlContent .= '    <introformat>1</introformat>'.PHP_EOL;
267
        $xmlContent .= '    <timeopen>'.($quizData['timeopen'] ?? 0).'</timeopen>'.PHP_EOL;
268
        $xmlContent .= '    <timeclose>'.($quizData['timeclose'] ?? 0).'</timeclose>'.PHP_EOL;
269
        $xmlContent .= '    <timelimit>'.($quizData['timelimit'] ?? 0).'</timelimit>'.PHP_EOL;
270
        $xmlContent .= '    <overduehandling>'.($quizData['overduehandling'] ?? 'autosubmit').'</overduehandling>'.PHP_EOL;
271
        $xmlContent .= '    <graceperiod>'.($quizData['graceperiod'] ?? 0).'</graceperiod>'.PHP_EOL;
272
        $xmlContent .= '    <preferredbehaviour>'.htmlspecialchars($quizData['preferredbehaviour']).'</preferredbehaviour>'.PHP_EOL;
273
        $xmlContent .= '    <canredoquestions>'.($quizData['canredoquestions'] ?? 0).'</canredoquestions>'.PHP_EOL;
274
        $xmlContent .= '    <attempts_number>'.($quizData['attempts_number'] ?? 0).'</attempts_number>'.PHP_EOL;
275
        $xmlContent .= '    <attemptonlast>'.($quizData['attemptonlast'] ?? 0).'</attemptonlast>'.PHP_EOL;
276
        $xmlContent .= '    <grademethod>'.$quizData['grademethod'].'</grademethod>'.PHP_EOL;
277
        $xmlContent .= '    <decimalpoints>'.$quizData['decimalpoints'].'</decimalpoints>'.PHP_EOL;
278
        $xmlContent .= '    <questiondecimalpoints>'.($quizData['questiondecimalpoints'] ?? -1).'</questiondecimalpoints>'.PHP_EOL;
279
280
        // Review options
281
        $xmlContent .= '    <reviewattempt>'.($quizData['reviewattempt'] ?? 69888).'</reviewattempt>'.PHP_EOL;
282
        $xmlContent .= '    <reviewcorrectness>'.($quizData['reviewcorrectness'] ?? 4352).'</reviewcorrectness>'.PHP_EOL;
283
        $xmlContent .= '    <reviewmarks>'.($quizData['reviewmarks'] ?? 4352).'</reviewmarks>'.PHP_EOL;
284
        $xmlContent .= '    <reviewspecificfeedback>'.($quizData['reviewspecificfeedback'] ?? 4352).'</reviewspecificfeedback>'.PHP_EOL;
285
        $xmlContent .= '    <reviewgeneralfeedback>'.($quizData['reviewgeneralfeedback'] ?? 4352).'</reviewgeneralfeedback>'.PHP_EOL;
286
        $xmlContent .= '    <reviewrightanswer>'.($quizData['reviewrightanswer'] ?? 4352).'</reviewrightanswer>'.PHP_EOL;
287
        $xmlContent .= '    <reviewoverallfeedback>'.($quizData['reviewoverallfeedback'] ?? 4352).'</reviewoverallfeedback>'.PHP_EOL;
288
289
        // Navigation and presentation settings
290
        $xmlContent .= '    <questionsperpage>'.$quizData['questionsperpage'].'</questionsperpage>'.PHP_EOL;
291
        $xmlContent .= '    <navmethod>'.htmlspecialchars($quizData['navmethod']).'</navmethod>'.PHP_EOL;
292
        $xmlContent .= '    <shuffleanswers>'.$quizData['shuffleanswers'].'</shuffleanswers>'.PHP_EOL;
293
        $xmlContent .= '    <sumgrades>'.$quizData['sumgrades'].'</sumgrades>'.PHP_EOL;
294
        $xmlContent .= '    <grade>'.$quizData['grade'].'</grade>'.PHP_EOL;
295
296
        // Timing and security
297
        $xmlContent .= '    <timecreated>'.($quizData['timecreated'] ?? time()).'</timecreated>'.PHP_EOL;
298
        $xmlContent .= '    <timemodified>'.($quizData['timemodified'] ?? time()).'</timemodified>'.PHP_EOL;
299
        $xmlContent .= '    <password>'.(isset($quizData['password']) ? htmlspecialchars($quizData['password']) : '').'</password>'.PHP_EOL;
300
        $xmlContent .= '    <subnet>'.(isset($quizData['subnet']) ? htmlspecialchars($quizData['subnet']) : '').'</subnet>'.PHP_EOL;
301
        $xmlContent .= '    <browsersecurity>'.(isset($quizData['browsersecurity']) ? htmlspecialchars($quizData['browsersecurity']) : '-').'</browsersecurity>'.PHP_EOL;
302
        $xmlContent .= '    <delay1>'.($quizData['delay1'] ?? 0).'</delay1>'.PHP_EOL;
303
        $xmlContent .= '    <delay2>'.($quizData['delay2'] ?? 0).'</delay2>'.PHP_EOL;
304
305
        // Additional options
306
        $xmlContent .= '    <showuserpicture>'.($quizData['showuserpicture'] ?? 0).'</showuserpicture>'.PHP_EOL;
307
        $xmlContent .= '    <showblocks>'.($quizData['showblocks'] ?? 0).'</showblocks>'.PHP_EOL;
308
        $xmlContent .= '    <completionattemptsexhausted>'.($quizData['completionattemptsexhausted'] ?? 0).'</completionattemptsexhausted>'.PHP_EOL;
309
        $xmlContent .= '    <completionpass>'.($quizData['completionpass'] ?? 0).'</completionpass>'.PHP_EOL;
310
        $xmlContent .= '    <completionminattempts>'.($quizData['completionminattempts'] ?? 0).'</completionminattempts>'.PHP_EOL;
311
        $xmlContent .= '    <allowofflineattempts>'.($quizData['allowofflineattempts'] ?? 0).'</allowofflineattempts>'.PHP_EOL;
312
313
        // Subplugin, if applicable
314
        $xmlContent .= '    <subplugin_quizaccess_seb_quiz>'.PHP_EOL;
315
        $xmlContent .= '    </subplugin_quizaccess_seb_quiz>'.PHP_EOL;
316
317
        // Add question instances
318
        $xmlContent .= '    <question_instances>'.PHP_EOL;
319
        foreach ($quizData['questions'] as $question) {
320
            $xmlContent .= '      <question_instance id="'.$question['id'].'">'.PHP_EOL;
321
            $xmlContent .= '        <slot>'.$question['id'].'</slot>'.PHP_EOL;
322
            $xmlContent .= '        <page>1</page>'.PHP_EOL;
323
            $xmlContent .= '        <requireprevious>0</requireprevious>'.PHP_EOL;
324
            $xmlContent .= '        <questionid>'.$question['id'].'</questionid>'.PHP_EOL;
325
            $xmlContent .= '        <questioncategoryid>'.$question['questioncategoryid'].'</questioncategoryid>'.PHP_EOL;
326
            $xmlContent .= '        <includingsubcategories>$@NULL@$</includingsubcategories>'.PHP_EOL;
327
            $xmlContent .= '        <maxmark>'.$question['maxmark'].'</maxmark>'.PHP_EOL;
328
            $xmlContent .= '      </question_instance>'.PHP_EOL;
329
        }
330
        $xmlContent .= '    </question_instances>'.PHP_EOL;
331
332
        // Quiz sections
333
        $xmlContent .= '    <sections>'.PHP_EOL;
334
        $xmlContent .= '      <section id="'.$quizData['id'].'">'.PHP_EOL;
335
        $xmlContent .= '        <firstslot>1</firstslot>'.PHP_EOL;
336
        $xmlContent .= '        <shufflequestions>0</shufflequestions>'.PHP_EOL;
337
        $xmlContent .= '      </section>'.PHP_EOL;
338
        $xmlContent .= '    </sections>'.PHP_EOL;
339
340
        // Add feedbacks
341
        $xmlContent .= '    <feedbacks>'.PHP_EOL;
342
        foreach ($quizData['feedbacks'] as $feedback) {
343
            $xmlContent .= '      <feedback id="'.$quizData['id'].'">'.PHP_EOL;
344
            $xmlContent .= '        <feedbacktext>'.htmlspecialchars($feedback['feedbacktext']).'</feedbacktext>'.PHP_EOL;
345
            $xmlContent .= '        <feedbacktextformat>1</feedbacktextformat>'.PHP_EOL;
346
            $xmlContent .= '        <mingrade>'.$feedback['mingrade'].'</mingrade>'.PHP_EOL;
347
            $xmlContent .= '        <maxgrade>'.$feedback['maxgrade'].'</maxgrade>'.PHP_EOL;
348
            $xmlContent .= '      </feedback>'.PHP_EOL;
349
        }
350
        $xmlContent .= '    </feedbacks>'.PHP_EOL;
351
352
        // Complete with placeholders for attempts and grades
353
        $xmlContent .= '    <overrides>'.PHP_EOL.'    </overrides>'.PHP_EOL;
354
        $xmlContent .= '    <grades>'.PHP_EOL.'    </grades>'.PHP_EOL;
355
        $xmlContent .= '    <attempts>'.PHP_EOL.'    </attempts>'.PHP_EOL;
356
357
        // Close the activity tag
358
        $xmlContent .= '  </quiz>'.PHP_EOL;
359
        $xmlContent .= '</activity>'.PHP_EOL;
360
361
        // Save the XML file
362
        $xmlFile = $destinationDir.'/quiz.xml';
363
        if (file_put_contents($xmlFile, $xmlContent) === false) {
364
            throw new Exception(get_lang('ErrorCreatingQuizXml'));
365
        }
366
    }
367
368
    /**
369
     * Exports a multiple-choice question in XML format.
370
     */
371
    private function exportMultichoiceQuestion(array $question): string
372
    {
373
        $xmlContent = '        <plugin_qtype_multichoice_question>'.PHP_EOL;
374
        $xmlContent .= '          <answers>'.PHP_EOL;
375
        foreach ($question['answers'] as $answer) {
376
            $xmlContent .= $this->exportAnswer($answer);
377
        }
378
        $xmlContent .= '          </answers>'.PHP_EOL;
379
        $xmlContent .= '          <multichoice id="'.($question['id'] ?? '0').'">'.PHP_EOL;
380
        $xmlContent .= '            <layout>0</layout>'.PHP_EOL;
381
        $xmlContent .= '            <single>1</single>'.PHP_EOL;
382
        $xmlContent .= '            <shuffleanswers>1</shuffleanswers>'.PHP_EOL;
383
        $xmlContent .= '            <correctfeedback>Your answer is correct.</correctfeedback>'.PHP_EOL;
384
        $xmlContent .= '            <correctfeedbackformat>1</correctfeedbackformat>'.PHP_EOL;
385
        $xmlContent .= '            <partiallycorrectfeedback>Your answer is partially correct.</partiallycorrectfeedback>'.PHP_EOL;
386
        $xmlContent .= '            <partiallycorrectfeedbackformat>1</partiallycorrectfeedbackformat>'.PHP_EOL;
387
        $xmlContent .= '            <incorrectfeedback>Your answer is incorrect.</incorrectfeedback>'.PHP_EOL;
388
        $xmlContent .= '            <incorrectfeedbackformat>1</incorrectfeedbackformat>'.PHP_EOL;
389
        $xmlContent .= '            <answernumbering>abc</answernumbering>'.PHP_EOL;
390
        $xmlContent .= '            <shownumcorrect>1</shownumcorrect>'.PHP_EOL;
391
        $xmlContent .= '          </multichoice>'.PHP_EOL;
392
        $xmlContent .= '        </plugin_qtype_multichoice_question>'.PHP_EOL;
393
394
        return $xmlContent;
395
    }
396
397
    /**
398
     * Exports a multiple-choice question with single=0 in XML format.
399
     */
400
    private function exportMultichoiceNosingleQuestion(array $question): string
401
    {
402
        // Similar structure to exportMultichoiceQuestion, but with single=0
403
        $xmlContent = str_replace('<single>1</single>', '<single>0</single>', $this->exportMultichoiceQuestion($question));
404
405
        return $xmlContent;
406
    }
407
408
    /**
409
     * Exports a true/false question in XML format.
410
     */
411
    private function exportTrueFalseQuestion(array $question): string
412
    {
413
        $xmlContent = '        <plugin_qtype_truefalse_question>'.PHP_EOL;
414
        $xmlContent .= '          <answers>'.PHP_EOL;
415
        foreach ($question['answers'] as $answer) {
416
            $xmlContent .= $this->exportAnswer($answer);
417
        }
418
        $xmlContent .= '          </answers>'.PHP_EOL;
419
        $xmlContent .= '          <truefalse id="'.($question['id'] ?? '0').'">'.PHP_EOL;
420
        $xmlContent .= '            <trueanswer>'.($question['answers'][0]['id'] ?? '0').'</trueanswer>'.PHP_EOL;
421
        $xmlContent .= '            <falseanswer>'.($question['answers'][1]['id'] ?? '0').'</falseanswer>'.PHP_EOL;
422
        $xmlContent .= '          </truefalse>'.PHP_EOL;
423
        $xmlContent .= '        </plugin_qtype_truefalse_question>'.PHP_EOL;
424
425
        return $xmlContent;
426
    }
427
428
    /**
429
     * Exports a short answer question in XML format.
430
     */
431
    private function exportShortAnswerQuestion(array $question): string
432
    {
433
        $xmlContent = '        <plugin_qtype_shortanswer_question>'.PHP_EOL;
434
        $xmlContent .= '          <answers>'.PHP_EOL;
435
        foreach ($question['answers'] as $answer) {
436
            $xmlContent .= $this->exportAnswer($answer);
437
        }
438
        $xmlContent .= '          </answers>'.PHP_EOL;
439
        $xmlContent .= '          <shortanswer id="'.($question['id'] ?? '0').'">'.PHP_EOL;
440
        $xmlContent .= '            <usecase>0</usecase>'.PHP_EOL;
441
        $xmlContent .= '          </shortanswer>'.PHP_EOL;
442
        $xmlContent .= '        </plugin_qtype_shortanswer_question>'.PHP_EOL;
443
444
        return $xmlContent;
445
    }
446
447
    /**
448
     * Exports a matching question in XML format.
449
     */
450
    private function exportMatchQuestion(array $question): string
451
    {
452
        $xmlContent = '        <plugin_qtype_match_question>'.PHP_EOL;
453
        $xmlContent .= '          <matchoptions id="'.htmlspecialchars($question['id'] ?? '0').'">'.PHP_EOL;
454
        $xmlContent .= '            <shuffleanswers>1</shuffleanswers>'.PHP_EOL;
455
        $xmlContent .= '            <correctfeedback>'.htmlspecialchars($question['correctfeedback'] ?? '').'</correctfeedback>'.PHP_EOL;
456
        $xmlContent .= '            <correctfeedbackformat>0</correctfeedbackformat>'.PHP_EOL;
457
        $xmlContent .= '            <partiallycorrectfeedback>'.htmlspecialchars($question['partiallycorrectfeedback'] ?? '').'</partiallycorrectfeedback>'.PHP_EOL;
458
        $xmlContent .= '            <partiallycorrectfeedbackformat>0</partiallycorrectfeedbackformat>'.PHP_EOL;
459
        $xmlContent .= '            <incorrectfeedback>'.htmlspecialchars($question['incorrectfeedback'] ?? '').'</incorrectfeedback>'.PHP_EOL;
460
        $xmlContent .= '            <incorrectfeedbackformat>0</incorrectfeedbackformat>'.PHP_EOL;
461
        $xmlContent .= '            <shownumcorrect>0</shownumcorrect>'.PHP_EOL;
462
        $xmlContent .= '          </matchoptions>'.PHP_EOL;
463
        $xmlContent .= '          <matches>'.PHP_EOL;
464
465
        $res = FillBlanks::getAnswerInfo($question['answers'][0]['text']);
466
        $words = $res['words'];
467
        $common_words = $res['common_words'];
468
469
        for ($i = 0; $i < count($common_words); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
470
            $answer = htmlspecialchars(trim(strip_tags($common_words[$i])));
471
            if (!empty(trim($answer))) {
472
                $xmlContent .= '            <match id="'.($i + 1).'">'.PHP_EOL;
473
                $xmlContent .= '              <questiontext>'.$answer.'</questiontext>'.PHP_EOL;
474
                $xmlContent .= '              <questiontextformat>0</questiontextformat>'.PHP_EOL;
475
                $xmlContent .= '              <answertext>'.htmlspecialchars(explode('|', $words[$i])[0]).'</answertext>'.PHP_EOL;
476
                $xmlContent .= '            </match>'.PHP_EOL;
477
            }
478
        }
479
480
        $xmlContent .= '          </matches>'.PHP_EOL;
481
        $xmlContent .= '        </plugin_qtype_match_question>'.PHP_EOL;
482
483
        return $xmlContent;
484
    }
485
486
    /**
487
     * Exports an answer in XML format.
488
     */
489
    private function exportAnswer(array $answer): string
490
    {
491
        return '            <answer id="'.($answer['id'] ?? '0').'">'.PHP_EOL.
492
            '              <answertext>'.htmlspecialchars($answer['text'] ?? 'No answer text').'</answertext>'.PHP_EOL.
493
            '              <answerformat>1</answerformat>'.PHP_EOL.
494
            '              <fraction>'.($answer['fraction'] ?? '0').'</fraction>'.PHP_EOL.
495
            '              <feedback>'.htmlspecialchars($answer['feedback'] ?? '').'</feedback>'.PHP_EOL.
496
            '              <feedbackformat>1</feedbackformat>'.PHP_EOL.
497
            '            </answer>'.PHP_EOL;
498
    }
499
}
500