Completed
Push — master ( 17e47f...c8c08e )
by Julito
65:46 queued 26:40
created

aiken_import.inc.php ➔ aiken_parse_file()   D

Complexity

Conditions 22
Paths 101

Size

Total Lines 89
Code Lines 62

Duplication

Lines 10
Ratio 11.24 %

Importance

Changes 0
Metric Value
cc 22
eloc 62
nc 101
nop 4
dl 10
loc 89
rs 4.6478
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* For licensing terms, see /license.txt */
3
/**
4
 * Library for the import of Aiken format
5
 * @author claro team <[email protected]>
6
 * @author Guillaume Lederer <[email protected]>
7
 * @author César Perales <[email protected]> Parse function for Aiken format
8
 * @package chamilo.exercise
9
 */
10
/**
11
 * Security check
12
 */
13
if (count(get_included_files()) == 1)
14
    die('---');
15
16
/**
17
 * Creates a temporary directory
18
 * @param $dir
19
 * @param string $prefix
20
 * @param int $mode
21
 * @return string
22
 */
23 View Code Duplication
function tempdir($dir, $prefix = 'tmp', $mode = 0777) {
24
    if (substr($dir, -1) != '/')
25
        $dir .= '/';
26
27
    do {
28
        $path = $dir . $prefix . mt_rand(0, 9999999);
29
    } while (!mkdir($path, $mode));
30
31
    return $path;
32
}
33
34
/**
35
 * This function displays the form for import of the zip file with qti2
36
 * @param   string  Report message to show in case of error
37
 */
38
function aiken_display_form($msg = '') {
39
    $name_tools = get_lang('ImportAikenQuiz');
40
    $form  = '<div class="actions">';
41
    $form .= '<a href="exercise.php?show=test">' . Display :: return_icon('back.png', get_lang('BackToExercisesList'),'',ICON_SIZE_MEDIUM).'</a>';
42
    $form .= '</div>';
43
    $form .= $msg;
44
    $form_validator  = new FormValidator('aiken_upload', 'post',api_get_self()."?".api_get_cidreq(), null, array('enctype' => 'multipart/form-data') );
45
    $form_validator->addElement('header', $name_tools);
46
    $form_validator->addElement('text', 'total_weight', get_lang('TotalWeight'));
47
    $form_validator->addElement('file', 'userFile', get_lang('DownloadFile'));
48
    $form_validator->addButtonUpload(get_lang('Send'), 'submit');
49
    $form .= $form_validator->return_form();
0 ignored issues
show
Deprecated Code introduced by
The method FormValidator::return_form() has been deprecated with message: use returnForm()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
50
    $form .= '<blockquote>'.get_lang('ImportAikenQuizExplanation').'<br /><pre>'.get_lang('ImportAikenQuizExplanationExample').'</pre></blockquote>';
51
    echo $form;
52
}
53
54
/**
55
 * Gets the uploaded file (from $_FILES) and unzip it to the given directory
56
 * @param string The directory where to do the work
57
 * @param string The path of the temporary directory where the exercise was uploaded and unzipped
58
 * @return bool True on success, false on failure
59
 */
