Completed
Push — master ( 176486...dcf38a )
by Julito
52:32 queued 19:58
created

MoodleImport::fixPathInText()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 2
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * Class MoodleImport
6
 *
7
 * @author José Loguercio <[email protected]>,
8
 * @author Julio Montoya <[email protected]>
9
 *
10
 * @package chamilo.library
11
 */
12
13
class MoodleImport
14
{
15
    /**
16
     * Import moodle file
17
     *
18
     * @param resource $uploadedFile *.* mbz file moodle course backup
19
     * @return bool
20
     */
21
    public function import($uploadedFile)
22
    {
23
        $file = $uploadedFile['tmp_name'];
24
25
        if (is_file($file) && is_readable($file)) {
26
            $package = new PclZip($file);
27
            $packageContent = $package->listContent();
28
            $mainFileKey = 0;
29
            foreach ($packageContent as $index => $value) {
30
                if ($value['filename'] == 'moodle_backup.xml') {
31
                    $mainFileKey = $index;
32
                    break;
33
                }
34
            }
35
36
            if (!$mainFileKey) {
37
                Display::addFlash(
38
                    Display::return_message(
39
                        get_lang('FailedToImportThisIsNotAMoodleFile'),
40
                        'error'
41
                    )
42
                );
43
            }
44
45
            $folder = api_get_unique_id();
46
            $destinationDir = api_get_path(SYS_ARCHIVE_PATH) . $folder;
47
            $coursePath = api_get_course_path();
48
            $sessionId = api_get_session_id();
49
            $groupId = api_get_group_id();
50
            $documentPath = api_get_path(SYS_COURSE_PATH) . $coursePath . '/document';
51
            $courseInfo = api_get_course_info();
52
53
            mkdir($destinationDir, api_get_permissions_for_new_directories(), true);
54
55
            create_unexisting_directory(
56
                $courseInfo,
57
                api_get_user_id(),
58
                $sessionId,
59
                $groupId,
60
                null,
61
                $documentPath,
62
                '/moodle',
63
                'Moodle Docs',
64
                0
65
            );
66
67
            $package->extract(
68
                PCLZIP_OPT_PATH,
69
                $destinationDir
70
            );
71
72
            // This process will upload all question resource files
73
            $filesXml = @file_get_contents($destinationDir.'/files.xml');
74
            $mainFileModuleValues = $this->getAllQuestionFiles($filesXml);
75
            $currentResourceFilePath = $destinationDir.'/files/';
76
77
            $importedFiles = [];
78
79
            foreach ($mainFileModuleValues as $fileInfo) {
80
                $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
81
                foreach (new RecursiveIteratorIterator($dirs) as $file) {
82
                    if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
83
                        $files = [];
84
                        $files['file']['name'] = $fileInfo['filename'];
85
                        $files['file']['tmp_name'] = $file->getPathname();
86
                        $files['file']['type'] = $fileInfo['mimetype'];
87
                        $files['file']['error'] = 0;
88
                        $files['file']['size'] = $fileInfo['filesize'];
89
                        $files['file']['from_file'] = true;
90
                        $files['file']['move_file'] = true;
91
                        $_POST['language'] = $courseInfo['language'];
92
                        $_POST['moodle_import'] = true;
93
94
                        $data = DocumentManager::upload_document(
95
                            $files,
96
                            '/moodle',
97
                            isset($fileInfo['title']) ? $fileInfo['title'] : pathinfo($fileInfo['filename'], PATHINFO_FILENAME),
98
                            '',
99
                            null,
100
                            null,
101
                            true,
102
                            true,
103
                            'file',
104
                            // This is to validate spaces as hyphens
105
                            false
106
                        );
107
108
                        if ($data) {
109
                            $importedFiles[$fileInfo['filename']] = basename($data['path']);
110
                        }
111
                    }
112
                }
113
            }
114
115
            $xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
116
117
            $doc = new DOMDocument();
118
            $res = @$doc->loadXML($xml);
119
            if ($res) {
120
                $activities = $doc->getElementsByTagName('activity');
121
                foreach ($activities as $activity) {
122
                    if ($activity->childNodes->length) {
123
                        $currentItem = [];
124
125
                        foreach ($activity->childNodes as $item) {
126
                            $currentItem[$item->nodeName] = $item->nodeValue;
127
                        }
128
129
                        $moduleName = isset($currentItem['modulename']) ? $currentItem['modulename'] : false;
130
                        switch ($moduleName) {
131
                            case 'forum':
132
                                require_once '../forum/forumfunction.inc.php';
133
                                $catForumValues = [];
134
135
                                // Read the current forum module xml.
136
                                $moduleDir = $currentItem['directory'];
137
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
138
                                $moduleValues = $this->readForumModule($moduleXml);
139
140
                                // Create a Forum category based on Moodle forum type.
141
                                $catForumValues['forum_category_title'] = $moduleValues['type'];
142
                                $catForumValues['forum_category_comment'] = '';
143
                                $catId = store_forumcategory($catForumValues, $courseInfo, false);
144
                                $forumValues = [];
145
                                $forumValues['forum_title'] = $moduleValues['name'];
146
                                $forumValues['forum_image'] = '';
147
                                $forumValues['forum_comment'] = $moduleValues['intro'];
148
                                $forumValues['forum_category'] = $catId;
149
                                $forumValues['moderated'] = 0;
150
151
                                store_forum($forumValues, $courseInfo);
152
                                break;
153
                            case 'quiz':
154
                                // Read the current quiz module xml.
155
                                // The quiz case is the very complicate process of all the import.
156
                                // Please if you want to review the script, try to see the readingXML functions.
157
                                // The readingXML functions in this clases do all the mayor work here.
158
159
                                $moduleDir = $currentItem['directory'];
160
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
161
                                $questionsXml = @file_get_contents($destinationDir.'/questions.xml');
162
                                $moduleValues = $this->readQuizModule($moduleXml);
163
164
                                // At this point we got all the prepared resources from Moodle file
165
                                // $moduleValues variable contains all the necesary info to the quiz import
166
                                // var_dump($moduleValues); // <-- uncomment this to see the final array
167
168
                                $exercise = new Exercise($courseInfo['real_id']);
169
                                $title = Exercise::format_title_variable($moduleValues['name']);
170
                                $exercise->updateTitle($title);
171
                                $exercise->updateDescription($moduleValues['intro']);
172
                                $exercise->updateAttempts($moduleValues['attempts_number']);
173
                                $exercise->updateFeedbackType(0);
174
175
                                // Match shuffle question with chamilo
176
                                switch ($moduleValues['shufflequestions']) {
177
                                    case '0':
178
                                        $exercise->setRandom(0);
179
                                        break;
180
                                    case '1':
181
                                        $exercise->setRandom(-1);
182
                                        break;
183
                                    default:
184
                                        $exercise->setRandom(0);
185
                                }
186
                                $exercise->updateRandomAnswers($moduleValues['shuffleanswers']);
187
                                // @todo divide to minutes
188
                                $exercise->updateExpiredTime($moduleValues['timelimit']);
189
190
                                if ($moduleValues['questionsperpage'] == 1) {
191
                                    $exercise->updateType(2);
192
                                } else {
193
                                    $exercise->updateType(1);
194
                                }
195
196
                                // Create the new Quiz
197
                                $exercise->save();
198
199
                                // Ok, we got the Quiz and create it, now its time to add the Questions
200
                                foreach ($moduleValues['question_instances'] as $index => $question) {
201
                                    $questionsValues = $this->readMainQuestionsXml($questionsXml, $question['questionid']);
202
                                    $moduleValues['question_instances'][$index] = $questionsValues;
203
                                    // Set Question Type from Moodle XML element <qtype>
204
                                    $qType = $moduleValues['question_instances'][$index]['qtype'];
205
                                    // Add the matched chamilo question type to the array
206
                                    $moduleValues['question_instances'][$index]['chamilo_qtype'] = $this->matchMoodleChamiloQuestionTypes($qType);
207
                                    $questionInstance = Question::getInstance($moduleValues['question_instances'][$index]['chamilo_qtype']);
208
                                    if ($questionInstance) {
209
                                        $questionInstance->updateTitle($moduleValues['question_instances'][$index]['name']);
210
                                        $questionText = $moduleValues['question_instances'][$index]['questiontext'];
211
212
                                        // Replace the path from @@PLUGINFILE@@ to a correct chamilo path
213
                                        $questionText = str_replace(
214
                                            '@@PLUGINFILE@@',
215
                                            '/courses/'.$coursePath.'/document/moodle',
216
                                            $questionText
217
                                        );
218
219
                                        if ($importedFiles) {
220
                                            foreach ($importedFiles as $old => $new) {
221
                                                $questionText = str_replace($old, $new, $questionText);
222
                                            }
223
                                        }
224
225
                                        $questionInstance->updateDescription($questionText);
226
                                        $questionInstance->updateLevel(1);
227
                                        $questionInstance->updateCategory(0);
228
229
                                        //Save normal question if NOT media
230
                                        if ($questionInstance->type != MEDIA_QUESTION) {
231
                                            $questionInstance->save($exercise->id);
232
233
                                            // modify the exercise
234
                                            $exercise->addToList($questionInstance->id);
235
                                            $exercise->update_question_positions();
236
                                        }
237
238
                                        $questionList = $moduleValues['question_instances'][$index]['plugin_qtype_'.$qType.'_question'];
239
                                        $currentQuestion = $moduleValues['question_instances'][$index];
240
241
                                        $this->processAnswers(
242
                                            $questionList,
243
                                            $qType,
244
                                            $questionInstance,
245
                                            $currentQuestion,
246
                                            $importedFiles
247
                                        );
248
                                    }
249
                                }
250
                                break;
251
                            case 'resource':
252
                                // Read the current resource module xml.
253
                                $moduleDir = $currentItem['directory'];
254
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
255
                                $filesXml = @file_get_contents($destinationDir.'/files.xml');
256
                                $moduleValues = $this->readResourceModule($moduleXml);
257
                                $mainFileModuleValues = $this->readMainFilesXml($filesXml, $moduleValues['contextid']);
258
                                $fileInfo = array_merge($moduleValues, $mainFileModuleValues, $currentItem);
259
                                $currentResourceFilePath = $destinationDir.'/files/';
260
                                $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
261
                                foreach (new RecursiveIteratorIterator($dirs) as $file) {
262
                                    if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
263
                                        $files = [];
264
                                        $files['file']['name'] = $fileInfo['filename'];
265
                                        $files['file']['tmp_name'] = $file->getPathname();
266
                                        $files['file']['type'] = $fileInfo['mimetype'];
267
                                        $files['file']['error'] = 0;
268
                                        $files['file']['size'] = $fileInfo['filesize'];
269
                                        $files['file']['from_file'] = true;
270
                                        $files['file']['move_file'] = true;
271
                                        $_POST['language'] = $courseInfo['language'];
272
                                        $_POST['moodle_import'] = true;
273
274
                                        DocumentManager::upload_document(
275
                                            $files,
276
                                            '/moodle',
277
                                            $fileInfo['title'],
278
                                            '',
279
                                            null,
280
                                            null,
281
                                            true,
282
                                            true
283
                                        );
284
                                    }
285
                                }
286
287
                                break;
288
                            case 'url':
289
                                // Read the current url module xml.
290
                                $moduleDir = $currentItem['directory'];
291
                                $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
292
                                $moduleValues = $this->readUrlModule($moduleXml);
293
                                $_POST['title'] = $moduleValues['name'];
294
                                $_POST['url'] = $moduleValues['externalurl'];
295
                                $_POST['description'] = $moduleValues['intro'];
296
                                $_POST['category_id'] = 0;
297
                                $_POST['target'] = '_blank';
298
299
                                Link::addlinkcategory("link");
300
                                break;
301
                        }
302
                    }
303
                }
304
            } else {
305
                removeDir($destinationDir);
306
                return false;
307
            }
308
        } else {
309
            return false;
310
        }
