Completed
Pull Request — 1.11.x (#1352)
by José
38:59
created

MoodleImport::traverseArray()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 4
nop 2
dl 0
loc 12
rs 9.2
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Class MoodleImport
6
 *
7
 * @author José Loguercio <[email protected]>
8
 * @package chamilo.library
9
 */
10
11
class MoodleImport
12
{
13
    /**
14
     * Read and validate the moodleFile
15
     *
16
     * @param resource $uploadedFile *.* mbz file moodle course backup
17
     * @return bool
18
     */
19
    public function readMoodleFile($uploadedFile)
20
    {
21
        $file = $uploadedFile['tmp_name'];
22
23
        if (is_file($file) && is_readable($file)) {
24
            $package = new PclZip($file);
25
            $packageContent = $package->listContent();
26
            $mainFileKey = 0;
27
            foreach ($packageContent as $index => $value) {
28
                if ($value['filename'] == 'moodle_backup.xml') {
29
                    $mainFileKey = $index;
30
                    break;
31
                }
32
            }
33
34
            if (!$mainFileKey) {
35
                Display::addFlash(Display::return_message(get_lang('FailedToImportThisIsNotAMoodleFile'), 'error'));
36
            }
37
38
            $folder = api_get_unique_id();
39
            $destinationDir = api_get_path(SYS_ARCHIVE_PATH).$folder;
40
            $coursePath = api_get_course_path();
41
            $courseInfo = api_get_course_info();
42
43
            mkdir($destinationDir, api_get_permissions_for_new_directories(), true);
44
45
            $package->extract(
46
                PCLZIP_OPT_PATH,
47
                $destinationDir
48
            );
49
50
            $xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
51
52
            $doc = new DOMDocument();
53
            $res = @$doc->loadXML($xml);
54
            if ($res) {
55
                $activities = $doc->getElementsByTagName('activity');
56
                foreach ($activities as $activity) {
57
                    if ($activity->childNodes->length) {
58
                        $currentItem = [];
59
60
                        foreach ($activity->childNodes as $item) {
61
                            $currentItem[$item->nodeName] = $item->nodeValue;
62
                        }
63
64
                        $moduleName = isset($currentItem['modulename']) ? $currentItem['modulename'] : false;
65
                        switch ($moduleName) {
66
                            case 'forum':
67
                                require_once '../forum/forumfunction.inc.php';
68
                                $catForumValues = [];
69
70
                                // Read the current forum module xml.
71
                                $moduleDir = $currentItem['directory'];
72
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
73
                                $moduleValues = $this->readForumModule($moduleXml);
74
75
                                // Create a Forum category based on Moodle forum type.
76
                                $catForumValues['forum_category_title'] = $moduleValues['type'];
77
                                $catForumValues['forum_category_comment'] = '';
78
                                $catId = store_forumcategory($catForumValues, [], false);
79
                                $forumValues = [];
80
                                $forumValues['forum_title'] = $moduleValues['name'];
81
                                $forumValues['forum_image'] = '';
82
                                $forumValues['forum_comment'] = $moduleValues['intro'];
83
                                $forumValues['forum_category'] = $catId;
84
                                $forumValues['moderated'] = 0;
85
86
                                store_forum($forumValues);
87
                                break;
88
                            case 'quiz':
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
89
90
                                // Read the current quiz module xml.
91
                                // The quiz case is the very complicate process of all the import.
92
                                // Please if you want to review the script, try to see the readingXML functions.
93
                                // The readingXML functions in this clases do all the mayor work here.
94
95
                                $moduleDir = $currentItem['directory'];
96
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
97
                                $questionsXml = @file_get_contents($destinationDir.'/questions.xml');
98
                                $moduleValues = $this->readQuizModule($moduleXml);
99
100
                                // At this point we got all the prepared resources from Moodle file
101
                                // $moduleValues variable contains all the necesary info to the quiz import
102
                                // var_dump($moduleValues); // <-- uncomment this to see the final array
103
104
                                // Lets do this ...
105
                                $exercise = new Exercise();
106
                                $exercise->updateTitle(Exercise::format_title_variable($moduleValues['name']));
107
                                $exercise->updateDescription($moduleValues['intro']);
108
                                $exercise->updateAttempts($moduleValues['attempts_number']);
109
                                $exercise->updateFeedbackType(0);
110
111
                                // Match shuffle question with chamilo
112
                                switch ($moduleValues['shufflequestions']) {
113
                                    case '0':
114
                                        $exercise->setRandom(0);
115
                                        break;
116
                                    case '1':
117
                                        $exercise->setRandom(-1);
118
                                        break;
119
                                    default:
120
                                        $exercise->setRandom(0);
121
                                }
122
                                $exercise->updateRandomAnswers($moduleValues['shuffleanswers']);
123
                                // @todo divide to minutes
124
                                $exercise->updateExpiredTime($moduleValues['timelimit']);
125
126
                                if ($moduleValues['questionsperpage'] == 1) {
127
                                    $exercise->updateType(2);
128
                                } else {
129
                                    $exercise->updateType(1);
130
                                }
131
132
                                // Create the new Quiz
133
                                $exercise->save();
134
135
                                // Ok, we got the Quiz and create it, now its time to add the Questions
136
                                foreach ($moduleValues['question_instances'] as $index => $question) {
137
                                    $questionsValues = $this->readMainQuestionsXml($questionsXml, $question['questionid']);
138
                                    $moduleValues['question_instances'][$index] = $questionsValues;
139
                                    // Set Question Type from Moodle XML element <qtype>
140
                                    $qType = $moduleValues['question_instances'][$index]['qtype'];
141
                                    // Add the matched chamilo question type to the array
142
                                    $moduleValues['question_instances'][$index]['chamilo_qtype'] = $this->matchMoodleChamiloQuestionTypes($qType);
143
                                    $questionInstance = Question::getInstance($moduleValues['question_instances'][$index]['chamilo_qtype']);
144
                                    if ($questionInstance) {
145
                                        $questionInstance->updateTitle($moduleValues['question_instances'][$index]['name']);
146
147
                                        // Replace the path from @@PLUGINFILE@@ to a correct chamilo path
148
                                        $moduleValues['question_instances'][$index]['questiontext'] = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document', $moduleValues['question_instances'][$index]['questiontext']);
149
                                        $questionInstance->updateDescription($moduleValues['question_instances'][$index]['questiontext']);
150
                                        $questionInstance->updateLevel(1);
151
                                        $questionInstance->updateCategory(0);
152
153
                                        //Save normal question if NOT media
154
                                        if ($questionInstance->type != MEDIA_QUESTION) {
155
                                            $questionInstance->save($exercise->id);
156
157
                                            // modify the exercise
158
                                            $exercise->addToList($questionInstance->id);
159
                                            $exercise->update_question_positions();
160
                                        }
161
162
                                        $questionList = $moduleValues['question_instances'][$index]['plugin_qtype_'.$qType.'_question'];
163
                                        $currentQuestion = $moduleValues['question_instances'][$index];
164
165
                                        $result = $this->processAnswers($questionList, $qType, $questionInstance, $currentQuestion);
166
                                    }
167
                                }
168
169
                                break;
170
                            case 'resource':
171
                                // Read the current resource module xml.
172
                                $moduleDir = $currentItem['directory'];
173
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
174
                                $filesXml = @file_get_contents($destinationDir.'/files.xml');
175
                                $moduleValues = $this->readResourceModule($moduleXml);
176
                                $mainFileModuleValues = $this->readMainFilesXml($filesXml, $moduleValues['contextid']);
177
                                $fileInfo = array_merge($moduleValues, $mainFileModuleValues, $currentItem);
178
                                $currentResourceFilePath = $destinationDir.'/files/';
179
                                $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
180
                                foreach (new RecursiveIteratorIterator($dirs) as $file) {
181
                                    if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
182
                                        $files = [];
183
                                        $files['file']['name'] = $fileInfo['filename'];
184
                                        $files['file']['tmp_name'] = $file->getPathname();
185
                                        $files['file']['type'] = $fileInfo['mimetype'];
186
                                        $files['file']['error'] = 0;
187
                                        $files['file']['size'] = $fileInfo['filesize'];
188
                                        $files['file']['from_file'] = true;
189
                                        $files['file']['move_file'] = true;
190
                                        $_POST['language'] = $courseInfo['language'];
191
                                        $_POST['moodle_import'] = true;
192
193
                                        DocumentManager::upload_document(
194
                                            $files,
195
                                            '/',
196
                                            $fileInfo['title'],
197
                                            '',
198
                                            null,
199
                                            null,
200
                                            true,
201
                                            true
202
                                        );
203
                                    }
204
                                }
205
206
                                break;
207
                            case 'url':
208
                                // Read the current url module xml.
209
                                $moduleDir = $currentItem['directory'];
210
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
211
                                $moduleValues = $this->readUrlModule($moduleXml);
212
                                $_POST['title'] = $moduleValues['name'];
213
                                $_POST['url'] = $moduleValues['externalurl'];
214
                                $_POST['description'] = $moduleValues['intro'];
215
                                $_POST['category_id'] = 0;
216
                                $_POST['target'] = '_blank';
217
218
                                Link::addlinkcategory("link");
219
                                break;
220
                        }
221
                    }
222
                }
223
224
                // This process will upload all question resource files
225
                $filesXml = @file_get_contents($destinationDir.'/files.xml');
226
                $mainFileModuleValues = $this->getAllQuestionFiles($filesXml);
227
                $currentResourceFilePath = $destinationDir.'/files/';
228
229
                foreach ($mainFileModuleValues as $fileInfo) {
230
                    $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
231
                    foreach (new RecursiveIteratorIterator($dirs) as $file) {
232
                        if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
233
                            $files = [];
234
                            $files['file']['name'] = $fileInfo['filename'];
235
                            $files['file']['tmp_name'] = $file->getPathname();
236
                            $files['file']['type'] = $fileInfo['mimetype'];
237
                            $files['file']['error'] = 0;
238
                            $files['file']['size'] = $fileInfo['filesize'];
239
                            $files['file']['from_file'] = true;
240
                            $files['file']['move_file'] = true;
241
                            $_POST['language'] = $courseInfo['language'];
242
                            $_POST['moodle_import'] = true;
243
244
                            DocumentManager::upload_document(
245
                                $files,
246
                                '/',
247
                                isset($fileInfo['title']) ? $fileInfo['title'] : pathinfo($fileInfo['filename'], PATHINFO_FILENAME),
248
                                '',
249
                                null,
250
                                null,
251
                                true,
252
                                true,
253
                                'file',
254
                                // This is to validate spaces as hyphens
255
                                false
256
                            );
257
                        }
258
                    }
259
                }
260
261
            } else {
262
                removeDir($destinationDir);
263
                return false;
264
            }
265
        } else {
266
            return false;
267
        }
268
269
        removeDir($destinationDir);
270
        return $packageContent[$mainFileKey];
271
    }