60
function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath) {
61
    $_course = api_get_course_info();
62
    $_user = api_get_user_info();
63
    //Check if the file is valid (not to big and exists)
64 View Code Duplication
    if (!isset ($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) {
65
        // upload failed
66
        return false;
67
    }
68
    if (preg_match('/.zip$/i', $_FILES['userFile']['name']) && handle_uploaded_document($_course, $_FILES['userFile'], $baseWorkDir, $uploadPath, $_user['user_id'], 0, null, 1, 'overwrite', false)) {
69
        if (!function_exists('gzopen')) {
70
            return false;
71
        }
72
        // upload successful
73
        return true;
74
    } elseif (preg_match('/.txt/i', $_FILES['userFile']['name']) && handle_uploaded_document($_course, $_FILES['userFile'], $baseWorkDir, $uploadPath, $_user['user_id'], 0, null, 0, 'overwrite', false)) {
75
        return true;
76
    } else {
77
        return false;
78
    }
79
}
80
/**
81
 * Main function to import the Aiken exercise
82
 * @return mixed True on success, error message on failure
83
 */
84
function aiken_import_exercise($file)
85
{
86
    global $exercise_info;
87
    global $element_pile;
88
    global $non_HTML_tag_to_avoid;
89
    global $record_item_body;
90
    // used to specify the question directory where files could be found in relation in any question
91
    global $questionTempDir;
92
    $archive_path = api_get_path(SYS_ARCHIVE_PATH) . 'aiken';
93
    $baseWorkDir = $archive_path;
94
95
    if (!is_dir($baseWorkDir)) {
96
        mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true);
97
    }
98
99
    $uploadPath = '/';
100
101
    // set some default values for the new exercise
102
    $exercise_info = array();
103
    $exercise_info['name'] = preg_replace('/.(zip|txt)$/i', '', $file);
104
    $exercise_info['question'] = array();
105
    $element_pile = array();
106
107
    // create parser and array to retrieve info from manifest
108
    $element_pile = array(); //pile to known the depth in which we are
109
110
    // if file is not a .zip, then we cancel all
111
    if (!preg_match('/.(zip|txt)$/i', $file)) {
112
        //Display :: display_error_message(get_lang('YouMustUploadAZipOrTxtFile'));
113
114
        return 'YouMustUploadAZipOrTxtFile';
115
    }
116
117
    // unzip the uploaded file in a tmp directory
118
    if (preg_match('/.(zip|txt)$/i', $file)) {
119
        if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) {
120
121
            return 'ThereWasAProblemWithYourFile';
122
        }
123
    }
124
125
    // find the different manifests for each question and parse them
126
    $exerciseHandle = opendir($baseWorkDir);
127
    $file_found = false;
128
    $operation = false;
129
    $result = false;
130
131
    // Parse every subdirectory to search txt question files
132
    while (false !== ($file = readdir($exerciseHandle))) {
133
        if (is_dir($baseWorkDir . '/' . $file) && $file != "." && $file != "..") {
134
            //find each manifest for each question repository found
135
            $questionHandle = opendir($baseWorkDir . '/' . $file);
136 View Code Duplication
            while (false !== ($questionFile = readdir($questionHandle))) {
137
                if (preg_match('/.txt$/i', $questionFile)) {
138
                    $result = aiken_parse_file(
139
                        $exercise_info,
140
                        $baseWorkDir,
141
                        $file,
142
                        $questionFile
143
                    );
144
                    $file_found = true;
145
                }
146
            }
147
        } elseif (preg_match('/.txt$/i', $file)) {
148
            $result = aiken_parse_file($exercise_info, $baseWorkDir, '', $file);
149
            $file_found = true;
150
        } // else ignore file
151
    }
152
153
    if (!$file_found) {
154
        $result = 'NoTxtFileFoundInTheZip';
155
    }
156
157
    if ($result !== true ) {
158
        return $result;
159
    }
160
161
    // Add exercise in tool
162
163
    // 1.create exercise
164
    $exercise = new Exercise();
165
    $exercise->exercise = $exercise_info['name'];
166
167
    $exercise->save();
168
    $last_exercise_id = $exercise->selectId();