311
312
        removeDir($destinationDir);
313
314
        return $packageContent[$mainFileKey];
315
    }
316
317
    /**
318
     * Read and validate the forum module XML
319
     *
320
     * @param resource $moduleXml XML file
321
     * @return mixed | array if is a valid xml file, false otherwise
322
     */
323 View Code Duplication
    public function readForumModule($moduleXml)
324
    {
325
        $moduleDoc = new DOMDocument();
326
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
327
        if ($moduleRes) {
328
            $activities = $moduleDoc->getElementsByTagName('forum');
329
            $currentItem = [];
330
            foreach ($activities as $activity) {
331
                if ($activity->childNodes->length) {
332
                    foreach ($activity->childNodes as $item) {
333
                        $currentItem[$item->nodeName] = $item->nodeValue;
334
                    }
335
                }
336
            }
337
338
            return $currentItem;
339
        }
340
341
        return false;
342
    }
343
344
    /**
345
     * Read and validate the resource module XML
346
     *
347
     * @param resource $moduleXml XML file
348
     * @return mixed | array if is a valid xml file, false otherwise
349
     */
350
    public function readResourceModule($moduleXml)
351
    {
352
        $moduleDoc = new DOMDocument();
353
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
354
        if ($moduleRes) {
355
            $activities = $moduleDoc->getElementsByTagName('resource');
356
            $mainActivity = $moduleDoc->getElementsByTagName('activity');
357
            $contextId = $mainActivity->item(0)->getAttribute('contextid');
358
            $currentItem = [];
359
            foreach ($activities as $activity) {
360
                if ($activity->childNodes->length) {
361
                    foreach ($activity->childNodes as $item) {
362
                        $currentItem[$item->nodeName] = $item->nodeValue;
363
                    }
364
                }
365
            }
366
367
            $currentItem['contextid'] = $contextId;
368
369
            return $currentItem;
370
        }
371
372
        return false;
373
    }