272
273
    /**
274
     * Read and validate the forum module XML
275
     *
276
     * @param resource $moduleXml XML file
277
     * @return mixed | array if is a valid xml file, false otherwise
278
     */
279 View Code Duplication
    public function readForumModule($moduleXml)
280
    {
281
        $moduleDoc = new DOMDocument();
282
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
283
        if ($moduleRes) {
284
            $activities = $moduleDoc->getElementsByTagName('forum');
285
            $currentItem = [];
286
            foreach ($activities as $activity) {
287
                if ($activity->childNodes->length) {
288
                    foreach ($activity->childNodes as $item) {
289
                        $currentItem[$item->nodeName] = $item->nodeValue;
290
                    }
291
                }
292
            }
293
294
            return $currentItem;
295
        }
296
297
        return false;
298
    }
299
300
    /**
301
     * Read and validate the resource module XML
302
     *
303
     * @param resource $moduleXml XML file
304
     * @return mixed | array if is a valid xml file, false otherwise
305
     */
306
    public function readResourceModule($moduleXml)
307
    {
308
        $moduleDoc = new DOMDocument();
309
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
310
        if ($moduleRes) {
311
            $activities = $moduleDoc->getElementsByTagName('resource');
312
            $mainActivity = $moduleDoc->getElementsByTagName('activity');
313
            $contextId = $mainActivity->item(0)->getAttribute('contextid');
314
            $currentItem = [];
315
            foreach ($activities as $activity) {
316
                if ($activity->childNodes->length) {
317
                    foreach ($activity->childNodes as $item) {
318
                        $currentItem[$item->nodeName] = $item->nodeValue;
319
                    }
320
                }
321
            }
322
323
            $currentItem['contextid'] = $contextId;
324
            return $currentItem;
325
        }
326
327
        return false;
328
    }
