Completed
Pull Request — 1.11.x (#1285)
by José
66:28 queued 22:09
created

MoodleImport::traverseArray()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

    doSomethingElse(); //wrong
    break;

}

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

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