Completed
Push — 1.10.x ( 4eb5d1...71e5e5 )
by Yannick
46:56
created

exercise_import.inc.php ➔ qti_parse_file()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 69
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 45
c 0
b 0
f 0
nc 4
nop 3
dl 0
loc 69
rs 8.7653

How to fix   Long Method   

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
/**
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 an 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 View Code Duplication
            while (false !== ($questionFile = readdir($questionHandle))) {
126
                if (preg_match('/.xml$/i', $questionFile)) {
127
                    $result = qti_parse_file($baseWorkDir, $file, $questionFile);
128
                    $filePath = $baseWorkDir . $file;
129
                    $file_found = true;
130
                }
131
            }
132
        } elseif (preg_match('/.xml$/i', $file)) {
133
134
            // Else ignore file
135
            $result = qti_parse_file($baseWorkDir, '', $file);
136
            $filePath = $baseWorkDir . '/' . $file;
137
            $file_found = true;
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
            $question->updateTitle(formatText($question_array['title']));
168
            //$question->updateDescription($question_array['title']);
169
            $type = $question->selectType();
170
            $question->type = constant($type);
171
            $question->save($last_exercise_id);
172
            $last_question_id = $question->selectId();
173
            //3. Create answer
174
            $answer = new Answer($last_question_id);
175
            $answer->new_nbrAnswers = count($question_array['answer']);
176
            $totalCorrectWeight = 0;
177
            foreach ($question_array['answer'] as $key => $answers) {
178
                $split = explode('_', $key);
179
                $i = $split[1];
180
                // Answer
181
                $answer->new_answer[$i] = formatText($answers['value']);
182
                // Comment
183
                $answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null;
184
                // Position
185
                $answer->new_position[$i] = $i;
186
                // Correct answers
187
                if (in_array($key, $question_array['correct_answers'])) {
188
                    $answer->new_correct[$i] = 1;
189
                } else {
190
                    $answer->new_correct[$i] = 0;
191
                }
192
                $answer->new_weighting[$i] = $question_array['weighting'][$key];
193
                if ($answer->new_correct[$i]) {
194
                    $totalCorrectWeight = $answer->new_weighting[$i];
195
                }
196
            }
197
            $question->updateWeighting($totalCorrectWeight);
198
            $question->save($last_exercise_id);
199
            $answer->save();
200
        }
201
202
        // delete the temp dir where the exercise was unzipped
203
        my_delete($baseWorkDir . $uploadPath);
204
        return $last_exercise_id;
205
    }
206
207
    return false;
208
}
209
210
/**
211
 * We assume the file charset is UTF8
212
 **/
213
function formatText($text)
214
{
215
    return api_html_entity_decode($text);
216
}
217
218
/**
219
 * Parses a given XML file and fills global arrays with the elements
220
 * @param string $exercisePath
221
 * @param string $file
222
 * @param string $questionFile
223
 * @return bool
224
 */
225
function qti_parse_file($exercisePath, $file, $questionFile)
226
{
227
    global $non_HTML_tag_to_avoid;
228
    global $record_item_body;
229
    global $questionTempDir;
230
231
    $questionTempDir = $exercisePath . '/' . $file . '/';
232
    $questionFilePath = $questionTempDir . $questionFile;
233
234
    if (!($fp = fopen($questionFilePath, 'r'))) {
235
        Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error'));
236
237
        return false;
238
    } else {
239
        $data = fread($fp, filesize($questionFilePath));
240
    }
241
242
    //parse XML question file
243
    $data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data);
244
245
    //used global variable start values declaration :
246
247
    $record_item_body = false;
248
    $non_HTML_tag_to_avoid = array(
249
        "SIMPLECHOICE",
250
        "CHOICEINTERACTION",
251
        "INLINECHOICEINTERACTION",
252
        "INLINECHOICE",
253
        "SIMPLEMATCHSET",
254
        "SIMPLEASSOCIABLECHOICE",
255
        "TEXTENTRYINTERACTION",
256
        "FEEDBACKINLINE",
257
        "MATCHINTERACTION",
258
        "ITEMBODY",
259
        "BR",
260
        "IMG"
261
    );