374
375
    /**
376
     * Read and validate the url module XML
377
     *
378
     * @param resource $moduleXml XML file
379
     * @return mixed | array if is a valid xml file, false otherwise
380
     */
381 View Code Duplication
    public function readUrlModule($moduleXml)
382
    {
383
        $moduleDoc = new DOMDocument();
384
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
385
        if ($moduleRes) {
386
            $activities = $moduleDoc->getElementsByTagName('url');
387
            $currentItem = [];
388
            foreach ($activities as $activity) {
389
                if ($activity->childNodes->length) {
390
                    foreach ($activity->childNodes as $item) {
391
                        $currentItem[$item->nodeName] = $item->nodeValue;
392
                    }
393
                }
394
            }
395
396
            return $currentItem;
397
        }
398
399
        return false;
400
    }
401
402
    /**
403
     * Read and validate the quiz module XML
404
     *
405
     * @param resource $moduleXml XML file
406
     * @return mixed | array if is a valid xml file, false otherwise
407
     */
408
    public function readQuizModule($moduleXml)
409
    {
410
        $moduleDoc = new DOMDocument();
411
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
412
        if ($moduleRes) {
413
            $activities = $moduleDoc->getElementsByTagName('quiz');
414
            $currentItem = [];
415
            foreach ($activities as $activity) {
416
                if ($activity->childNodes->length) {
417
                    foreach ($activity->childNodes as $item) {
418
                        $currentItem[$item->nodeName] = $item->nodeValue;
419
                    }
420
                }
421
            }
422
423
            $questions = $moduleDoc->getElementsByTagName('question_instance');
424
425
            $questionList = [];
426
            $counter = 0;
427
            foreach ($questions as $question) {
428
                if ($question->childNodes->length) {
429
                    foreach ($question->childNodes as $item) {
430
                        $questionList[$counter][$item->nodeName] = $item->nodeValue;
431
                    }
432
                    $counter++;
433
                }
434
435
            }
436
            $currentItem['question_instances'] = $questionList;
437
            return $currentItem;
438
        }
439
440
        return false;
441
    }