329
330
    /**
331
     * Read and validate the url module XML
332
     *
333
     * @param resource $moduleXml XML file
334
     * @return mixed | array if is a valid xml file, false otherwise
335
     */
336 View Code Duplication
    public function readUrlModule($moduleXml)
337
    {
338
        $moduleDoc = new DOMDocument();
339
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
340
        if ($moduleRes) {
341
            $activities = $moduleDoc->getElementsByTagName('url');
342
            $currentItem = [];
343
            foreach ($activities as $activity) {
344
                if ($activity->childNodes->length) {
345
                    foreach ($activity->childNodes as $item) {
346
                        $currentItem[$item->nodeName] = $item->nodeValue;
347
                    }
348
                }
349
            }
350
351
            return $currentItem;
352
        }
353
354
        return false;
355
    }
356
357
    /**
358
     * Read and validate the quiz module XML
359
     *
360
     * @param resource $moduleXml XML file
361
     * @return mixed | array if is a valid xml file, false otherwise
362
     */
363
    public function readQuizModule($moduleXml)
364
    {
365
        $moduleDoc = new DOMDocument();
366
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
367
        if ($moduleRes) {
368
            $activities = $moduleDoc->getElementsByTagName('quiz');
369
            $currentItem = [];
370
            foreach ($activities as $activity) {
371
                if ($activity->childNodes->length) {
372
                    foreach ($activity->childNodes as $item) {
373
                        $currentItem[$item->nodeName] = $item->nodeValue;
374
                    }
375
                }
376
            }
377
378
            $questions = $moduleDoc->getElementsByTagName('question_instance');
379
380
            $questionList = [];
381
            $counter = 0;
382
            foreach ($questions as $question) {
383
                if ($question->childNodes->length) {
384
                    foreach ($question->childNodes as $item) {
385
                        $questionList[$counter][$item->nodeName] = $item->nodeValue;
386
                    }
387
                    $counter++;
388
                }
389
390
            }
391
            $currentItem['question_instances'] = $questionList;
392
            return $currentItem;
393
        }
394
395
        return false;
396
    }
