Completed
Push — 1.10.x ( da67d2...840f49 )
by Yannick
43:10
created

exercise_import.inc.php ➔ isQtiManifest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 10
loc 10
rs 9.4285
c 1
b 0
f 1
1
<?php
2
/**
3
 * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
4
 *
5
 * @license http://www.gnu.org/copyleft/gpl.html (GPL) GENERAL PUBLIC LICENSE
6
 *
7
 * @package chamilo.exercise
8
 *
9
 * @author claro team <[email protected]>
10
 * @author Guillaume Lederer <[email protected]>
11
 */
12
13
/**
14
 * function to create a temporary directory (SAME AS IN MODULE ADMIN)
15
 */
16 View Code Duplication
function tempdir($dir, $prefix = 'tmp', $mode = 0777)
0 ignored issues
show
Best Practice introduced by
The function tempdir() has been defined more than once; this definition is ignored, only the first definition in main/exercice/export/aiken/aiken_import.inc.php (L23-32) 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...
17
{
18
    if (substr($dir, -1) != '/') {
19
        $dir .= '/';
20
    }
21
22
    do {
23
        $path = $dir.$prefix.mt_rand(0, 9999999);
24
    } while (!mkdir($path, $mode));
25
26
    return $path;
27
}
28
29
/**
30
 * Unzip the exercise in the temp folder
31
 * @param string The path of the temporary directory where the exercise was uploaded and unzipped
32
 * @param string
33
 * @param string $baseWorkDir
34
 * @param string $uploadPath
35
 * @return bool
36
 */
37
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...
38
{
39
    $_course = api_get_course_info();
40
    $_user = api_get_user_info();
41
42
    //Check if the file is valid (not to big and exists)
43 View Code Duplication
    if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
44
        // upload failed
45
        return false;
46
    }
47
48
    if (preg_match('/.zip$/i', $_FILES['userFile']['name']) &&
49
        handle_uploaded_document(
50
            $_course,
51
            $_FILES['userFile'],
52
            $baseWorkDir,
53
            $uploadPath,
54
            $_user['user_id'],
55
            0,
56
            null,
57
            1
58
        )
59
    ) {
60
        return true;
61
    }
62
    return false;
63
}
64
65
/**
66
 * Imports an exercise in QTI format if the XML structure can be found in it
67
 * @param array $file
68
 * @return string|array as a backlog of what was really imported, and error or debug messages to display
69
 */
70
function import_exercise($file)
71
{
72
    global $exercise_info;
73
    global $element_pile;
74
    global $non_HTML_tag_to_avoid;
75
    global $record_item_body;
76
    // used to specify the question directory where files could be found in relation in any question
77
    global $questionTempDir;
78
79
    $archive_path = api_get_path(SYS_ARCHIVE_PATH) . 'qti2';
80
    $baseWorkDir = $archive_path;
81
82
    if (!is_dir($baseWorkDir)) {
83
        mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
84
    }
85
86
    $uploadPath = '/';
87
88
    // set some default values for the new exercise
89
    $exercise_info = array();
90
    $exercise_info['name'] = preg_replace('/.zip$/i', '', $file);
91
    $exercise_info['question'] = array();
92
    $element_pile = array();
93
94
    // create parser and array to retrieve info from manifest
95
    $element_pile = array(); //pile to known the depth in which we are
96
    //$module_info = array (); //array to store the info we need
97
98
    // if file is not a .zip, then we cancel all
99
100
    if (!preg_match('/.zip$/i', $file)) {
101
102
        return 'UplZipCorrupt';
103
    }
104
105
    // unzip the uploaded file in a tmp directory
106
    if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
107
108
        return 'UplZipCorrupt';
109
    }
110
111
    // find the different manifests for each question and parse them.
112
113
    $exerciseHandle = opendir($baseWorkDir);
114
    //$question_number = 0;
115
    $file_found = false;
116
    $operation = false;
117
    $result = false;
