Completed
Push — 1.10.x ( 2fbd07...b13725 )
by Yannick
74:13 queued 27:54
created

exercise_import.inc.php ➔ endElementQti1()   F

Complexity

Conditions 17
Paths 432

Size

Total Lines 73
Code Lines 51

Duplication

Lines 21
Ratio 28.77 %

Importance

Changes 3
Bugs 0 Features 3
Metric Value
cc 17
eloc 51
c 3
b 0
f 3
nc 432
nop 3
dl 21
loc 73
rs 3.9193

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
 * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
5
 * @package chamilo.exercise
6
 * @author claro team <[email protected]>
7
 * @author Guillaume Lederer <[email protected]>
8
 * @author Yannick Warnier <[email protected]>
9
 */
10
11
/**
12
 * Unzip the exercise in the temp folder
13
 * @param string The path of the temporary directory where the exercise was uploaded and unzipped
14
 * @param string
15
 * @param string $baseWorkDir
16
 * @param string $uploadPath
17
 * @return bool
18
 */
19
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)
0 ignored issues
show
Best Practice introduced by
The function get_and_unzip_uploaded_exercise() has been defined more than once; this definition is ignored, only the first definition in main/exercice/export/aiken/aiken_import.inc.php (L62-81) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
20
{
21
    $_course = api_get_course_info();
22
    $_user = api_get_user_info();
23
24
    //Check if the file is valid (not to big and exists)
25 View Code Duplication
    if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
26
        // upload failed
27
        return false;
28
    }
29
30
    if (preg_match('/.zip$/i', $_FILES['userFile']['name']) &&
31
        handle_uploaded_document(
32
            $_course,
33
            $_FILES['userFile'],
34
            $baseWorkDir,
35
            $uploadPath,
36
            $_user['user_id'],
37
            0,
38
            null,
39
            1
40
        )
41
    ) {
42
        return true;
43
    }
44
    return false;
45
}
46
47
/**
48
 * Imports an exercise in QTI format if the XML structure can be found in it
49
 * @param array $file
50
 * @return string|array as a backlog of what was really imported, and error or debug messages to display
51
 */
52
function import_exercise($file)
53
{
54
    global $exercise_info;
55
    global $element_pile;
56
    global $non_HTML_tag_to_avoid;
57
    global $record_item_body;
58
    // used to specify the question directory where files could be found in relation in any question
59
    global $questionTempDir;
60
    global $resourcesLinks;
61
62
    $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH) . 'qti2';
63
64
    if (!is_dir($baseWorkDir)) {
65
        mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
66
    }
67
68
    $uploadPath = '/';
69
70
    // set some default values for the new exercise
71
    $exercise_info = array();
72
    $exercise_info['name'] = preg_replace('/.zip$/i', '', $file);
73
    $exercise_info['question'] = array();
74
    $element_pile = array();
75
76
    // create parser and array to retrieve info from manifest
77
    $element_pile = array(); //pile to known the depth in which we are
78
    //$module_info = array (); //array to store the info we need
79
80
    // if file is not a .zip, then we cancel all
81
82
    if (!preg_match('/.zip$/i', $file)) {
83
84
        return 'UplZipCorrupt';
85
    }
86
87
    // unzip the uploaded file in a tmp directory
88
    if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
89
90
        return 'UplZipCorrupt';
91
    }
92
93
    // find the different manifests for each question and parse them.
94
    $exerciseHandle = opendir($baseWorkDir);
95
    //$question_number = 0;
96
    $file_found = false;
97
    $operation = false;
98
    $result = false;
99
    $filePath = null;
100
    $resourcesLinks = array();
101
102
    // parse every subdirectory to search xml question files and other assets to be imported
103
    // The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if
104
    // the imsmanifest.xml file is read.
105
    while (false !== ($file = readdir($exerciseHandle))) {
106
        if (is_dir($baseWorkDir . '/' . $file) && $file != "." && $file != "..") {
107
            // Find each manifest for each question repository found
108
            $questionHandle = opendir($baseWorkDir . '/' . $file);
109
            // Only analyse one level of subdirectory - no recursivity here
110
            while (false !== ($questionFile = readdir($questionHandle))) {
111
                if (preg_match('/.xml$/i', $questionFile)) {
112
                    $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file . '/' . $questionFile);
113
                    if ($isQti) {
114
                        $result = qti_parse_file($baseWorkDir, $file, $questionFile);
115
                        $filePath = $baseWorkDir . $file;
116
                        $file_found = true;
117
                    } else {
118
                        $isManifest = isQtiManifest($baseWorkDir . '/' . $file . '/' . $questionFile);
119
                        if ($isManifest) {
120
                            $resourcesLinks = qtiProcessManifest($baseWorkDir . '/' . $file . '/' . $questionFile);
121
                        }
122
                    }
123
                }
124
            }
125
        } elseif (preg_match('/.xml$/i', $file)) {
126
            $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file);
