MoodleImport   F
last analyzed

Complexity

Total Complexity 397

Size/Duplication

Total Lines 2545
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 397
eloc 1452
dl 0
loc 2545
rs 0.8
c 0
b 0
f 0

41 Methods

Rating   Name   Duplication   Size   Complexity  
C processSectionFolderModule() 0 117 15
A getQuestionTypeOptionsTag() 0 8 3
B readFolderModuleFilesXml() 0 35 10
A processScorm() 0 31 5
A getQuestionTypeAnswersTag() 0 8 3
A readScormModule() 0 24 6
A traverseArray() 0 8 4
A readFolderModule() 0 24 5
A readSections() 0 26 6
A readSectionModule() 0 20 5
B countSectionActivities() 0 25 7
B readForumModule() 0 46 11
B matchMoodleChamiloQuestionTypes() 0 25 11
D getAllQuestionFiles() 0 57 18
A fixPathInText() 0 13 3
B readLessonModule() 0 45 10
B readMainFilesXml() 0 33 9
B readQuizModule() 0 35 8
A processMultipleAnswer() 0 30 2
A getDocActivityAttributes() 0 11 2
B readQuizGradeModule() 0 31 8
A readHtmlModule() 0 20 5
C readMainQuestionsXml() 0 63 14
A processGlossary() 0 24 5
B processSectionMultimedia() 0 49 9
A replaceMoodleChamiloCoursePath() 0 14 2
A processTrueFalse() 0 31 4
A readResourceModule() 0 24 5
D processLesson() 0 144 17
C processAssignment() 0 53 13
A processUniqueAnswer() 0 31 4
D readAssignModule() 0 56 18
B processHtmlDocument() 0 73 6
A readGlossaryModule() 0 24 5
A readUrlModule() 0 20 5
A processSectionItem() 0 19 1
A isEmptyDir() 0 6 1
C processFillBlanks() 0 77 12
C processAnswers() 0 146 13
F import() 0 556 102
A processSections() 0 47 5

How to fix   Complexity   

Complex Class

Complex classes like MoodleImport often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MoodleImport, and based on these observations, apply Extract Interface, too.

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
        // This process will upload all question resource files
89
        $filesXml = @file_get_contents($destinationDir.'/files.xml');
90
        $mainFileModuleValues = $this->getAllQuestionFiles($filesXml);
91
        $currentResourceFilePath = $destinationDir.'/files/';
92
        $importedFiles = [];
93
        if ($debug) {
94
            error_log('loading files');
95
        }
96
97
        $_POST['moodle_import'] = true;
98
        $_POST['language'] = $courseInfo['language'];
99
100
        $modScormFileZips = [];
101
        $allFiles = [];
102
        foreach ($mainFileModuleValues as $fileInfo) {
103
            $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
104
            foreach (new RecursiveIteratorIterator($dirs) as $file) {
105
                if (!is_file($file) || false === strpos($file, $fileInfo['contenthash'])) {
106
                    continue;
107
                }
108
109
                if (isset($importedFiles[$fileInfo['filename']])) {
110
                    continue;
111
                }
112
113
                if ($debug) {
114
                    error_log($fileInfo['filename']);
115
                }
116
                $files = [];
117
                $files['file']['name'] = $fileInfo['filename'];
118
                $files['file']['tmp_name'] = $file->getPathname();
119
                $files['file']['type'] = $fileInfo['mimetype'];
120
                $files['file']['error'] = 0;
121
                $files['file']['size'] = $fileInfo['filesize'];
122
                $files['file']['from_file'] = true;
123
                $files['file']['move_file'] = true;
124
125
                if (isset($fileInfo['modscorm']) && true === $fileInfo['modscorm']) {
126
                    if ('application/zip' == $fileInfo['mimetype']) {
127
                        $modScormFileZips[$fileInfo['contenthash']] = $files;
128
                    }
129
                    continue;
130
                }
131
                $allFiles[$fileInfo['contextid']][] = $files;
132
            }
133
        }
134
135
        $xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
136
        $doc = new DOMDocument();
137
        $res = @$doc->loadXML($xml);
138
139
        if (empty($res)) {
140
            removeDir($destinationDir);
141
            unlink($filePath);
142
143
            throw new Exception(get_lang('FailedToImportThisIsNotAMoodleFile'));
144
        }
145
146
        // It process the sections as learnpaths
147
        $sections = $this->readSections($xml, $destinationDir);
148
        $activities = $doc->getElementsByTagName('activity');
149
        $sectionLpValues = $this->processSections($sections, $activities);
150
151
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
152
153
        if ($debug) {
154
            error_log('Loading activities: '.count($activities));
155
        }
156
        $n = 1;
