Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

main/exercise/export/exercise_import.inc.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use Symfony\Component\DomCrawler\Crawler;
7
8
/**
9
 * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
10
 * @author claro team <[email protected]>
11
 * @author Guillaume Lederer <[email protected]>
12
 * @author Yannick Warnier <[email protected]>
13
 */
14
15
/**
16
 * Unzip the exercise in the temp folder.
17
 *
18
 * @param string $baseWorkDir The path of the temporary directory where the exercise was uploaded and unzipped
19
 * @param string $uploadPath
20
 *
21
 * @return bool
22
 */
23
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
24
{
25
    $_course = api_get_course_info();
26
    $_user = api_get_user_info();
27
28
    //Check if the file is valid (not to big and exists)
29
    if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
30
        // upload failed
31
        return false;
32
    }
33
34
    if (preg_match('/.zip$/i', $_FILES['userFile']['name'])) {
35
        return handle_uploaded_document(
36
            $_course,
37
            $_FILES['userFile'],
38
            $baseWorkDir,
39
            $uploadPath,
40
            $_user['user_id'],
41
            0,
42
            null,
43
            1,
44
            null,
45
            null,
46
            true,
47
            null,
48
            null,
49
            false
50
        );
51
    }
52
53
    return false;
54
}
55
56
/**
57
 * Imports an exercise in QTI format if the XML structure can be found in it.
58
 *
59
 * @param array $file
60
 *
61
 * @return string|array as a backlog of what was really imported, and error or debug messages to display
62
 */
63
function import_exercise($file)
64
{
65
    global $exerciseInfo;
66
    global $resourcesLinks;
67
68
    $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH).'qti2/';
69
    if (!is_dir($baseWorkDir)) {
70
        mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
71
    }
72
73
    $uploadPath = api_get_unique_id().'/';
74
75
    if (!is_dir($baseWorkDir.$uploadPath)) {
76
        mkdir($baseWorkDir.$uploadPath, api_get_permissions_for_new_directories(), true);
77
    }
78
79
    // set some default values for the new exercise
80
    $exerciseInfo = [];
81
    $exerciseInfo['name'] = preg_replace('/.zip$/i', '', $file);
82
    $exerciseInfo['question'] = [];
83
84
    // if file is not a .zip, then we cancel all
85
    if (!preg_match('/.zip$/i', $file)) {
86
        return 'UplZipCorrupt';
87
    }
88
89
    // unzip the uploaded file in a tmp directory
90
    if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
91
        return 'UplZipCorrupt';
92
    }
93
94
    $baseWorkDir = $baseWorkDir.$uploadPath;
95
96
    // find the different manifests for each question and parse them.
97
    $exerciseHandle = opendir($baseWorkDir);
98
    $fileFound = false;
99
    $result = false;
100
    $filePath = null;
101
    $resourcesLinks = [];
102
103
    // parse every subdirectory to search xml question files and other assets to be imported
104
    // The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if
105
    // the imsmanifest.xml file is read.
106
    while (false !== ($file = readdir($exerciseHandle))) {
107
        if (is_dir($baseWorkDir.'/'.$file) && $file != "." && $file != "..") {
108
            // Find each manifest for each question repository found
109
            $questionHandle = opendir($baseWorkDir.'/'.$file);
110
            // Only analyse one level of subdirectory - no recursivity here
111
            while (false !== ($questionFile = readdir($questionHandle))) {
112
                if (preg_match('/.xml$/i', $questionFile)) {
113
                    $isQti = isQtiQuestionBank($baseWorkDir.'/'.$file.'/'.$questionFile);
114
                    if ($isQti) {
115
                        $result = qti_parse_file($baseWorkDir, $file, $questionFile);
116
                        $filePath = $baseWorkDir.$file;
117
                        $fileFound = true;
118
                    } else {
119
                        $isManifest = isQtiManifest($baseWorkDir.'/'.$file.'/'.$questionFile);
120
                        if ($isManifest) {
121
                            $resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file.'/'.$questionFile);
122
                        }
123
                    }
124
                }
125
            }
126
        } elseif (preg_match('/.xml$/i', $file)) {
127
            $isQti = isQtiQuestionBank($baseWorkDir.'/'.$file);
128
            if ($isQti) {
129
                $result = qti_parse_file($baseWorkDir, '', $file);
130
                $filePath = $baseWorkDir.'/'.$file;
131
                $fileFound = true;
132
            } else {
133
                $isManifest = isQtiManifest($baseWorkDir.'/'.$file);
134
                if ($isManifest) {
135
                    $resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file);
136
                }
137
            }