127
            if ($isQti) {
128
                $result = qti_parse_file($baseWorkDir, '', $file);
129
                $filePath = $baseWorkDir . '/' . $file;
130
                $file_found = true;
131
            } else {
132
                $isManifest = isQtiManifest($baseWorkDir . '/' . $file);
133
                if ($isManifest) {
134
                    $resourcesLinks = qtiProcessManifest($baseWorkDir . '/' . $file);
135
                }
136
            }
137
138
        }
139
    }
140
141
    if (!$file_found) {
142
143
        return 'NoXMLFileFoundInTheZip';
144
    }
145
146
    if ($result == false) {
147
148
        return false;
149
    }
150
151
    $doc = new DOMDocument();
152
    $doc->load($filePath);
153
154
    // 1. Create exercise.
155
    $exercise = new Exercise();
156
    $exercise->exercise = $exercise_info['name'];
157
158
    $exercise->save();
159
    $last_exercise_id = $exercise->selectId();
160
    if (!empty($last_exercise_id)) {
161
        // For each question found...
162
        foreach ($exercise_info['question'] as $question_array) {
163
            //2. Create question
164
            $question = new Ims2Question();
165
            $question->type = $question_array['type'];
166
            $question->setAnswer();
167
            if (strlen($question_array['title']) < 50) {
168
                $question->updateTitle(formatText(strip_tags($question_array['title'])) . '...');
169
            } else {
170
                $question->updateTitle(formatText(substr(strip_tags($question_array['title']), 0, 50)));
171
                $question->updateDescription($question_array['title']);
172
            }
173
            //$question->updateDescription($question_array['title']);
174
            $question->save($last_exercise_id);
175
            $last_question_id = $question->selectId();
176
            //3. Create answer
177
            $answer = new Answer($last_question_id);
178
            $answer->new_nbrAnswers = count($question_array['answer']);
179
            $totalCorrectWeight = 0;
180
            $j = 1;
181
            $matchAnswerIds = array();
182
            foreach ($question_array['answer'] as $key => $answers) {
183
                if (preg_match('/_/', $key)) {
184
                    $split = explode('_', $key);
185
                    $i = $split[1];
186
                } else {
187
                    $i = $j;
188
                    $j++;
189
                    $matchAnswerIds[$key] = $j;
190
                }
191
                // Answer
192
                $answer->new_answer[$i] = formatText($answers['value']);
193
                // Comment
194
                $answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
195
                // Position
196
                $answer->new_position[$i] = $i;
197
                // Correct answers
198
                if (in_array($key, $question_array['correct_answers'])) {
199
                    $answer->new_correct[$i] = 1;
200
                } else {
201
                    $answer->new_correct[$i] = 0;
202
                }
203
                $answer->new_weighting[$i] = $question_array['weighting'][$key];
204
                if ($answer->new_correct[$i]) {
205
                    $totalCorrectWeight += $answer->new_weighting[$i];
206
                }
207
            }
208
            $question->updateWeighting($totalCorrectWeight);
209
            $question->save($last_exercise_id);
210
            $answer->save();
211
        }
212
213
        // delete the temp dir where the exercise was unzipped
214
        my_delete($baseWorkDir . $uploadPath);
215
        return $last_exercise_id;
216
    }
217
218
    return false;
219
}
220
221
/**
222
 * We assume the file charset is UTF8
223
 **/
224
function formatText($text)
225
{
226
    return api_html_entity_decode($text);
227
}
228
229
/**
230
 * Parses a given XML file and fills global arrays with the elements
231
 * @param string $exercisePath
232
 * @param string $file
233
 * @param string $questionFile
234
 * @return bool
235
 */