157
        foreach ($activities as $activity) {
158
            if (empty($activity->childNodes->length)) {
159
                continue;
160
            }
161
162
            $currentItem = [];
163
            foreach ($activity->childNodes as $item) {
164
                $currentItem[$item->nodeName] = $item->nodeValue;
165
            }
166
167
            $moduleName = isset($currentItem['modulename']) ? $currentItem['modulename'] : false;
168
            if ($debug) {
169
                error_log('moduleName: '.$moduleName);
170
            }
171
172
            switch ($moduleName) {
173
                case 'lesson':
174
                    $moduleDir = $currentItem['directory'];
175
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
176
                    $moduleValues = $this->readLessonModule($moduleXml);
177
                    $this->processLesson($moduleValues, $allFiles);
178
                    break;
179
                case 'assign':
180
                    $moduleDir = $currentItem['directory'];
181
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
182
                    $moduleValues = $this->readAssignModule($moduleXml);
183
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
184
                    $assignId = $this->processAssignment($moduleValues, $allFiles, $sectionPath);
185
186
                    // It is added as item in Learnpath
187
                    if (!empty($currentItem['sectionid']) && !empty($assignId)) {
188
                        $this->processSectionItem($sectionLpValues[$currentItem['sectionid']]['lpId'], 'student_publication', $assignId, $moduleValues['name'], $n);
189
                        $n++;
190
                    }
191
                    break;
192
                case 'scorm':
193
                    $moduleDir = $currentItem['directory'];
194
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
195
                    $moduleValues = $this->readScormModule($moduleXml);
196
                    $this->processScorm($moduleValues, $modScormFileZips);
197
                    break;
198
                case 'glossary':
199
                    $moduleDir = $currentItem['directory'];
200
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
201
                    $moduleValues = $this->readGlossaryModule($moduleXml, $currentItem['moduleid']);
202
                    $this->processGlossary($moduleValues, $currentItem['moduleid'], $allFiles, '');
203
                    break;
204
                case 'label':
205
                case 'page':
206
                    $moduleDir = $currentItem['directory'];
207
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
208
                    $moduleValues = $this->readHtmlModule($moduleXml, $moduleName);
209
                    $sectionPath = isset($currentItem['sectionid']) ? '/'.$sectionLpValues[$currentItem['sectionid']]['sectionPath'].'/' : '/';
210
                    $contextId = $moduleValues['attributes']['contextid'];
211
                    if (isset($allFiles[$contextId])) {
212
                        $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
213
                    }
214
                    $documentId = $this->processHtmlDocument($moduleValues, $moduleName, $importedFiles, $sectionPath);
215
216
                    // It is added as item in Learnpath
217
                    if (!empty($currentItem['sectionid']) && !empty($documentId)) {
218
                        $this->processSectionItem($sectionLpValues[$currentItem['sectionid']]['lpId'], 'document', $documentId, $moduleValues['name'], $n);
219
                        $n++;
220
                    }
221
                    break;
222
                case 'forum':
223
                    $catForumValues = [];
224
                    // Read the current forum module xml.
225
                    $moduleDir = $currentItem['directory'];
226
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
227
                    $moduleValues = $this->readForumModule($moduleXml);
228
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
229
                    $contextId = $moduleValues['attributes']['contextid'];
230
                    if (isset($allFiles[$contextId])) {
231
                        $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
232
                    }
233
234
                    // Create a Forum category based on Moodle forum type.
235
                    $catForumValues['forum_category_title'] = $moduleValues['type'];
236
                    $catForumValues['forum_category_comment'] = '';
237
                    $catId = store_forumcategory(
238
                        $catForumValues,
239
                        $courseInfo,
240
                        false
241
                    );
242
                    $forumValues = [];
243
                    $forumValues['forum_title'] = $moduleValues['name'];
244
                    $forumValues['forum_image'] = '';
245
                    $moduleValues['intro'] = $this->replaceMoodleChamiloCoursePath($moduleValues['intro'], $sectionPath);
246
                    if ($importedFiles) {
247
                        $this->fixPathInText($importedFiles, $moduleValues['intro']);
248
                    }
249
                    $forumValues['forum_comment'] = $moduleValues['intro'];
250
                    $forumValues['forum_category'] = $catId;
251
                    $forumValues['moderated'] = 0;
252
253
                    $forumId = store_forum($forumValues, $courseInfo, true);
254
                    if (!empty($moduleValues['discussions'])) {
255
                        $forum = get_forums($forumId);
256
                        foreach ($moduleValues['discussions'] as $discussion) {
257
                            $moduleValues['intro'] = $this->replaceMoodleChamiloCoursePath($moduleValues['intro'], $sectionPath);
258
                            if ($importedFiles) {
259
                                $this->fixPathInText($importedFiles, $moduleValues['intro']);
260
                            }
261
                            $postText = '';
262
                            if (!empty($discussion['posts'])) {
263
                                $postText = $discussion['posts'][0]['message'];
264
                                $postText = $this->replaceMoodleChamiloCoursePath($postText, $sectionPath);
265
                                if ($importedFiles) {
266
                                    $this->fixPathInText($importedFiles, $postText);
267
                                }
268
                            }
269
                            store_thread(
270
                                $forum,
271
                                [
272
                                    'forum_id' => $forumId,
273
                                    'thread_id' => 0,
274
                                    'gradebook' => 0,
275
                                    'post_title' => $discussion['name'],
276
                                    'post_text' => $postText,
277
                                    'category_id' => 1,
278
                                    'numeric_calification' => 0,
279
                                    'calification_notebook_title' => 0,
280
                                    'weight_calification' => 0.00,
281
                                    'thread_peer_qualify' => 0,
282
                                    'lp_item_id' => 0,
283
                                ],
284
                                [],
285
                                false
286
                            );
287
                        }
288
                    }
289
                    // It is added as item in Learnpath
290
                    if (!empty($currentItem['sectionid']) && !empty($forumId)) {
291
                        $this->processSectionItem($sectionLpValues[$currentItem['sectionid']]['lpId'], 'forum', $forumId, $moduleValues['name'], $n);
292
                        $n++;
293
                    }
294
                    break;
295
                case 'quiz':
296
                    // Read the current quiz module xml.
297
                    // The quiz case is the very complicate process of all the import.
298
                    // Please if you want to review the script, try to see the readingXML functions.
299
                    // The readingXML functions in this clases do all the mayor work here.
300
301
                    $moduleDir = $currentItem['directory'];
302
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
303
                    $questionsXml = @file_get_contents($destinationDir.'/questions.xml');
304
                    $moduleValues = $this->readQuizModule($moduleXml);
305
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
306
                    $contextId = $moduleValues['attributes']['contextid'];
307
                    if (isset($allFiles[$contextId])) {
308
                        $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
309
                    }
310
                    // At this point we got all the prepared resources from Moodle file
311
                    // $moduleValues variable contains all the necesary info to the quiz import
312
                    // var_dump($moduleValues); // <-- uncomment this to see the final array
313
314
                    $exercise = new Exercise($courseInfo['real_id']);
315
                    if ($debug) {
316
                        error_log('quiz:'.$moduleValues['name']);
317
                    }
318
319
                    $title = Exercise::format_title_variable($moduleValues['name']);
320
                    $exercise->updateTitle($title);
321
                    $introText = $this->replaceMoodleChamiloCoursePath($moduleValues['intro'], $sectionPath);
322
                    $exercise->updateDescription($introText);
323
                    $exercise->updateAttempts($moduleValues['attempts_number']);
324
                    $feedbackType = 2;
325
                    if (in_array($moduleValues['preferredbehaviour'], ['adaptive', 'adaptivenopenalty'])) {
326
                        $feedbackType = 1;
327
                    } elseif (in_array($moduleValues['preferredbehaviour'], ['immediatefeedback', 'immediatecbm'])) {
328
                        $feedbackType = 3;
329
                    } elseif ('deferredfeedback' === $moduleValues['preferredbehaviour']) {
330
                        $feedbackType = 0;
331
                    }
332
                    $exercise->updateFeedbackType($feedbackType);
333
334
                    // Match shuffle question with chamilo
335
                    if (isset($moduleValues['shufflequestions']) &&
336
                        (int) $moduleValues['shufflequestions'] === 1
337
                    ) {
338
                        $exercise->setRandom(-1);
339
                    } else {
340
                        $exercise->setRandom(0);
341
                    }
342
                    $exercise->updateRandomAnswers(!empty($moduleValues['shuffleanswers']));
343
                    $limeLimit = 0;
344
                    if (!empty($moduleValues['timelimit'])) {
345
                        $limeLimit = round($moduleValues['timelimit'] / 60);
346
                    }
347
                    $exercise->updateExpiredTime((int) $limeLimit);
348
                    $gradesXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/grades.xml');
349
                    $gradeQuizValues = $this->readQuizGradeModule($gradesXml, $moduleValues['quiz_id']);
350
                    $exercise->pass_percentage = 0;
351
                    if (!empty($gradeQuizValues['grademax'])) {
352
                        $gradeMax = (int) $gradeQuizValues['grademax'];
353
                        $gradePass = (int) $gradeQuizValues['gradepass'];
354
                        $exercise->pass_percentage = round(($gradePass * 100) / $gradeMax);
355
                    }
356
357
                    if ($moduleValues['questionsperpage'] == 1) {
358
                        $exercise->updateType(2);
359
                    } else {
360
                        $exercise->updateType(1);
361
                    }
362
                    $exercise->start_time = null;
363
                    if (!empty($moduleValues['timeopen'])) {
364
                        $exercise->start_time = api_get_utc_datetime($moduleValues['timeopen']);
365
                    }
366
                    $exercise->end_time = null;
367
                    if (!empty($moduleValues['timeclose'])) {
368
                        $exercise->end_time = api_get_utc_datetime($moduleValues['timeclose']);
369
                    }
370
371
                    // Create the new Quiz
372
                    $exercise->save();
373
374
                    // Ok, we got the Quiz and create it, now its time to add the Questions
375
                    foreach ($moduleValues['question_instances'] as $index => $question) {
376
                        $questionsValues = $this->readMainQuestionsXml($questionsXml, $question['questionid']);
377
                        $moduleValues['question_instances'][$index] = $questionsValues;
378
                        // Set Question Type from Moodle XML element <qtype>
379
                        $qType = $questionsValues['qtype'];
380
                        $questionType = $this->matchMoodleChamiloQuestionTypes($questionsValues);
381
                        $questionInstance = Question::getInstance($questionType);
382
                        if (empty($questionInstance)) {
383
                            continue;
384
                        }
385
                        if ($debug) {
386
                            error_log('question: '.$question['questionid']);
387
                        }
388
389
                        $questionInstance->updateTitle($questionsValues['name']);
390
                        $questionText = $questionsValues['questiontext'];
391
392
                        $questionText = $this->replaceMoodleChamiloCoursePath($questionText, $sectionPath);
393
394
                        if ($importedFiles) {
395
                            $this->fixPathInText($importedFiles, $questionText);
396
                        }
397
398
                        $questionInstance->updateDescription($questionText);
399
                        $questionInstance->updateLevel(1);
400
                        $questionInstance->updateCategory(0);
401
402
                        //Save normal question if NOT media
403
                        if ($questionInstance->type != MEDIA_QUESTION) {
404
                            $questionInstance->save($exercise);
405
                            // modify the exercise
406
                            $exercise->addToList($questionInstance->iid);
407
                            $exercise->update_question_positions();
408
                        }
409
410
                        $questionList = $moduleValues['question_instances'][$index]['plugin_qtype_'.$qType.'_question'];
411
                        $currentQuestion = $moduleValues['question_instances'][$index];
412
413
                        $this->processAnswers(
414
                            $exercise,
415
                            $questionList,
416
                            $qType,
417
                            $questionInstance,
418
                            $currentQuestion,
419
                            $importedFiles,
420
                            $sectionPath
421
                        );
422
                    }
423
                    // It is added as item in Learnpath
424
                    if (!empty($currentItem['sectionid']) && !empty($exercise->iid)) {
425
                        $this->processSectionItem($sectionLpValues[$currentItem['sectionid']]['lpId'], 'quiz', $exercise->iid, $title, $n);
426
                        $n++;
427
                    }
428
                    break;
429
                case 'folder':
430
                    $moduleDir = $currentItem['directory'];
431
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
432
                    $filesXml = @file_get_contents($destinationDir.'/files.xml');
433
                    $moduleValues = $this->readFolderModule($moduleXml);
434
                    $mainFileModuleValues = $this->readFolderModuleFilesXml(
435
                        $filesXml,
436
                        $moduleValues['contextid']
437
                    );
438
                    $resourcesFiles = [];
439
                    $currentResourceFilePath = $destinationDir.'/files/';
440
                    $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
441
                    foreach (new RecursiveIteratorIterator($dirs) as $file) {
442
                        foreach ($mainFileModuleValues['files'] as $info) {
443
                            if (!is_file($file) || false === strpos($file, $info['contenthash'])) {
444
                                continue;
445
                            }
446
                            $files = [];
447
                            $files['file']['name'] = $info['filename'];
448
                            $files['file']['tmp_name'] = $file->getPathname();
449
                            $files['file']['type'] = $info['mimetype'];
450
                            $files['file']['error'] = 0;
451
                            $files['file']['size'] = $info['filesize'];
452
                            $files['file']['from_file'] = true;
453
                            $files['file']['move_file'] = true;
454
                            $files['file']['filepath'] = $info['filepath'];
455
                            $resourcesFiles[] = $files;
456
                        }
457
                    }
458
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
459
                    $lpId = (int) $sectionLpValues[$currentItem['sectionid']]['lpId'];
460
                    $this->processSectionFolderModule($mainFileModuleValues, $sectionPath, $moduleValues['name'], $resourcesFiles, $lpId, $n);
461
                    $n++;
462
463
                    break;
464
                case 'resource':
465
                    // Read the current resource module xml.
466
                    $moduleDir = $currentItem['directory'];
467
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
468
                    $filesXml = @file_get_contents($destinationDir.'/files.xml');
469
                    $moduleValues = $this->readResourceModule($moduleXml);
470
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
471
                    $mainFileModuleValues = $this->readMainFilesXml(
472
                        $filesXml,
473
                        $moduleValues['contextid']
474
                    );
475
                    $resourcesFiles = [];
476
                    $fileInfo = array_merge($moduleValues, $mainFileModuleValues, $currentItem);
477
                    $currentResourceFilePath = $destinationDir.'/files/';
478
                    $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
479
                    foreach (new RecursiveIteratorIterator($dirs) as $file) {
480
                        if (!is_file($file) || false === strpos($file, $fileInfo['contenthash'])) {
481
                            continue;
482
                        }
483
484
                        $files = [];
485
                        $files['file']['name'] = $fileInfo['filename'];
486
                        $files['file']['tmp_name'] = $file->getPathname();
487
                        $files['file']['type'] = $fileInfo['mimetype'];
488
                        $files['file']['error'] = 0;
489
                        $files['file']['size'] = $fileInfo['filesize'];
490
                        $files['file']['from_file'] = true;
491
                        $files['file']['move_file'] = true;
492
                        $_POST['language'] = $courseInfo['language'];
493
                        $_POST['moodle_import'] = true;
494
495
                        $resourcesFiles[] = $files;
496
                    }
497
                    if (!empty($resourcesFiles)) {
498
                        $lpId = 0;
499
                        if (!empty($currentItem['sectionid'])) {
500
                            $lpId = $sectionLpValues[$currentItem['sectionid']]['lpId'];
501
                        }
502
                        $importedFiles = $this->processSectionMultimedia($resourcesFiles, $sectionPath, $lpId, $n);
503
                        $n++;
504
                    }
505
506
                    break;
507
                case 'url':
508
                    // Read the current url module xml.
509
                    $moduleDir = $currentItem['directory'];
510
                    $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
511
                    $moduleValues = $this->readUrlModule($moduleXml);
512
                    $sectionPath = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionPath'] : '';
513
                    $sectionName = isset($sectionLpValues[$currentItem['sectionid']]) ? $sectionLpValues[$currentItem['sectionid']]['sectionName'] : '';
514
                    $categoryId = 0;
515
                    if (!empty($sectionName)) {
516
                        $category = Link::getCategoryByName($sectionName);
517
                        if (!empty($category)) {
518
                            $categoryId = $category['iid'];
519
                        } else {
520
                            $_POST['category_title'] = $sectionName;
521
                            $_POST['description'] = '';
522
                            $categoryId = Link::addlinkcategory('category');
523
                        }
524
                    }
525
                    $contextId = $moduleValues['attributes']['contextid'];
526
                    if (isset($allFiles[$contextId])) {
527
                        $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
528
                    }
529
                    $_POST['title'] = $moduleValues['name'];
530
                    $_POST['url'] = $moduleValues['externalurl'];
531
                    $moduleValues['intro'] = $this->replaceMoodleChamiloCoursePath($moduleValues['intro'], $sectionPath);
532
                    if ($importedFiles) {
533
                        $this->fixPathInText($importedFiles, $moduleValues['intro']);
534
                    }
535
                    $_POST['description'] = strip_tags($moduleValues['intro']);
536
                    $_POST['category_id'] = $categoryId;
537
                    $_POST['target'] = '_blank';
538
539
                    $linkId = Link::addlinkcategory('link');
540
                    // It is added as item in Learnpath
541
                    if (!empty($currentItem['sectionid']) && !empty($linkId)) {
542
                        $this->processSectionItem($sectionLpValues[$currentItem['sectionid']]['lpId'], 'link', $linkId, $moduleValues['name'], $n);
543
                        $n++;
544
                    }
545
                    break;
546
            }
547
        }
548
549
        if (!empty($sectionLpValues)) {
550
            foreach ($sectionLpValues as $section) {
551
                if (!empty($section['sectionPath'])) {
552
                    $documentPath = '/'.$section['sectionPath'];
553
                    $baseWorkDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
554
                    $checkDir = $baseWorkDir.$documentPath;
555
                    if ($this->isEmptyDir($checkDir)) {
556
                        $document = DocumentManager::getDocumentByPathInCourse($courseInfo, $documentPath);
557
                        my_delete($checkDir);
558
                        // Hard delete.
559
                        DocumentManager::deleteDocumentFromDb(
560
                            $document[0]['iid'],
561
                            $courseInfo,
562
                            api_get_session_id(),
563
                            true
564
                        );
565
                    }
566
                }
567
            }
568
        }
