MoodleImport::readForumModule()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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