442
443
    /**
444
     * Search the current file resource in main Files XML
445
     *
446
     * @param resource $filesXml XML file
447
     * @param int $contextId
448
     * @return mixed | array if is a valid xml file, false otherwise
449
     */
450
    public function readMainFilesXml($filesXml, $contextId)
451
    {
452
        $moduleDoc = new DOMDocument();
453
        $moduleRes = @$moduleDoc->loadXML($filesXml);
454
        if ($moduleRes) {
455
            $activities = $moduleDoc->getElementsByTagName('file');
456
            $currentItem = [];
457
            foreach ($activities as $activity) {
458
                if ($activity->childNodes->length) {
459
                    $isThisItemThatIWant = false;
460
                    foreach ($activity->childNodes as $item) {
461
                        if (!$isThisItemThatIWant && $item->nodeName == 'contenthash') {
462
                            $currentItem['contenthash'] = $item->nodeValue;
463
                        }
464
                        if ($item->nodeName == 'contextid' && intval($item->nodeValue) == intval($contextId) && !$isThisItemThatIWant) {
465
                            $isThisItemThatIWant = true;
466
                            continue;
467
                        }
468
469
                        if ($isThisItemThatIWant && $item->nodeName == 'filename') {
470
                            $currentItem['filename'] = $item->nodeValue;
471
                        }
472
473
                        if ($isThisItemThatIWant && $item->nodeName == 'filesize') {
474
                            $currentItem['filesize'] = $item->nodeValue;
475
                        }
476
477
                        if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
478
                            break;
479
                        }
480
481 View Code Duplication
                        if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
482
                            $currentItem['mimetype'] = $item->nodeValue;
483
                            break 2;
484
                        }
485
                    }
486
                }
487
            }
488
489
            return $currentItem;
490
        }
491
492
        return false;
493
    }