236
function qti_parse_file($exercisePath, $file, $questionFile)
237
{
238
    global $non_HTML_tag_to_avoid;
239
    global $record_item_body;
240
    global $questionTempDir;
241
242
    $questionTempDir = $exercisePath . '/' . $file . '/';
243
    $questionFilePath = $questionTempDir . $questionFile;
244
245
    if (!($fp = fopen($questionFilePath, 'r'))) {
246
        Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
247
248
        return false;
249
    } else {
250
        $data = fread($fp, filesize($questionFilePath));
251
    }
252
253
    //parse XML question file
254
    //$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
255
    $data = stripGivenTags($data, array('p', 'front'));
256
    $qtiVersion = array();
257
    $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion);
258
    $qtiMainVersion = 2; //by default, assume QTI version 2
259
    if ($match) {
260
        $qtiMainVersion = $qtiVersion[1];
261
    }
262
263
    //used global variable start values declaration :
264
265
    $record_item_body = false;
266
    $non_HTML_tag_to_avoid = array(
267
        "SIMPLECHOICE",
268
        "CHOICEINTERACTION",
269
        "INLINECHOICEINTERACTION",
270
        "INLINECHOICE",
271
        "SIMPLEMATCHSET",
272
        "SIMPLEASSOCIABLECHOICE",
273
        "TEXTENTRYINTERACTION",
274
        "FEEDBACKINLINE",
275
        "MATCHINTERACTION",
276
        "ITEMBODY",
277
        "BR",
278
        "IMG"
279
    );
280
281
    $question_format_supported = true;
282
283
    $xml_parser = xml_parser_create();
284
    xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false);
285
    if ($qtiMainVersion == 1) {
286
        xml_set_element_handler($xml_parser, 'startElementQti1', 'endElementQti1');
287
        xml_set_character_data_handler($xml_parser, 'elementDataQti1');
288
    } else {
289
        xml_set_element_handler($xml_parser, 'startElementQti2', 'endElementQti2');
290
        xml_set_character_data_handler($xml_parser, 'elementDataQti2');
291
    }
292
    if (!xml_parse($xml_parser, $data, feof($fp))) {
293
        // if reading of the xml file in not successful :
294
        // set errorFound, set error msg, break while statement
295
        $error = xml_get_error_code();
296
        Display::addFlash(
297
            Display::return_message(
298
                get_lang('Error reading XML file') . sprintf('[%d:%d]', xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser)),
299
                'error'
300
            )
301
        );
302
303
        return false;
304
    }
305
306
    //close file
307
    fclose($fp);
308
    if (!$question_format_supported) {
309
        Display::addFlash(
310
            Display::return_message(
311
                get_lang(
312
                    'Unknown question format in file %file',
313
                    array(
314
                        '%file' => $questionFile,
315
                    )
316
                ),
317
                'error'
318
            )
319
        );
320
321
        return false;
322
    }
323
    return true;
324
}
325
326
/**
327
 * Function used by the SAX xml parser when the parser meets a opening tag
328
 *
329
 * @param object $parser xml parser created with "xml_parser_create()"
330
 * @param string $name name of the element
331
 * @param array $attributes
332
 */
333
function startElementQti2($parser, $name, $attributes)
334
{
335
    global $element_pile;
336
    global $exercise_info;
337
    global $current_question_ident;
338
    global $current_answer_id;
339
    global $current_match_set;
340
    global $currentAssociableChoice;
341
    global $current_question_item_body;
342
    global $record_item_body;
343
    global $non_HTML_tag_to_avoid;
344
    global $current_inlinechoice_id;
345
    global $cardinality;
346
    global $questionTempDir;
347
348
    array_push($element_pile, $name);
349
    $current_element = end($element_pile);
350 View Code Duplication
    if (sizeof($element_pile) >= 2) {
351
        $parent_element = $element_pile[sizeof($element_pile) - 2];
352
    } else {
353
        $parent_element = "";
354
    }
355 View Code Duplication
    if (sizeof($element_pile) >= 3) {
356
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
357
    } else {
358
        $grant_parent_element = "";
359
    }
360
361 View Code Duplication
    if ($record_item_body) {
362
363
        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
364
            $current_question_item_body .= "<" . $name;
365
            foreach ($attributes as $attribute_name => $attribute_value) {
366
                $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\"";
367
            }
368
            $current_question_item_body .= ">";
369
        } else {
370
            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
371
            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
372
373
            if ($current_element == 'INLINECHOICEINTERACTION') {
374
                $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**";
375
            }
376
            if ($current_element == 'TEXTENTRYINTERACTION') {
377
                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
378
                $current_question_item_body .= "[" . $correct_answer_value . "]";
379
380
            }
381
            if ($current_element == 'BR') {
382
                $current_question_item_body .= "<br />";
383
            }
384
        }
385
    }