262
263
    $question_format_supported = true;
264
265
    $xml_parser = xml_parser_create();
266
    xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, false);
267
    xml_set_element_handler($xml_parser, 'startElement', 'endElement');
268
    xml_set_character_data_handler($xml_parser, 'elementData');
269
    if (!xml_parse($xml_parser, $data, feof($fp))) {
270
        // if reading of the xml file in not successful :
271
        // set errorFound, set error msg, break while statement
272
        Display:: display_error_message(get_lang('Error reading XML file'));
273
        return false;
274
    }
275
276
    //close file
277
    fclose($fp);
278
    if (!$question_format_supported) {
279
        Display::addFlash(
280
            Display::return_message(
281
                get_lang(
282
                    'Unknown question format in file %file',
283
                    array(
284
                        '%file' => $questionFile,
285
                    )
286
                ),
287
                'error'
288
            )
289
        );
290
        return false;
291
    }
292
    return true;
293
}
294
295
/**
296
 * Function used by the SAX xml parser when the parser meets a opening tag
297
 *
298
 * @param object $parser xml parser created with "xml_parser_create()"
299
 * @param string $name name of the element
300
 * @param array $attributes
301
 */
302
function startElement($parser, $name, $attributes)
303
{
304
    global $element_pile;
305
    global $exercise_info;
306
    global $current_question_ident;
307
    global $current_answer_id;
308
    global $current_match_set;
309
    global $currentAssociableChoice;
310
    global $current_question_item_body;
311
    global $record_item_body;
312
    global $non_HTML_tag_to_avoid;
313
    global $current_inlinechoice_id;
314
    global $cardinality;
315
    global $questionTempDir;
316
317
    array_push($element_pile, $name);
318
    $current_element = end($element_pile);
319 View Code Duplication
    if (sizeof($element_pile) >= 2) {
320
        $parent_element = $element_pile[sizeof($element_pile) - 2];
321
    } else {
322
        $parent_element = "";
323
    }
324 View Code Duplication
    if (sizeof($element_pile) >= 3) {
325
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
326
    } else {
327
        $grant_parent_element = "";
328
    }
329
330
    if ($record_item_body) {
331
332
        if ((!in_array($current_element, $non_HTML_tag_to_avoid))) {
333
            $current_question_item_body .= "<" . $name;
334
            foreach ($attributes as $attribute_name => $attribute_value) {
335
                $current_question_item_body .= " " . $attribute_name . "=\"" . $attribute_value . "\"";
336
            }
337
            $current_question_item_body .= ">";
338
        } else {
339
            //in case of FIB question, we replace the IMS-QTI tag b y the correct answer between "[" "]",
340
            //we first save with claroline tags ,then when the answer will be parsed, the claroline tags will be replaced
341
342
            if ($current_element == 'INLINECHOICEINTERACTION') {
343
344
                $current_question_item_body .= "**claroline_start**" . $attributes['RESPONSEIDENTIFIER'] . "**claroline_end**";
345
            }
346
            if ($current_element == 'TEXTENTRYINTERACTION') {
347
                $correct_answer_value = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
348
                $current_question_item_body .= "[" . $correct_answer_value . "]";
349
350
            }
351
            if ($current_element == 'BR') {
352
                $current_question_item_body .= "<br />";
353
            }
354
        }
355
    }
356
    switch ($current_element) {
357
        case 'ASSESSMENTITEM':
358
            //retrieve current question
359
            $current_question_ident = $attributes['IDENTIFIER'];
360
            $exercise_info['question'][$current_question_ident] = array();
361
            $exercise_info['question'][$current_question_ident]['answer'] = array();
362
            $exercise_info['question'][$current_question_ident]['correct_answers'] = array();
363
            $exercise_info['question'][$current_question_ident]['title'] = $attributes['TITLE'];
364
            $exercise_info['question'][$current_question_ident]['tempdir'] = $questionTempDir;
365
            break;
366
        case 'SECTION':
367
            //retrieve exercise name
368
            if (isset($attributes['TITLE']) && !empty($attributes['TITLE'])) {
369
                $exercise_info['name'] = $attributes['TITLE'];
370
            }
371
            break;
372
        case 'RESPONSEDECLARATION':
373
            // Retrieve question type
374 View Code Duplication
            if ("multiple" == $attributes['CARDINALITY']) {
375
                $exercise_info['question'][$current_question_ident]['type'] = 'MCMA';
376
                $cardinality = 'multiple';
377
            }
378 View Code Duplication
            if ("single" == $attributes['CARDINALITY']) {
379
                $exercise_info['question'][$current_question_ident]['type'] = 'MCUA';
380
                $cardinality = 'single';
381
            }
382
            //needed for FIB
383
            $current_answer_id = $attributes['IDENTIFIER'];
384
            break;
385
        case 'INLINECHOICEINTERACTION':
386
            $exercise_info['question'][$current_question_ident]['type'] = 'FIB';
387
            $exercise_info['question'][$current_question_ident]['subtype'] = 'LISTBOX_FILL';
388
            $current_answer_id = $attributes['RESPONSEIDENTIFIER'];
389
            break;
390
        case 'INLINECHOICE':
391
            $current_inlinechoice_id = $attributes['IDENTIFIER'];
392
            break;
393
        case 'TEXTENTRYINTERACTION':
394
            $exercise_info['question'][$current_question_ident]['type'] = 'FIB';
395
            $exercise_info['question'][$current_question_ident]['subtype'] = 'TEXTFIELD_FILL';
396
            $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
397
            //replace claroline tags
398
            break;
399
        case 'MATCHINTERACTION':
400
            $exercise_info['question'][$current_question_ident]['type'] = 'MATCHING';
401
            break;
402
        case 'SIMPLEMATCHSET':
403
            if (!isset($current_match_set)) {
404
                $current_match_set = 1;
405
            } else {
406
                $current_match_set++;
407
            }
408
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set] = array();
409
            break;
410
        case 'SIMPLEASSOCIABLECHOICE':
411
            $currentAssociableChoice = $attributes['IDENTIFIER'];
412
            break;
413
        //retrieve answers id for MCUA and MCMA questions
414
        case 'SIMPLECHOICE':
415
            $current_answer_id = $attributes['IDENTIFIER'];
416
            if (!isset($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id])) {
417
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id] = array();
418
            }
