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

QuizExport   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 483
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 49
eloc 303
c 3
b 0
f 0
dl 0
loc 483
rs 8.48

15 Methods

Rating   Name   Duplication   Size   Complexity  
A export() 0 20 1
B getData() 0 65 4
B exportQuestion() 0 43 6
A getAnswersForQuestion() 0 18 5
B mapQuestionType() 0 10 7
A exportMatchQuestion() 0 34 3
B createQuizXml() 0 106 7
A exportMultichoiceNosingleQuestion() 0 6 1
A getDefaultCategoryId() 0 3 1
A getQuestionsForQuiz() 0 21 4
A exportAnswer() 0 9 1
A exportMultichoiceQuestion() 0 24 2
A exportShortAnswerQuestion() 0 14 2
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
     * 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