118
    $filePath = null;
119
120
    // parse every subdirectory to search xml question files
121
    while (false !== ($file = readdir($exerciseHandle))) {
122
        if (is_dir($baseWorkDir . '/' . $file) && $file != "." && $file != "..") {
123
            // Find each manifest for each question repository found
124
            $questionHandle = opendir($baseWorkDir . '/' . $file);
125
            while (false !== ($questionFile = readdir($questionHandle))) {
126 View Code Duplication
                if (preg_match('/.xml$/i', $questionFile)) {
127
                    $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file . '/' . $questionFile);
128
                    if ($isQti) {
129
                        $result = qti_parse_file($baseWorkDir, $file, $questionFile);
130
                        $filePath = $baseWorkDir . $file;
131
                        $file_found = true;
132
                    }
133
                }
134
            }
135 View Code Duplication
        } elseif (preg_match('/.xml$/i', $file)) {
136
            $isQti = isQtiQuestionBank($baseWorkDir . '/' . $file);
137
            //$isManifest = isQtiManifest($baseWorkDir . '/' . $file);
138
            if ($isQti) {
139
                $result = qti_parse_file($baseWorkDir, '', $file);
140
                $filePath = $baseWorkDir . '/' . $file;
141
                $file_found = true;
142
            }
143
        }
144
    }
145
146
    if (!$file_found) {
147
148
        return 'NoXMLFileFoundInTheZip';
149
    }
150
151
    if ($result == false) {
152
153
        return false;
154
    }
155
156
    $doc = new DOMDocument();
157
    $doc->load($filePath);
158
159
    // 1. Create exercise.
160
    $exercise = new Exercise();
161
    $exercise->exercise = $exercise_info['name'];
162
163
    $exercise->save();
164
    $last_exercise_id = $exercise->selectId();
165
    if (!empty($last_exercise_id)) {
166
        // For each question found...
167
        foreach ($exercise_info['question'] as $question_array) {
168
            //2. Create question
169
            $question = new Ims2Question();
170
            $question->type = $question_array['type'];
171
            $question->setAnswer();
172
            if (strlen($question_array['title']) < 50) {
173
                $question->updateTitle(formatText(strip_tags($question_array['title'])) . '...');
174
            } else {
175
                $question->updateTitle(formatText(substr(strip_tags($question_array['title']), 0, 50)));
176
                $question->updateDescription($question_array['title']);
177
            }
178
            //$question->updateDescription($question_array['title']);
179
            $question->save($last_exercise_id);
180
            $last_question_id = $question->selectId();
181
            //3. Create answer
182
            $answer = new Answer($last_question_id);
183
            $answer->new_nbrAnswers = count($question_array['answer']);
184
            $totalCorrectWeight = 0;
185
            $j = 1;
186
            $matchAnswerIds = array();
187
            foreach ($question_array['answer'] as $key => $answers) {
188
                if (preg_match('/_/', $key)) {
189
                    $split = explode('_', $key);
190
                    $i = $split[1];
191
                } else {
192
                    $i = $j;
193
                    $j++;
194
                    $matchAnswerIds[$key] = $j;
195
                }
196
                // Answer
197
                $answer->new_answer[$i] = formatText($answers['value']);
198
                // Comment
199
                $answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
200
                // Position
201
                $answer->new_position[$i] = $i;
202
                // Correct answers
203
                if (in_array($key, $question_array['correct_answers'])) {
204
                    $answer->new_correct[$i] = 1;
205
                } else {
206
                    $answer->new_correct[$i] = 0;
207
                }
208
                $answer->new_weighting[$i] = $question_array['weighting'][$key];
209
                if ($answer->new_correct[$i]) {
210
                    $totalCorrectWeight += $answer->new_weighting[$i];
211
                }
212
            }
213
            $question->updateWeighting($totalCorrectWeight);
214
            $question->save($last_exercise_id);
215
            $answer->save();
216
        }
217
218
        // delete the temp dir where the exercise was unzipped
219
        my_delete($baseWorkDir . $uploadPath);
220
        return $last_exercise_id;
221
    }