419
            break;
420
        case 'MAPENTRY':
421
            if ($parent_element == "MAPPING") {
422
                $answer_id = $attributes['MAPKEY'];
423 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['weighting'])) {
424
                    $exercise_info['question'][$current_question_ident]['weighting'] = array();
425
                }
426
                $exercise_info['question'][$current_question_ident]['weighting'][$answer_id] = $attributes['MAPPEDVALUE'];
427
            }
428
            break;
429
        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...
430
            if (isset ($attributes['DEFAULTVALUE'])) {
431
                $exercise_info['question'][$current_question_ident]['default_weighting'] = $attributes['DEFAULTVALUE'];
432
            }
433
        case 'ITEMBODY':
434
            $record_item_body = true;
435
            $current_question_item_body = '';
436
            break;
437
        case 'IMG':
438
            $exercise_info['question'][$current_question_ident]['attached_file_url'] = $attributes['SRC'];
439
            break;
440
    }
441
}
442
443
/**
444
 * Function used by the SAX xml parser when the parser meets a closing tag
445
 *
446
 * @param $parser xml parser created with "xml_parser_create()"
447
 * @param $name name of the element
448
 */
449
function endElement($parser, $name)
450
{
451
    global $element_pile;
452
    global $exercise_info;
453
    global $current_question_ident;
454
    global $record_item_body;
455
    global $current_question_item_body;
456
    global $non_HTML_tag_to_avoid;
457
    global $cardinality;
458
459
    $current_element = end($element_pile);
460
461
    //treat the record of the full content of itembody tag :
462
463
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
464
        $current_question_item_body .= "</" . $name . ">";
465
    }