569
570
        if ($debug) {
571
            error_log('Finish');
572
        }
573
574
        removeDir($destinationDir);
575
        unlink($filePath);
576
577
        return true;
578
    }
579
580
    /**
581
     * Replace the path from @@PLUGINFILE@@ to a correct chamilo path.
582
     *
583
     * @param $text
584
     * @param string $sectionPath
585
     *
586
     * @return string
587
     */
588
    public function replaceMoodleChamiloCoursePath($text, $sectionPath = '')
589
    {
590
        if (!empty($sectionPath)) {
591
            $sectionPath = '/'.$sectionPath;
592
        }
593
        $multimediaPath = $sectionPath.'/Multimedia';
594
        $coursePath = api_get_course_path();
595
        $text = str_replace(
596
            '@@PLUGINFILE@@',
597
            '/courses/'.$coursePath.'/document'.$multimediaPath,
598
            $text
599
        );
600
601
        return $text;
602
    }
603
604
    /**
605
     * It adds module item by section as learnpath item.
606
     *
607
     * @param $lpId
608
     * @param $itemType
609
     * @param $itemId
610
     * @param $itemTitle
611
     * @param int $dspOrder
612
     *
613
     * @return void
614
     */
615
    public function processSectionItem($lpId, $itemType, $itemId, $itemTitle, $dspOrder = 0)
616
    {
617
        $lp = new \learnpath(
618
            api_get_course_id(),
619
            $lpId,
620
            api_get_user_id()
621
        );
622
623
        $lpItemId = $lp->add_item(
624
            0,
625
            0,
626
            $itemType,
627
            $itemId,
628
            $itemTitle,
629
            '',
630
            0,
631
            0,
632
            0,
633
            $dspOrder
634
        );
635
    }
636
637
    /**
638
     * It adds the section module as learnpath.
639
     *
640
     * @param $sections
641
     *
642
     * @return array|false
643
     */
644
    public function processSections($sections, $activities)
645
    {
646
        if (empty($sections)) {
647
            return false;
648
        }
649
650
        $courseInfo = api_get_course_info();
651
        $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
652
        $i = 1;
653
        $lpAdded = [];
654
        foreach ($sections as $sectionId => $section) {
655
            $countSectionActivities = $this->countSectionActivities($activities, $sectionId);
656
            if ($countSectionActivities > 0) {
657
                $lpName = $section['name'];
658
                if ('$@NULL@$' == $lpName) {
659
                    $lpName = get_lang('Topic').' '.$i;
660
                }
661
                $lpDescription = $section['summary'];
662
                $lpId = learnpath::add_lp(
663
                    api_get_course_id(),
664
                    $lpName,
665
                    $lpDescription,
666
                    'chamilo',
667
                    'manual'
668
                );
669
                $dirName = api_replace_dangerous_char($lpName);
670
                create_unexisting_directory(
671
                    $courseInfo,
672
                    api_get_user_id(),
673
                    api_get_session_id(),
674
                    api_get_group_id(),
675
                    null,
676
                    $documentPath,
677
                    '/'.$dirName,
678
                    $lpName,
679
                    0
680
                );
681
                $lpAdded[$sectionId] = [
682
                    'lpId' => $lpId,
683
                    'sectionPath' => $dirName,
684
                    'sectionName' => $lpName,
685
                ];
686
                $i++;
687
            }
688
        }
689
690
        return $lpAdded;
691
    }
692
693
    /**
694
     * It counts the activities inside a section module.
695
     *
696
     * @param $activities
697
     * @param $sectionId
698
     *
699
     * @return int|void
700
     */
701
    public function countSectionActivities($activities, $sectionId)
702
    {
703
        $sectionActivities = [];
704
        $modulesLpTypes = ['url', 'resource', 'quiz', 'forum', 'page', 'label', 'assign'];
705
        $i = 0;
706
        foreach ($activities as $activity) {
707
            if (empty($activity->childNodes->length)) {
708
                continue;
709
            }
710
            $currentItem = [];
711
            foreach ($activity->childNodes as $item) {
712
                $currentItem[$item->nodeName] = $item->nodeValue;
713
            }
714
            if (!empty($currentItem['sectionid']) && in_array($currentItem['modulename'], $modulesLpTypes)) {
715
                $sectionActivities[$currentItem['sectionid']][$i] = $currentItem;
716
                $i++;
717
            }
718
        }
719
720
        $countActivities = 0;
721
        if (isset($sectionActivities[$sectionId])) {
722
            $countActivities = count($sectionActivities[$sectionId]);
723
        }
724
725
        return $countActivities;
726
    }
727
728
    /**
729
     * It gets the sections from xml module.
730
     *
731
     * @param $xml
732
     * @param $destinationDir
733
     *
734
     * @return array|false
735
     */
736
    public function readSections($xml, $destinationDir)
737
    {
738
        $doc = new DOMDocument();
739
        $res = @$doc->loadXML($xml);
740
        if (empty($res)) {
741
            return false;
742
        }
743
744
        $sections = [];
745
        $sectionNodes = $doc->getElementsByTagName('section');
746
        foreach ($sectionNodes as $section) {
747
            if (empty($section->childNodes->length)) {
748
                continue;
749
            }
750
            $currentItem = [];
751
            foreach ($section->childNodes as $item) {
752
                $currentItem[$item->nodeName] = $item->nodeValue;
753
            }
754
            if (!empty($currentItem['directory'])) {
755
                $sectionDir = $destinationDir.'/'.$currentItem['directory'];
756
                $sectionInfoXml = @file_get_contents($sectionDir.'/section.xml');
757
                $sections[$currentItem['sectionid']] = $this->readSectionModule($sectionInfoXml);
758
            }
759
        }
760
761
        return $sections;
762
    }
763
764
    /**
765
     * It reads module xml to get section info.
766
     *
767
     * @param $sectionInfoXml
768
     *
769
     * @return array|false
770
     */
771
    public function readSectionModule($sectionInfoXml)
772
    {
773
        $doc = new DOMDocument();
774
        $res = @$doc->loadXML($sectionInfoXml);
775
        if (empty($res)) {
776
            return false;
777
        }
778
779
        $sectionInfo = [];
780
        $sectionNode = $doc->getElementsByTagName('section');
781
        foreach ($sectionNode as $section) {
782
            if (empty($section->childNodes->length)) {
783
                continue;
784
            }
785
            foreach ($section->childNodes as $item) {
786
                $sectionInfo[$item->nodeName] = $item->nodeValue;
787
            }
788
        }
789
790
        return $sectionInfo;
791
    }
792
793
    /**
794
     * It gets lesson information from module xml.
795
     *
796
     * @param $moduleXml
797
     *
798
     * @return array|false
799
     */
800
    public function readLessonModule($moduleXml)
801
    {
802
        $doc = new DOMDocument();
803
        $res = @$doc->loadXML($moduleXml);
804
        if (empty($res)) {
805
            return false;
806
        }
807
808
        $activities = $doc->getElementsByTagName('lesson');
809
        $currentItem = [];
810
        foreach ($activities as $activity) {
811
            if ($activity->childNodes->length) {
812
                foreach ($activity->childNodes as $item) {
813
                    $currentItem[$item->nodeName] = $item->nodeValue;
814
                }
815
            }
816
        }
817
818
        $pages = $doc->getElementsByTagName('page');
819
820
        $pagesList = [];
821
        $counter = 0;
822
        foreach ($pages as $page) {
823
            if ($page->childNodes->length) {
824
                foreach ($page->childNodes as $item) {
825
                    $pagesList[$counter][$item->nodeName] = $item->nodeValue;
826
                }
827
                $i = 0;
828
                $answerNodes = $page->getElementsByTagName("answer");
829
                $answers = [];
830
                foreach ($answerNodes as $answer) {
831
                    foreach ($answer->childNodes as $n) {
832
                        $answers[$i][$n->nodeName] = $n->nodeValue;
833
                    }
834
                    $i++;
835
                }
836
                $pagesList[$counter]['answers'] = $answers;
837
                $counter++;
838
            }
839
        }
840
        $currentItem['pages'] = $pagesList;
841
        $attributes = $this->getDocActivityAttributes($doc);
842
        $currentItem['attributes'] = $attributes;
843
844
        return $currentItem;
845
    }
846
847
    /**
848
     * It gets assignment information from module xml.
849
     *
850
     * @param $moduleXml
851
     *
852
     * @return array|false
853
     */
854
    public function readAssignModule($moduleXml)
855
    {
856
        $doc = new DOMDocument();
857
        $res = @$doc->loadXML($moduleXml);
858
        if (empty($res)) {
859
            return false;
860
        }
861
862
        $info = [];
863
        $entries = $doc->getElementsByTagName('assign');
864
        foreach ($entries as $entry) {
865
            if (empty($entry->childNodes->length)) {
866
                continue;
867
            }
868
            foreach ($entry->childNodes as $item) {
869
                if (in_array($item->nodeName, ['name', 'intro', 'duedate', 'cutoffdate', 'grade'])) {
870
                    $info[$item->nodeName] = $item->nodeValue;
871
                }
872
            }
873
        }
874
875
        $configOpts = $doc->getElementsByTagName('plugin_config');
876
        $config = [];
877
        $counter = 0;
878
        foreach ($configOpts as $opt) {
879
            if (empty($opt->childNodes->length)) {
880
                continue;
881
            }
882
            $pluginName = '';
883
            foreach ($opt->childNodes as $item) {
884
                if ('#text' == $item->nodeName) {
885
                    continue;
886
                }
887
                if ('plugin' == $item->nodeName && !in_array($item->nodeValue, ['onlinetext', 'file'])) {
888
                    break;
889
                }
890
                if ('subtype' == $item->nodeName && 'assignsubmission' != $item->nodeValue) {
891
                    break;
892
                }
893
                if ('name' == $item->nodeName && 'enabled' != $item->nodeValue) {
894
                    break;
895
                }
896
                if ('plugin' == $item->nodeName) {
897
                    $pluginName = $item->nodeValue;
898
                }
899
                if ('value' == $item->nodeName) {
900
                    $config[$pluginName]['enabled'] = (int) $item->nodeValue;
901
                }
902
            }
903
            $counter++;
904
        }
905
        $info['config'] = $config;
906
        $attributes = $this->getDocActivityAttributes($doc);
907
        $info['attributes'] = $attributes;
908
909
        return $info;
910
    }
911
912
    /**
913
     * It gets the attributes of each activity from module xml.
914
     *
915
     * @param $doc
916
     *
917
     * @return array
918
     */
919
    public function getDocActivityAttributes($doc)
920
    {
921
        $activityAttr = [];
922
        $searchActivity = $doc->getElementsByTagName('activity');
923
        foreach ($searchActivity as $searchNode) {
924
            $activityAttr['contextid'] = $searchNode->getAttribute('contextid');
925
            $activityAttr['modulename'] = $searchNode->getAttribute('modulename');
926
            $activityAttr['moduleid'] = $searchNode->getAttribute('moduleid');
927
        }
928
929
        return $activityAttr;
930
    }
931
932
    /**
933
     * It gest scorm information from module xml.
934
     *
935
     * @param $moduleXml
936
     *
937
     * @return array|false
938
     */
939
    public function readScormModule($moduleXml)