397
398
    /**
399
     * Search the current file resource in main Files XML
400
     *
401
     * @param resource $filesXml XML file
402
     * @param int $contextId
403
     * @return mixed | array if is a valid xml file, false otherwise
404
     */
405
    public function readMainFilesXml($filesXml, $contextId)
406
    {
407
        $moduleDoc = new DOMDocument();
408
        $moduleRes = @$moduleDoc->loadXML($filesXml);
409
        if ($moduleRes) {
410
            $activities = $moduleDoc->getElementsByTagName('file');
411
            $currentItem = [];
412
            foreach ($activities as $activity) {
413
                if ($activity->childNodes->length) {
414
                    $isThisItemThatIWant = false;
415
                    foreach ($activity->childNodes as $item) {
416
                        if (!$isThisItemThatIWant && $item->nodeName == 'contenthash') {
417
                            $currentItem['contenthash'] = $item->nodeValue;
418
                        }
419
                        if ($item->nodeName == 'contextid' && intval($item->nodeValue) == intval($contextId) && !$isThisItemThatIWant) {
420
                            $isThisItemThatIWant = true;
421
                            continue;
422
                        }
423
424
                        if ($isThisItemThatIWant && $item->nodeName == 'filename') {
425
                            $currentItem['filename'] = $item->nodeValue;
426
                        }
427
428
                        if ($isThisItemThatIWant && $item->nodeName == 'filesize') {
429
                            $currentItem['filesize'] = $item->nodeValue;
430
                        }
431
432
                        if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
433
                            break;
434
                        }
435
436 View Code Duplication
                        if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
437
                            $currentItem['mimetype'] = $item->nodeValue;
438
                            break 2;
439
                        }
440
                    }
441
                }
442
            }