466
467
    switch ($name) {
468
        case 'ITEMBODY':
469
            $record_item_body = false;
470
            if ($exercise_info['question'][$current_question_ident]['type'] == 'FIB') {
471
                $exercise_info['question'][$current_question_ident]['response_text'] = $current_question_item_body;
472
            } else {
473
                $exercise_info['question'][$current_question_ident]['statement'] = $current_question_item_body;
474
            }
475
            break;
476
    }
477
    array_pop($element_pile);
478
}
479
480
/**
481
 * @param $parser
482
 * @param $data
483
 */
484
function elementData($parser, $data)
485
{
486
    global $element_pile;
487
    global $exercise_info;
488
    global $current_question_ident;
489
    global $current_answer_id;
490
    global $current_match_set;
491
    global $currentAssociableChoice;
492
    global $current_question_item_body;
493
    global $record_item_body;
494
    global $non_HTML_tag_to_avoid;
495
    global $current_inlinechoice_id;
496
    global $cardinality;
497
498
    $current_element = end($element_pile);
499 View Code Duplication
    if (sizeof($element_pile) >= 2) {
500
        $parent_element = $element_pile[sizeof($element_pile) - 2];
501
    } else {
502
        $parent_element = "";
503
    }
504 View Code Duplication
    if (sizeof($element_pile) >= 3) {
505
        $grant_parent_element = $element_pile[sizeof($element_pile) - 3];
506
    } else {
507
        $grant_parent_element = "";
508
    }
509
510
    //treat the record of the full content of itembody tag (needed for question statment and/or FIB text:
511
512
    if ($record_item_body && (!in_array($current_element, $non_HTML_tag_to_avoid))) {
513
        $current_question_item_body .= $data;
514
    }
515
516
    switch ($current_element) {
517 View Code Duplication
        case 'SIMPLECHOICE':
518
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'])) {
519
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] = trim($data);
520
            } else {
521
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['value'] .= '' . trim($data);
522
            }
523
            break;
524 View Code Duplication
        case 'FEEDBACKINLINE':
525
            if (!isset ($exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'])) {
526
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] = trim($data);
527
            } else {
528
                $exercise_info['question'][$current_question_ident]['answer'][$current_answer_id]['feedback'] .= ' ' . trim($data);
529
            }
530
            break;
531
        case 'SIMPLEASSOCIABLECHOICE':
532
            $exercise_info['question'][$current_question_ident]['answer'][$current_match_set][$currentAssociableChoice] = trim($data);
533
            break;
534
        case 'VALUE':
535
            if ($parent_element == "CORRECTRESPONSE") {
536
                if ($cardinality == "single") {
537
                    $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id] = $data;
538
                } else {
539
                    $exercise_info['question'][$current_question_ident]['correct_answers'][] = $data;
540
                }
541
            }
542
            break;
543
        case 'ITEMBODY':
544
            $current_question_item_body .= $data;
545
            break;
546
        case 'INLINECHOICE':
547
            // if this is the right answer, then we must replace the claroline tags in the FIB text bye the answer between "[" and "]" :
548
            $answer_identifier = $exercise_info['question'][$current_question_ident]['correct_answers'][$current_answer_id];
549
            if ($current_inlinechoice_id == $answer_identifier) {
550
                $current_question_item_body = str_replace(
551
                    "**claroline_start**" . $current_answer_id . "**claroline_end**",
552
                    "[" . $data . "]",
553
                    $current_question_item_body
554
                );
555
            } else {
556 View Code Duplication
                if (!isset ($exercise_info['question'][$current_question_ident]['wrong_answers'])) {
557
                    $exercise_info['question'][$current_question_ident]['wrong_answers'] = array();
558
                }
559
                $exercise_info['question'][$current_question_ident]['wrong_answers'][] = $data;
560
            }
561
            break;
562
    }
563
}
564