386
387
    switch ($current_element) {
388
        case 'ASSESSMENTITEM':
389
            //retrieve current question
390
            $current_question_ident = $attributes['IDENTIFIER'];
391
            $exercise_info['question'][$current_question_ident] = array();
392
            $exercise_info['question'][$current_question_ident]['answer'] = array();
393
            $exercise_info['question'][$current_question_ident]['correct_answers'] = array();
394
            $exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
395
            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
396
            break;
397
        case 'SECTION':
398
            //retrieve exercise name
399
            if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) {
400
                $exercise_info['name'] = $attributes['TITLE'];
401
            }
402
            break;
403
        case 'RESPONSEDECLARATION':
404
            // Retrieve question type
405 View Code Duplication
            if ("multiple" == $attributes['CARDINALITY']) {
406
                $exercise_info['question'][$current_question_ident]['type'] = MCMA;
407
                $cardinality = 'multiple';
408
            }
409 View Code Duplication
            if ("single" == $attributes['CARDINALITY']) {
410
                $exercise_info['question'][$current_question_ident]['type'] = MCUA;
411
                $cardinality = 'single';
412
            }
413
            //needed for FIB
414
            $current_answer_id = $attributes['IDENTIFIER'];
415
            break;
416
        case 'INLINECHOICEINTERACTION':
417
            $exercise_info['question'][$current_question_ident]['type'] = FIB;
418
            $exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
419
            $current_answer_id = $attributes['RESPONSEIDENTIFIER'];
420
            break;
421
        case 'INLINECHOICE':
422
            $current_inlinechoice_id = $attributes['IDENTIFIER'];
423
            break;
424
        case 'TEXTENTRYINTERACTION':
425
            $exercise_info['question'][$current_question_ident]['type'] = FIB;
426
            $exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
427
            $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
428
            //replace claroline tags
429
            break;
430
        case 'MATCHINTERACTION':
431
            $exercise_info['question'][$current_question_ident]['type'] = MATCHING;
432
            break;
433
        case 'SIMPLEMATCHSET':
434
            if (!isset($current_match_set)) {
435
                $current_match_set = 1;
436
            } else {
437
                $current_match_set++;
438
            }
439
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = array();
440
            break;
441
        case 'SIMPLEASSOCIABLECHOICE':
442
            $currentAssociableChoice = $attributes['IDENTIFIER'];
443
            break;
444
        //retrieve answers id for MCUA and MCMA questions
445
        case 'SIMPLECHOICE':
446
            $current_answer_id = $attributes['IDENTIFIER'];
447 View Code Duplication
            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id])) {
448
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = array();
449
            }
450
            break;
451
        case 'MAPENTRY':
452
            if ($parent_element == "MAPPING") {
453
                $answer_id = $attributes['MAPKEY'];
454 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['weighting'])) {
455
                    $exercise_info['question'][$current_question_ident]['weighting'] = array();
456
                }
457
                $exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
458
            }
459
            break;
460
        case 'MAPPING':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
461
            if (isset ($attributes['DEFAULTVALUE'])) {
462
                $exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
463
            }
464
        case 'ITEMBODY':
465
            $record_item_body = true;
466
            $current_question_item_body = '';
467
            break;
468
        case 'IMG':
469
            $exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
470
            break;
471
    }
472
}
473
474
/**
475
 * Function used by the SAX xml parser when the parser meets a closing tag
476
 *
477
 * @param $parser xml parser created with "xml_parser_create()"
478
 * @param $name name of the element
479
 */
480
function endElementQti2($parser, $name)
481
{
482
    global $element_pile;
483
    global $exercise_info;
484
    global $current_question_ident;
485
    global $record_item_body;
486
    global $current_question_item_body;
487
    global $non_HTML_tag_to_avoid;
488
    global $cardinality;
489
490
    $current_element = end($element_pile);
491
492
    //treat the record of the full content of itembody tag :
493
494
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
495
        $current_question_item_body .= "</" . $name . ">";
496
    }
497
498
    switch ($name) {
499
        case 'ITEMBODY':
500
            $record_item_body = false;
501
            if ($exercise_info['question'][$current_question_ident]['type'] == FIB) {
502
                $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
503
            } else {
504
                $exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
505
            }
506
            break;
507
    }
508
    array_pop($element_pile);
509
}
510
511
/**
512
 * @param $parser
513
 * @param $data
514
 */