443
444
            return $currentItem;
445
        }
446
447
        return false;
448
    }
449
450
    /**
451
     * Search the current quiestion resource in main Questions XML
452
     *
453
     * @param resource $questionsXml XML file
454
     * @param int $questionId
455
     * @return mixed | array if is a valid xml file, false otherwise
456
     */
457
    public function readMainQuestionsXml($questionsXml, $questionId)
458
    {
459
        $moduleDoc = new DOMDocument();
460
        $moduleRes = @$moduleDoc->loadXML($questionsXml);
461
        if ($moduleRes) {
462
            $questions = $moduleDoc->getElementsByTagName('question');
463
            $currentItem = [];
464
            foreach ($questions as $question) {
465
                if (intval($question->getAttribute('id')) == $questionId) {
466
                    if ($question->childNodes->length) {
467
                        $currentItem['questionid'] = $questionId;
468
                        $questionType = '';
469
                        foreach ($question->childNodes as $item) {
470
                            $currentItem[$item->nodeName] = $item->nodeValue;
471
                            if ($item->nodeName == 'qtype') {
472
                                $questionType = $item->nodeValue;
473
                            }
474
475
                            if ($item->nodeName == 'plugin_qtype_'.$questionType.'_question') {
476
                                $answer = $item->getElementsByTagName($this->getQuestionTypeAnswersTag($questionType));
477
                                $currentItem['plugin_qtype_'.$questionType.'_question'] = [];
478
                                for ($i = 0; $i <= $answer->length - 1; $i++) {
479
                                    $currentItem['plugin_qtype_'.$questionType.'_question'][$i]['answerid'] = $answer->item($i)->getAttribute('id');
480
                                    foreach ($answer->item($i)->childNodes as $properties) {
481
                                        $currentItem['plugin_qtype_'.$questionType.'_question'][$i][$properties->nodeName] = $properties->nodeValue;
482
                                    }
483
                                }
484
485
                                $typeValues = $item->getElementsByTagName($this->getQuestionTypeOptionsTag($questionType));
486
                                for ($i = 0; $i <= $typeValues->length - 1; $i++) {
487
                                    foreach ($typeValues->item($i)->childNodes as $properties) {
488
                                        $currentItem[$questionType.'_values'][$properties->nodeName] = $properties->nodeValue;
489
                                        if ($properties->nodeName == 'sequence') {
490
                                            $sequence = $properties->nodeValue;
491
                                            $sequenceIds = explode(',', $sequence);
492
                                            foreach ($sequenceIds as $qId) {
493
                                                $questionMatch = $this->readMainQuestionsXml($questionsXml, $qId);
494
                                                $currentItem['plugin_qtype_'.$questionType.'_question'][] = $questionMatch;
495
                                            }
496
                                        }
497
                                    }
498
                                }
499
                            }
500
                        }
501
                    }
502
                }
503
            }
504
505
            $this->traverseArray($currentItem, ['#text', 'question_hints', 'tags']);
506
            return $currentItem;
507
        }
508
509
        return false;
510
    }
511
512
    /**
513
     * return the correct question type options tag
514
     *
515
     * @param string $questionType name
516
     * @return string question type tag
517
     */
518
    public function getQuestionTypeOptionsTag($questionType)
519
    {
520
        switch ($questionType) {
521
            case 'match':
522
            case 'ddmatch':
523
                return 'matchoptions';
524
                break;
525
            default:
526
                return $questionType;
527
                break;
528
        }
529
    }
530
531
    /**
532
     * return the correct question type answers tag
533
     *
534
     * @param string $questionType name
535
     * @return string question type tag
536
     */
537
    public function getQuestionTypeAnswersTag($questionType)
538
    {
539
        switch ($questionType) {
540
            case 'match':
541
            case 'ddmatch':
542
                return 'match';
543
                break;
544
            default:
545
                return 'answer';
546
                break;
547
        }
548
    }
549
550
    /**
551
     *
552
     * @param string $moodleQuestionType
553
     * @return integer Chamilo question type
554
     */
555
    public function matchMoodleChamiloQuestionTypes($moodleQuestionType)