138
        }
139
    }
140
141
    if (!$fileFound) {
142
        return 'NoXMLFileFoundInTheZip';
143
    }
144
145
    if ($result == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
146
        return false;
147
    }
148
    // 1. Create exercise.
149
    $exercise = new Exercise();
150
    $exercise->exercise = $exerciseInfo['name'];
151
152
    // Random QTI support
153
    if (isset($exerciseInfo['order_type'])) {
154
        if ($exerciseInfo['order_type'] == 'Random') {
155
            $exercise->setQuestionSelectionType(2);
156
            $exercise->random = -1;
157
        }
158
    }
159
160
    if (!empty($exerciseInfo['description'])) {
161
        $exercise->updateDescription(formatText(strip_tags($exerciseInfo['description'])));
162
    }
163
164
    $exercise->save();
165
    $last_exercise_id = $exercise->selectId();
166
    $courseId = api_get_course_int_id();
167
    if (!empty($last_exercise_id)) {
168
        // For each question found...
169
        foreach ($exerciseInfo['question'] as $question_array) {
170
            if (!in_array($question_array['type'], [UNIQUE_ANSWER, MULTIPLE_ANSWER, FREE_ANSWER])) {
171
                continue;
172
            }
173
            //2. Create question
174
            $question = new Ims2Question();
175
            $question->type = $question_array['type'];
176
            if (empty($question->type)) {
177
                // If the type was not provided, assume this is a multiple choice, unique answer type (the most basic)
178
                $question->type = MCUA;
179
            }
180
            $question->setAnswer();
181
            $description = '';
182
            $question->updateTitle(formatText(strip_tags($question_array['title'])));
183
184
            if (isset($question_array['category'])) {
185
                $category = formatText(strip_tags($question_array['category']));
186
                if (!empty($category)) {
187
                    $categoryId = TestCategory::get_category_id_for_title(
188
                        $category,
189
                        $courseId
190
                    );
191
192
                    if (empty($categoryId)) {
193
                        $cat = new TestCategory();
194
                        $cat->name = $category;
195
                        $cat->description = '';
196
                        $categoryId = $cat->save($courseId);
197
                        if ($categoryId) {
198
                            $question->category = $categoryId;
199
                        }
200
                    } else {
201
                        $question->category = $categoryId;
202
                    }
203
                }
204
            }
205
206
            if (!empty($question_array['description'])) {
207
                $description .= $question_array['description'];
208
            }
209
210
            $question->updateDescription($description);
211
            $question->save($exercise);
212
213
            $last_question_id = $question->selectId();
214
            //3. Create answer
215
            $answer = new Answer($last_question_id);
216
            $answerList = $question_array['answer'];
217
            $answer->new_nbrAnswers = count($answerList);
218
            $totalCorrectWeight = 0;
219
            $j = 1;
220
            $matchAnswerIds = [];
221
            if (!empty($answerList)) {
222
                foreach ($answerList as $key => $answers) {
223
                    if (preg_match('/_/', $key)) {
224
                        $split = explode('_', $key);
225
                        $i = $split[1];
226
                    } else {
227
                        $i = $j;
228
                        $j++;
229
                        $matchAnswerIds[$key] = $j;
230
                    }
231
232
                    // Answer
233
                    $answer->new_answer[$i] = isset($answers['value']) ? formatText($answers['value']) : '';
234
                    // Comment
235
                    $answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
236
                    // Position
237
                    $answer->new_position[$i] = $i;
238
                    // Correct answers
239
                    if (in_array($key, $question_array['correct_answers'])) {
240
                        $answer->new_correct[$i] = 1;
241
                    } else {
242
                        $answer->new_correct[$i] = 0;
243
                    }
244
245
                    $answer->new_weighting[$i] = 0;
246
                    if (isset($question_array['weighting'][$key])) {
247
                        $answer->new_weighting[$i] = $question_array['weighting'][$key];
248
                    }
249
                    if ($answer->new_correct[$i]) {
250
                        $totalCorrectWeight += $answer->new_weighting[$i];
251
                    }
252
                }
253
            }
254
255
            if ($question->type == FREE_ANSWER) {
256
                $totalCorrectWeight = $question_array['weighting'][0];
257
            }
258
259
            $question->updateWeighting($totalCorrectWeight);
260
            $question->save($exercise);
261
            $answer->save();
262
        }
263
264
        // delete the temp dir where the exercise was unzipped
265
        my_delete($baseWorkDir.$uploadPath);
266
267
        return $last_exercise_id;
268
    }