940
    {
941
        $doc = new DOMDocument();
942
        $res = @$doc->loadXML($moduleXml);
943
        if (empty($res)) {
944
            return false;
945
        }
946
947
        $info = [];
948
        $entries = $doc->getElementsByTagName('scorm');
949
        $i = 0;
950
        foreach ($entries as $entry) {
951
            if (empty($entry->childNodes->length)) {
952
                continue;
953
            }
954
            foreach ($entry->childNodes as $item) {
955
                if (in_array($item->nodeName, ['name', 'reference', 'sha1hash', 'scormtype'])) {
956
                    $info[$i][$item->nodeName] = $item->nodeValue;
957
                }
958
            }
959
            $i++;
960
        }
961
962
        return $info;
963
    }
964
965
    /**
966
     * Get glossary information from module xml.
967
     *
968
     * @param $moduleXml
969
     * @param $moduleId
970
     *
971
     * @return array|false
972
     */
973
    public function readGlossaryModule($moduleXml, $moduleId)
974
    {
975
        $doc = new DOMDocument();
976
        $res = @$doc->loadXML($moduleXml);
977
        if (empty($res)) {
978
            return false;
979
        }
980
981
        $glossaryInfo = [];
982
        $entries = $doc->getElementsByTagName('entry');
983
        $i = 0;
984
        foreach ($entries as $entry) {
985
            if (empty($entry->childNodes->length)) {
986
                continue;
987
            }
988
            foreach ($entry->childNodes as $item) {
989
                $glossaryInfo[$moduleId][$i][$item->nodeName] = $item->nodeValue;
990
            }
991
            $i++;
992
        }
993
        $attributes = $this->getDocActivityAttributes($doc);
994
        $glossaryInfo['attributes'] = $attributes;
995
996
        return $glossaryInfo;
997
    }
998
999
    /**
1000
     * It reads item html from module to documents.
1001
     *
1002
     * @param $moduleXml
1003
     * @param $moduleName
1004
     *
1005
     * @return array|false
1006
     */
1007
    public function readHtmlModule($moduleXml, $moduleName)
1008
    {
1009
        $moduleDoc = new DOMDocument();
1010
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1011
        if (empty($moduleRes)) {
1012
            return false;
1013
        }
1014
        $activities = $moduleDoc->getElementsByTagName($moduleName);
1015
        $currentItem = [];
1016
        foreach ($activities as $activity) {
1017
            if ($activity->childNodes->length) {
1018
                foreach ($activity->childNodes as $item) {
1019
                    $currentItem[$item->nodeName] = $item->nodeValue;
1020
                }
1021
            }
1022
        }
1023
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1024
        $currentItem['attributes'] = $attributes;
1025
1026
        return $currentItem;
1027
    }
1028
1029
    /**
1030
     * It addes the files from a resources type folder.
1031
     *
1032
     * @param     $files
1033
     * @param     $mainFolderName
1034
     * @param     $sectionPath
1035
     * @param int $lpId
1036
     * @param int $n
1037
     */
1038
    public function processSectionFolderModule($mainFileModuleValues, $sectionPath, $mainFolderName, $resourcesFiles, $lpId = 0, $dspOrder = 0)
1039
    {
1040
        if (!empty($mainFileModuleValues['folder'])) {
1041
            $courseInfo = api_get_course_info();
1042
            $chapters = [];
1043
            $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
1044
            // It creates the first lp chapter (module folder name)
1045
            $safeMainFolderName = api_replace_dangerous_char($mainFolderName);
1046
            $documentData = create_unexisting_directory(
1047
                $courseInfo,
1048
                api_get_user_id(),
1049
                api_get_session_id(),
1050
                api_get_group_id(),
1051
                null,
1052
                $documentPath,
1053
                '/'.$sectionPath.'/'.$safeMainFolderName,
1054
                $mainFolderName,
1055
                0
1056
            );
1057
            if (!empty($lpId) && !empty($documentData['iid'])) {
1058
                $lp = new \learnpath(
1059
                    api_get_course_id(),
1060
                    $lpId,
1061
                    api_get_user_id()
1062
                );
1063
                $lpItemId = $lp->add_item(
1064
                    0,
1065
                    0,
1066
                    'dir',
1067
                    $documentData['iid'],
1068
                    $mainFolderName,
1069
                    '',
1070
                    0,
1071
                    0,
1072
                    0,
1073
                    $dspOrder
1074
                );
1075
                $chapters['/'] = $lpItemId;
1076
            }
1077
            // It checks the subfolder for second level.
1078
            foreach ($mainFileModuleValues['folder'] as $folder) {
1079
                if ('/' == $folder['filepath']) {
1080
                    continue;
1081
                }
1082
                $folder['filepath'] = trim($folder['filepath'], '/');
1083
                $arrFolderPath = explode('/', $folder['filepath']);
1084
                if (1 == count($arrFolderPath)) {
1085
                    $folderName = $arrFolderPath[0];
1086
                    $safeFolderName = api_replace_dangerous_char($folderName);
1087
                    $documentSubData = create_unexisting_directory(
1088
                        $courseInfo,
1089
                        api_get_user_id(),
1090
                        api_get_session_id(),
1091
                        api_get_group_id(),
1092
                        null,
1093
                        $documentPath,
1094
                        '/'.$sectionPath.'/'.$safeMainFolderName.'/'.$safeFolderName,
1095
                        $folderName,
1096
                        0
1097
                    );
1098
                    if (!empty($lpId) && !empty($documentSubData['iid'])) {
1099
                        $lp = new \learnpath(
1100
                            api_get_course_id(),
1101
                            $lpId,
1102
                            api_get_user_id()
1103
                        );
1104
                        $lpItemId = $lp->add_item(
1105
                            $chapters['/'],
1106
                            0,
1107
                            'dir',
1108
                            $documentSubData['iid'],
1109
                            $folderName,
1110
                            '',
1111
                            0,
1112
                            0,
1113
                            0
1114
                        );
1115
                        $chapters["/$folderName/"] = $lpItemId;
1116
                    }
1117
                }
1118
            }
1119
            if (!empty($resourcesFiles)) {
1120
                foreach ($resourcesFiles as $file) {
1121
                    $title = pathinfo($file['file']['name'], PATHINFO_FILENAME);
1122
                    $path = $file['file']['filepath'];
1123
                    if (1 == count(explode('/', trim($path, '/')))) {
1124
                        $safePath = api_replace_dangerous_char($path);
1125
                        $newSafePath = !empty($safePath) ? '/'.$sectionPath.'/'.$safeMainFolderName.'/'.$safePath : '/'.$sectionPath.'/'.$safeMainFolderName;
1126
                        $data = DocumentManager::upload_document(
1127
                            $file,
1128
                            $newSafePath,
1129
                            $title,
1130
                            '',
1131
                            null,
1132
                            'overwrite',
1133
                            true,
1134
                            true,
1135
                            'file',
1136
                            false
1137
                        );
1138
                        if (!empty($lpId) && !empty($data['iid'])) {
1139
                            // It is added as item in Learnpath
1140
                            $lp = new \learnpath(
1141
                                api_get_course_id(),
1142
                                $lpId,
1143
                                api_get_user_id()
1144
                            );
1145
                            $lpItemId = $lp->add_item(
1146
                                $chapters[$path],
1147
                                0,
1148
                                'document',
1149
                                $data['iid'],
1150
                                $title,
1151
                                '',
1152
                                0,
1153
                                0,
1154
                                0
1155
                            );
1156
                        }
1157
                    }
1158
                }
1159
            }
1160
        }
1161
    }
1162
1163
    /**
1164
     * It reorganizes files imported to documents.
1165
     *
1166
     * @param $files
1167
     * @param $sectionPath
1168
     *
1169
     * @return array
1170
     */
1171
    public function processSectionMultimedia($files, $sectionPath, $lpId = 0, $n = 0)
1172
    {
1173
        $importedFiles = [];
1174
        if (!empty($files)) {
1175
            $courseInfo = api_get_course_info();
1176
            if (!empty($sectionPath)) {
1177
                $sectionPath = '/'.trim($sectionPath, '/\\');
1178
            }
1179
            $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
1180
            $multimediaPath = $documentPath.$sectionPath.'/Multimedia';
1181
            if (!is_dir($multimediaPath)) {
1182
                create_unexisting_directory(
1183
                    $courseInfo,
1184
                    api_get_user_id(),
1185
                    api_get_session_id(),
1186
                    api_get_group_id(),
1187
                    null,
1188
                    $documentPath,
1189
                    $sectionPath.'/Multimedia',
1190
                    'Multimedia',
1191
                    0
1192
                );
1193
            }
1194
            foreach ($files as $file) {
1195
                $title = pathinfo($file['file']['name'], PATHINFO_FILENAME);
1196
                $path = !empty($lpId) ? $sectionPath : $sectionPath.'/Multimedia';
1197
                $data = DocumentManager::upload_document(
1198
                    $file,
1199
                    $path,
1200
                    $title,
1201
                    '',
1202
                    null,
1203
                    'overwrite',
1204
                    true,
1205
                    true,
1206
                    'file',
1207
                    false
1208
                );
1209
                if ($data) {
1210
                    $importedFiles[$file['file']['name']] = basename($data['path']);
1211
                    // It is added as item in Learnpath
1212
                    if (!empty($lpId) && !empty($data['iid'])) {
1213
                        $this->processSectionItem($lpId, 'document', $data['iid'], $title, $n);
1214
                    }
1215
                }
1216
            }
1217
        }
1218
1219
        return $importedFiles;
1220
    }
1221
1222
    /**
1223
     * It saves a learnpath from module xml.
1224
     *
1225
     * @param $moduleValues
1226
     *
1227
     * @return false
1228
     */
1229
    public function processLesson($moduleValues, $allFiles = [])