556
    {
557
        switch ($moodleQuestionType) {
558
            case 'multichoice':
559
                return UNIQUE_ANSWER;
560
                break;
561
            case 'multianswer':
562
            case 'shortanswer':
563
            case 'match':
564
                return FILL_IN_BLANKS;
565
                break;
566
            case 'match':
567
            case 'essay':
568
                return FREE_ANSWER;
569
                break;
570
            case 'truefalse':
571
                return UNIQUE_ANSWER_NO_OPTION;
572
            break;
573
        }
574
    }
575
576
    /**
577
     * Process Moodle Answers to Chamilo
578
     *
579
     * @param array $questionList
580
     * @param string $questionType
581
     * @param object $questionInstance Question/Answer instance
582
     * @param array $currentQuestion
583
     * @return integer db response
584
     */
585
    public function processAnswers($questionList, $questionType, $questionInstance, $currentQuestion)
586
    {
587
        switch ($questionType) {
588 View Code Duplication
            case 'multichoice':
589
                $objAnswer = new Answer($questionInstance->id);
590
                $questionWeighting = 0;
591
                foreach ($questionList as $slot => $answer) {
592
                    $this->processUniqueAnswer($objAnswer, $answer, $slot + 1, $questionWeighting);
593
                }
594
595
                // saves the answers into the data base
596
                $objAnswer->save();
597
                // sets the total weighting of the question
598
                $questionInstance->updateWeighting($questionWeighting);
599
                $questionInstance->save();
600
601
                return true;
602
                break;
603
            case 'multianswer':
604
                $objAnswer = new Answer($questionInstance->id);
605
606
                $coursePath = api_get_course_path();
607
608
                $placeholder = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document', $currentQuestion['questiontext']);
609
610
                $optionsValues = [];
611
612
                foreach ($questionList as $slot => $subQuestion) {
613
                    $qtype = $subQuestion['qtype'];
614
                    $optionsValues[] = $this->processFillBlanks($objAnswer, $qtype, $subQuestion['plugin_qtype_'.$qtype.'_question'], $placeholder, $slot + 1);
615
                }
616
617
                $answerOptionsWeight = '::';
618
                $answerOptionsSize = '';
619
                $questionWeighting = 0;
620 View Code Duplication
                foreach ($optionsValues as $index => $value) {
621
                    $questionWeighting += $value['weight'];
622
                    $answerOptionsWeight .= $value['weight'].',';
623
                    $answerOptionsSize .= $value['size'].',';
624
                }
625
626
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
627
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
628
629
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
630
631
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
632
633
                // This is a minor trick to clean the question description that in a multianswer is the main placeholder
634
                $questionInstance->updateDescription('');
635
                // sets the total weighting of the question
636
                $questionInstance->updateWeighting($questionWeighting);
637
                $questionInstance->save();
638
                // saves the answers into the data base
639
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
640
                $objAnswer->save();
641
642
                return true;
643
            case 'match':
644
                $objAnswer = new Answer($questionInstance->id);
645
                $placeholder = '';
646
647
                $optionsValues = $this->processFillBlanks($objAnswer, 'match', $questionList, $placeholder, 0);
648
649
                $answerOptionsWeight = '::';
650
                $answerOptionsSize = '';
651
                $questionWeighting = 0;
652 View Code Duplication
                foreach ($optionsValues as $index => $value) {
0 ignored issues
show
Bug introduced by
The expression $optionsValues of type array|null|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
653
                    $questionWeighting += $value['weight'];
654
                    $answerOptionsWeight .= $value['weight'].',';
655
                    $answerOptionsSize .= $value['size'].',';
656
                }
657
658
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
659
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
660
661
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
662
663
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
664
665
                // sets the total weighting of the question
666
                $questionInstance->updateWeighting($questionWeighting);
667
                $questionInstance->save();
668
                // saves the answers into the data base
669
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
670
                $objAnswer->save();
671
672
                return true;
673
                break;
674
            case 'shortanswer':
675
            case 'ddmatch':
676
                $questionWeighting = $currentQuestion['defaultmark'];
677
                $questionInstance->updateWeighting($questionWeighting);
678
                $questionInstance->updateDescription(get_lang('ThisQuestionIsNotSupportedYet'));
679
                $questionInstance->save();
680
                return false;
681
                break;
682
            case 'essay':
683
                $questionWeighting = $currentQuestion['defaultmark'];
684
                $questionInstance->updateWeighting($questionWeighting);
685
                $questionInstance->save();
686
                return true;
687
                break;
688 View Code Duplication
            case 'truefalse':
689
                $objAnswer = new Answer($questionInstance->id);
690
                $questionWeighting = 0;
691
                foreach ($questionList as $slot => $answer) {
692
                    $this->processTrueFalse($objAnswer, $answer, $slot + 1, $questionWeighting);
693
                }
694
695
                // saves the answers into the data base
696
                $objAnswer->save();
697
                // sets the total weighting of the question
698
                $questionInstance->updateWeighting($questionWeighting);
699
                $questionInstance->save();
700
                return false;
701
                break;
702
            default:
703
                return false;
704
                break;
705
        }
706
    }