269
270
    return false;
271
}
272
273
/**
274
 * We assume the file charset is UTF8.
275
 */
276
function formatText($text)
277
{
278
    return api_html_entity_decode($text);
279
}
280
281
/**
282
 * Parses a given XML file and fills global arrays with the elements.
283
 *
284
 * @param string $exercisePath
285
 * @param string $file
286
 * @param string $questionFile
287
 *
288
 * @return bool
289
 */
290
function qti_parse_file($exercisePath, $file, $questionFile)
291
{
292
    global $record_item_body;
293
    global $questionTempDir;
294
295
    $questionTempDir = $exercisePath.'/'.$file.'/';
296
    $questionFilePath = $questionTempDir.$questionFile;
297
298
    if (!($fp = fopen($questionFilePath, 'r'))) {
299
        Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
300
301
        return false;
302
    }
303
304
    $data = fread($fp, filesize($questionFilePath));
305
306
    //close file
307
    fclose($fp);
308
309
    //parse XML question file
310
    //$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
311
    $data = ChamiloApi::stripGivenTags($data, ['p', 'front']);
312
    $qtiVersion = [];
313
    $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion);
314
    $qtiMainVersion = 2; //by default, assume QTI version 2
315
    if ($match) {
316
        $qtiMainVersion = $qtiVersion[1];
317
    }
318
319
    //used global variable start values declaration:
320
    $record_item_body = false;
321
322
    if ($qtiMainVersion != 2) {
323
        Display::addFlash(
324
            Display::return_message(
325
                get_lang('UnsupportedQtiVersion'),
326
                'error'
327
            )
328
        );
329
330
        return false;
331
    }
332
333
    parseQti2($data);
334
335
    return true;
336
}
337
338
/**
339
 * Function used to parser a QTI2 xml file.
340
 *
341
 * @param string $xmlData
342
 */