222
223
    return false;
224
}
225
226
/**
227
 * We assume the file charset is UTF8
228
 **/
229
function formatText($text)
230
{
231
    return api_html_entity_decode($text);
232
}
233
234
/**
235
 * Parses a given XML file and fills global arrays with the elements
236
 * @param string $exercisePath
237
 * @param string $file
238
 * @param string $questionFile
239
 * @return bool
240
 */
241
function qti_parse_file($exercisePath, $file, $questionFile)
242
{
243
    global $non_HTML_tag_to_avoid;
244
    global $record_item_body;
245
    global $questionTempDir;
246
247
    $questionTempDir = $exercisePath . '/' . $file . '/';
248
    $questionFilePath = $questionTempDir . $questionFile;
249
250
    if (!($fp = fopen($questionFilePath, 'r'))) {
251
        Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
252
253
        return false;
254
    } else {
255
        $data = fread($fp, filesize($questionFilePath));
256
    }
257
258
    //parse XML question file
259
    $data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
260
    $qtiVersion = array();
261
    $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion);
262
    $qtiMainVersion = 2; //by default, assume QTI version 2
263
    if ($match) {
264
        $qtiMainVersion = $qtiVersion[1];
265
    }
266
267
    //used global variable start values declaration :
268
269
    $record_item_body = false;
270
    $non_HTML_tag_to_avoid = array(
271
        "SIMPLECHOICE",
272
        "CHOICEINTERACTION",
273
        "INLINECHOICEINTERACTION",
274
        "INLINECHOICE",
275
        "SIMPLEMATCHSET",
276
        "SIMPLEASSOCIABLECHOICE",
277
        "TEXTENTRYINTERACTION",
278
        "FEEDBACKINLINE",
279
        "MATCHINTERACTION",
280
        "ITEMBODY",
281
        "BR",
282
        "IMG"
283
    );
284
285
    $question_format_supported = true;
286
287
    $xml_parser = xml_parser_create();
288
    xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false);
289
    if ($qtiMainVersion == 1) {
290
        xml_set_element_handler($xml_parser, 'startElementQti1', 'endElementQti1');
291
        xml_set_character_data_handler($xml_parser, 'elementDataQti1');
292
    } else {
293
        xml_set_element_handler($xml_parser, 'startElementQti2', 'endElementQti2');
294
        xml_set_character_data_handler($xml_parser, 'elementDataQti2');
295
    }
296
    if (!xml_parse($xml_parser, $data, feof($fp))) {
297
        // if reading of the xml file in not successful :
298
        // set errorFound, set error msg, break while statement
299
        Display:: display_error_message(get_lang('Error reading XML file'));
300
        return false;
301
    }
302
303
    //close file
304
    fclose($fp);
305
    if (!$question_format_supported) {
306
        Display::addFlash(
307
            Display::return_message(
308
                get_lang(
309
                    'Unknown question format in file %file',
310
                    array(
311
                        '%file' => $questionFile,
312
                    )
313
                ),
314
                'error'
315
            )
316
        );
317
        return false;
318
    }
319
    return true;
320
}
321
322
/**
323
 * Function used by the SAX xml parser when the parser meets a opening tag
324
 *
325
 * @param object $parser xml parser created with "xml_parser_create()"
326
 * @param string $name name of the element
327
 * @param array $attributes
328
 */