515
function elementDataQti2($parser, $data)
516
{
517
    global $element_pile;
518
    global $exercise_info;
519
    global $current_question_ident;
520
    global $current_answer_id;
521
    global $current_match_set;
522
    global $currentAssociableChoice;
523
    global $current_question_item_body;
524
    global $record_item_body;
525
    global $non_HTML_tag_to_avoid;
526
    global $current_inlinechoice_id;
527
    global $cardinality;
528
    global $resourcesLinks;
529
530
    $current_element = end($element_pile);
531 View Code Duplication
    if (sizeof($element_pile) >= 2) {
532
        $parent_element = $element_pile[sizeof($element_pile) - 2];
533
    } else {
534
        $parent_element = "";
535
    }
536 View Code Duplication
    if (sizeof($element_pile) >= 3) {
537
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
538
    } else {
539
        $grant_parent_element = "";
540
    }
541
542
    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
543
544
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
545
        $current_question_item_body .= $data;
546
    }
547
548
    switch ($current_element) {
549 View Code Duplication
        case 'SIMPLECHOICE':
550
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
551
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
552
            } else {
553
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= '' . trim($data);
554
            }
555
            break;
556 View Code Duplication
        case 'FEEDBACKINLINE':
557
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
558
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
559
            } else {
560
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' ' . trim($data);
561
            }
562
            break;
563
        case 'SIMPLEASSOCIABLECHOICE':
564
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
565
            break;
566
        case 'VALUE':
567
            if ($parent_element == "CORRECTRESPONSE") {
568
                if ($cardinality == "single") {
569
                    $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id] = $data;
570
                } else {
571
                    $exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
572
                }
573
            }
574
            break;
575
        case 'ITEMBODY':
576
            // Replace relative links by links to the documents in the course
577
            // $resourcesLinks is only defined by qtiProcessManifest()
578 View Code Duplication
            if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
579
                foreach ($resourcesLinks['manifest'] as $key => $value) {
580
                    $data = preg_replace('|' . $value . '|', $resourcesLinks['web'][$key], $data);
581
                }
582
            }
583
            $current_question_item_body .= $data;
584
            break;
585
        case 'INLINECHOICE':
586
            // if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
587
            $answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
588
            if ($current_inlinechoice_id == $answer_identifier) {
589
                $current_question_item_body = str_replace(
590
                    "**claroline_start**" . $current_answer_id . "**claroline_end**",
591
                    "[" . $data . "]",
592
                    $current_question_item_body
593
                );
594
            } else {
595 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
596
                    $exercise_info['question'][$current_question_ident]['wrong_answers'] = array();
597
                }
598
                $exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
599
            }
600
            break;
601
    }
602
}
603
604
/**
605
 * Function used by the SAX xml parser when the parser meets a opening tag for QTI1
606
 *
607
 * @param object $parser xml parser created with "xml_parser_create()"
608
 * @param string $name name of the element
609
 * @param array $attributes
610
 */
611
function startElementQti1($parser, $name, $attributes)
612
{
613
    global $element_pile;
614
    global $exercise_info;
615
    global $current_question_ident;
616
    global $current_answer_id;
617
    global $current_match_set;
618
    global $currentAssociableChoice;
619
    global $current_question_item_body;
620
    global $record_item_body;
621
    global $non_HTML_tag_to_avoid;
622
    global $current_inlinechoice_id;
623
    global $cardinality;
624
    global $questionTempDir;
625
    global $lastLabelFieldName;
626
    global $lastLabelFieldValue;
627
628
    array_push($element_pile, $name);
629
    $current_element = end($element_pile);
630 View Code Duplication
    if (sizeof($element_pile) >= 2) {
631
        $parent_element = $element_pile[sizeof($element_pile) - 2];
632
    } else {
633
        $parent_element = "";
634
    }
635 View Code Duplication
    if (sizeof($element_pile) >= 3) {
636
        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
637
    } else {
638
        $grand_parent_element = "";
639
    }
640 View Code Duplication
    if (sizeof($element_pile) >= 4) {
641
        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
642
    } else {
643
        $great_grand_parent_element = "";
644
    }
645
646
647 View Code Duplication
    if ($record_item_body) {
648
649
        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
650
            $current_question_item_body .= "<" . $name;
651
            foreach ($attributes as $attribute_name => $attribute_value) {
652
                $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\"";
653
            }
654
            $current_question_item_body .= ">";
655
        } else {
656
            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
657
            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
658
659
            if ($current_element == 'INLINECHOICEINTERACTION') {
660
                $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**";
661
            }
662
            if ($current_element == 'TEXTENTRYINTERACTION') {
663
                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
664
                $current_question_item_body .= "[" . $correct_answer_value . "]";
665
666
            }
667
            if ($current_element == 'BR') {
668
                $current_question_item_body .= "<br />";
669
            }
670
        }
671
    }
