Passed
Push — 1.11.x ( 065453...ddd2b0 )
by Yannick
09:02
created

QuizExport   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 474
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 47
eloc 300
dl 0
loc 474
rs 8.64
c 2
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A export() 0 20 1
B mapQuestionType() 0 10 7
A getAnswersForQuestion() 0 17 5
A exportMatchQuestion() 0 34 3
B createQuizXml() 0 106 7
A exportMultichoiceNosingleQuestion() 0 5 1
A getQuestionsForQuiz() 0 19 3
A exportAnswer() 0 9 1
A exportMultichoiceQuestion() 0 24 2
B getData() 0 65 4
A exportShortAnswerQuestion() 0 14 2
B exportQuestion() 0 43 6
A getFeedbacksForQuiz() 0 16 3
A exportTrueFalseQuestion() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like QuizExport often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QuizExport, and based on these observations, apply Extract Interface, too.

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
     * Retrieves the questions for a specific quiz.
119
     */
120
    private function getQuestionsForQuiz(int $quizId): array
121
    {
122
        $questions = [];
123
        $quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
124
125
        foreach ($quizResources as $questionId => $questionData) {
126
            if (in_array($questionId, $this->course->resources[RESOURCE_QUIZ][$quizId]->obj->question_ids)) {
127
                $questions[] = [
128
                    'id' => $questionData->source_id,
129
                    'questiontext' => $questionData->question,
130
                    'qtype' => $this->mapQuestionType($questionData->quiz_type),
131
                    'questioncategoryid' => $questionData->question_category ?? 0,
132
                    'answers' => $this->getAnswersForQuestion($questionData->source_id),
133
                    'maxmark' => $questionData->ponderation ?? 1,
134
                ];
135
            }
136
        }
137
138
        return $questions;
139
    }
140
141
    /**
142
     * Maps the quiz type code to a descriptive string.
143
     */
144
    private function mapQuestionType(string $quizType): string
145
    {
146
        switch ($quizType) {
147
            case UNIQUE_ANSWER: return 'multichoice';
148
            case MULTIPLE_ANSWER: return 'multichoice_nosingle';
149
            case FILL_IN_BLANKS: return 'match';
150
            case FREE_ANSWER: return 'shortanswer';
151
            case CALCULATED_ANSWER: return 'calculated';
152
            case UPLOAD_ANSWER: return 'fileupload';
153
            default: return 'unknown';
154
        }
155
    }
156
157
    /**
158
     * Retrieves the answers for a specific question ID.
159
     */
160
    private function getAnswersForQuestion(int $questionId): array
161
    {
162
        $answers = [];
163
        $quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
164
165
        foreach ($quizResources as $questionData) {
166
            if ($questionData->source_id == $questionId) {
167
                foreach ($questionData->answers as $answer) {
168
                    $answers[] = [
169
                        'text' => $answer['answer'],
170
                        'fraction' => $answer['correct'] == '1' ? 100 : 0,
171
                        'feedback' => $answer['comment'],
172
                    ];
173
                }
174
            }
175
        }
176
        return $answers;
177
    }
178
179
    /**
180
     * Retrieves feedbacks for a specific quiz.
181
     */
182
    private function getFeedbacksForQuiz(int $quizId): array
183
    {
184
        $feedbacks = [];
185
        $quizResources = $this->course->resources[RESOURCE_QUIZ] ?? [];
186
187
        foreach ($quizResources as $quiz) {
188
            if ($quiz->obj->iid == $quizId) {
189
                $feedbacks[] = [
190
                    'feedbacktext' => $quiz->obj->description ?? '',
191
                    'mingrade' => 0.00000,
192
                    'maxgrade' => $quiz->obj->grade ?? 10.00000,
193
                ];
194
            }
195
        }
196
197
        return $feedbacks;
198
    }
199
200
    /**
201
     * Creates the quiz.xml file.
202
     */
203
    private function createQuizXml(array $quizData, string $destinationDir): void