329
function startElementQti2($parser, $name, $attributes)
330
{
331
    global $element_pile;
332
    global $exercise_info;
333
    global $current_question_ident;
334
    global $current_answer_id;
335
    global $current_match_set;
336
    global $currentAssociableChoice;
337
    global $current_question_item_body;
338
    global $record_item_body;
339
    global $non_HTML_tag_to_avoid;
340
    global $current_inlinechoice_id;
341
    global $cardinality;
342
    global $questionTempDir;
343
344
    array_push($element_pile, $name);
345
    $current_element = end($element_pile);
346 View Code Duplication
    if (sizeof($element_pile) >= 2) {
347
        $parent_element = $element_pile[sizeof($element_pile) - 2];
348
    } else {
349
        $parent_element = "";
350
    }
351 View Code Duplication
    if (sizeof($element_pile) >= 3) {
352
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
353
    } else {
354
        $grant_parent_element = "";
355
    }
356
357 View Code Duplication
    if ($record_item_body) {
358
359
        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
360
            $current_question_item_body .= "<" . $name;
361
            foreach ($attributes as $attribute_name => $attribute_value) {
362
                $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\"";
363
            }
364
            $current_question_item_body .= ">";
365
        } else {
366
            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
367
            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
368
369
            if ($current_element == 'INLINECHOICEINTERACTION') {
370
                $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**";
371
            }
372
            if ($current_element == 'TEXTENTRYINTERACTION') {
373
                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
374
                $current_question_item_body .= "[" . $correct_answer_value . "]";
375
376
            }
377
            if ($current_element == 'BR') {
378
                $current_question_item_body .= "<br />";
379
            }
380
        }
381
    }
382
383
    switch ($current_element) {
384
        case 'ASSESSMENTITEM':
385
            //retrieve current question
386
            $current_question_ident = $attributes['IDENTIFIER'];
387
            $exercise_info['question'][$current_question_ident] = array();
388
            $exercise_info['question'][$current_question_ident]['answer'] = array();
389
            $exercise_info['question'][$current_question_ident]['correct_answers'] = array();
390
            $exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
391
            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
392
            break;
393
        case 'SECTION':
394
            //retrieve exercise name
395
            if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) {
396
                $exercise_info['name'] = $attributes['TITLE'];
397
            }
398
            break;
399
        case 'RESPONSEDECLARATION':
400
            // Retrieve question type
401 View Code Duplication
            if ("multiple" == $attributes['CARDINALITY']) {
402
                $exercise_info['question'][$current_question_ident]['type'] = MCMA;
403
                $cardinality = 'multiple';
404
            }
405 View Code Duplication
            if ("single" == $attributes['CARDINALITY']) {
406
                $exercise_info['question'][$current_question_ident]['type'] = MCUA;
407
                $cardinality = 'single';
408
            }
409
            //needed for FIB
410
            $current_answer_id = $attributes['IDENTIFIER'];
411
            break;
412
        case 'INLINECHOICEINTERACTION':
413
            $exercise_info['question'][$current_question_ident]['type'] = FIB;
414
            $exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
415
            $current_answer_id = $attributes['RESPONSEIDENTIFIER'];
416
            break;
417
        case 'INLINECHOICE':
418
            $current_inlinechoice_id = $attributes['IDENTIFIER'];
419
            break;
420
        case 'TEXTENTRYINTERACTION':
421
            $exercise_info['question'][$current_question_ident]['type'] = FIB;
422
            $exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
423
            $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
424
            //replace claroline tags
425
            break;
426
        case 'MATCHINTERACTION':
427
            $exercise_info['question'][$current_question_ident]['type'] = MATCHING;
428
            break;
429
        case 'SIMPLEMATCHSET':
430
            if (!isset($current_match_set)) {
431
                $current_match_set = 1;
432
            } else {
433
                $current_match_set++;
434
            }
435
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = array();
436
            break;
437
        case 'SIMPLEASSOCIABLECHOICE':
438
            $currentAssociableChoice = $attributes['IDENTIFIER'];
439
            break;
440
        //retrieve answers id for MCUA and MCMA questions
441
        case 'SIMPLECHOICE':
442
            $current_answer_id = $attributes['IDENTIFIER'];
443 View Code Duplication
            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id])) {
444
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = array();
445
            }
446
            break;
447
        case 'MAPENTRY':