672
673
    switch ($current_element) {
674
        case 'ASSESSMENT':
675
            // This is the assessment element: we don't care, we just want questions
676
            if (!empty($attributes['TITLE'])) {
677
                $exercise_info['name'] = $attributes['TITLE'];
678
            }
679
            break;
680
        case 'ITEM':
681
            //retrieve current question
682
            $current_question_ident = $attributes['IDENT'];
683
            $exercise_info['question'][$current_question_ident] = array();
684
            $exercise_info['question'][$current_question_ident]['answer'] = array();
685
            $exercise_info['question'][$current_question_ident]['correct_answers'] = array();
686
            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
687
            break;
688
        case 'SECTION':
689
            break;
690
        case 'RESPONSE_LID':
691
            // Retrieve question type
692
            if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
693
                $cardinality = 'multiple';
694
            }
695
            if ("single" == strtolower($attributes['RCARDINALITY'])) {
696
                $cardinality = 'single';
697
            }
698
            //needed for FIB
699
            $current_answer_id = $attributes['IDENT'];
700
            $current_question_item_body = '';
701
            break;
702
        case 'RENDER_CHOICE';
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

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

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

Loading history...
703
            break;
704
        case 'RESPONSE_LABEL':
705 View Code Duplication
            if (!empty($attributes['IDENT'])) {
706
                $current_answer_id = $attributes['IDENT'];
707
                //set the placeholder for the answer to come (in endElementQti1)
708
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = '';
709
            }
710
            break;
711
        case 'DECVAR':
712
            if ($parent_element == 'OUTCOMES' && $grand_parent_element == 'RESPROCESSING') {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
713
                // The following attributes are available
714
                //$attributes['VARTYPE'];
715
                //$attributes['DEFAULTVAL'];
716
                //$attributes['MINVALUE'];
717
                //$attributes['MAXVALUE'];
718
            }
719
            break;
720
        case 'VAREQUAL':
721
            if ($parent_element == 'CONDITIONVAR' && $grand_parent_element == 'RESPCONDITION') {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
722
                // The following attributes are available
723
                //$attributes['RESPIDENT']
724
            }
725
            break;
726
        case 'SETVAR':
727
            if ($parent_element == 'RESPCONDITION') {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
728
                // The following attributes are available
729
                //$attributes['ACTION']
730
            }
731
            break;
732
        case 'IMG':
733
            break;
734
        case 'MATTEXT':
735
            if ($parent_element == 'MATERIAL') {
736
                if ($grand_parent_element == 'PRESENTATION') {
737
                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
738
                }
739
            }
740
    }
741
}
742
743
/**
744
 * Function used by the SAX xml parser when the parser meets a closing tag for QTI1
745
 *
746
 * @param object $parser xml parser created with "xml_parser_create()"
747
 * @param string $name name of the element
748
 * @param array $attributes The element attributes
749
 */
750
function endElementQti1($parser, $name, $attributes)
751
{
752
    global $element_pile;
753
    global $exercise_info;
754
    global $current_question_ident;
755
    global $record_item_body;
756
    global $current_question_item_body;
757
    global $non_HTML_tag_to_avoid;
758
    global $cardinality;
759
    global $lastLabelFieldName;
760
    global $lastLabelFieldValue;
761
    global $resourcesLinks;
762
763
    $current_element = end($element_pile);
764 View Code Duplication
    if (sizeof($element_pile) >= 2) {
765
        $parent_element = $element_pile[sizeof($element_pile) - 2];
766
    } else {
767
        $parent_element = "";
768
    }
769 View Code Duplication
    if (sizeof($element_pile) >= 3) {
770
        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
771
    } else {
772
        $grand_parent_element = "";
773
    }
774 View Code Duplication
    if (sizeof($element_pile) >= 4) {
775
        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
776
    } else {
777
        $great_grand_parent_element = "";
778
    }
779
780
    //treat the record of the full content of itembody tag :
781
782
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
783
        $current_question_item_body .= "</" . $name . ">";
784
    }