707
708
    /**
709
     * Process Chamilo Unique Answer
710
     *
711
     * @param object $objAnswer
712
     * @param array $answerValues
713
     * @param integer $position
714
     * @param integer $questionWeighting
715
     * @return integer db response
716
     */
717 View Code Duplication
    public function processUniqueAnswer($objAnswer, $answerValues, $position, &$questionWeighting)
718
    {
719
        $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
720
        $answer = $answerValues['answertext'];
721
        $comment = $answerValues['feedback'];
722
        $weighting = $answerValues['fraction'];
723
        $weighting = abs($weighting);
724
        if ($weighting > 0) {
725
            $questionWeighting += $weighting;
726
        }
727
        $goodAnswer =  $correct ? true : false;
728
729
        $objAnswer->createAnswer(
730
            $answer,
731
            $goodAnswer,
732
            $comment,
733
            $weighting,
734
            $position,
735
            null,
736
            null,
737
            ''
738
        );
739
    }
740
741
    /**
742
     * Process Chamilo True False
743
     *
744
     * @param object $objAnswer
745
     * @param array $answerValues
746
     * @param integer $position
747
     * @param integer $questionWeighting
748
     * @return integer db response
749
     */
750 View Code Duplication
    public function processTrueFalse($objAnswer, $answerValues, $position, &$questionWeighting)
751
    {
752
        $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
753
        $answer = $answerValues['answertext'];
754
        $comment = $answerValues['feedback'];
755
        $weighting = $answerValues['fraction'];
756
        $weighting = abs($weighting);
757
        if ($weighting > 0) {
758
            $questionWeighting += $weighting;
759
        }
760
        $goodAnswer =  $correct ? true : false;
761
762
        $objAnswer->createAnswer(
763
            $answer,
764
            $goodAnswer,
765
            $comment,
766
            $weighting,
767
            $position,
768
            null,
769
            null,
770
            ''
771
        );
772
    }
773
774
    /**
775
     * Process Chamilo FillBlanks
776
     *
777
     * @param object $objAnswer
778
     * @param array $questionType
779
     * @param array $answerValues
780
     * @param string $placeholder
781
     * @param integer $position
782
     * @return integer db response
783
     */
784
    public function processFillBlanks($objAnswer, $questionType, $answerValues, &$placeholder, $position)