448
            if ($parent_element == "MAPPING") {
449
                $answer_id = $attributes['MAPKEY'];
450 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['weighting'])) {
451
                    $exercise_info['question'][$current_question_ident]['weighting'] = array();
452
                }
453
                $exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
454
            }
455
            break;
456
        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...
457
            if (isset ($attributes['DEFAULTVALUE'])) {
458
                $exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
459
            }
460
        case 'ITEMBODY':
461
            $record_item_body = true;
462
            $current_question_item_body = '';
463
            break;
464
        case 'IMG':
465
            $exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
466
            break;
467
    }
468
}
469
470
/**
471
 * Function used by the SAX xml parser when the parser meets a closing tag
472
 *
473
 * @param $parser xml parser created with "xml_parser_create()"
474
 * @param $name name of the element
475
 */
476
function endElementQti2($parser, $name)
477
{
478
    global $element_pile;
479
    global $exercise_info;
480
    global $current_question_ident;
481
    global $record_item_body;
482
    global $current_question_item_body;
483
    global $non_HTML_tag_to_avoid;
484
    global $cardinality;
485
486
    $current_element = end($element_pile);
487
488
    //treat the record of the full content of itembody tag :
489
490
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
491
        $current_question_item_body .= "</" . $name . ">";
492
    }
493
494
    switch ($name) {
495
        case 'ITEMBODY':
496
            $record_item_body = false;
497
            if ($exercise_info['question'][$current_question_ident]['type'] == FIB) {
498
                $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
499
            } else {
500
                $exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
501
            }
502
            break;
503
    }
504
    array_pop($element_pile);
505
}
506
507
/**
508
 * @param $parser
509
 * @param $data
510
 */
511
function elementDataQti2($parser, $data)
512
{
513
    global $element_pile;
514
    global $exercise_info;
515
    global $current_question_ident;
516
    global $current_answer_id;
517
    global $current_match_set;
518
    global $currentAssociableChoice;
519
    global $current_question_item_body;
520
    global $record_item_body;
521
    global $non_HTML_tag_to_avoid;
522
    global $current_inlinechoice_id;
523
    global $cardinality;
524
525
    $current_element = end($element_pile);
526 View Code Duplication
    if (sizeof($element_pile) >= 2) {
527
        $parent_element = $element_pile[sizeof($element_pile) - 2];
528
    } else {
529
        $parent_element = "";
530
    }
531 View Code Duplication
    if (sizeof($element_pile) >= 3) {
532
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
533
    } else {
534
        $grant_parent_element = "";
535
    }
536
537
    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
538
539
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
540
        $current_question_item_body .= $data;
541
    }
542
543
    switch ($current_element) {
544 View Code Duplication
        case 'SIMPLECHOICE':
545
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
546
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
547
            } else {
548
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= '' . trim($data);
549
            }
550
            break;
551 View Code Duplication
        case 'FEEDBACKINLINE':
552
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
553
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
554
            } else {
555
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' ' . trim($data);
556
            }
557
            break;
558
        case 'SIMPLEASSOCIABLECHOICE':
559
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
560
            break;
561
        case 'VALUE':
562
            if ($parent_element == "CORRECTRESPONSE") {
563
                if ($cardinality == "single") {
564
                    $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id] = $data;
565
                } else {
566
                    $exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
567
                }
568
            }
569
            break;
570
        case 'ITEMBODY':
571
            $current_question_item_body .= $data;
572
            break;
573
        case 'INLINECHOICE':
574
            // if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
575
            $answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
576
            if ($current_inlinechoice_id == $answer_identifier) {
577
                $current_question_item_body = str_replace(
578
                    "**claroline_start**" . $current_answer_id . "**claroline_end**",
579
                    "[" . $data . "]",
580
                    $current_question_item_body
581
                );
582
            } else {
583 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
584
                    $exercise_info['question'][$current_question_ident]['wrong_answers'] = array();
585
                }
586
                $exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
587
            }
588
            break;