204
    {
205
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
206
        $xmlContent .= '<activity id="' . $quizData['id'] . '" moduleid="' . $quizData['moduleid'] . '" modulename="quiz" contextid="' . $quizData['contextid'] . '">' . PHP_EOL;
207
        $xmlContent .= '  <quiz id="' . $quizData['id'] . '">' . PHP_EOL;
208
        $xmlContent .= '    <name>' . htmlspecialchars($quizData['name']) . '</name>' . PHP_EOL;
209
        $xmlContent .= '    <intro>' . htmlspecialchars($quizData['intro']) . '</intro>' . PHP_EOL;
210
        $xmlContent .= '    <introformat>1</introformat>' . PHP_EOL;
211
        $xmlContent .= '    <timeopen>' . ($quizData['timeopen'] ?? 0) . '</timeopen>' . PHP_EOL;
212
        $xmlContent .= '    <timeclose>' . ($quizData['timeclose'] ?? 0) . '</timeclose>' . PHP_EOL;
213
        $xmlContent .= '    <timelimit>' . ($quizData['timelimit'] ?? 0) . '</timelimit>' . PHP_EOL;
214
        $xmlContent .= '    <overduehandling>' . ($quizData['overduehandling'] ?? 'autosubmit') . '</overduehandling>' . PHP_EOL;
215
        $xmlContent .= '    <graceperiod>' . ($quizData['graceperiod'] ?? 0) . '</graceperiod>' . PHP_EOL;
216
        $xmlContent .= '    <preferredbehaviour>' . htmlspecialchars($quizData['preferredbehaviour']) . '</preferredbehaviour>' . PHP_EOL;
217
        $xmlContent .= '    <canredoquestions>' . ($quizData['canredoquestions'] ?? 0) . '</canredoquestions>' . PHP_EOL;
218
        $xmlContent .= '    <attempts_number>' . ($quizData['attempts_number'] ?? 0) . '</attempts_number>' . PHP_EOL;
219
        $xmlContent .= '    <attemptonlast>' . ($quizData['attemptonlast'] ?? 0) . '</attemptonlast>' . PHP_EOL;
220
        $xmlContent .= '    <grademethod>' . $quizData['grademethod'] . '</grademethod>' . PHP_EOL;
221
        $xmlContent .= '    <decimalpoints>' . $quizData['decimalpoints'] . '</decimalpoints>' . PHP_EOL;
222
        $xmlContent .= '    <questiondecimalpoints>' . ($quizData['questiondecimalpoints'] ?? -1) . '</questiondecimalpoints>' . PHP_EOL;
223
224
        // Review options
225
        $xmlContent .= '    <reviewattempt>' . ($quizData['reviewattempt'] ?? 69888) . '</reviewattempt>' . PHP_EOL;
226
        $xmlContent .= '    <reviewcorrectness>' . ($quizData['reviewcorrectness'] ?? 4352) . '</reviewcorrectness>' . PHP_EOL;
227
        $xmlContent .= '    <reviewmarks>' . ($quizData['reviewmarks'] ?? 4352) . '</reviewmarks>' . PHP_EOL;
228
        $xmlContent .= '    <reviewspecificfeedback>' . ($quizData['reviewspecificfeedback'] ?? 4352) . '</reviewspecificfeedback>' . PHP_EOL;
229
        $xmlContent .= '    <reviewgeneralfeedback>' . ($quizData['reviewgeneralfeedback'] ?? 4352) . '</reviewgeneralfeedback>' . PHP_EOL;
230
        $xmlContent .= '    <reviewrightanswer>' . ($quizData['reviewrightanswer'] ?? 4352) . '</reviewrightanswer>' . PHP_EOL;
231
        $xmlContent .= '    <reviewoverallfeedback>' . ($quizData['reviewoverallfeedback'] ?? 4352) . '</reviewoverallfeedback>' . PHP_EOL;
232
233
        // Navigation and presentation settings
234
        $xmlContent .= '    <questionsperpage>' . $quizData['questionsperpage'] . '</questionsperpage>' . PHP_EOL;
235
        $xmlContent .= '    <navmethod>' . htmlspecialchars($quizData['navmethod']) . '</navmethod>' . PHP_EOL;
236
        $xmlContent .= '    <shuffleanswers>' . $quizData['shuffleanswers'] . '</shuffleanswers>' . PHP_EOL;
237
        $xmlContent .= '    <sumgrades>' . $quizData['sumgrades'] . '</sumgrades>' . PHP_EOL;
238
        $xmlContent .= '    <grade>' . $quizData['grade'] . '</grade>' . PHP_EOL;
239
240
        // Timing and security
241
        $xmlContent .= '    <timecreated>' . ($quizData['timecreated'] ?? time()) . '</timecreated>' . PHP_EOL;
242
        $xmlContent .= '    <timemodified>' . ($quizData['timemodified'] ?? time()) . '</timemodified>' . PHP_EOL;
243
        $xmlContent .= '    <password>' . (isset($quizData['password']) ? htmlspecialchars($quizData['password']) : '') . '</password>' . PHP_EOL;
244
        $xmlContent .= '    <subnet>' . (isset($quizData['subnet']) ? htmlspecialchars($quizData['subnet']) : '') . '</subnet>' . PHP_EOL;
245
        $xmlContent .= '    <browsersecurity>' . (isset($quizData['browsersecurity']) ? htmlspecialchars($quizData['browsersecurity']) : '-') . '</browsersecurity>' . PHP_EOL;
246
        $xmlContent .= '    <delay1>' . ($quizData['delay1'] ?? 0) . '</delay1>' . PHP_EOL;
247
        $xmlContent .= '    <delay2>' . ($quizData['delay2'] ?? 0) . '</delay2>' . PHP_EOL;
248
249
        // Additional options
250
        $xmlContent .= '    <showuserpicture>' . ($quizData['showuserpicture'] ?? 0) . '</showuserpicture>' . PHP_EOL;
251
        $xmlContent .= '    <showblocks>' . ($quizData['showblocks'] ?? 0) . '</showblocks>' . PHP_EOL;
252
        $xmlContent .= '    <completionattemptsexhausted>' . ($quizData['completionattemptsexhausted'] ?? 0) . '</completionattemptsexhausted>' . PHP_EOL;
253
        $xmlContent .= '    <completionpass>' . ($quizData['completionpass'] ?? 0) . '</completionpass>' . PHP_EOL;
254
        $xmlContent .= '    <completionminattempts>' . ($quizData['completionminattempts'] ?? 0) . '</completionminattempts>' . PHP_EOL;
255
        $xmlContent .= '    <allowofflineattempts>' . ($quizData['allowofflineattempts'] ?? 0) . '</allowofflineattempts>' . PHP_EOL;
256
257
        // Subplugin, if applicable
258
        $xmlContent .= '    <subplugin_quizaccess_seb_quiz>' . PHP_EOL;
259
        $xmlContent .= '    </subplugin_quizaccess_seb_quiz>' . PHP_EOL;
260
261
        // Add question instances
262
        $xmlContent .= '    <question_instances>' . PHP_EOL;
263
        foreach ($quizData['questions'] as $question) {
264
            $xmlContent .= '      <question_instance id="' . $question['id'] . '">' . PHP_EOL;
265
            $xmlContent .= '        <slot>' . $question['id'] . '</slot>' . PHP_EOL;
266
            $xmlContent .= '        <page>1</page>' . PHP_EOL;
267
            $xmlContent .= '        <requireprevious>0</requireprevious>' . PHP_EOL;
268
            $xmlContent .= '        <questionid>' . $question['id'] . '</questionid>' . PHP_EOL;
269
            $xmlContent .= '        <questioncategoryid>' . $question['questioncategoryid'] . '</questioncategoryid>' . PHP_EOL;
270
            $xmlContent .= '        <includingsubcategories>$@NULL@$</includingsubcategories>' . PHP_EOL;
271
            $xmlContent .= '        <maxmark>' . $question['maxmark'] . '</maxmark>' . PHP_EOL;
272
            $xmlContent .= '      </question_instance>' . PHP_EOL;
273
        }
274
        $xmlContent .= '    </question_instances>' . PHP_EOL;
275
276
        // Quiz sections
277
        $xmlContent .= '    <sections>' . PHP_EOL;
278
        $xmlContent .= '      <section id="'.$quizData['id'].'">' . PHP_EOL;
279
        $xmlContent .= '        <firstslot>1</firstslot>' . PHP_EOL;
280
        $xmlContent .= '        <shufflequestions>0</shufflequestions>' . PHP_EOL;
281
        $xmlContent .= '      </section>' . PHP_EOL;
282
        $xmlContent .= '    </sections>' . PHP_EOL;
283
284
        // Add feedbacks
285
        $xmlContent .= '    <feedbacks>' . PHP_EOL;
286
        foreach ($quizData['feedbacks'] as $feedback) {
287
            $xmlContent .= '      <feedback id="'.$quizData['id'].'">' . PHP_EOL;
288
            $xmlContent .= '        <feedbacktext>' . htmlspecialchars($feedback['feedbacktext']) . '</feedbacktext>' . PHP_EOL;
289
            $xmlContent .= '        <feedbacktextformat>1</feedbacktextformat>' . PHP_EOL;
290
            $xmlContent .= '        <mingrade>' . $feedback['mingrade'] . '</mingrade>' . PHP_EOL;
291
            $xmlContent .= '        <maxgrade>' . $feedback['maxgrade'] . '</maxgrade>' . PHP_EOL;
292
            $xmlContent .= '      </feedback>' . PHP_EOL;
293
        }
294
        $xmlContent .= '    </feedbacks>' . PHP_EOL;
295
296
        // Complete with placeholders for attempts and grades
297
        $xmlContent .= '    <overrides>' . PHP_EOL . '    </overrides>' . PHP_EOL;
298
        $xmlContent .= '    <grades>' . PHP_EOL . '    </grades>' . PHP_EOL;
299
        $xmlContent .= '    <attempts>' . PHP_EOL . '    </attempts>' . PHP_EOL;
300
301
        // Close the activity tag
302
        $xmlContent .= '  </quiz>' . PHP_EOL;
303
        $xmlContent .= '</activity>' . PHP_EOL;
304
305
        // Save the XML file
306
        $xmlFile = $destinationDir . '/quiz.xml';
307
        if (file_put_contents($xmlFile, $xmlContent) === false) {
308
            throw new Exception(get_lang('ErrorCreatingQuizXml'));
309
        }
310
    }