494
495
    /**
496
     * Search the current question resource in main Questions XML
497
     *
498
     * @param resource $questionsXml XML file
499
     * @param int $questionId
500
     * @return mixed | array if is a valid xml file, false otherwise
501
     */
502
    public function readMainQuestionsXml($questionsXml, $questionId)
503
    {
504
        $moduleDoc = new DOMDocument();
505
        $moduleRes = @$moduleDoc->loadXML($questionsXml);
506
        if ($moduleRes) {
507
            $questions = $moduleDoc->getElementsByTagName('question');
508
            $currentItem = [];
509
            foreach ($questions as $question) {
510
                if (intval($question->getAttribute('id')) == $questionId) {
511
                    if ($question->childNodes->length) {
512
                        $currentItem['questionid'] = $questionId;
513
                        $questionType = '';
514
                        foreach ($question->childNodes as $item) {
515
                            $currentItem[$item->nodeName] = $item->nodeValue;
516
                            if ($item->nodeName == 'qtype') {
517
                                $questionType = $item->nodeValue;
518
                            }
519
520
                            if ($item->nodeName == 'plugin_qtype_'.$questionType.'_question') {
521
                                $answer = $item->getElementsByTagName($this->getQuestionTypeAnswersTag($questionType));
522
                                $currentItem['plugin_qtype_'.$questionType.'_question'] = [];
523
                                for ($i = 0; $i <= $answer->length - 1; $i++) {
524
                                    $currentItem['plugin_qtype_'.$questionType.'_question'][$i]['answerid'] = $answer->item($i)->getAttribute('id');
525
                                    foreach ($answer->item($i)->childNodes as $properties) {
526
                                        $currentItem['plugin_qtype_'.$questionType.'_question'][$i][$properties->nodeName] = $properties->nodeValue;
527
                                    }
528
                                }
529
530
                                $typeValues = $item->getElementsByTagName($this->getQuestionTypeOptionsTag($questionType));
531
                                for ($i = 0; $i <= $typeValues->length - 1; $i++) {
532
                                    foreach ($typeValues->item($i)->childNodes as $properties) {
533
                                        $currentItem[$questionType.'_values'][$properties->nodeName] = $properties->nodeValue;
534
                                        if ($properties->nodeName == 'sequence') {
535
                                            $sequence = $properties->nodeValue;
536
                                            $sequenceIds = explode(',', $sequence);
537
                                            foreach ($sequenceIds as $qId) {
538
                                                $questionMatch = $this->readMainQuestionsXml($questionsXml, $qId);
539
                                                $currentItem['plugin_qtype_'.$questionType.'_question'][] = $questionMatch;
540
                                            }
541
                                        }
542
                                    }
543
                                }
544
                            }
545
                        }
546
                    }
547
                }
548
            }
549
550
            $this->traverseArray($currentItem, ['#text', 'question_hints', 'tags']);
551
            return $currentItem;
552
        }
553
554
        return false;
555
    }
556
557
    /**
558
     * return the correct question type options tag
559
     *
560
     * @param string $questionType name
561
     * @return string question type tag
562
     */
563
    public function getQuestionTypeOptionsTag($questionType)
564
    {
565
        switch ($questionType) {
566
            case 'match':
567
            case 'ddmatch':
568
                return 'matchoptions';
569
                break;
570
            default:
571
                return $questionType;
572
                break;
573
        }
574
    }
575
576
    /**
577
     * return the correct question type answers tag
578
     *
579
     * @param string $questionType name
580
     * @return string question type tag
581
     */
582
    public function getQuestionTypeAnswersTag($questionType)
583
    {
584
        switch ($questionType) {
585
            case 'match':
586
            case 'ddmatch':
587
                return 'match';
588
                break;
589
            default:
590
                return 'answer';
591
                break;
592
        }
593
    }
594
595
    /**
596
     *
597
     * @param string $moodleQuestionType
598
     * @return integer Chamilo question type
599
     */
600
    public function matchMoodleChamiloQuestionTypes($moodleQuestionType)
601
    {
602
        switch ($moodleQuestionType) {
603
            case 'multichoice':
604
                return UNIQUE_ANSWER;
605
                break;
606
            case 'multianswer':
607
            case 'shortanswer':
608
            case 'match':
609
                return FILL_IN_BLANKS;
610
                break;
611
            case 'essay':
612
                return FREE_ANSWER;
613
                break;
614
            case 'truefalse':
615
                return UNIQUE_ANSWER_NO_OPTION;
616
                break;
617
        }
618
    }