343
function parseQti2($xmlData)
344
{
345
    global $exerciseInfo;
346
    global $questionTempDir;
347
    global $resourcesLinks;
348
349
    $crawler = new Crawler($xmlData);
350
    $nodes = $crawler->filter('*');
351
352
    $currentQuestionIdent = '';
353
    $currentAnswerId = '';
354
    $currentQuestionItemBody = '';
355
    $cardinality = '';
356
    $nonHTMLTagToAvoid = [
357
        'prompt',
358
        'simpleChoice',
359
        'choiceInteraction',
360
        'inlineChoiceInteraction',
361
        'inlineChoice',
362
        'soMPLEMATCHSET',
363
        'simpleAssociableChoice',
364
        'textEntryInteraction',
365
        'feedbackInline',
366
        'matchInteraction',
367
        'extendedTextInteraction',
368
        'itemBody',
369
        'br',
370
        'img',
371
    ];
372
    $currentMatchSet = null;
373
374
    /** @var DOMElement $node */
375
    foreach ($nodes as $node) {
376
        if ('#text' === $node->nodeName) {
377
            continue;
378
        }
379
380
        switch ($node->nodeName) {
381
            case 'assessmentItem':
382
                $currentQuestionIdent = $node->getAttribute('identifier');
383
384
                $exerciseInfo['question'][$currentQuestionIdent] = [
385
                    'answer' => [],
386
                    'correct_answers' => [],
387
                    'title' => $node->getAttribute('title'),
388
                    'category' => $node->getAttribute('category'),
389
                    'type' => '',
390
                    'tempdir' => $questionTempDir,
391
                    'description' => null,
392
                ];
393
                break;
394
            case 'section':
395
                $title = $node->getAttribute('title');
396
397
                if (!empty($title)) {
398
                    $exerciseInfo['name'] = $title;
399
                }
400
                break;
401
            case 'responseDeclaration':
402
                if ('multiple' === $node->getAttribute('cardinality')) {
403
                    $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCMA;
404
                    $cardinality = 'multiple';
405
                }
406
407
                if ('single' === $node->getAttribute('cardinality')) {
408
                    $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCUA;
409
                    $cardinality = 'single';
410
                }
411
412
                $currentAnswerId = $node->getAttribute('identifier');
413
                break;
414
            case 'inlineChoiceInteraction':
415
                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
416
                $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'LISTBOX_FILL';
417
                $currentAnswerId = $node->getAttribute('responseIdentifier');
418
                break;
419
            case 'inlineChoice':
420
                $answerIdentifier = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
421
422
                if ($node->getAttribute('identifier') == $answerIdentifier) {
423
                    $currentQuestionItemBody = str_replace(
424
                        "**claroline_start**".$currentAnswerId."**claroline_end**",
425
                        "[".$node->nodeValue."]",
426
                        $currentQuestionItemBody
427
                    );
428
                } else {
429
                    if (!isset($exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'])) {
430
                        $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'] = [];
431
                    }
432
433
                    $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'][] = $node->nodeValue;
434
                }
435
                break;
436
            case 'textEntryInteraction':
437
                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB;
438
                $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'TEXTFIELD_FILL';
439
                $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
440
                break;
441
            case 'matchInteraction':
442
                $exerciseInfo['question'][$currentQuestionIdent]['type'] = MATCHING;
443
                break;
444
            case 'extendedTextInteraction':
445
                $exerciseInfo['question'][$currentQuestionIdent]['type'] = FREE_ANSWER;
446
                $exerciseInfo['question'][$currentQuestionIdent]['description'] = $node->nodeValue;
447
                break;
448
            case 'simpleMatchSet':
449
                if (!isset($currentMatchSet)) {
450
                    $currentMatchSet = 1;
451
                } else {
452
                    $currentMatchSet++;
453
                }
454
                $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet] = [];
455
                break;
456
            case 'simpleAssociableChoice':
457
                $currentAssociableChoice = $node->getAttribute('identifier');
458
459
                $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet][$currentAssociableChoice] = trim($node->nodeValue);
460
                break;
461
            case 'simpleChoice':
462
                $currentAnswerId = $node->getAttribute('identifier');
463
                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId])) {
464
                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId] = [];
465
                }
466
467
                //$simpleChoiceValue = $node->nodeValue;
468
                $simpleChoiceValue = '';
469
                /** @var DOMElement $childNode */
470
                foreach ($node->childNodes as $childNode) {
471
                    if ('feedbackInline' === $childNode->nodeName) {
472
                        continue;
473
                    }
474
                    $simpleChoiceValue .= $childNode->nodeValue;
475
                }
476
                $simpleChoiceValue = trim($simpleChoiceValue);
477
                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'])) {
478
                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] = $simpleChoiceValue;
479
                } else {
480
                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] .= $simpleChoiceValue;
481
                }
482
                break;
483
            case 'mapEntry':
484
                if (in_array($node->parentNode->nodeName, ['mapping', 'mapEntry'])) {
485
                    $answer_id = $node->getAttribute('mapKey');
486
487
                    if (!isset($exerciseInfo['question'][$currentQuestionIdent]['weighting'])) {
488
                        $exerciseInfo['question'][$currentQuestionIdent]['weighting'] = [];
489
                    }
490
491
                    $exerciseInfo['question'][$currentQuestionIdent]['weighting'][$answer_id] = $node->getAttribute(
492
                        'mappedValue'
493
                    );
494
                }
495
                break;
496
            case 'mapping':
497
                $defaultValue = $node->getAttribute('defaultValue');
498
                if (!empty($defaultValue)) {
499
                    $exerciseInfo['question'][$currentQuestionIdent]['default_weighting'] = $defaultValue;
500
                }