311
312
    /**
313
     * Exports a question in XML format.
314
     */
315
    public function exportQuestion(array $question): string
316
    {
317
        $xmlContent = '      <question id="' . ($question['id'] ?? '0') . '">' . PHP_EOL;
318
        $xmlContent .= '        <parent>0</parent>' . PHP_EOL;
319
        $xmlContent .= '        <name>' . htmlspecialchars($question['questiontext'] ?? 'No question text') . '</name>' . PHP_EOL;
320
        $xmlContent .= '        <questiontext>' . htmlspecialchars($question['questiontext'] ?? 'No question text') . '</questiontext>' . PHP_EOL;
321
        $xmlContent .= '        <questiontextformat>1</questiontextformat>' . PHP_EOL;
322
        $xmlContent .= '        <generalfeedback></generalfeedback>' . PHP_EOL;
323
        $xmlContent .= '        <generalfeedbackformat>1</generalfeedbackformat>' . PHP_EOL;
324
        $xmlContent .= '        <defaultmark>' . ($question['maxmark'] ?? '0') . '</defaultmark>' . PHP_EOL;
325
        $xmlContent .= '        <penalty>0.3333333</penalty>' . PHP_EOL;
326
        $xmlContent .= '        <qtype>' . htmlspecialchars(str_replace('_nosingle', '', $question['qtype']) ?? 'unknown') . '</qtype>' . PHP_EOL;
327
        $xmlContent .= '        <length>1</length>' . PHP_EOL;
328
        $xmlContent .= '        <stamp>moodle+' . time() . '+QUESTIONSTAMP</stamp>' . PHP_EOL;
329
        $xmlContent .= '        <version>moodle+' . time() . '+VERSIONSTAMP</version>' . PHP_EOL;
330
        $xmlContent .= '        <hidden>0</hidden>' . PHP_EOL;
331
        $xmlContent .= '        <timecreated>' . time() . '</timecreated>' . PHP_EOL;
332
        $xmlContent .= '        <timemodified>' . time() . '</timemodified>' . PHP_EOL;
333
        $xmlContent .= '        <createdby>2</createdby>' . PHP_EOL;
334
        $xmlContent .= '        <modifiedby>2</modifiedby>' . PHP_EOL;
335
336
        // Add question type-specific content
337
        switch ($question['qtype']) {
338
            case 'multichoice':
339
                $xmlContent .= $this->exportMultichoiceQuestion($question);
340
                break;
341
            case 'multichoice_nosingle':
342
                $xmlContent .= $this->exportMultichoiceNosingleQuestion($question);
343
                break;
344
            case 'truefalse':
345
                $xmlContent .= $this->exportTrueFalseQuestion($question);
346
                break;
347
            case 'shortanswer':
348
                $xmlContent .= $this->exportShortAnswerQuestion($question);
349
                break;
350
            case 'match':
351
                $xmlContent .= $this->exportMatchQuestion($question);
352
                break;
353
        }
354
355
        $xmlContent .= '      </question>' . PHP_EOL;
356
357
        return $xmlContent;
358
    }