785
786
    switch ($name) {
787
        case 'MATTEXT':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
788
            if ($parent_element == 'MATERIAL') {
789
                // For some reason an item in a hierarchy <item><presentation><material><mattext> doesn't seem to
790
                // catch the grandfather 'presentation', so we check for 'item' as a patch (great-grand-father)
791
                if ($grand_parent_element == 'PRESENTATION' OR $grand_parent_element == 'ITEM') {
792
                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
793
                    $current_question_item_body = '';
794
                } elseif ($grand_parent_element == 'RESPONSE_LABEL') {
795
                    $last = '';
796
                    foreach ($exercise_info['question'][$current_question_ident]['answer'] as $key => $value) {
797
                        $last = $key;
798
                    }
799
                    $exercise_info['question'][$current_question_ident]['answer'][$last]['value'] = $current_question_item_body;
800
                    $current_question_item_body = '';
801
                }
802
            }
803
        case 'RESPONSE_LID':
804
            // Retrieve question type
805
            if (!isset($exercise_info['question'][$current_question_ident]['type'])) {
806 View Code Duplication
                if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
807
                    $exercise_info['question'][$current_question_ident]['type'] = MCMA;
808
                }
809 View Code Duplication
                if ("single" == strtolower($attributes['RCARDINALITY'])) {
810
                    $exercise_info['question'][$current_question_ident]['type'] = MCUA;
811
                }
812
            }
813
            $current_question_item_body = '';
814
            //needed for FIB
815
            $current_answer_id = $attributes['IDENT'];
816
            break;
817
        case 'ITEMMETADATA':
818
            $current_question_item_body = '';
819
            break;
820
    }
821
    array_pop($element_pile);
822
}
823
824
/**
825
 * QTI1 element parser
826
 * @param $parser
827
 * @param $data
828
 */
829
function elementDataQti1($parser, $data)
830
{
831
    global $element_pile;
832
    global $exercise_info;
833
    global $current_question_ident;
834
    global $current_answer_id;
835
    global $current_match_set;
836
    global $currentAssociableChoice;
837
    global $current_question_item_body;
838
    global $record_item_body;
839
    global $non_HTML_tag_to_avoid;
840
    global $current_inlinechoice_id;
841
    global $cardinality;
842
    global $lastLabelFieldName;
843
    global $lastLabelFieldValue;
844
    global $resourcesLinks;
845
846
    $current_element = end($element_pile);
847 View Code Duplication
    if (sizeof($element_pile) >= 2) {
848
        $parent_element = $element_pile[sizeof($element_pile) - 2];
849
    } else {
850
        $parent_element = "";
851
    }
852
    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
853
854
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
855
        $current_question_item_body .= $data;
856
    }
857
858
    switch ($current_element) {
859
        case 'FIELDLABEL':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
860
            if (!empty($data)) {
861
                $lastLabelFieldName = $current_element;
862
                $lastLabelFieldValue = $data;
863
            }
864
        case 'FIELDENTRY':
865
            $current_question_item_body = $data;
866
            switch ($lastLabelFieldValue) {
867
                case 'cc_profile':
868
                    // The following values might be proprietary in MATRIX software. No specific reference
869
                    // in QTI doc: http://www.imsglobal.org/question/qtiv1p2/imsqti_asi_infov1p2.html#1415855
870
                    switch ($data) {
871
                        case 'cc.true_false.v0p1':
872
                            //this is a true-false question (translated to multiple choice in Chamilo because true-false comes with "I don't know")
873
                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
874
                            break;
875
                        case 'cc.multiple_choice.v0p1':
876
                            //this is a multiple choice (unique answer) question
877
                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
878
                            break;
879
                        case 'cc.multiple_response.v0p1':
880
                            //this is a multiple choice (unique answer) question
881
                            $exercise_info['question'][$current_question_ident]['type'] = MCMA;
882
                            break;
883
                    }
884
                    break;
885
                case 'cc_weighting':
886
                    //defines the total weight of the question
887
                    $exercise_info['question'][$current_question_ident]['default_weighting'] = $lastLabelFieldValue;
888
                    break;
889
                case 'assessment_question_identifierref':
890
                    //placeholder - not used yet
891
                    // Possible values are not defined by qti v1.2
892
                    break;
893
            }
894
            break;
895
        case 'MATTEXT':
896
            // Replace relative links by links to the documents in the course
897
            // $resourcesLinks is only defined by qtiProcessManifest()
898 View Code Duplication
            if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) {
899
                foreach ($resourcesLinks['manifest'] as $key=>$value) {
900
                    $data = preg_replace('|' . $value . '|', $resourcesLinks['web'][$key], $data);
901
                }
902
            }