501
                // no break ?
502
            case 'itemBody':
503
                $nodeValue = $node->nodeValue;
504
                $currentQuestionItemBody = '';
505
506
                /** @var DOMElement $childNode */
507
                foreach ($node->childNodes as $childNode) {
508
                    if ('#text' === $childNode->nodeName) {
509
                        continue;
510
                    }
511
512
                    if (!in_array($childNode->nodeName, $nonHTMLTagToAvoid)) {
513
                        $currentQuestionItemBody .= '<'.$childNode->nodeName;
514
515
                        if ($childNode->attributes) {
516
                            foreach ($childNode->attributes as $attribute) {
517
                                $currentQuestionItemBody .= ' '.$attribute->nodeName.'="'.$attribute->nodeValue.'"';
518
                            }
519
                        }
520
521
                        $currentQuestionItemBody .= '>'.$childNode->nodeValue.'</'.$node->nodeName.'>';
522
523
                        continue;
524
                    }
525
526
                    if ('inlineChoiceInteraction' === $childNode->nodeName) {
527
                        $currentQuestionItemBody .= "**claroline_start**".$childNode->attr('responseIdentifier')
528
                            ."**claroline_end**";
529
                        continue;
530
                    }
531
532
                    if ('textEntryInteraction' === $childNode->nodeName) {
533
                        $correct_answer_value = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId];
534
                        $currentQuestionItemBody .= "[".$correct_answer_value."]";
535
536
                        continue;
537
                    }
538
539
                    if ('br' === $childNode->nodeName) {
540
                        $currentQuestionItemBody .= '<br>';
541
                    }
542
                }
543
544
                // Replace relative links by links to the documents in the course
545
                // $resourcesLinks is only defined by qtiProcessManifest()
546
                if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
547
                    foreach ($resourcesLinks['manifest'] as $key => $value) {
548
                        $nodeValue = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $nodeValue);
549
                    }
550
                }
551
552
                $currentQuestionItemBody .= $node->firstChild->nodeValue;
553
554
                if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FIB) {
555
                    $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody;
556
                } else {
557
                    if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FREE_ANSWER) {
558
                        $currentQuestionItemBody = trim($currentQuestionItemBody);
559
560
                        if (!empty($currentQuestionItemBody)) {
561
                            $exerciseInfo['question'][$currentQuestionIdent]['description'] = $currentQuestionItemBody;
562
                        }
563
                    } else {
564
                        $exerciseInfo['question'][$currentQuestionIdent]['statement'] = $currentQuestionItemBody;
565
                    }
566
                }
567
                break;
568
            case 'img':
569
                $exerciseInfo['question'][$currentQuestionIdent]['attached_file_url'] = $node->getAttribute('src');
570
                break;
571
            case 'order':
572
                $orderType = $node->getAttribute('order_type');
573
574
                if (!empty($orderType)) {
575
                    $exerciseInfo['order_type'] = $orderType;
576
                }
577
                break;
578
            case 'feedbackInline':
579
                if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'])) {
580
                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] = trim(
581
                        $node->nodeValue
582
                    );
583
                } else {
584
                    $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] .= trim(
585
                        $node->nodeValue
586
                    );
587
                }
588
                break;
589
            case 'value':
590
                if ('correctResponse' === $node->parentNode->nodeName) {
591
                    $nodeValue = trim($node->nodeValue);
592
593
                    if ('single' === $cardinality) {
594
                        $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$nodeValue] = $nodeValue;
595
                    } else {
596
                        $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][] = $nodeValue;
597
                    }
598
                }
599
600
                if ('outcomeDeclaration' === $node->parentNode->parentNode->nodeName) {
601
                    $nodeValue = trim($node->nodeValue);
602
603
                    if (!empty($nodeValue)) {
604
                        $exerciseInfo['question'][$currentQuestionIdent]['weighting'][0] = $nodeValue;
605
                    }
606
                }
607
                break;
608
            case 'mattext':
