Passed
Push — 1.11.x ( 93e109...b6b57d )
by Julito
11:10
created

MoodleImport::import()   F

Complexity

Conditions 46
Paths > 20000

Size

Total Lines 368
Code Lines 243

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 46
eloc 243
c 1
b 0
f 0
nc 463859
nop 1
dl 0
loc 368
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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