619
620
    /**
621
     * Fix moodle files that contains spaces
622
     * @param array $importedFiles
623
     * @param string $text
624
     * @return mixed
625
     */
626
    public function fixPathInText($importedFiles, &$text)
627
    {
628
        if ($importedFiles) {
629
            foreach ($importedFiles as $old => $new) {
630
                $text = str_replace($old, $new, $text);
631
                $old = str_replace(' ', '%20', $old);
632
                $text = str_replace($old, $new, $text);
633
                //$text = utf8_encode($text);
634
            }
635
        }
636
637
        return $text;
638
    }
639
640
    /**
641
     * Process Moodle Answers to Chamilo
642
     *
643
     * @param array $questionList
644
     * @param string $questionType
645
     * @param object $questionInstance Question/Answer instance
646
     * @param array $currentQuestion
647
     * @param array $importedFiles
648
     * @return integer db response
649
     */
650
    public function processAnswers($questionList, $questionType, $questionInstance, $currentQuestion, $importedFiles)
651
    {
652
        switch ($questionType) {
653 View Code Duplication
            case 'multichoice':
654
                $objAnswer = new Answer($questionInstance->id);
655
                $questionWeighting = 0;
656
                foreach ($questionList as $slot => $answer) {
657
                    $this->processUniqueAnswer(
658
                        $objAnswer,
659
                        $answer,
660
                        $slot + 1,
661
                        $questionWeighting,
662
                        $importedFiles
663
                    );
664
                }
665
666
                // saves the answers into the data base
667
                $objAnswer->save();
668
                // sets the total weighting of the question
669
                $questionInstance->updateWeighting($questionWeighting);
670
                $questionInstance->save();
671
672
                return true;
673
                break;
674
            case 'multianswer':
675
                $objAnswer = new Answer($questionInstance->id);
676
                $coursePath = api_get_course_path();
677
                $placeholder = str_replace(
678
                    '@@PLUGINFILE@@',
679
                    '/courses/'.$coursePath.'/document/moodle',
680
                    $currentQuestion['questiontext']
681
                );
682
                $optionsValues = [];
683
                foreach ($questionList as $slot => $subQuestion) {
684
                    $qtype = $subQuestion['qtype'];
685
                    $optionsValues[] = $this->processFillBlanks(
686
                        $objAnswer,
687
                        $qtype,
688
                        $subQuestion['plugin_qtype_'.$qtype.'_question'],
689
                        $placeholder,
690
                        $slot + 1,
691
                        $importedFiles
692
                    );
693
                }
694
695
                $answerOptionsWeight = '::';
696
                $answerOptionsSize = '';
697
                $questionWeighting = 0;
698 View Code Duplication
                foreach ($optionsValues as $index => $value) {
699
                    $questionWeighting += $value['weight'];
700
                    $answerOptionsWeight .= $value['weight'].',';
701
                    $answerOptionsSize .= $value['size'].',';
702
                }
703
704
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
705
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
706
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
707
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
708
709
                // This is a minor trick to clean the question description that in a multianswer is the main placeholder
710
                $questionInstance->updateDescription('');
711
                // sets the total weighting of the question
712
                $questionInstance->updateWeighting($questionWeighting);
713
                $questionInstance->save();
714
                $this->fixPathInText($importedFiles, $placeholder);
715
716
                // saves the answers into the data base
717
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
718
                $objAnswer->save();
719
720
                return true;
721
            case 'match':
722
                $objAnswer = new Answer($questionInstance->id);
723
                $placeholder = '';
724
725
                $optionsValues = $this->processFillBlanks(
726
                    $objAnswer,
727
                    'match',
728
                    $questionList,
729
                    $placeholder,
730
                    0,
731
                    $importedFiles
732
                );
733
734
                $answerOptionsWeight = '::';
735
                $answerOptionsSize = '';
736
                $questionWeighting = 0;
737 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...
738
                    $questionWeighting += $value['weight'];
739
                    $answerOptionsWeight .= $value['weight'].',';
740
                    $answerOptionsSize .= $value['size'].',';
741
                }
742
743
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
744
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
745
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
746
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
747
748
                // sets the total weighting of the question
749
                $questionInstance->updateWeighting($questionWeighting);
750
                $questionInstance->save();
751
                // saves the answers into the data base
752
753
                $this->fixPathInText($importedFiles, $placeholder);
754
755
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
756
                $objAnswer->save();
757
758
                return true;
759
                break;
760
            case 'shortanswer':
761
            case 'ddmatch':
762
                $questionWeighting = $currentQuestion['defaultmark'];
763
                $questionInstance->updateWeighting($questionWeighting);
764
                $questionInstance->updateDescription(get_lang('ThisQuestionIsNotSupportedYet'));
765
                $questionInstance->save();
766
                return false;
767
                break;
768
            case 'essay':
769
                $questionWeighting = $currentQuestion['defaultmark'];
770
                $questionInstance->updateWeighting($questionWeighting);
771
                $questionInstance->save();
772
                return true;
773
                break;
774 View Code Duplication
            case 'truefalse':
775
                $objAnswer = new Answer($questionInstance->id);
776
                $questionWeighting = 0;
777
                foreach ($questionList as $slot => $answer) {
778
                    $this->processTrueFalse(
779
                        $objAnswer,
780
                        $answer,
781
                        $slot + 1,
782
                        $questionWeighting,
783
                        $importedFiles
784
                    );
785
                }
786
787
                // saves the answers into the data base
788
                $objAnswer->save();
789
                // sets the total weighting of the question
790
                $questionInstance->updateWeighting($questionWeighting);
791
                $questionInstance->save();
792
                return false;
793
                break;
794
            default:
795
                return false;
796
                break;
797
        }