589
    }
590
}
591
592
/**
593
 * Function used by the SAX xml parser when the parser meets a opening tag for QTI1
594
 *
595
 * @param object $parser xml parser created with "xml_parser_create()"
596
 * @param string $name name of the element
597
 * @param array $attributes
598
 */
599
function startElementQti1($parser, $name, $attributes)
600
{
601
    global $element_pile;
602
    global $exercise_info;
603
    global $current_question_ident;
604
    global $current_answer_id;
605
    global $current_match_set;
606
    global $currentAssociableChoice;
607
    global $current_question_item_body;
608
    global $record_item_body;
609
    global $non_HTML_tag_to_avoid;
610
    global $current_inlinechoice_id;
611
    global $cardinality;
612
    global $questionTempDir;
613
    global $lastLabelFieldName;
614
    global $lastLabelFieldValue;
615
616
    array_push($element_pile, $name);
617
    $current_element = end($element_pile);
618 View Code Duplication
    if (sizeof($element_pile) >= 2) {
619
        $parent_element = $element_pile[sizeof($element_pile) - 2];
620
    } else {
621
        $parent_element = "";
622
    }
623 View Code Duplication
    if (sizeof($element_pile) >= 3) {
624
        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
625
    } else {
626
        $grand_parent_element = "";
627
    }
628 View Code Duplication
    if (sizeof($element_pile) >= 4) {
629
        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
630
    } else {
631
        $great_grand_parent_element = "";
632
    }
633
634
635 View Code Duplication
    if ($record_item_body) {
636
637
        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
638
            $current_question_item_body .= "<" . $name;
639
            foreach ($attributes as $attribute_name => $attribute_value) {
640
                $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\"";
641
            }
642
            $current_question_item_body .= ">";
643
        } else {
644
            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
645
            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
646
647
            if ($current_element == 'INLINECHOICEINTERACTION') {
648
                $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**";
649
            }
650
            if ($current_element == 'TEXTENTRYINTERACTION') {
651
                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
652
                $current_question_item_body .= "[" . $correct_answer_value . "]";
653
654
            }
655
            if ($current_element == 'BR') {
656
                $current_question_item_body .= "<br />";
657
            }
658
        }
659
    }
660
661
    switch ($current_element) {
662
        case 'ASSESSMENT':
663
            // This is the assessment element: we don't care, we just want questions
664
            if (!empty($attributes['TITLE'])) {
665
                $exercise_info['name'] = $attributes['TITLE'];
666
            }
667
            break;
668
        case 'ITEM':
669
            //retrieve current question
670
            $current_question_ident = $attributes['IDENT'];
671
            $exercise_info['question'][$current_question_ident] = array();
672
            $exercise_info['question'][$current_question_ident]['answer'] = array();
673
            $exercise_info['question'][$current_question_ident]['correct_answers'] = array();
674
            //$exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
675
            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
676
            break;
677
        case 'SECTION':
678
            //retrieve exercise name
679
            //if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) {
680
            //    $exercise_info['name'] = $attributes['TITLE'];
681
            //}
682
            break;
683
        case 'RESPONSE_LID':
684
            // Retrieve question type
685
            if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
686
                $cardinality = 'multiple';
687
            }
688
            if ("single" == strtolower($attributes['RCARDINALITY'])) {
689
                $cardinality = 'single';
690
            }
691
            //needed for FIB
692
            $current_answer_id = $attributes['IDENT'];
693
            $current_question_item_body = '';
694
            break;
695
        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...
696
            break;
697
        case 'RESPONSE_LABEL':
698 View Code Duplication
            if (!empty($attributes['IDENT'])) {
699
                $current_answer_id = $attributes['IDENT'];
700
                //set the placeholder for the answer to come (in endElementQti1)
701
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = '';
702
            }
703
            break;
704
        case 'DECVAR':
705
            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...
706
                // The following attributes are available
707
                //$attributes['VARTYPE'];