1230
    {
1231
        if (!empty($moduleValues['pages'])) {
1232
            $qtypes = [
1233
                20 => 'page',
1234
                10 => 'essay',
1235
                5 => 'matching',
1236
                3 => 'multichoice',
1237
                1 => 'shortanswer',
1238
                2 => 'truefalse',
1239
            ];
1240
            $items = $moduleValues['pages'];
1241
            $lpName = $moduleValues['name'];
1242
            $lpDescription = api_utf8_decode($moduleValues['intro']);
1243
            $lpId = learnpath::add_lp(
1244
                api_get_course_id(),
1245
                $lpName,
1246
                $lpDescription,
1247
                'chamilo',
1248
                'manual'
1249
            );
1250
1251
            $dirName = api_replace_dangerous_char($lpName);
1252
            $courseInfo = api_get_course_info();
1253
            $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
1254
            create_unexisting_directory(
1255
                $courseInfo,
1256
                api_get_user_id(),
1257
                api_get_session_id(),
1258
                api_get_group_id(),
1259
                null,
1260
                $documentPath,
1261
                '/'.$dirName,
1262
                $lpName,
1263
                0
1264
            );
1265
1266
            $importedFiles = [];
1267
            $contextId = $moduleValues['attributes']['contextid'];
1268
            if (isset($allFiles[$contextId])) {
1269
                $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $dirName);
1270
            }
1271
1272
            $questionList = [];
1273
            foreach ($items as $item) {
1274
                if (in_array($item['qtype'], array_keys($qtypes))) {
1275
                    $qTypeName = $qtypes[$item['qtype']];
1276
                    switch ($qTypeName) {
1277
                        case 'page':
1278
                            $pageValues = [];
1279
                            $pageValues['name'] = $item['title'];
1280
                            $pageValues['content'] = $item['contents'];
1281
                            $sectionPath = '/'.$dirName.'/';
1282
                            $documentId = $this->processHtmlDocument($pageValues, 'page', $importedFiles, $sectionPath);
1283
                            $this->processSectionItem($lpId, 'document', $documentId, $pageValues['name']);
1284
                            break;
1285
                        case 'essay':
1286
                        case 'match':
1287
                        case 'multichoice':
1288
                        case 'shortanswer':
1289
                        case 'truefalse':
1290
                            $qType = $qtypes[$item['qtype']];
1291
                            $question = [];
1292
                            $question['qtype'] = $qType;
1293
                            $question['name'] = $item['title'];
1294
                            $question['questiontext'] = api_utf8_decode($item['contents']);
1295
                            $question[$qType.'_values']['single'] = 1;
1296
                            $question['questionType'] = $this->matchMoodleChamiloQuestionTypes($question);
1297
                            $answers = [];
1298
                            if (!empty($item['answers'])) {
1299
                                $defaultmark = 0;
1300
                                foreach ($item['answers'] as $answer) {
1301
                                    $answerValue = [];
1302
                                    $answerValue['answertext'] = api_utf8_decode($answer['answer_text']);
1303
                                    $answerValue['feedback'] = api_utf8_decode($answer['response']);
1304
                                    $answerValue['fraction'] = $answer['score'];
1305
                                    $defaultmark += $answer['score'];
1306
                                    $answers[] = $answerValue;
1307
                                }
1308
                                $question['defaultmark'] = $defaultmark;
1309
                            }
1310
                            $question['answers'] = $answers;
1311
                            $questionList[] = $question;
1312
                            break;
1313
                    }
1314
                }
1315
            }
1316
1317
            if (!empty($questionList)) {
1318
                $courseInfo = api_get_course_info();
1319
                // It creates a quiz for those questions.
1320
                $exercise = new Exercise($courseInfo['real_id']);
1321
                $quizLpName = $lpName.' - '.get_lang('Quiz');
1322
                $title = Exercise::format_title_variable($quizLpName);
1323
                $exercise->updateTitle($title);
1324
                $moduleValues['intro'] = $this->replaceMoodleChamiloCoursePath($moduleValues['intro'], $dirName);
1325
                $exercise->updateDescription(api_utf8_decode($moduleValues['intro']));
1326
                $exercise->updateAttempts(0);
1327
                $exercise->updateFeedbackType(0);
1328
                $exercise->setRandom(0);
1329
                $exercise->updateRandomAnswers(false);
1330
                $exercise->updateExpiredTime(0);
1331
                $exercise->updateType(2);
1332
                $exercise->updateResultsDisabled(0);
1333
1334
                // Create the new Quiz
1335
                $exercise->save();
1336
1337
                $this->processSectionItem($lpId, 'quiz', $exercise->iid, $quizLpName);
1338
1339
                // Ok, we got the Quiz and create it, now its time to add the Questions
1340
                foreach ($questionList as $question) {
1341
                    $questionInstance = Question::getInstance($question['questionType']);
1342
                    if (empty($questionInstance)) {
1343
                        continue;
1344
                    }
1345
                    $questionInstance->updateTitle($question['name']);
1346
                    $questionText = $question['questiontext'];
1347
                    $questionText = $this->replaceMoodleChamiloCoursePath($questionText, $dirName);
1348
                    $questionInstance->updateDescription($questionText);
1349
                    $questionInstance->updateLevel(1);
1350
                    $questionInstance->updateCategory(0);
1351
1352
                    //Save normal question if NOT media
1353
                    if ($questionInstance->type != MEDIA_QUESTION) {
1354
                        $questionInstance->save($exercise);
1355
                        // modify the exercise
1356
                        $exercise->addToList($questionInstance->iid);
1357
                        $exercise->update_question_positions();
1358
                    }
1359
                    $this->processAnswers(
1360
                        $exercise,
1361
                        $question['answers'],
1362
                        $question['qtype'],
1363
                        $questionInstance,
1364
                        $question,
1365
                        $importedFiles,
1366
                        $dirName
1367
                    );
1368
                }
1369
            }
1370
        }
1371
1372
        return false;
1373
    }
1374
1375
    /**
1376
     * It saves a student publication from module xml.
1377
     *
1378
     * @param $assign
1379
     *
1380
     * @return bool|int
1381
     */
1382
    public function processAssignment($assign, $allFiles = [], $sectionPath = '')
1383
    {
1384
        if (!empty($assign)) {
1385
            require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
1386
1387
            $importedFiles = [];
1388
            $contextId = $assign['attributes']['contextid'];
1389
            if (isset($allFiles[$contextId])) {
1390
                $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
1391
            }
1392
1393
            $values = [];
1394
            $values['new_dir'] = $assign['name'];
1395
            if (!empty($assign['cutoffdate'])) {
1396
                $values['enableEndDate'] = 1;
1397
                $values['ends_on'] = date('Y-m-d H:i', $assign['cutoffdate']);
1398
            }
1399
            if (!empty($assign['duedate'])) {
1400
                $values['enableExpiryDate'] = 1;
1401
                $values['expires_on'] = date('Y-m-d H:i', $assign['duedate']);
1402
            }
1403
            $values['work_title'] = $assign['name'];
1404
            $assign['intro'] = $this->replaceMoodleChamiloCoursePath($assign['intro'], $sectionPath);
1405
            if ($importedFiles) {
1406
                $this->fixPathInText($importedFiles, $assign['intro']);
1407
            }
1408
1409
            $values['description'] = api_utf8_decode($assign['intro']);
1410
            $values['qualification'] = (int) $assign['grade'];
1411
            $values['weight'] = (int) $assign['grade'];
1412
            $values['allow_text_assignment'] = 2;
1413
            if (!empty($assign['config'])) {
1414
                if (1 == (int) $assign['config']['onlinetext']['enabled'] && 1 == (int) $assign['config']['file']['enabled']) {
1415
                    $values['allow_text_assignment'] = 0;
1416
                } elseif (1 == (int) $assign['config']['onlinetext']['enabled'] && empty($assign['config']['file']['enabled'])) {
1417
                    $values['allow_text_assignment'] = 1;
1418
                } elseif (empty($assign['config']['onlinetext']['enabled']) && 1 == (int) $assign['config']['file']['enabled']) {
1419
                    $values['allow_text_assignment'] = 2;
1420
                }
1421
            }
1422
1423
            $assignId = addDir(
1424
                $values,
1425
                api_get_user_id(),
1426
                api_get_course_info(),
1427
                api_get_group_id(),
1428
                api_get_session_id()
1429
            );
1430
1431
            return $assignId;
1432
        }
1433
1434
        return false;
1435
    }
1436
1437
    /**
1438
     * It saves a scorm from module xml.
1439
     *
1440
     * @param $moduleValues
1441
     * @param $modScormFileZips
1442
     *
1443
     * @return bool
1444
     */
1445
    public function processScorm($moduleValues, $modScormFileZips)
1446
    {
1447
        if (!empty($moduleValues)) {
1448
            foreach ($moduleValues as $info) {
1449
                $sha1hash = $info['sha1hash'];
1450
                if (!empty($modScormFileZips[$sha1hash])) {
1451
                    $scormFile = $modScormFileZips[$sha1hash];
1452
                    $oScorm = new scorm();
1453
                    $manifest = $oScorm->import_package(
1454
                        $scormFile['file']
1455
                    );
1456
                    if (!empty($manifest)) {
1457
                        $oScorm->parse_manifest($manifest);
1458
                        $oScorm->import_manifest(
1459
                            api_get_course_id(),
1460
                            1,
1461
                            0,
1462
                            0,
1463
                            $info['name']
1464
                        );
1465
                    }
1466
                    $oScorm->set_proximity($info['scormtype']);
1467
                    $oScorm->set_maker('Scorm');
1468
                    $oScorm->set_jslib('scorm_api.php');
1469
                }
1470
            }
1471
1472
            return true;
1473
        }
1474
1475
        return false;
1476
    }
1477
1478
    /**
1479
     * It saves glossary terms from module xml.
1480
     *
1481
     * @param $moduleValues
1482
     * @param $moduleId
1483
     *
1484
     * @return bool
1485
     */
1486
    public function processGlossary($moduleValues, $moduleId, $allFiles = [], $sectionPath = '/')
1487
    {
1488
        $importedFiles = [];
1489
        $contextId = $moduleValues['attributes']['contextid'];
1490
        if (isset($allFiles[$contextId])) {
1491
            $importedFiles = $this->processSectionMultimedia($allFiles[$contextId], $sectionPath);
1492
        }
1493
1494
        if (!empty($moduleValues[$moduleId])) {
1495
            foreach ($moduleValues[$moduleId] as $entry) {
1496
                $values = [];
1497
                $values['name'] = $entry['concept'];
1498
                $entry['definition'] = $this->replaceMoodleChamiloCoursePath($entry['definition'], $sectionPath);
1499
                if ($importedFiles) {
1500
                    $this->fixPathInText($importedFiles, $entry['definition']);
1501
                }
1502
                $values['description'] = $entry['definition'];
1503
                GlossaryManager::save_glossary($values);
1504
            }
1505
1506
            return true;
1507
        }
1508
1509
        return false;
1510
    }
1511
1512
    /**
1513
     * It process the module as document html.
1514
     *
1515
     * @param $moduleValues
1516
     * @param $moduleName
1517
     *
1518
     * @return false|int
1519
     */
1520
    public function processHtmlDocument($moduleValues, $moduleName, $importedFiles = [], $sectionPath = '/')
1521
    {
1522
        $dir = $sectionPath;
1523
        $filepath = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/document'.$dir;
1524
        $title = trim($moduleValues['name']);
1525
        if ('$@NULL@$' == $title) {
1526
            $title = get_lang('Tag');
1527
        }
1528
1529
        // Setting the filename
1530
        $filename = $title;
1531
        $filename = addslashes(trim($filename));
1532
        $filename = Security::remove_XSS($filename);
1533
        $filename = api_replace_dangerous_char($filename);
1534
        $filename = disable_dangerous_file($filename);
1535
        $filename .= DocumentManager::getDocumentSuffix(
1536
            api_get_course_info(),
1537
            api_get_session_id(),
1538
            api_get_group_id()
1539
        );
1540
1541
        $extension = 'html';
1542
        $content = ('page' == $moduleName ? $moduleValues['content'] : $moduleValues['intro']);
1543
        $content = api_html_entity_decode($content);
1544
        $cleanSectionPath = trim($sectionPath, '/\\');
1545
        $content = $this->replaceMoodleChamiloCoursePath($content, $cleanSectionPath);
1546
1547
        if ($importedFiles) {
1548
            $this->fixPathInText($importedFiles, $content);
1549
        }
1550
1551
        if ($fp = @fopen($filepath.$filename.'.'.$extension, 'w')) {
1552
            $content = str_replace(
1553
                api_get_path(WEB_COURSE_PATH),
1554
                api_get_configuration_value('url_append').api_get_path(REL_COURSE_PATH),
1555
                $content
1556
            );
1557
1558
            fputs($fp, $content);
1559
            fclose($fp);
1560
            chmod($filepath.$filename.'.'.$extension, api_get_permissions_for_new_files());
1561
            $fileSize = filesize($filepath.$filename.'.'.$extension);
1562
            $saveFilePath = $dir.$filename.'.'.$extension;
1563
1564
            $documentId = add_document(
1565
                api_get_course_info(),
1566
                $saveFilePath,
1567
                'file',
1568
                $fileSize,
1569
                $title
1570
            );
1571
1572
            if ($documentId) {
1573
                api_item_property_update(
1574
                    api_get_course_info(),
1575
                    TOOL_DOCUMENT,
1576
                    $documentId,
1577
                    'DocumentAdded',
1578
                    api_get_user_id(),
1579
                    [],
1580
                    null,
1581
                    null,
1582
                    null,
1583
                    api_get_session_id()
1584
                );
1585
                // Update parent folders
1586
                item_property_update_on_folder(api_get_course_info(), $dir, api_get_user_id());
1587
            }
1588
1589
            return $documentId;
1590
        }
1591
1592
        return false;
1593
    }