798
    }
799
800
    /**
801
     * Process Chamilo Unique Answer
802
     *
803
     * @param object $objAnswer
804
     * @param array $answerValues
805
     * @param integer $position
806
     * @param integer $questionWeighting
807
     * @param array $importedFiles
808
     * @return integer db response
809
     */
810 View Code Duplication
    public function processUniqueAnswer($objAnswer, $answerValues, $position, &$questionWeighting, $importedFiles)
811
    {
812
        $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
813
        $answer = $answerValues['answertext'];
814
        $comment = $answerValues['feedback'];
815
        $weighting = $answerValues['fraction'];
816
        $weighting = abs($weighting);
817
        if ($weighting > 0) {
818
            $questionWeighting += $weighting;
819
        }
820
        $goodAnswer =  $correct ? true : false;
821
822
        $this->fixPathInText($importedFiles, $answer);
823
824
        $objAnswer->createAnswer(
825
            $answer,
826
            $goodAnswer,
827
            $comment,
828
            $weighting,
829
            $position,
830
            null,
831
            null,
832
            ''
833
        );
834
    }
835
836
    /**
837
     * Process Chamilo True False
838
     *
839
     * @param object $objAnswer
840
     * @param array $answerValues
841
     * @param integer $position
842
     * @param integer $questionWeighting
843
     * @param array $importedFiles
844
     *
845
     * @return integer db response
846
     */
847 View Code Duplication
    public function processTrueFalse($objAnswer, $answerValues, $position, &$questionWeighting, $importedFiles)
848
    {
849
        $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
850
        $answer = $answerValues['answertext'];
851
        $comment = $answerValues['feedback'];
852
        $weighting = $answerValues['fraction'];
853
        $weighting = abs($weighting);
854
        if ($weighting > 0) {
855
            $questionWeighting += $weighting;
856
        }
857
        $goodAnswer =  $correct ? true : false;
858
859
        $this->fixPathInText($importedFiles, $answer);
860
861
        $objAnswer->createAnswer(
862
            $answer,
863
            $goodAnswer,
864
            $comment,
865
            $weighting,
866
            $position,
867
            null,
868
            null,
869
            ''
870
        );
871
    }
872
873
    /**
874
     * Process Chamilo FillBlanks
875
     *
876
     * @param object $objAnswer
877
     * @param array $questionType
878
     * @param array $answerValues
879
     * @param string $placeholder
880
     * @param integer $position
881
     * @param array $importedFiles
882
     * @return integer db response
883
     *
884
     */
885
    public function processFillBlanks($objAnswer, $questionType, $answerValues, &$placeholder, $position, $importedFiles)