708
                //$attributes['DEFAULTVAL'];
709
                //$attributes['MINVALUE'];
710
                //$attributes['MAXVALUE'];
711
            }
712
            break;
713
        case 'VAREQUAL':
714
            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...
715
                // The following attributes are available
716
                //$attributes['RESPIDENT']
717
            }
718
            break;
719
        case 'SETVAR':
720
            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...
721
                // The following attributes are available
722
                //$attributes['ACTION']
723
            }
724
            break;
725
        case 'IMG':
726
            //$exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
727
            break;
728
        case 'MATTEXT':
729
            if ($parent_element == 'MATERIAL') {
730
                if ($grand_parent_element == 'PRESENTATION') {
731
                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
732
                }
733
            }
734
    }
735
}
736
737
/**
738
 * Function used by the SAX xml parser when the parser meets a closing tag for QTI1
739
 *
740
 * @param object $parser xml parser created with "xml_parser_create()"
741
 * @param string $name name of the element
742
 * @param array $attributes The element attributes
743
 */
744
function endElementQti1($parser, $name, $attributes)
745
{
746
    global $element_pile;
747
    global $exercise_info;
748
    global $current_question_ident;
749
    global $record_item_body;
750
    global $current_question_item_body;
751
    global $non_HTML_tag_to_avoid;
752
    global $cardinality;
753
    global $lastLabelFieldName;
754
    global $lastLabelFieldValue;
755
756
    $current_element = end($element_pile);
757 View Code Duplication
    if (sizeof($element_pile) >= 2) {
758
        $parent_element = $element_pile[sizeof($element_pile) - 2];
759
    } else {
760
        $parent_element = "";
761
    }
762 View Code Duplication
    if (sizeof($element_pile) >= 3) {
763
        $grand_parent_element = $element_pile[sizeof($element_pile) - 3];
764
    } else {
765
        $grand_parent_element = "";
766
    }
767 View Code Duplication
    if (sizeof($element_pile) >= 4) {
768
        $great_grand_parent_element = $element_pile[sizeof($element_pile) - 4];
769
    } else {
770
        $great_grand_parent_element = "";
771
    }
772
773
    //treat the record of the full content of itembody tag :
774
775
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
776
        $current_question_item_body .= "</" . $name . ">";
777
    }
778
779
    switch ($name) {
780
        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...
781
            if ($parent_element == 'MATERIAL') {
782
                if ($grand_parent_element == 'PRESENTATION') {
783
                    $exercise_info['question'][$current_question_ident]['title'] = $current_question_item_body;
784
                    $current_question_item_body = '';
785
                } elseif ($grand_parent_element == 'RESPONSE_LABEL') {
786
                    $last = '';
787
                    foreach ($exercise_info['question'][$current_question_ident]['answer'] as $key => $value) {
788
                        $last = $key;
789
                    }
790
                    $exercise_info['question'][$current_question_ident]['answer'][$last]['value'] = $current_question_item_body;
791
                    $current_question_item_body = '';
792
                }
793
            }
794
        case 'RESPONSE_LID':
795
            // Retrieve question type
796
            if (!isset($exercise_info['question'][$current_question_ident]['type'])) {
797 View Code Duplication
                if ("multiple" == strtolower($attributes['RCARDINALITY'])) {
798
                    $exercise_info['question'][$current_question_ident]['type'] = MCMA;
799
                }
800 View Code Duplication
                if ("single" == strtolower($attributes['RCARDINALITY'])) {
801
                    $exercise_info['question'][$current_question_ident]['type'] = MCUA;
802
                }
803
            }
804
            $current_question_item_body = '';
805
            //needed for FIB
806
            $current_answer_id = $attributes['IDENT'];
807
            break;
808
        case 'ITEMMETADATA':
809
            $current_question_item_body = '';
810
            break;
811
    }
812
    array_pop($element_pile);
813
}
814
815
/**
816
 * QTI1 element parser
817
 * @param $parser
818
 * @param $data
819
 */