903
            if (!empty($current_question_item_body)) {
904
                $current_question_item_body .= $data;
905
            } else {
906
                $current_question_item_body = $data;
907
            }
908
            break;
909
        case 'VAREQUAL':
910
            $lastLabelFieldName = 'VAREQUAL';
911
            $lastLabelFieldValue = $data;
912
            break;
913
        case 'SETVAR':
914
            if ($parent_element == 'RESPCONDITION') {
915
                // The following attributes are available
916
                //$attributes['ACTION']
917
                $exercise_info['question'][$current_question_ident]['correct_answers'][] = $lastLabelFieldValue;
918
                $exercise_info['question'][$current_question_ident]['weighting'][$lastLabelFieldValue] = $data;
919
            }
920
            break;
921
922
    }
923
}
924
925
/**
926
 * Check if a given file is an IMS/QTI question bank file
927
 * @param string $filePath The absolute filepath
928
 * @return bool Whether it is an IMS/QTI question bank or not
929
 */
930 View Code Duplication
function isQtiQuestionBank($filePath)
931
{
932
    $data = file_get_contents($filePath);
933
    if (!empty($data)) {
934
        $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data);
935
        if ($match) {
936
            return true;
937
        }
938
    }
939
940
    return false;
941
}
942
943
/**
944
 * Check if a given file is an IMS/QTI manifest file (listing of extra files)
945
 * @param string $filePath The absolute filepath
946
 * @return bool Whether it is an IMS/QTI manifest file or not
947
 */
948 View Code Duplication
function isQtiManifest($filePath)
949
{
950
    $data = file_get_contents($filePath);
951
    if (!empty($data)) {
952
        $match = preg_match('/imsccv(\d)p(\d)/', $data);
953
        if ($match) {
954
            return true;
955
        }
956
    }
957
958
    return false;
959
}
960
961
/**
962
 * Processes an IMS/QTI manifest file: store links to new files to be able to transform them into the questions text
963
 * @param string $filePath The absolute filepath
964
 * @param array $links List of filepaths changes
0 ignored issues
show
Bug introduced by
There is no parameter named $links. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
965
 * @return bool
966
 */
967
function qtiProcessManifest($filePath)
968
{
969
    $xml = simplexml_load_file($filePath);
970
    $course = api_get_course_info();
971
    $sessionId = api_get_session_id();
972
    $courseDir = $course['path'];
973
    $sysPath = api_get_path(SYS_COURSE_PATH);
974
    $exercisesSysPath = $sysPath . $courseDir . '/document/';
975
    $webPath = api_get_path(WEB_CODE_PATH);
976
    $exercisesWebPath = $webPath . 'document/document.php?' . api_get_cidreq() . '&action=download&id=';
977
    $links = array(
978
        'manifest' => array(),
979
        'system' => array(),
980
        'web' => array(),
981
    );
982
    $tableDocuments = Database::get_course_table(TABLE_DOCUMENT);
983
    $countResources = count($xml->resources->resource->file);
984
    for ($i=0; $i < $countResources; $i++) {
985
        $file = $xml->resources->resource->file[$i];
986
        $href = '';
987
        foreach ($file->attributes() as $key => $value) {
988
            if ($key == 'href') {
989
                if (substr($value, -3, 3) != 'xml') {
990
                    $href = $value;
991
                }
992
            }
993
        }
994
        if (!empty($href)) {
995
            $links['manifest'][] = (string) $href;
996
            $links['system'][] = $exercisesSysPath . strtolower($href);
997
            $specialHref = Database::escape_string(preg_replace('/_/', '-', strtolower($href)));
998
            $specialHref = preg_replace('/(-){2,8}/', '-', $specialHref);
999
1000
            $sql = "SELECT iid FROM " . $tableDocuments . " WHERE c_id = " . $course['real_id'] . " AND session_id = $sessionId AND path = '/" . $specialHref . "'";
1001
            $result = Database::query($sql);
1002
            $documentId = 0;
1003
            while ($row = Database::fetch_assoc($result)) {
1004
                $documentId = $row['iid'];
1005
            }
1006
            $links['web'][] = $exercisesWebPath . $documentId;
1007
        }
1008
    }
1009
    return $links;
1010
}