886
    {
887
        $coursePath = api_get_course_path();
888
889
        switch ($questionType) {
890
            case 'multichoice':
891
                $optionsValues = [];
892
                $correctAnswer = '';
893
                $othersAnswer = '';
894
                foreach ($answerValues as $answer) {
895
                    $correct = intval($answer['fraction']);
896
                    if ($correct) {
897
                        $correctAnswer .= $answer['answertext'].'|';
898
                        $optionsValues['weight'] = $answer['fraction'];
899
                        $optionsValues['size'] = '200';
900
                    } else {
901
                        $othersAnswer .= $answer['answertext'].'|';
902
                    }
903
                }
904
                $currentAnswers = $correctAnswer.$othersAnswer;
905
                $currentAnswers = '['.substr($currentAnswers, 0, -1).']';
906
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
907
908
                return $optionsValues;
909
                break;
910
            case 'shortanswer':
911
                $optionsValues = [];
912
                $correctAnswer = '';
913
                foreach ($answerValues as $answer) {
914
                    $correct = intval($answer['fraction']);
915
                    if ($correct) {
916
                        $correctAnswer .= $answer['answertext'];
917
                        $optionsValues['weight'] = $answer['fraction'];
918
                        $optionsValues['size'] = '200';
919
                    }
920
                }
921
922
                $currentAnswers = '['.$correctAnswer.']';
923
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
924
925
                return $optionsValues;
926
                break;
927
            case 'match':
928
                $answers = [];
929
                // Here first we need to extract all the possible answers
930
                foreach ($answerValues as $slot => $answer) {
931
                    $answers[$slot] = $answer['answertext'];
932
                }
933
934
                // Now we set the order of the values matching the correct answer and set it to the first element
935
                $optionsValues = [];
936
                foreach ($answerValues as $slot => $answer) {
937
                    $correctAnswer = '';
938
                    $othersAnswers = '';
939
                    $correctAnswer .= $answer['answertext'].'|';
940
941
                    foreach ($answers as $other) {
942
                        if ($other !== $answer['answertext']) {
943
                            $othersAnswers .= $other.'|';
944
                        }
945
                    }
946
947
                    $optionsValues[$slot]['weight'] = 1;
948
                    $optionsValues[$slot]['size'] = '200';
949
950
                    $currentAnswers = $correctAnswer.$othersAnswers;
951
                    $currentAnswers = '['.substr($currentAnswers, 0, -1).'] ';
952
                    $answer['questiontext'] = str_replace(
953
                        '@@PLUGINFILE@@',
954
                        '/courses/'.$coursePath.'/document/moodle',
955
                        $answer['questiontext']
956
                    );
957
958
                    $placeholder .= '<p> ' . strip_tags($answer['questiontext']).' '.$currentAnswers . ' </p>';
959
                }
960
961
                return $optionsValues;
962
963
                break;
964
            default:
965
                return false;
966
                break;
967
        }
968
    }
969
970
    /**
971
     * get All files associated with a question
972
     *
973
     * @param $filesXml
974
     * @return array
975
     */
976
    public function getAllQuestionFiles($filesXml)
977
    {
978
        $moduleDoc = new DOMDocument();
979
        $moduleRes = @$moduleDoc->loadXML($filesXml);
980
        $allFiles = [];
981
        if ($moduleRes) {
982
            $activities = $moduleDoc->getElementsByTagName('file');
983
            foreach ($activities as $activity) {
984
                $currentItem = [];
985
                $thisIsAnInvalidItem = false;
986
987
                if ($activity->childNodes->length) {
988
                    foreach ($activity->childNodes as $item) {
989
                        if ($item->nodeName == 'component' && $item->nodeValue == 'mod_resource') {
990
                            $thisIsAnInvalidItem = true;
991
                        }
992
993
                        if ($item->nodeName == 'contenthash') {
994
                            $currentItem['contenthash'] = $item->nodeValue;
995
                        }
996
997
                        if ($item->nodeName == 'filename') {
998
                            $currentItem['filename'] = $item->nodeValue;
999
                        }
1000
1001
                        if ($item->nodeName == 'filesize') {
1002
                            $currentItem['filesize'] = $item->nodeValue;
1003
                        }
1004
1005
                        if ($item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
1006
                            $thisIsAnInvalidItem = true;
1007
                        }
1008
1009 View Code Duplication
                        if ($item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
1010
                            $currentItem['mimetype'] = $item->nodeValue;
1011
                        }
1012
                    }
1013
                }
1014
1015
                if (!$thisIsAnInvalidItem) {
1016
                    $allFiles[] = $currentItem;
1017
                }
1018
            }
1019
        }
1020
1021
        return $allFiles;
1022
    }
1023
1024
1025
    /**
1026
     * Litle utility to delete the unuseful tags
1027
     *
1028
     * @param $array
1029
     * @param $keys
1030
     */
1031
    public function traverseArray(&$array, $keys)
1032
    {
1033
        foreach ($array as $key => &$value) {
1034
            if (is_array($value)) {
1035
                $this->traverseArray($value, $keys);
1036
            } else {
1037
                if (in_array($key, $keys)) {
1038
                    unset($array[$key]);
1039
                }
1040
            }
1041
        }
1042
    }
1043
}
1044