169
    if (!empty($last_exercise_id)) {
170
        // For each question found...
171
        foreach ($exercise_info['question'] as $key => $question_array) {
172
            //2.create question
173
            $question = new Aiken2Question();
174
            $question->type = $question_array['type'];
175
            $question->setAnswer();
176
            $question->updateTitle($question_array['title']);
177
178
            if (isset($question_array['description'])) {
179
                $question->updateDescription($question_array['description']);
180
            }
181
            $type = $question->selectType();
182
            $question->type = constant($type);
183
            $question->save($last_exercise_id);
184
            $last_question_id = $question->selectId();
185
            //3. Create answer
186
            $answer = new Answer($last_question_id);
187
188
            $answer->new_nbrAnswers = count($question_array['answer']);
189
            $max_score = 0;
190
191
            foreach ($question_array['answer'] as $key => $answers) {
192
                $key++;
193
                $answer->new_answer[$key] = $answers['value'];
194
                $answer->new_position[$key] = $key;
195
                // Correct answers ...
196
                if (in_array($key, $question_array['correct_answers'])) {
197
                    $answer->new_correct[$key] = 1;
198
                    if (isset($question_array['feedback'])) {
199
                        $answer->new_comment[$key] = $question_array['feedback'];
200
                    }
201
                } else {
202
                    $answer->new_correct[$key] = 0;
203
                }
204
205
                if (isset($question_array['weighting'][$key - 1])) {
206
                    $answer->new_weighting[$key] = $question_array['weighting'][$key - 1];
207
                    $max_score += $question_array['weighting'][$key - 1];
208
                }
209
            }
210
211
            $answer->save();
212
            // Now that we know the question score, set it!
213
            $question->updateWeighting($max_score);
214
            $question->save();
215
        }
216
217
        // Delete the temp dir where the exercise was unzipped
218
        my_delete($baseWorkDir . $uploadPath);
219
        $operation = $last_exercise_id;
220
    }
221
222
    return $operation;
223
}
224
225
/**
226
 * Parses an Aiken file and builds an array of exercise + questions to be
227
 * imported by the import_exercise() function
228
 * @param array The reference to the array in which to store the questions
229
 * @param string Path to the directory with the file to be parsed (without final /)
230
 * @param string Name of the last directory part for the file (without /)
231
 * @param string Name of the file to be parsed (including extension)
232
 * @return mixed True on success, error message on error
233
 * @assert ('','','') === false
234
 */