1594
1595
    /**
1596
     * Read and validate the forum module XML.
1597
     *
1598
     * @param resource $moduleXml XML file
1599
     *
1600
     * @return mixed|array if is a valid xml file, false otherwise
1601
     */
1602
    public function readForumModule($moduleXml)
1603
    {
1604
        $moduleDoc = new DOMDocument();
1605
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1606
        if (empty($moduleRes)) {
1607
            return false;
1608
        }
1609
        $activities = $moduleDoc->getElementsByTagName('forum');
1610
        $currentItem = [];
1611
        foreach ($activities as $activity) {
1612
            if ($activity->childNodes->length) {
1613
                foreach ($activity->childNodes as $item) {
1614
                    if (in_array($item->nodeName, ['type', 'name', 'intro', 'grade_forum'])) {
1615
                        $currentItem[$item->nodeName] = $item->nodeValue;
1616
                    }
1617
                }
1618
            }
1619
        }
1620
1621
        $discussions = $moduleDoc->getElementsByTagName('discussion');
1622
1623
        $discussionsList = [];
1624
        $counter = 0;
1625
        foreach ($discussions as $discussion) {
1626
            if ($discussion->childNodes->length) {
1627
                foreach ($discussion->childNodes as $item) {
1628
                    $discussionsList[$counter][$item->nodeName] = $item->nodeValue;
1629
                }
1630
                $i = 0;
1631
                $postsNodes = $discussion->getElementsByTagName("post");
1632
                $posts = [];
1633
                foreach ($postsNodes as $post) {
1634
                    foreach ($post->childNodes as $n) {
1635
                        $posts[$i][$n->nodeName] = $n->nodeValue;
1636
                    }
1637
                    $i++;
1638
                }
1639
                $discussionsList[$counter]['posts'] = $posts;
1640
                $counter++;
1641
            }
1642
        }
1643
        $currentItem['discussions'] = $discussionsList;
1644
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1645
        $currentItem['attributes'] = $attributes;
1646
1647
        return $currentItem;
1648
    }
1649
1650
    /**
1651
     * Read and validate the folder module XML.
1652
     *
1653
     * @param resource $moduleXml XML file
1654
     *
1655
     * @return mixed|array if is a valid xml file, false otherwise
1656
     */
1657
    public function readFolderModule($moduleXml)
1658
    {
1659
        $moduleDoc = new DOMDocument();
1660
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1661
        if (empty($moduleRes)) {
1662
            return false;
1663
        }
1664
        $activities = $moduleDoc->getElementsByTagName('folder');
1665
        $mainActivity = $moduleDoc->getElementsByTagName('activity');
1666
        $contextId = $mainActivity->item(0)->getAttribute('contextid');
1667
        $currentItem = [];
1668
        foreach ($activities as $activity) {
1669
            if ($activity->childNodes->length) {
1670
                foreach ($activity->childNodes as $item) {
1671
                    $currentItem[$item->nodeName] = $item->nodeValue;
1672
                }
1673
            }
1674
        }
1675
1676
        $currentItem['contextid'] = $contextId;
1677
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1678
        $currentItem['attributes'] = $attributes;
1679
1680
        return $currentItem;
1681
    }
1682
1683
    /**
1684
     * Read and validate the resource module XML.
1685
     *
1686
     * @param resource $moduleXml XML file
1687
     *
1688
     * @return mixed|array if is a valid xml file, false otherwise
1689
     */
1690
    public function readResourceModule($moduleXml)
1691
    {
1692
        $moduleDoc = new DOMDocument();
1693
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1694
        if (empty($moduleRes)) {
1695
            return false;
1696
        }
1697
        $activities = $moduleDoc->getElementsByTagName('resource');
1698
        $mainActivity = $moduleDoc->getElementsByTagName('activity');
1699
        $contextId = $mainActivity->item(0)->getAttribute('contextid');
1700
        $currentItem = [];
1701
        foreach ($activities as $activity) {
1702
            if ($activity->childNodes->length) {
1703
                foreach ($activity->childNodes as $item) {
1704
                    $currentItem[$item->nodeName] = $item->nodeValue;
1705
                }
1706
            }
1707
        }
1708
1709
        $currentItem['contextid'] = $contextId;
1710
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1711
        $currentItem['attributes'] = $attributes;
1712
1713
        return $currentItem;
1714
    }
1715
1716
    /**
1717
     * Read and validate the url module XML.
1718
     *
1719
     * @param resource $moduleXml XML file
1720
     *
1721
     * @return mixed|array if is a valid xml file, false otherwise
1722
     */
1723
    public function readUrlModule($moduleXml)
1724
    {
1725
        $moduleDoc = new DOMDocument();
1726
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1727
        if (empty($moduleRes)) {
1728
            return false;
1729
        }
1730
        $activities = $moduleDoc->getElementsByTagName('url');
1731
        $currentItem = [];
1732
        foreach ($activities as $activity) {
1733
            if ($activity->childNodes->length) {
1734
                foreach ($activity->childNodes as $item) {
1735
                    $currentItem[$item->nodeName] = $item->nodeValue;
1736
                }
1737
            }
1738
        }
1739
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1740
        $currentItem['attributes'] = $attributes;
1741
1742
        return $currentItem;
1743
    }
1744
1745
    /**
1746
     * It gets the grade values about a quiz imported.
1747
     *
1748
     * @param $gradeXml
1749
     * @param $quizId
1750
     *
1751
     * @return false|mixed
1752
     */
1753
    public function readQuizGradeModule($gradeXml, $quizId)
1754
    {
1755
        $doc = new DOMDocument();
1756
        $gradeRes = @$doc->loadXML($gradeXml);
1757
        if (empty($gradeRes)) {
1758
            return false;
1759
        }
1760
        $entries = $doc->getElementsByTagName('grade_item');
1761
        $info = [];
1762
        $i = 0;
1763
        foreach ($entries as $entry) {
1764
            if (empty($entry->childNodes->length)) {
1765
                continue;
1766
            }
1767
            foreach ($entry->childNodes as $item) {
1768
                if (in_array($item->nodeName, ['iteminstance', 'grademax', 'grademin', 'gradepass'])) {
1769
                    $info[$i][$item->nodeName] = $item->nodeValue;
1770
                }
1771
            }
1772
            $i++;
1773
        }
1774
        $grades = [];
1775
        if (!empty($info)) {
1776
            foreach ($info as $res) {
1777
                $grades[$res['iteminstance']] = $res;
1778
            }
1779
1780
            return $grades[$quizId];
1781
        }
1782
1783
        return false;
1784
    }
1785
1786
    /**
1787
     * Read and validate the quiz module XML.
1788
     *
1789
     * @param resource $moduleXml XML file
1790
     *
1791
     * @return mixed|array if is a valid xml file, false otherwise
1792
     */
1793
    public function readQuizModule($moduleXml)
1794
    {
1795
        $moduleDoc = new DOMDocument();
1796
        $moduleRes = @$moduleDoc->loadXML($moduleXml);
1797
        if (empty($moduleRes)) {
1798
            return false;
1799
        }
1800
        $activities = $moduleDoc->getElementsByTagName('quiz');
1801
        $currentItem = [];
1802
        foreach ($activities as $activity) {
1803
            if ($activity->childNodes->length) {
1804
                foreach ($activity->childNodes as $item) {
1805
                    $currentItem[$item->nodeName] = $item->nodeValue;
1806
                }
1807
                $currentItem['quiz_id'] = $activity->getAttribute('id');
1808
            }
1809
        }
1810
1811
        $questions = $moduleDoc->getElementsByTagName('question_instance');
1812
1813
        $questionList = [];
1814
        $counter = 0;
1815
        foreach ($questions as $question) {
1816
            if ($question->childNodes->length) {
1817
                foreach ($question->childNodes as $item) {
1818
                    $questionList[$counter][$item->nodeName] = $item->nodeValue;
1819
                }
1820
                $counter++;
1821
            }
1822
        }
1823
        $currentItem['question_instances'] = $questionList;
1824
        $attributes = $this->getDocActivityAttributes($moduleDoc);
1825
        $currentItem['attributes'] = $attributes;
1826
1827
        return $currentItem;
1828
    }
1829
1830
    /**
1831
     * Search the files of a resource type folder in main Files XML.
1832
     *
1833
     * @param resource $filesXml  XML file
1834
     * @param int      $contextId
1835
     *
1836
     * @return mixed|array if is a valid xml file, false otherwise
1837
     */
1838
    public function readFolderModuleFilesXml($filesXml, $contextId = null)
1839
    {
1840
        $moduleDoc = new DOMDocument();
1841
        $moduleRes = @$moduleDoc->loadXML($filesXml);
1842
1843
        if (empty($moduleRes)) {
1844
            return false;
1845
        }
1846
        $activities = $moduleDoc->getElementsByTagName('file');
1847
        $filesInfo = [];
1848
        $i = 0;
1849
        foreach ($activities as $activity) {
1850
            if (empty($activity->childNodes->length)) {
1851
                continue;
1852
            }
1853
            foreach ($activity->childNodes as $item) {
1854
                if (in_array($item->nodeName, ['filename', 'filesize', 'contenthash', 'contextid', 'filepath', 'filesize', 'mimetype'])) {
1855
                    $filesInfo[$i][$item->nodeName] = $item->nodeValue;
1856
                }
1857
            }
1858
            $i++;
1859
        }
1860
        $currentItem = [];
1861
        if (!empty($filesInfo)) {
1862
            foreach ($filesInfo as $info) {
1863
                if (!empty($info['filesize'])) {
1864
                    $currentItem[$info['contextid']]['files'][] = $info;
1865
                } else {
1866
                    $currentItem[$info['contextid']]['folder'][] = $info;
1867
                }
1868
            }
1869
        }
1870
        $files = isset($contextId) ? $currentItem[$contextId] : $currentItem;
1871
1872
        return $files;
1873
    }
1874
1875
    /**
1876
     * Search the current file resource in main Files XML.
1877
     *
1878
     * @param resource $filesXml  XML file
1879
     * @param int      $contextId
1880
     *
1881
     * @return mixed|array if is a valid xml file, false otherwise
1882
     */
1883
    public function readMainFilesXml($filesXml, $contextId)
1884
    {
1885
        $moduleDoc = new DOMDocument();
1886
        $moduleRes = @$moduleDoc->loadXML($filesXml);
1887
1888
        if (empty($moduleRes)) {
1889
            return false;
1890
        }
1891
1892
        $activities = $moduleDoc->getElementsByTagName('file');
1893
        $filesInfo = [];
1894
        $i = 0;
1895
        foreach ($activities as $activity) {
1896
            if (empty($activity->childNodes->length)) {
1897
                continue;
1898
            }
1899
            foreach ($activity->childNodes as $item) {
1900
                if (in_array($item->nodeName, ['filename', 'filesize', 'contenthash', 'contextid', 'filesize', 'mimetype'])) {
1901
                    $filesInfo[$i][$item->nodeName] = $item->nodeValue;
1902
                }
1903
            }
1904
            $i++;
1905
        }
1906
        $currentItem = [];
1907
        if (!empty($filesInfo)) {
1908
            foreach ($filesInfo as $info) {
1909
                if (!empty($info['filesize'])) {
1910
                    $currentItem[$info['contextid']] = $info;
1911
                }
1912
            }
1913
        }
1914
1915
        return $currentItem[$contextId];
1916
    }