359
360
    /**
361
     * Exports a multiple-choice question in XML format.
362
     */
363
    private function exportMultichoiceQuestion(array $question): string
364
    {
365
        $xmlContent = '        <plugin_qtype_multichoice_question>' . PHP_EOL;
366
        $xmlContent .= '          <answers>' . PHP_EOL;
367
        foreach ($question['answers'] as $answer) {
368
            $xmlContent .= $this->exportAnswer($answer);
369
        }
370
        $xmlContent .= '          </answers>' . PHP_EOL;
371
        $xmlContent .= '          <multichoice id="' . ($question['id'] ?? '0') . '">' . PHP_EOL;
372
        $xmlContent .= '            <layout>0</layout>' . PHP_EOL;
373
        $xmlContent .= '            <single>1</single>' . PHP_EOL;
374
        $xmlContent .= '            <shuffleanswers>1</shuffleanswers>' . PHP_EOL;
375
        $xmlContent .= '            <correctfeedback>Your answer is correct.</correctfeedback>' . PHP_EOL;
376
        $xmlContent .= '            <correctfeedbackformat>1</correctfeedbackformat>' . PHP_EOL;
377
        $xmlContent .= '            <partiallycorrectfeedback>Your answer is partially correct.</partiallycorrectfeedback>' . PHP_EOL;
378
        $xmlContent .= '            <partiallycorrectfeedbackformat>1</partiallycorrectfeedbackformat>' . PHP_EOL;
379
        $xmlContent .= '            <incorrectfeedback>Your answer is incorrect.</incorrectfeedback>' . PHP_EOL;
380
        $xmlContent .= '            <incorrectfeedbackformat>1</incorrectfeedbackformat>' . PHP_EOL;
381
        $xmlContent .= '            <answernumbering>abc</answernumbering>' . PHP_EOL;
382
        $xmlContent .= '            <shownumcorrect>1</shownumcorrect>' . PHP_EOL;
383
        $xmlContent .= '          </multichoice>' . PHP_EOL;
384
        $xmlContent .= '        </plugin_qtype_multichoice_question>' . PHP_EOL;
385
386
        return $xmlContent;
387
    }