235
function aiken_parse_file(&$exercise_info, $exercisePath, $file, $questionFile) {
236
    global $questionTempDir;
237
238
    $questionTempDir = $exercisePath . '/' . $file . '/';
239
    $questionFilePath = $questionTempDir . $questionFile;
240
241
    if (!is_file($questionFilePath)) {
242
        return 'FileNotFound';
243
    }
244
    $data = file($questionFilePath);
245
246
    $question_index = 0;
247
    $correct_answer = '';
248
    $answers_array = array();
249
    $new_question = true;
250
    foreach ($data as $line => $info) {
251
        if ($question_index > 0 && $new_question == true && preg_match('/^(\r)?\n/',$info)) {
252
            // double empty line
253
            continue;
254
        }
255
        $new_question = false;
256
        //make sure it is transformed from iso-8859-1 to utf-8 if in that form
257
        if (!mb_check_encoding($info,'utf-8') && mb_check_encoding($info,'iso-8859-1')) {
258
            $info = utf8_encode($info);
259
        }
260
        $exercise_info['question'][$question_index]['type'] = 'MCUA';
261
        if (preg_match('/^([A-Za-z])(\)|\.)\s(.*)/', $info, $matches)) {
262
            //adding one of the possible answers
263
            $exercise_info['question'][$question_index]['answer'][]['value'] = $matches[3];
264
            $answers_array[] = $matches[1];
265
        } elseif (preg_match('/^ANSWER:\s?([A-Z])\s?/', $info, $matches)) {
266
            //the correct answers
267
            $correct_answer_index = array_search($matches[1], $answers_array);
268
            $exercise_info['question'][$question_index]['correct_answers'][] = $correct_answer_index + 1;
269
            //weight for correct answer
270
            $exercise_info['question'][$question_index]['weighting'][$correct_answer_index] = 1;
271 View Code Duplication
        } elseif (preg_match('/^ANSWER_EXPLANATION:\s?(.*)/', $info, $matches)) {
272
            //Comment of correct answer
273
            $correct_answer_index = array_search($matches[1], $answers_array);
274
            $exercise_info['question'][$question_index]['feedback'] = $matches[1];
275
        } elseif (preg_match('/^TEXTO_CORRECTA:\s?(.*)/', $info, $matches)) {
276
            //Comment of correct answer (Spanish e-ducativa format)
277
            $correct_answer_index = array_search($matches[1], $answers_array);
278
            $exercise_info['question'][$question_index]['feedback'] = $matches[1];
279 View Code Duplication
        } elseif (preg_match('/^T:\s?(.*)/', $info, $matches)) {
280
            //Question Title
281
            $correct_answer_index = array_search($matches[1], $answers_array);
282
            $exercise_info['question'][$question_index]['title'] = $matches[1];
283
        } elseif (preg_match('/^TAGS:\s?([A-Z])\s?/', $info, $matches)) {
284
             //TAGS for chamilo >= 1.10
285
            $exercise_info['question'][$question_index]['answer_tags'] = explode(',', $matches[1]);
286
        } elseif (preg_match('/^ETIQUETAS:\s?([A-Z])\s?/', $info, $matches)) {
287
            //TAGS for chamilo >= 1.10 (Spanish e-ducativa format)
288
            $exercise_info['question'][$question_index]['answer_tags'] = explode(',', $matches[1]);
289
        } elseif (preg_match('/^(\r)?\n/',$info)) {
290
            //moving to next question (tolerate \r\n or just \n)
291
            if (empty($exercise_info['question'][$question_index]['correct_answers'])) {
292
                error_log('Aiken: Error in question index '.$question_index.': no correct answer defined');
293
                return 'ExerciseAikenErrorNoCorrectAnswerDefined';
294
            }
295
            if (empty($exercise_info['question'][$question_index]['answer'])) {
296
                error_log('Aiken: Error in question index '.$question_index.': no answer option given');
297
                return 'ExerciseAikenErrorNoAnswerOptionGiven';
298
            }
299
            $question_index++;
300
            //emptying answers array when moving to next question
301
            $answers_array = array();
302
            $new_question = true;
303
        } else {
304
            if (empty($exercise_info['question'][$question_index]['title'])) {
305
                if (strlen($info) < 100) {
306
                    $exercise_info['question'][$question_index]['title'] = $info;
307
                } else {
308
                    //Question itself (use a 100-chars long title and a larger description)
309
                    $exercise_info['question'][$question_index]['title'] = trim(substr($info, 0, 100)) . '...';
310
                    $exercise_info['question'][$question_index]['description'] = $info;
311
                }
312
            } else {
313
                $exercise_info['question'][$question_index]['description'] = $info;
314
            }
315
        }
316
    }
317
    $total_questions = count($exercise_info['question']);
318
    $total_weight = (!empty($_POST['total_weight'])) ? intval($_POST['total_weight']) : 20;
319
    foreach  ($exercise_info['question'] as $key => $question) {
320
        $exercise_info['question'][$key]['weighting'][current(array_keys($exercise_info['question'][$key]['weighting']))] = $total_weight / $total_questions;
321
    }
322
    return true;
323
}
324
325
/**
326
 * Imports the zip file
327
 * @param array $array_file ($_FILES)
328
 */
329
function aiken_import_file($array_file) {
330
331
    $unzip = 0;
332
    $process = process_uploaded_file($array_file, false);
333
    if (preg_match('/\.(zip|txt)$/i', $array_file['name'])) {
334
        // if it's a zip, allow zip upload
335
        $unzip = 1;
336
    }
337
338
    if ($process && $unzip == 1) {
339
        $imported = aiken_import_exercise($array_file['name']);
340
        if (is_numeric($imported) && !empty($imported)) {
341
            return $imported;
342
343
        } else {
344
            $msg = Display::return_message(get_lang($imported), 'error');
345
346
            return $msg;
347
        }
348
    }
349
}
350