Passed
Push — master ( 4c9a82...f78b85 )
by Julito
12:50 queued 04:06
created

endElementQti2()   B

Complexity

Conditions 10
Paths 80

Size

Total Lines 51
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 37
nc 80
nop 2
dl 0
loc 51
rs 7.6666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
5
use Symfony\Component\DomCrawler\Crawler;
6
7
/**
8
 * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
9
 *
10
 * @package chamilo.exercise
11
 *
12
 * @author claro team <[email protected]>
13
 * @author Guillaume Lederer <[email protected]>
14
 * @author Yannick Warnier <[email protected]>
15
 */
16
17
/**
18
 * Unzip the exercise in the temp folder.
19
 *
20
 * @param string $baseWorkDir The path of the temporary directory where the exercise was uploaded and unzipped
21
 * @param string $uploadPath
22
 *
23
 * @return bool
24
 */
25
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
26
{
27
    $_course = api_get_course_info();
28
    $_user = api_get_user_info();
29
30
    //Check if the file is valid (not to big and exists)
31
    if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
32
        // upload failed
33
        return false;
34
    }
35
36
    if (preg_match('/.zip$/i', $_FILES['userFile']['name'])) {
37
        $result = handle_uploaded_document(
38
            $_course,
39
            $_FILES['userFile'],
40
            $baseWorkDir,
41
            $uploadPath,
42
            $_user['user_id'],
43
            0,
44
            null,
45
            1,
46
            null,
47
            null,
48
            true,
49
            null,
50
            null,
51
            false
52
        );
53
54
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type Chamilo\CourseBundle\Entity\CDocument which is incompatible with the documented return type boolean.
Loading history...
55
    }
56
57
    return false;
58
}
59
60
/**
61
 * Imports an exercise in QTI format if the XML structure can be found in it.
62
 *
63
 * @param array $file
64
 *
65
 * @return string|array as a backlog of what was really imported, and error or debug messages to display
66
 */
67
function import_exercise($file)
68
{
69
    global $exerciseInfo;
70
    global $resourcesLinks;
71
72
    $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH).'qti2/';
73
    if (!is_dir($baseWorkDir)) {
74
        mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
75
    }
76
77
    $uploadPath = api_get_unique_id().'/';
78
79
    if (!is_dir($baseWorkDir.$uploadPath)) {
80
        mkdir($baseWorkDir.$uploadPath, api_get_permissions_for_new_directories(), true);
81
    }
82
83
    // set some default values for the new exercise
84
    $exerciseInfo = [];
85
    $exerciseInfo['name'] = preg_replace('/.zip$/i', '', $file);
86
    $exerciseInfo['question'] = [];
87
88
    // if file is not a .zip, then we cancel all
89
    if (!preg_match('/.zip$/i', $file)) {
90
        return 'UplZipCorrupt';
91
    }
92
93
    // unzip the uploaded file in a tmp directory
94
    if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
95
        return 'UplZipCorrupt';
96
    }
97
98
    $baseWorkDir = $baseWorkDir.$uploadPath;
99
100
    // find the different manifests for each question and parse them.
101
    $exerciseHandle = opendir($baseWorkDir);
102
    $fileFound = false;
103
    $result = false;
104
    $filePath = null;
105
    $resourcesLinks = [];
106
107
    // parse every subdirectory to search xml question files and other assets to be imported
108
    // The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if
109
    // the imsmanifest.xml file is read.
110
    while (false !== ($file = readdir($exerciseHandle))) {
0 ignored issues
show
Bug introduced by
It seems like $exerciseHandle can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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