388
389
    /**
390
     * Exports a multiple-choice question with single=0 in XML format.
391
     */
392
    private function exportMultichoiceNosingleQuestion(array $question): string
393
    {
394
        // Similar structure to exportMultichoiceQuestion, but with single=0
395
        $xmlContent = str_replace('<single>1</single>', '<single>0</single>', $this->exportMultichoiceQuestion($question));
396
        return $xmlContent;
397
    }
398
399
    /**
400
     * Exports a true/false question in XML format.
401
     */
402
    private function exportTrueFalseQuestion(array $question): string
403
    {
404
        $xmlContent = '        <plugin_qtype_truefalse_question>' . PHP_EOL;
405
        $xmlContent .= '          <answers>' . PHP_EOL;
406
        foreach ($question['answers'] as $answer) {
407
            $xmlContent .= $this->exportAnswer($answer);
408
        }
409
        $xmlContent .= '          </answers>' . PHP_EOL;
410
        $xmlContent .= '          <truefalse id="' . ($question['id'] ?? '0') . '">' . PHP_EOL;
411
        $xmlContent .= '            <trueanswer>' . ($question['answers'][0]['id'] ?? '0') . '</trueanswer>' . PHP_EOL;
412
        $xmlContent .= '            <falseanswer>' . ($question['answers'][1]['id'] ?? '0') . '</falseanswer>' . PHP_EOL;
413
        $xmlContent .= '          </truefalse>' . PHP_EOL;
414
        $xmlContent .= '        </plugin_qtype_truefalse_question>' . PHP_EOL;
415
416
        return $xmlContent;
417
    }
418
419
    /**
420
     * Exports a short answer question in XML format.
421
     */
422
    private function exportShortAnswerQuestion(array $question): string