609
                if ('flow_mat' === $node->parentNode->parentNode->nodeName &&
610
                    ('presentation_material' === $node->parentNode->parentNode->parentNode->nodeName ||
611
                        'section' === $node->parentNode->parentNode->parentNode->nodeName
612
                    )
613
                ) {
614
                    $nodeValue = trim($node->nodeValue);
615
616
                    if (!empty($nodeValue)) {
617
                        $exerciseInfo['description'] = $node->nodeValue;
618
                    }
619
                }
620
                break;
621
            case 'prompt':
622
                $description = trim($node->nodeValue);
623
                $description = htmlspecialchars_decode($description);
624
                $description = Security::remove_XSS($description);
625
626
                if (!empty($description)) {
627
                    $exerciseInfo['question'][$currentQuestionIdent]['description'] = $description;
628
                }
629
                break;
630
        }
631
    }
632
}
633
634
/**
635
 * Check if a given file is an IMS/QTI question bank file.
636
 *
637
 * @param string $filePath The absolute filepath
638
 *
639
 * @return bool Whether it is an IMS/QTI question bank or not
640
 */
641
function isQtiQuestionBank($filePath)
642
{
643
    $data = file_get_contents($filePath);
644
    if (!empty($data)) {
645
        $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data);
646
        // @todo allow other types
647
        //$match2 = preg_match('/imsqti_v(\d)p(\d)/', $data);
648
649
        if ($match) {
650
            return true;
651
        }
652
    }
653
654
    return false;
655
}
656
657
/**
658
 * Check if a given file is an IMS/QTI manifest file (listing of extra files).
659
 *
660
 * @param string $filePath The absolute filepath
661
 *
662
 * @return bool Whether it is an IMS/QTI manifest file or not
663
 */
664
function isQtiManifest($filePath)
665
{
666
    $data = file_get_contents($filePath);
667
    if (!empty($data)) {
668
        $match = preg_match('/imsccv(\d)p(\d)/', $data);
669
        if ($match) {
670
            return true;
671
        }
672
    }
673
674
    return false;
675
}
676
677
/**
678
 * Processes an IMS/QTI manifest file: store links to new files
679
 * to be able to transform them into the questions text.
680
 *
681
 * @param string $filePath The absolute filepath
682
 *
683
 * @return bool
684
 */
685
function qtiProcessManifest($filePath)
686
{
687
    $xml = simplexml_load_file($filePath);
688
    $course = api_get_course_info();
689
    $sessionId = api_get_session_id();
690
    $courseDir = $course['path'];
691
    $sysPath = api_get_path(SYS_COURSE_PATH);
692
    $exercisesSysPath = $sysPath.$courseDir.'/document/';
693
    $webPath = api_get_path(WEB_CODE_PATH);
694
    $exercisesWebPath = $webPath.'document/document.php?'.api_get_cidreq().'&action=download&id=';
695
    $links = [
696
        'manifest' => [],
697
        'system' => [],
698
        'web' => [],
699
    ];
700
    $tableDocuments = Database::get_course_table(TABLE_DOCUMENT);
701
    $countResources = count($xml->resources->resource->file);
702
    for ($i = 0; $i < $countResources; $i++) {
703
        $file = $xml->resources->resource->file[$i];
704
        $href = '';
705
        foreach ($file->attributes() as $key => $value) {
706
            if ('href' == $key) {
707
                if ('xml' != substr($value, -3, 3)) {
708
                    $href = $value;
709
                }
710
            }
711
        }
712
        if (!empty($href)) {
713
            $links['manifest'][] = (string) $href;
714
            $links['system'][] = $exercisesSysPath.strtolower($href);
715
            $specialHref = Database::escape_string(preg_replace('/_/', '-', strtolower($href)));
716
            $specialHref = preg_replace('/(-){2,8}/', '-', $specialHref);
717
718
            $sql = "SELECT iid FROM $tableDocuments
719
                    WHERE
720
                        c_id = ".$course['real_id']." AND
721
                        session_id = $sessionId AND
722
                        path = '/".$specialHref."'";
723
            $result = Database::query($sql);
724
            $documentId = 0;
725
            while ($row = Database::fetch_assoc($result)) {
726
                $documentId = $row['iid'];
727
            }
728
            $links['web'][] = $exercisesWebPath.$documentId;
729
        }
730
    }
731
732
    return $links;
733
}
734