|
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(); |
|
|
|
|
|
|
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
|
|
|
|
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.