423
    {
424
        $xmlContent = '        <plugin_qtype_shortanswer_question>' . PHP_EOL;
425
        $xmlContent .= '          <answers>' . PHP_EOL;
426
        foreach ($question['answers'] as $answer) {
427
            $xmlContent .= $this->exportAnswer($answer);
428
        }
429
        $xmlContent .= '          </answers>' . PHP_EOL;
430
        $xmlContent .= '          <shortanswer id="' . ($question['id'] ?? '0') . '">' . PHP_EOL;
431
        $xmlContent .= '            <usecase>0</usecase>' . PHP_EOL;
432
        $xmlContent .= '          </shortanswer>' . PHP_EOL;
433
        $xmlContent .= '        </plugin_qtype_shortanswer_question>' . PHP_EOL;
434
435
        return $xmlContent;
436
    }
437
438
    /**
439
     * Exports a matching question in XML format.
440
     */
441
    private function exportMatchQuestion(array $question): string
442
    {
443
        $xmlContent = '        <plugin_qtype_match_question>' . PHP_EOL;
444
        $xmlContent .= '          <matchoptions id="' . htmlspecialchars($question['id'] ?? '0') . '">' . PHP_EOL;
445
        $xmlContent .= '            <shuffleanswers>1</shuffleanswers>' . PHP_EOL;
446
        $xmlContent .= '            <correctfeedback>' . htmlspecialchars($question['correctfeedback'] ?? '') . '</correctfeedback>' . PHP_EOL;
447
        $xmlContent .= '            <correctfeedbackformat>0</correctfeedbackformat>' . PHP_EOL;
448
        $xmlContent .= '            <partiallycorrectfeedback>' . htmlspecialchars($question['partiallycorrectfeedback'] ?? '') . '</partiallycorrectfeedback>' . PHP_EOL;
449
        $xmlContent .= '            <partiallycorrectfeedbackformat>0</partiallycorrectfeedbackformat>' . PHP_EOL;
450
        $xmlContent .= '            <incorrectfeedback>' . htmlspecialchars($question['incorrectfeedback'] ?? '') . '</incorrectfeedback>' . PHP_EOL;
451
        $xmlContent .= '            <incorrectfeedbackformat>0</incorrectfeedbackformat>' . PHP_EOL;
452
        $xmlContent .= '            <shownumcorrect>0</shownumcorrect>' . PHP_EOL;
453
        $xmlContent .= '          </matchoptions>' . PHP_EOL;
454
        $xmlContent .= '          <matches>' . PHP_EOL;
455
456
        $res = FillBlanks::getAnswerInfo($question['answers'][0]['text']);
457
        $words = $res['words'];
458
        $common_words = $res['common_words'];
459
460
        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...
461
            $answer = htmlspecialchars(trim(strip_tags($common_words[$i])));
462
            if (!empty(trim($answer))) {
463
                $xmlContent .= '            <match id="' . ($i + 1) . '">' . PHP_EOL;
464
                $xmlContent .= '              <questiontext>' . $answer . '</questiontext>' . PHP_EOL;
465
                $xmlContent .= '              <questiontextformat>0</questiontextformat>' . PHP_EOL;
466
                $xmlContent .= '              <answertext>' . htmlspecialchars(explode('|', $words[$i])[0]) . '</answertext>' . PHP_EOL;
467
                $xmlContent .= '            </match>' . PHP_EOL;
468
            }
469
        }
470
471
        $xmlContent .= '          </matches>' . PHP_EOL;
472
        $xmlContent .= '        </plugin_qtype_match_question>' . PHP_EOL;
473
474
        return $xmlContent;
475
    }
476
477
    /**
478
     * Exports an answer in XML format.
479
     */
480
    private function exportAnswer(array $answer): string
481
    {
482
        return '            <answer id="' . ($answer['id'] ?? '0') . '">' . PHP_EOL .
483
            '              <answertext>' . htmlspecialchars($answer['text'] ?? 'No answer text') . '</answertext>' . PHP_EOL .
484
            '              <answerformat>1</answerformat>' . PHP_EOL .
485
            '              <fraction>' . ($answer['fraction'] ?? '0') . '</fraction>' . PHP_EOL .
486
            '              <feedback>' . htmlspecialchars($answer['feedback'] ?? '') . '</feedback>' . PHP_EOL .
487
            '              <feedbackformat>1</feedbackformat>' . PHP_EOL .
488
            '            </answer>' . PHP_EOL;
489
    }
490
}
491
492