1917
1918
    /**
1919
     * Search the current question resource in main Questions XML.
1920
     *
1921
     * @param resource $questionsXml XML file
1922
     * @param int      $questionId
1923
     *
1924
     * @return mixed|array if is a valid xml file, false otherwise
1925
     */
1926
    public function readMainQuestionsXml($questionsXml, $questionId)
1927
    {
1928
        $moduleDoc = new DOMDocument();
1929
        $moduleRes = @$moduleDoc->loadXML($questionsXml);
1930
        if (empty($moduleRes)) {
1931
            return false;
1932
        }
1933
1934
        $questions = $moduleDoc->getElementsByTagName('question');
1935
        $currentItem = [];
1936
        foreach ($questions as $question) {
1937
            if ((int) $question->getAttribute('id') !== (int) $questionId) {
1938
                continue;
1939
            }
1940
1941
            if (empty($question->childNodes->length)) {
1942
                continue;
1943
            }
1944
1945
            $currentItem['questionid'] = $questionId;
1946
            $questionType = '';
1947
            foreach ($question->childNodes as $item) {
1948
                $currentItem[$item->nodeName] = $item->nodeValue;
1949
                if ('qtype' === $item->nodeName) {
1950
                    $questionType = $item->nodeValue;
1951
                }
1952
1953
                if ($item->nodeName != 'plugin_qtype_'.$questionType.'_question') {
1954
                    continue;
1955
                }
1956
1957
                $answer = $item->getElementsByTagName($this->getQuestionTypeAnswersTag($questionType));
1958
                $currentItem['plugin_qtype_'.$questionType.'_question'] = [];
1959
                for ($i = 0; $i <= $answer->length - 1; $i++) {
1960
                    $label = 'plugin_qtype_'.$questionType.'_question';
1961
                    $currentItem[$label][$i]['answerid'] = $answer->item($i)->getAttribute('id');
1962
                    foreach ($answer->item($i)->childNodes as $properties) {
1963
                        $currentItem[$label][$i][$properties->nodeName] = $properties->nodeValue;
1964
                    }
1965
                }
1966
1967
                $typeValues = $item->getElementsByTagName($this->getQuestionTypeOptionsTag($questionType));
1968
                for ($i = 0; $i <= $typeValues->length - 1; $i++) {
1969
                    foreach ($typeValues->item($i)->childNodes as $properties) {
1970
                        $currentItem[$questionType.'_values'][$properties->nodeName] = $properties->nodeValue;
1971
                        if ($properties->nodeName !== 'sequence') {
1972
                            continue;
1973
                        }
1974
1975
                        $sequence = $properties->nodeValue;
1976
                        $sequenceIds = explode(',', $sequence);
1977
                        foreach ($sequenceIds as $qId) {
1978
                            $questionMatch = $this->readMainQuestionsXml($questionsXml, $qId);
1979
                            $currentItem['plugin_qtype_'.$questionType.'_question'][] = $questionMatch;
1980
                        }
1981
                    }
1982
                }
1983
            }
1984
        }
1985
1986
        $this->traverseArray($currentItem, ['#text', 'question_hints', 'tags']);
1987
1988
        return $currentItem;
1989
    }
1990
1991
    /**
1992
     * return the correct question type options tag.
1993
     *
1994
     * @param string $questionType name
1995
     *
1996
     * @return string question type tag
1997
     */
1998
    public function getQuestionTypeOptionsTag($questionType)
1999
    {
2000
        switch ($questionType) {
2001
            case 'match':
2002
            case 'ddmatch':
2003
                return 'matchoptions';
2004
            default:
2005
                return $questionType;
2006
        }
2007
    }
2008
2009
    /**
2010
     * return the correct question type answers tag.
2011
     *
2012
     * @param string $questionType name
2013
     *
2014
     * @return string question type tag
2015
     */
2016
    public function getQuestionTypeAnswersTag($questionType)
2017
    {
2018
        switch ($questionType) {
2019
            case 'match':
2020
            case 'ddmatch':
2021
                return 'match';
2022
            default:
2023
                return 'answer';
2024
        }
2025
    }
2026
2027
    /**
2028
     * @param array Result of readMainQuestionsXml
2029
     *
2030
     * @return int Chamilo question type
2031
     */
2032
    public function matchMoodleChamiloQuestionTypes($questionsValues)
2033
    {
2034
        $moodleQuestionType = $questionsValues['qtype'];
2035
        $questionOptions = $moodleQuestionType.'_values';
2036
        // Check <single> located in <plugin_qtype_multichoice_question><multichoice><single><single>
2037
        if (
2038
            'multichoice' === $moodleQuestionType &&
2039
            isset($questionsValues[$questionOptions]) &&
2040
            isset($questionsValues[$questionOptions]['single']) &&
2041
            1 === (int) $questionsValues[$questionOptions]['single']
2042
        ) {
2043
            return UNIQUE_ANSWER;
2044
        }
2045
2046
        switch ($moodleQuestionType) {
2047
            case 'multichoice':
2048
                return MULTIPLE_ANSWER;
2049
            case 'multianswer':
2050
            case 'match':
2051
                return FILL_IN_BLANKS;
2052
            case 'essay':
2053
            case 'shortanswer':
2054
                return FREE_ANSWER;
2055
            case 'truefalse':
2056
                return UNIQUE_ANSWER;
2057
        }
2058
    }
2059
2060
    /**
2061
     * Fix moodle files that contains spaces.
2062
     *
2063
     * @param array  $importedFiles
2064
     * @param string $text
2065
     *
2066
     * @return mixed
2067
     */
2068
    public function fixPathInText($importedFiles, &$text)
2069
    {
2070
        if ($importedFiles) {
2071
            foreach ($importedFiles as $old => $new) {
2072
                // Ofaj fix moodle file names
2073
                // In some questions moodle text contains file with name like:
2074
                // Bild%20Check-In-Formular%20Ausfu%CC%88llen.jpg"
2075
                // rawurlencode function transforms '' (whitespace) to %20 and so on
2076
                $text = str_replace(rawurlencode($old), $new, $text);
2077
            }
2078
        }
2079
2080
        return $text;
2081
    }
2082
2083
    /**
2084
     * Process Moodle Answers to Chamilo.
2085
     *
2086
     * @param Exercise $exercise
2087
     * @param array    $questionList
2088
     * @param string   $questionType
2089
     * @param Question $questionInstance Question/Answer instance
2090
     * @param array    $currentQuestion
2091
     * @param array    $importedFiles
2092
     *
2093
     * @return int db response
2094
     */
2095
    public function processAnswers(
2096
        $exercise,
2097
        $questionList,
2098
        $questionType,
2099
        $questionInstance,
2100
        $currentQuestion,
2101
        $importedFiles,
2102
        $sectionPath = ''
2103
    ) {
2104
        switch ($questionType) {
2105
            case 'multichoice':
2106
                $objAnswer = new Answer($questionInstance->iid);
2107
                $questionWeighting = 0;
2108
                foreach ($questionList as $slot => $answer) {
2109
                    $this->processMultipleAnswer(
2110
                        $objAnswer,
2111
                        $answer,
2112
                        $slot + 1,
2113
                        $questionWeighting,
2114
                        $importedFiles,
2115
                        $sectionPath
2116
                    );
2117
                }
2118
2119
                // saves the answers into the data base
2120
                $objAnswer->save();
2121
                // sets the total weighting of the question
2122
                $questionInstance->updateWeighting($questionWeighting);
2123
                $questionInstance->save($exercise);
2124
2125
                return true;
2126
            case 'multianswer':
2127
                $objAnswer = new Answer($questionInstance->iid);
2128
                $coursePath = api_get_course_path();
2129
                $placeholder = $this->replaceMoodleChamiloCoursePath($currentQuestion['questiontext'], $sectionPath);
2130
                $optionsValues = [];
2131
                foreach ($questionList as $slot => $subQuestion) {
2132
                    $qtype = $subQuestion['qtype'];
2133
                    $optionsValues[] = $this->processFillBlanks(
2134
                        $objAnswer,
2135
                        $qtype,
2136
                        $subQuestion['plugin_qtype_'.$qtype.'_question'],
2137
                        $placeholder,
2138
                        $slot + 1
2139
                    );
2140
                }
2141
2142
                $answerOptionsWeight = '::';
2143
                $answerOptionsSize = '';
2144
                $questionWeighting = 0;
2145
                foreach ($optionsValues as $index => $value) {
2146
                    $questionWeighting += $value['weight'];
2147
                    $answerOptionsWeight .= $value['weight'].',';
2148
                    $answerOptionsSize .= $value['size'].',';
2149
                }
2150
2151
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
2152
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
2153
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
2154
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
2155
2156
                // This is a minor trick to clean the question description that in a multianswer is the main placeholder
2157
                $questionInstance->updateDescription('');
2158
                // sets the total weighting of the question
2159
                $questionInstance->updateWeighting($questionWeighting);
2160
                $questionInstance->save($exercise);
2161
                $this->fixPathInText($importedFiles, $placeholder);
2162
2163
                // saves the answers into the data base
2164
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
2165
                $objAnswer->save();
2166
2167
                return true;
2168
            case 'match':
2169
                $objAnswer = new Answer($questionInstance->iid);
2170
                $placeholder = '';
2171
2172
                $optionsValues = $this->processFillBlanks(
2173
                    $objAnswer,
2174
                    'match',
2175
                    $questionList,
2176
                    $placeholder,
2177
                    0,
2178
                    $sectionPath
2179
                );
2180
2181
                $answerOptionsWeight = '::';
2182
                $answerOptionsSize = '';
2183
                $questionWeighting = 0;
2184
                foreach ($optionsValues as $index => $value) {
2185
                    $questionWeighting += $value['weight'];
2186
                    $answerOptionsWeight .= $value['weight'].',';
2187
                    $answerOptionsSize .= $value['size'].',';
2188
                }
2189
2190
                $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
2191
                $answerOptionsSize = substr($answerOptionsSize, 0, -1);
2192
                $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
2193
                $placeholder = $placeholder.PHP_EOL.$answerOptions;
2194
2195
                // sets the total weighting of the question
2196
                $questionInstance->updateWeighting($questionWeighting);
2197
                $questionInstance->save($exercise);
2198
                // saves the answers into the database
2199
                $this->fixPathInText($importedFiles, $placeholder);
2200
                $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
2201
                $objAnswer->save();
2202
2203
                return true;
2204
            case 'ddmatch':
2205
                $questionWeighting = $currentQuestion['defaultmark'];
2206
                $questionInstance->updateWeighting($questionWeighting);
2207
                $questionInstance->updateDescription(get_lang('ThisQuestionIsNotSupportedYet'));
2208
                $questionInstance->save($exercise);
2209
2210
                return false;
2211
            case 'shortanswer':
2212
            case 'essay':
2213
                $questionWeighting = $currentQuestion['defaultmark'];
2214
                $questionInstance->updateWeighting($questionWeighting);
2215
                $questionInstance->save($exercise);
2216
2217
                return true;
2218
            case 'truefalse':
2219
                $objAnswer = new Answer($questionInstance->iid);
2220
                $questionWeighting = 0;
2221
                foreach ($questionList as $slot => $answer) {
2222
                    $this->processTrueFalse(
2223
                        $objAnswer,
2224
                        $answer,
2225
                        $slot + 1,
2226
                        $questionWeighting,
2227
                        $importedFiles,
2228
                        $sectionPath
2229
                    );
2230
                }
2231
2232
                // saves the answers into the data base
2233
                $objAnswer->save();
2234
                // sets the total weighting of the question
2235
                $questionInstance->updateWeighting($questionWeighting);
2236
                $questionInstance->save($exercise);
2237
2238
                return false;
2239
            default:
2240
                return false;
2241
        }
2242
    }