785
    {
786
787
        $coursePath = api_get_course_path();
788
789
        switch ($questionType) {
790
            case 'multichoice':
791
                $optionsValues = [];
792
793
                $correctAnswer = '';
794
                $othersAnswer = '';
795
                foreach ($answerValues as $answer) {
796
                    $correct = intval($answer['fraction']);
797
                    if ($correct) {
798
                        $correctAnswer .= $answer['answertext'].'|';
799
                        $optionsValues['weight'] = $answer['fraction'];
800
                        $optionsValues['size'] = '200';
801
                    } else {
802
                        $othersAnswer .= $answer['answertext'].'|';
803
                    }
804
                }
805
                $currentAnswers = $correctAnswer.$othersAnswer;
806
                $currentAnswers = '['.substr($currentAnswers, 0, -1).']';
807
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
808
809
                return $optionsValues;
810
811
                break;
812
            case 'shortanswer':
813
                $optionsValues = [];
814
815
                $correctAnswer = '';
816
817
                foreach ($answerValues as $answer) {
818
                    $correct = intval($answer['fraction']);
819
                    if ($correct) {
820
                        $correctAnswer .= $answer['answertext'];
821
                        $optionsValues['weight'] = $answer['fraction'];
822
                        $optionsValues['size'] = '200';
823
                    }
824
                }
825
826
                $currentAnswers = '['.$correctAnswer.']';
827
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
828
829
                return $optionsValues;
830
831
                break;
832
            case 'match':
833
                $answers = [];
834
                // Here first we need to extract all the possible answers
835
                foreach ($answerValues as $slot => $answer) {
836
                    $answers[$slot] = $answer['answertext'];
837
                }
838
839
                // Now we set the order of the values matching the correct answer and set it to the first element
840
                $optionsValues = [];
841
                foreach ($answerValues as $slot => $answer) {
842
                    $correctAnswer = '';
843
                    $othersAnswers = '';
844
                    $correctAnswer .= $answer['answertext'].'|';
845
846
                    foreach ($answers as $other) {
847
                        if ($other !== $answer['answertext']) {
848
                            $othersAnswers .= $other.'|';
849
                        }
850
                    }
851
852
                    $optionsValues[$slot]['weight'] = 1;
853
                    $optionsValues[$slot]['size'] = '200';
854
855
                    $currentAnswers = $correctAnswer.$othersAnswers;
856
                    $currentAnswers = '['.substr($currentAnswers, 0, -1).'] ';
857
                    $answer['questiontext'] = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document', $answer['questiontext']);
858
859
                    $placeholder .= '<p> ' . strip_tags($answer['questiontext']).' '.$currentAnswers . ' </p>';
860
                }
861
862
                return $optionsValues;
863
864
                break;
865
            default:
866
                return false;
867
                break;
868
        }
869
    }
870
871
    /**
872
     * get All files associated with a question
873
     *
874
     * @param $filesXml
875
     * @return array
876
     */
877
    public function getAllQuestionFiles($filesXml)
878
    {
879
        $moduleDoc = new DOMDocument();
880
        $moduleRes = @$moduleDoc->loadXML($filesXml);
881
        $allFiles = [];
882
        if ($moduleRes) {
883
            $activities = $moduleDoc->getElementsByTagName('file');
884
            foreach ($activities as $activity) {
885
886
                $currentItem = [];
887
888
                $thisIsAnInvalidItem = false;
889
890
                if ($activity->childNodes->length) {
891
                    foreach ($activity->childNodes as $item ) {
892
                        if ($item->nodeName == 'component' && $item->nodeValue == 'mod_resource') {
893
                            $thisIsAnInvalidItem = true;
894
                        }
895
896
                        if ($item->nodeName == 'contenthash') {
897
                            $currentItem['contenthash'] = $item->nodeValue;
898
                        }
899
900
                        if ($item->nodeName == 'filename') {
901
                            $currentItem['filename'] = $item->nodeValue;
902
                        }
903
904
                        if ($item->nodeName == 'filesize') {
905
                            $currentItem['filesize'] = $item->nodeValue;
906
                        }
907
908
                        if ($item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
909
                            $thisIsAnInvalidItem = true;
910
                        }
911
912 View Code Duplication
                        if ($item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
913
                            $currentItem['mimetype'] = $item->nodeValue;
914
                        }
915
                    }
916
                }
917
918
                if (!$thisIsAnInvalidItem) {
919
                    $allFiles[] = $currentItem;
920
                }
921
            }
922
        }
923
924
        return $allFiles;
925
    }
926
927
928
    /**
929
     * Litle utility to delete the unuseful tags
930
     *
931
     * @param $array
932
     * @param $keys
933
     */
934
    public function traverseArray(&$array, $keys)
935
    {
936
        foreach ($array as $key => &$value) {
937
            if (is_array($value)) {
938
                $this->traverseArray($value, $keys);
939
            } else {
940
                if (in_array($key, $keys)){
941
                    unset($array[$key]);
942
                }
943
            }
944
        }
945
    }
946
947
}