Test Setup Failed
Push — master ( f71949...6c6bd7 )
by Julito
55:21
created

aiken_import.inc.php ➔ aiken_display_form()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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