2243
2244
    /**
2245
     * Process Chamilo Unique Answer.
2246
     *
2247
     * @param object $objAnswer
2248
     * @param array  $answerValues
2249
     * @param int    $position
2250
     * @param int    $questionWeighting
2251
     * @param array  $importedFiles
2252
     *
2253
     * @return int db response
2254
     */
2255
    public function processUniqueAnswer(
2256
        $objAnswer,
2257
        $answerValues,
2258
        $position,
2259
        &$questionWeighting,
2260
        $importedFiles,
2261
        $sectionPath = ''
2262
    ) {
2263
        $correct = (int) $answerValues['fraction'] ? (int) $answerValues['fraction'] : 0;
2264
        $answer = $answerValues['answertext'];
2265
        $comment = $answerValues['feedback'];
2266
        $weighting = $answerValues['fraction'];
2267
        $weighting = abs($weighting);
2268
        if ($weighting > 0) {
2269
            $questionWeighting += $weighting;
2270
        }
2271
        $goodAnswer = $correct ? true : false;
2272
2273
        $this->fixPathInText($importedFiles, $answer);
2274
        $answer = $this->replaceMoodleChamiloCoursePath($answer, $sectionPath);
2275
        $comment = $this->replaceMoodleChamiloCoursePath($comment, $sectionPath);
2276
2277
        $objAnswer->createAnswer(
2278
            $answer,
2279
            $goodAnswer,
2280
            $comment,
2281
            $weighting,
2282
            $position,
2283
            null,
2284
            null,
2285
            ''
2286
        );
2287
    }
2288
2289
    public function processMultipleAnswer(
2290
        Answer $objAnswer,
2291
        $answerValues,
2292
        $position,
2293
        &$questionWeighting,
2294
        $importedFiles,
2295
        $sectionPath = ''
2296
    ) {
2297
        $answer = $answerValues['answertext'];
2298
        $comment = $answerValues['feedback'];
2299
        $weighting = $answerValues['fraction'];
2300
        //$weighting = abs($weighting);
2301
        if ($weighting > 0) {
2302
            $questionWeighting += $weighting;
2303
        }
2304
        $goodAnswer = $weighting > 0;
2305
2306
        $this->fixPathInText($importedFiles, $answer);
2307
        $answer = $this->replaceMoodleChamiloCoursePath($answer, $sectionPath);
2308
        $comment = $this->replaceMoodleChamiloCoursePath($comment, $sectionPath);
2309
2310
        $objAnswer->createAnswer(
2311
            $answer,
2312
            $goodAnswer,
2313
            $comment,
2314
            $weighting,
2315
            $position,
2316
            null,
2317
            null,
2318
            ''
2319
        );
2320
    }
2321
2322
    /**
2323
     * Process Chamilo True False.
2324
     *
2325
     * @param Answer $objAnswer
2326
     * @param array  $answerValues
2327
     * @param int    $position
2328
     * @param int    $questionWeighting
2329
     * @param array  $importedFiles
2330
     *
2331
     * @return int db response
2332
     */
2333
    public function processTrueFalse(
2334
        $objAnswer,
2335
        $answerValues,
2336
        $position,
2337
        &$questionWeighting,
2338
        $importedFiles,
2339
        $sectionPath = ''
2340
    ) {
2341
        $correct = (int) $answerValues['fraction'] ? (int) $answerValues['fraction'] : 0;
2342
        $answer = $answerValues['answertext'];
2343
        $comment = $answerValues['feedback'];
2344
        $weighting = $answerValues['fraction'];
2345
        $weighting = abs($weighting);
2346
        if ($weighting > 0) {
2347
            $questionWeighting += $weighting;
2348
        }
2349
        $goodAnswer = $correct ? true : false;
2350
2351
        $this->fixPathInText($importedFiles, $answer);
2352
        $answer = $this->replaceMoodleChamiloCoursePath($answer, $sectionPath);
2353
        $comment = $this->replaceMoodleChamiloCoursePath($comment, $sectionPath);
2354
2355
        $objAnswer->createAnswer(
2356
            $answer,
2357
            $goodAnswer,
2358
            $comment,
2359
            $weighting,
2360
            $position,
2361
            null,
2362
            null,
2363
            ''
2364
        );
2365
    }
2366
2367
    /**
2368
     * Process Chamilo FillBlanks.
2369
     *
2370
     * @param object $objAnswer
2371
     * @param array  $questionType
2372
     * @param array  $answerValues
2373
     * @param string $placeholder
2374
     * @param int    $position
2375
     *
2376
     * @return int db response
2377
     */
2378
    public function processFillBlanks(
2379
        $objAnswer,
2380
        $questionType,
2381
        $answerValues,
2382
        &$placeholder,
2383
        $position,
2384
        $sectionPath = ''
2385
    ) {
2386
        switch ($questionType) {
2387
            case 'multichoice':
2388
                $optionsValues = [];
2389
                $correctAnswer = '';
2390
                $othersAnswer = '';
2391
                foreach ($answerValues as $answer) {
2392
                    $correct = (int) $answer['fraction'];
2393
                    if ($correct) {
2394
                        $correctAnswer .= $answer['answertext'].'|';
2395
                        $optionsValues['weight'] = $answer['fraction'];
2396
                        $optionsValues['size'] = '200';
2397
                    } else {
2398
                        $othersAnswer .= $answer['answertext'].'|';
2399
                    }
2400
                }
2401
                $currentAnswers = $correctAnswer.$othersAnswer;
2402
                $currentAnswers = '['.substr($currentAnswers, 0, -1).']';
2403
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
2404
2405
                return $optionsValues;
2406
            case 'shortanswer':
2407
                $optionsValues = [];
2408
                $correctAnswer = '';
2409
                foreach ($answerValues as $answer) {
2410
                    $correct = (int) $answer['fraction'];
2411
                    if ($correct) {
2412
                        $correctAnswer .= $answer['answertext'];
2413
                        $optionsValues['weight'] = $answer['fraction'];
2414
                        $optionsValues['size'] = '200';
2415
                    }
2416
                }
2417
2418
                $currentAnswers = '['.$correctAnswer.']';
2419
                $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
2420
2421
                return $optionsValues;
2422
            case 'match':
2423
                $answers = [];
2424
                // Here first we need to extract all the possible answers
2425
                foreach ($answerValues as $slot => $answer) {
2426
                    $answers[$slot] = $answer['answertext'];
2427
                }
2428
2429
                // Now we set the order of the values matching the correct answer and set it to the first element
2430
                $optionsValues = [];
2431
                foreach ($answerValues as $slot => $answer) {
2432
                    $correctAnswer = '';
2433
                    $othersAnswers = '';
2434
                    $correctAnswer .= $answer['answertext'].'|';
2435
2436
                    foreach ($answers as $other) {
2437
                        if ($other !== $answer['answertext']) {
2438
                            $othersAnswers .= $other.'|';
2439
                        }
2440
                    }
2441
2442
                    $optionsValues[$slot]['weight'] = 1;
2443
                    $optionsValues[$slot]['size'] = '200';
2444
2445
                    $currentAnswers = htmlentities($correctAnswer.$othersAnswers);
2446
                    $currentAnswers = '['.substr($currentAnswers, 0, -1).'] ';
2447
                    $answer['questiontext'] = $this->replaceMoodleChamiloCoursePath($answer['questiontext'], $sectionPath);
2448
2449
                    $placeholder .= '<p> '.strip_tags($answer['questiontext']).' '.$currentAnswers.' </p>';
2450
                }
2451
2452
                return $optionsValues;
2453
            default:
2454
                return false;
2455
        }
2456
    }
2457
2458
    /**
2459
     * get All files associated with a question.
2460
     *
2461
     * @param $filesXml
2462
     *
2463
     * @return array
2464
     */
2465
    public function getAllQuestionFiles($filesXml)
2466
    {
2467
        $moduleDoc = new DOMDocument();
2468
        $moduleRes = @$moduleDoc->loadXML($filesXml);
2469
2470
        if (empty($moduleRes)) {
2471
            return [];
2472
        }
2473
2474
        $allFiles = [];
2475
        $activities = $moduleDoc->getElementsByTagName('file');
2476
        foreach ($activities as $activity) {
2477
            $currentItem = [];
2478
            $thisIsAnInvalidItem = false;
2479
2480
            if ($activity->childNodes->length) {
2481
                foreach ($activity->childNodes as $item) {
2482
                    if ($item->nodeName == 'component' && $item->nodeValue == 'mod_resource') {
2483
                        $thisIsAnInvalidItem = true;
2484
                    }
2485
2486
                    if ($item->nodeName == 'component' && $item->nodeValue == 'mod_scorm') {
2487
                        $currentItem['modscorm'] = true;
2488
                    }
2489
2490
                    if ($item->nodeName == 'contenthash') {
2491
                        $currentItem['contenthash'] = $item->nodeValue;
2492
                    }
2493
2494
                    if ($item->nodeName == 'filename') {
2495
                        $currentItem['filename'] = $item->nodeValue;
2496
                    }
2497
2498
                    if ($item->nodeName == 'filesize') {
2499
                        $currentItem['filesize'] = $item->nodeValue;
2500
                    }
2501
2502
                    if ($item->nodeName == 'contextid') {
2503
                        $currentItem['contextid'] = $item->nodeValue;
2504
                    }
2505
2506
                    if ($item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
2507
                        $thisIsAnInvalidItem = true;
2508
                    }
2509
2510
                    if ($item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
2511
                        $currentItem['mimetype'] = $item->nodeValue;
2512
                    }
2513
                }
2514
            }
2515
2516
            if (!$thisIsAnInvalidItem) {
2517
                $allFiles[] = $currentItem;
2518
            }
2519
        }
2520
2521
        return $allFiles;
2522
    }
2523
2524
    /**
2525
     * Litle utility to delete the unuseful tags.
2526
     *
2527
     * @param $array
2528
     * @param $keys
2529
     */
2530
    public function traverseArray(&$array, $keys)
2531
    {
2532
        foreach ($array as $key => &$value) {
2533
            if (is_array($value)) {
2534
                $this->traverseArray($value, $keys);
2535
            } else {
2536
                if (in_array($key, $keys)) {
2537
                    unset($array[$key]);
2538
                }
2539
            }
2540
        }
2541
    }
2542
2543
    /**
2544
     * Check if folder is empty or not.
2545
     *
2546
     * @param $dir
2547
     *
2548
     * @return bool
2549
     */
2550
    private function isEmptyDir($dir)
2551
    {
2552
        $iterator = new FilesystemIterator($dir);
2553
        $isDirEmpty = !$iterator->valid();
2554
2555
        return $isDirEmpty;
2556
    }
2557
}
2558