820
function elementDataQti1($parser, $data)
821
{
822
    global $element_pile;
823
    global $exercise_info;
824
    global $current_question_ident;
825
    global $current_answer_id;
826
    global $current_match_set;
827
    global $currentAssociableChoice;
828
    global $current_question_item_body;
829
    global $record_item_body;
830
    global $non_HTML_tag_to_avoid;
831
    global $current_inlinechoice_id;
832
    global $cardinality;
833
    global $lastLabelFieldName;
834
    global $lastLabelFieldValue;
835
836
    $current_element = end($element_pile);
837 View Code Duplication
    if (sizeof($element_pile) >= 2) {
838
        $parent_element = $element_pile[sizeof($element_pile) - 2];
839
    } else {
840
        $parent_element = "";
841
    }
842
    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
843
844
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
845
        $current_question_item_body .= $data;
846
    }
847
848
    switch ($current_element) {
849
        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...
850
            if (!empty($data)) {
851
                $lastLabelFieldName = $current_element;
852
                $lastLabelFieldValue = $data;
853
            }
854
        case 'FIELDENTRY':
855
            $current_question_item_body = $data;
856
            switch ($lastLabelFieldValue) {
857
                case 'cc_profile':
858
                    // The following values might be proprietary in MATRIX software. No specific reference
859
                    // in QTI doc: http://www.imsglobal.org/question/qtiv1p2/imsqti_asi_infov1p2.html#1415855
860
                    switch ($data) {
861
                        case 'cc.true_false.v0p1':
862
                            //this is a true-false question (translated to multiple choice in Chamilo because true-false comes with "I don't know")
863
                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
864
                            break;
865
                        case 'cc.multiple_choice.v0p1':
866
                            //this is a multiple choice (unique answer) question
867
                            $exercise_info['question'][$current_question_ident]['type'] = MCUA;
868
                            break;
869
                        case 'cc.multiple_response.v0p1':
870
                            //this is a multiple choice (unique answer) question
871
                            $exercise_info['question'][$current_question_ident]['type'] = MCMA;
872
                            break;
873
                    }
874
                    break;
875
                case 'cc_weighting':
876
                    //defines the total weight of the question
877
                    $exercise_info['question'][$current_question_ident]['default_weighting'] = $lastLabelFieldValue;
878
                    break;
879
                case 'assessment_question_identifierref':
880
                    //placeholder - not used yet
881
                    // Possible values are not defined by qti v1.2
882
                    break;
883
            }
884
            break;
885
        case 'MATTEXT':
886
            if (!empty($current_question_item_body)) {
887
                $current_question_item_body .= $data;
888
            } else {
889
                $current_question_item_body = $data;
890
            }
891
            break;
892
        case 'VAREQUAL':
893
            $lastLabelFieldName = 'VAREQUAL';
894
            $lastLabelFieldValue = $data;
895
            break;
896
        case 'SETVAR':
897
            if ($parent_element == 'RESPCONDITION') {
898
                // The following attributes are available
899
                //$attributes['ACTION']
900
                $exercise_info['question'][$current_question_ident]['correct_answers'][] = $lastLabelFieldValue;
901
                $exercise_info['question'][$current_question_ident]['weighting'][$lastLabelFieldValue] = $data;
902
            }
903
            break;
904
905
    }
906
}
907
908 View Code Duplication
function isQtiQuestionBank($filePath) {
909
    $data = file_get_contents($filePath);
910
    if (!empty($data)) {
911
        $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data);
912
        if ($match) {
913
            return true;
914
        }
915
    }
916
    return false;
917
}
918 View Code Duplication
function isQtiManifest($filePath) {
919
    $data = file_get_contents($filePath);
920
    if (!empty($data)) {
921
        $match = preg_match('/imsccv(\d)p(\d)/', $data);
922
        if ($match) {
923
            return true;
924
        }
925
    }
926
    return false;
927
}