1 | <?php |
||
2 | |||
3 | /* For licensing terms, see /license.txt */ |
||
4 | |||
5 | use Chamilo\CoreBundle\Component\Utils\ChamiloApi; |
||
6 | use Symfony\Component\DomCrawler\Crawler; |
||
7 | |||
8 | /** |
||
9 | * @copyright (c) 2001-2006 Universite catholique de Louvain (UCL) |
||
10 | * @author claro team <[email protected]> |
||
11 | * @author Guillaume Lederer <[email protected]> |
||
12 | * @author Yannick Warnier <[email protected]> |
||
13 | */ |
||
14 | |||
15 | /** |
||
16 | * Unzip the exercise in the temp folder. |
||
17 | * |
||
18 | * @param string $baseWorkDir The path of the temporary directory where the exercise was uploaded and unzipped |
||
19 | * @param string $uploadPath |
||
20 | * |
||
21 | * @return bool |
||
22 | */ |
||
23 | function get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath) |
||
24 | { |
||
25 | $_course = api_get_course_info(); |
||
26 | $_user = api_get_user_info(); |
||
27 | |||
28 | //Check if the file is valid (not to big and exists) |
||
29 | if (!isset($_FILES['userFile']) || !is_uploaded_file($_FILES['userFile']['tmp_name'])) { |
||
30 | // upload failed |
||
31 | return false; |
||
32 | } |
||
33 | |||
34 | if (preg_match('/.zip$/i', $_FILES['userFile']['name'])) { |
||
35 | return handle_uploaded_document( |
||
36 | $_course, |
||
37 | $_FILES['userFile'], |
||
38 | $baseWorkDir, |
||
39 | $uploadPath, |
||
40 | $_user['user_id'], |
||
41 | 0, |
||
42 | null, |
||
43 | 1, |
||
44 | null, |
||
45 | null, |
||
46 | true, |
||
47 | null, |
||
48 | null, |
||
49 | false |
||
50 | ); |
||
51 | } |
||
52 | |||
53 | return false; |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * Imports an exercise in QTI format if the XML structure can be found in it. |
||
58 | * |
||
59 | * @param array $file |
||
60 | * |
||
61 | * @return string|array as a backlog of what was really imported, and error or debug messages to display |
||
62 | */ |
||
63 | function import_exercise($file) |
||
64 | { |
||
65 | global $exerciseInfo; |
||
66 | global $resourcesLinks; |
||
67 | |||
68 | $baseWorkDir = api_get_path(SYS_ARCHIVE_PATH).'qti2/'; |
||
69 | if (!is_dir($baseWorkDir)) { |
||
70 | mkdir($baseWorkDir, api_get_permissions_for_new_directories(), true); |
||
71 | } |
||
72 | |||
73 | $uploadPath = api_get_unique_id().'/'; |
||
74 | |||
75 | if (!is_dir($baseWorkDir.$uploadPath)) { |
||
76 | mkdir($baseWorkDir.$uploadPath, api_get_permissions_for_new_directories(), true); |
||
77 | } |
||
78 | |||
79 | // set some default values for the new exercise |
||
80 | $exerciseInfo = []; |
||
81 | $exerciseInfo['name'] = preg_replace('/.zip$/i', '', $file); |
||
82 | $exerciseInfo['question'] = []; |
||
83 | |||
84 | // if file is not a .zip, then we cancel all |
||
85 | if (!preg_match('/.zip$/i', $file)) { |
||
86 | return 'UplZipCorrupt'; |
||
87 | } |
||
88 | |||
89 | // unzip the uploaded file in a tmp directory |
||
90 | if (!get_and_unzip_uploaded_exercise($baseWorkDir, $uploadPath)) { |
||
91 | return 'UplZipCorrupt'; |
||
92 | } |
||
93 | |||
94 | $baseWorkDir = $baseWorkDir.$uploadPath; |
||
95 | |||
96 | // find the different manifests for each question and parse them. |
||
97 | $exerciseHandle = opendir($baseWorkDir); |
||
98 | $fileFound = false; |
||
99 | $result = false; |
||
100 | $filePath = null; |
||
101 | $resourcesLinks = []; |
||
102 | |||
103 | // parse every subdirectory to search xml question files and other assets to be imported |
||
104 | // The assets-related code is a bit fragile as it has to deal with files renamed by Chamilo and it only works if |
||
105 | // the imsmanifest.xml file is read. |
||
106 | while (false !== ($file = readdir($exerciseHandle))) { |
||
107 | if (is_dir($baseWorkDir.'/'.$file) && $file != "." && $file != "..") { |
||
108 | // Find each manifest for each question repository found |
||
109 | $questionHandle = opendir($baseWorkDir.'/'.$file); |
||
110 | // Only analyse one level of subdirectory - no recursivity here |
||
111 | while (false !== ($questionFile = readdir($questionHandle))) { |
||
112 | if (preg_match('/.xml$/i', $questionFile)) { |
||
113 | $isQti = isQtiQuestionBank($baseWorkDir.'/'.$file.'/'.$questionFile); |
||
114 | if ($isQti) { |
||
115 | $result = qti_parse_file($baseWorkDir, $file, $questionFile); |
||
116 | $filePath = $baseWorkDir.$file; |
||
117 | $fileFound = true; |
||
118 | } else { |
||
119 | $isManifest = isQtiManifest($baseWorkDir.'/'.$file.'/'.$questionFile); |
||
120 | if ($isManifest) { |
||
121 | $resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file.'/'.$questionFile); |
||
122 | } |
||
123 | } |
||
124 | } |
||
125 | } |
||
126 | } elseif (preg_match('/.xml$/i', $file)) { |
||
127 | $isQti = isQtiQuestionBank($baseWorkDir.'/'.$file); |
||
128 | if ($isQti) { |
||
129 | $result = qti_parse_file($baseWorkDir, '', $file); |
||
130 | $filePath = $baseWorkDir.'/'.$file; |
||
131 | $fileFound = true; |
||
132 | } else { |
||
133 | $isManifest = isQtiManifest($baseWorkDir.'/'.$file); |
||
134 | if ($isManifest) { |
||
135 | $resourcesLinks = qtiProcessManifest($baseWorkDir.'/'.$file); |
||
136 | } |
||
137 | } |
||
138 | } |
||
139 | } |
||
140 | |||
141 | if (!$fileFound) { |
||
142 | return 'NoXMLFileFoundInTheZip'; |
||
143 | } |
||
144 | |||
145 | if ($result == false) { |
||
0 ignored issues
–
show
|
|||
146 | return false; |
||
147 | } |
||
148 | // 1. Create exercise. |
||
149 | $exercise = new Exercise(); |
||
150 | $exercise->exercise = $exerciseInfo['name']; |
||
151 | |||
152 | // Random QTI support |
||
153 | if (isset($exerciseInfo['order_type'])) { |
||
154 | if ($exerciseInfo['order_type'] == 'Random') { |
||
155 | $exercise->setQuestionSelectionType(2); |
||
156 | $exercise->random = -1; |
||
157 | } |
||
158 | } |
||
159 | |||
160 | if (!empty($exerciseInfo['description'])) { |
||
161 | $exercise->updateDescription(formatText(strip_tags($exerciseInfo['description']))); |
||
162 | } |
||
163 | |||
164 | $exercise->save(); |
||
165 | $last_exercise_id = $exercise->selectId(); |
||
166 | $courseId = api_get_course_int_id(); |
||
167 | if (!empty($last_exercise_id)) { |
||
168 | // For each question found... |
||
169 | foreach ($exerciseInfo['question'] as $question_array) { |
||
170 | if (!in_array($question_array['type'], [UNIQUE_ANSWER, MULTIPLE_ANSWER, FREE_ANSWER])) { |
||
171 | continue; |
||
172 | } |
||
173 | //2. Create question |
||
174 | $question = new Ims2Question(); |
||
175 | $question->type = $question_array['type']; |
||
176 | if (empty($question->type)) { |
||
177 | // If the type was not provided, assume this is a multiple choice, unique answer type (the most basic) |
||
178 | $question->type = MCUA; |
||
179 | } |
||
180 | $question->setAnswer(); |
||
181 | $description = ''; |
||
182 | $question->updateTitle(formatText(strip_tags($question_array['title']))); |
||
183 | |||
184 | if (isset($question_array['category'])) { |
||
185 | $category = formatText(strip_tags($question_array['category'])); |
||
186 | if (!empty($category)) { |
||
187 | $categoryId = TestCategory::get_category_id_for_title( |
||
188 | $category, |
||
189 | $courseId |
||
190 | ); |
||
191 | |||
192 | if (empty($categoryId)) { |
||
193 | $cat = new TestCategory(); |
||
194 | $cat->name = $category; |
||
195 | $cat->description = ''; |
||
196 | $categoryId = $cat->save($courseId); |
||
197 | if ($categoryId) { |
||
198 | $question->category = $categoryId; |
||
199 | } |
||
200 | } else { |
||
201 | $question->category = $categoryId; |
||
202 | } |
||
203 | } |
||
204 | } |
||
205 | |||
206 | if (!empty($question_array['description'])) { |
||
207 | $description .= $question_array['description']; |
||
208 | } |
||
209 | |||
210 | $question->updateDescription($description); |
||
211 | $question->save($exercise); |
||
212 | |||
213 | $last_question_id = $question->selectId(); |
||
214 | //3. Create answer |
||
215 | $answer = new Answer($last_question_id); |
||
216 | $answerList = $question_array['answer']; |
||
217 | $answer->new_nbrAnswers = count($answerList); |
||
218 | $totalCorrectWeight = 0; |
||
219 | $j = 1; |
||
220 | $matchAnswerIds = []; |
||
221 | if (!empty($answerList)) { |
||
222 | foreach ($answerList as $key => $answers) { |
||
223 | if (preg_match('/_/', $key)) { |
||
224 | $split = explode('_', $key); |
||
225 | $i = $split[1]; |
||
226 | } else { |
||
227 | $i = $j; |
||
228 | $j++; |
||
229 | $matchAnswerIds[$key] = $j; |
||
230 | } |
||
231 | |||
232 | // Answer |
||
233 | $answer->new_answer[$i] = isset($answers['value']) ? formatText($answers['value']) : ''; |
||
234 | // Comment |
||
235 | $answer->new_comment[$i] = isset($answers['feedback']) ? formatText($answers['feedback']) : null; |
||
236 | // Position |
||
237 | $answer->new_position[$i] = $i; |
||
238 | // Correct answers |
||
239 | if (in_array($key, $question_array['correct_answers'])) { |
||
240 | $answer->new_correct[$i] = 1; |
||
241 | } else { |
||
242 | $answer->new_correct[$i] = 0; |
||
243 | } |
||
244 | |||
245 | $answer->new_weighting[$i] = 0; |
||
246 | if (isset($question_array['weighting'][$key])) { |
||
247 | $answer->new_weighting[$i] = $question_array['weighting'][$key]; |
||
248 | } |
||
249 | if ($answer->new_correct[$i]) { |
||
250 | $totalCorrectWeight += $answer->new_weighting[$i]; |
||
251 | } |
||
252 | } |
||
253 | } |
||
254 | |||
255 | if ($question->type == FREE_ANSWER) { |
||
256 | $totalCorrectWeight = $question_array['weighting'][0]; |
||
257 | } |
||
258 | |||
259 | $question->updateWeighting($totalCorrectWeight); |
||
260 | $question->save($exercise); |
||
261 | $answer->save(); |
||
262 | } |
||
263 | |||
264 | // delete the temp dir where the exercise was unzipped |
||
265 | my_delete($baseWorkDir.$uploadPath); |
||
266 | |||
267 | return $last_exercise_id; |
||
268 | } |
||
269 | |||
270 | return false; |
||
271 | } |
||
272 | |||
273 | /** |
||
274 | * We assume the file charset is UTF8. |
||
275 | */ |
||
276 | function formatText($text) |
||
277 | { |
||
278 | return api_html_entity_decode($text); |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Parses a given XML file and fills global arrays with the elements. |
||
283 | * |
||
284 | * @param string $exercisePath |
||
285 | * @param string $file |
||
286 | * @param string $questionFile |
||
287 | * |
||
288 | * @return bool |
||
289 | */ |
||
290 | function qti_parse_file($exercisePath, $file, $questionFile) |
||
291 | { |
||
292 | global $record_item_body; |
||
293 | global $questionTempDir; |
||
294 | |||
295 | $questionTempDir = $exercisePath.'/'.$file.'/'; |
||
296 | $questionFilePath = $questionTempDir.$questionFile; |
||
297 | |||
298 | if (!($fp = fopen($questionFilePath, 'r'))) { |
||
299 | Display::addFlash(Display::return_message(get_lang('Error opening question\'s XML file'), 'error')); |
||
300 | |||
301 | return false; |
||
302 | } |
||
303 | |||
304 | $data = fread($fp, filesize($questionFilePath)); |
||
305 | |||
306 | //close file |
||
307 | fclose($fp); |
||
308 | |||
309 | //parse XML question file |
||
310 | //$data = str_replace(array('<p>', '</p>', '<front>', '</front>'), '', $data); |
||
311 | $data = ChamiloApi::stripGivenTags($data, ['p', 'front']); |
||
312 | $qtiVersion = []; |
||
313 | $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data, $qtiVersion); |
||
314 | $qtiMainVersion = 2; //by default, assume QTI version 2 |
||
315 | if ($match) { |
||
316 | $qtiMainVersion = $qtiVersion[1]; |
||
317 | } |
||
318 | |||
319 | //used global variable start values declaration: |
||
320 | $record_item_body = false; |
||
321 | |||
322 | if ($qtiMainVersion != 2) { |
||
323 | Display::addFlash( |
||
324 | Display::return_message( |
||
325 | get_lang('UnsupportedQtiVersion'), |
||
326 | 'error' |
||
327 | ) |
||
328 | ); |
||
329 | |||
330 | return false; |
||
331 | } |
||
332 | |||
333 | parseQti2($data); |
||
334 | |||
335 | return true; |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Function used to parser a QTI2 xml file. |
||
340 | * |
||
341 | * @param string $xmlData |
||
342 | */ |
||
343 | function parseQti2($xmlData) |
||
344 | { |
||
345 | global $exerciseInfo; |
||
346 | global $questionTempDir; |
||
347 | global $resourcesLinks; |
||
348 | |||
349 | $crawler = new Crawler($xmlData); |
||
350 | $nodes = $crawler->filter('*'); |
||
351 | |||
352 | $currentQuestionIdent = ''; |
||
353 | $currentAnswerId = ''; |
||
354 | $currentQuestionItemBody = ''; |
||
355 | $cardinality = ''; |
||
356 | $nonHTMLTagToAvoid = [ |
||
357 | 'prompt', |
||
358 | 'simpleChoice', |
||
359 | 'choiceInteraction', |
||
360 | 'inlineChoiceInteraction', |
||
361 | 'inlineChoice', |
||
362 | 'soMPLEMATCHSET', |
||
363 | 'simpleAssociableChoice', |
||
364 | 'textEntryInteraction', |
||
365 | 'feedbackInline', |
||
366 | 'matchInteraction', |
||
367 | 'extendedTextInteraction', |
||
368 | 'itemBody', |
||
369 | 'br', |
||
370 | 'img', |
||
371 | ]; |
||
372 | $currentMatchSet = null; |
||
373 | |||
374 | /** @var DOMElement $node */ |
||
375 | foreach ($nodes as $node) { |
||
376 | if ('#text' === $node->nodeName) { |
||
377 | continue; |
||
378 | } |
||
379 | |||
380 | switch ($node->nodeName) { |
||
381 | case 'assessmentItem': |
||
382 | $currentQuestionIdent = $node->getAttribute('identifier'); |
||
383 | |||
384 | $exerciseInfo['question'][$currentQuestionIdent] = [ |
||
385 | 'answer' => [], |
||
386 | 'correct_answers' => [], |
||
387 | 'title' => $node->getAttribute('title'), |
||
388 | 'category' => $node->getAttribute('category'), |
||
389 | 'type' => '', |
||
390 | 'tempdir' => $questionTempDir, |
||
391 | 'description' => null, |
||
392 | ]; |
||
393 | break; |
||
394 | case 'section': |
||
395 | $title = $node->getAttribute('title'); |
||
396 | |||
397 | if (!empty($title)) { |
||
398 | $exerciseInfo['name'] = $title; |
||
399 | } |
||
400 | break; |
||
401 | case 'responseDeclaration': |
||
402 | if ('multiple' === $node->getAttribute('cardinality')) { |
||
403 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCMA; |
||
404 | $cardinality = 'multiple'; |
||
405 | } |
||
406 | |||
407 | if ('single' === $node->getAttribute('cardinality')) { |
||
408 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = MCUA; |
||
409 | $cardinality = 'single'; |
||
410 | } |
||
411 | |||
412 | $currentAnswerId = $node->getAttribute('identifier'); |
||
413 | break; |
||
414 | case 'inlineChoiceInteraction': |
||
415 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB; |
||
416 | $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'LISTBOX_FILL'; |
||
417 | $currentAnswerId = $node->getAttribute('responseIdentifier'); |
||
418 | break; |
||
419 | case 'inlineChoice': |
||
420 | $answerIdentifier = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId]; |
||
421 | |||
422 | if ($node->getAttribute('identifier') == $answerIdentifier) { |
||
423 | $currentQuestionItemBody = str_replace( |
||
424 | "**claroline_start**".$currentAnswerId."**claroline_end**", |
||
425 | "[".$node->nodeValue."]", |
||
426 | $currentQuestionItemBody |
||
427 | ); |
||
428 | } else { |
||
429 | if (!isset($exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'])) { |
||
430 | $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'] = []; |
||
431 | } |
||
432 | |||
433 | $exerciseInfo['question'][$currentQuestionIdent]['wrong_answers'][] = $node->nodeValue; |
||
434 | } |
||
435 | break; |
||
436 | case 'textEntryInteraction': |
||
437 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = FIB; |
||
438 | $exerciseInfo['question'][$currentQuestionIdent]['subtype'] = 'TEXTFIELD_FILL'; |
||
439 | $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody; |
||
440 | break; |
||
441 | case 'matchInteraction': |
||
442 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = MATCHING; |
||
443 | break; |
||
444 | case 'extendedTextInteraction': |
||
445 | $exerciseInfo['question'][$currentQuestionIdent]['type'] = FREE_ANSWER; |
||
446 | $exerciseInfo['question'][$currentQuestionIdent]['description'] = $node->nodeValue; |
||
447 | break; |
||
448 | case 'simpleMatchSet': |
||
449 | if (!isset($currentMatchSet)) { |
||
450 | $currentMatchSet = 1; |
||
451 | } else { |
||
452 | $currentMatchSet++; |
||
453 | } |
||
454 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet] = []; |
||
455 | break; |
||
456 | case 'simpleAssociableChoice': |
||
457 | $currentAssociableChoice = $node->getAttribute('identifier'); |
||
458 | |||
459 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentMatchSet][$currentAssociableChoice] = trim($node->nodeValue); |
||
460 | break; |
||
461 | case 'simpleChoice': |
||
462 | $currentAnswerId = $node->getAttribute('identifier'); |
||
463 | if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId])) { |
||
464 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId] = []; |
||
465 | } |
||
466 | |||
467 | //$simpleChoiceValue = $node->nodeValue; |
||
468 | $simpleChoiceValue = ''; |
||
469 | /** @var DOMElement $childNode */ |
||
470 | foreach ($node->childNodes as $childNode) { |
||
471 | if ('feedbackInline' === $childNode->nodeName) { |
||
472 | continue; |
||
473 | } |
||
474 | $simpleChoiceValue .= $childNode->nodeValue; |
||
475 | } |
||
476 | $simpleChoiceValue = trim($simpleChoiceValue); |
||
477 | if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'])) { |
||
478 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] = $simpleChoiceValue; |
||
479 | } else { |
||
480 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['value'] .= $simpleChoiceValue; |
||
481 | } |
||
482 | break; |
||
483 | case 'mapEntry': |
||
484 | if (in_array($node->parentNode->nodeName, ['mapping', 'mapEntry'])) { |
||
485 | $answer_id = $node->getAttribute('mapKey'); |
||
486 | |||
487 | if (!isset($exerciseInfo['question'][$currentQuestionIdent]['weighting'])) { |
||
488 | $exerciseInfo['question'][$currentQuestionIdent]['weighting'] = []; |
||
489 | } |
||
490 | |||
491 | $exerciseInfo['question'][$currentQuestionIdent]['weighting'][$answer_id] = $node->getAttribute( |
||
492 | 'mappedValue' |
||
493 | ); |
||
494 | } |
||
495 | break; |
||
496 | case 'mapping': |
||
497 | $defaultValue = $node->getAttribute('defaultValue'); |
||
498 | if (!empty($defaultValue)) { |
||
499 | $exerciseInfo['question'][$currentQuestionIdent]['default_weighting'] = $defaultValue; |
||
500 | } |
||
501 | // no break ? |
||
502 | case 'itemBody': |
||
503 | $nodeValue = $node->nodeValue; |
||
504 | $currentQuestionItemBody = ''; |
||
505 | |||
506 | /** @var DOMElement $childNode */ |
||
507 | foreach ($node->childNodes as $childNode) { |
||
508 | if ('#text' === $childNode->nodeName) { |
||
509 | continue; |
||
510 | } |
||
511 | |||
512 | if (!in_array($childNode->nodeName, $nonHTMLTagToAvoid)) { |
||
513 | $currentQuestionItemBody .= '<'.$childNode->nodeName; |
||
514 | |||
515 | if ($childNode->attributes) { |
||
516 | foreach ($childNode->attributes as $attribute) { |
||
517 | $currentQuestionItemBody .= ' '.$attribute->nodeName.'="'.$attribute->nodeValue.'"'; |
||
518 | } |
||
519 | } |
||
520 | |||
521 | $currentQuestionItemBody .= '>'.$childNode->nodeValue.'</'.$node->nodeName.'>'; |
||
522 | |||
523 | continue; |
||
524 | } |
||
525 | |||
526 | if ('inlineChoiceInteraction' === $childNode->nodeName) { |
||
527 | $currentQuestionItemBody .= "**claroline_start**".$childNode->attr('responseIdentifier') |
||
528 | ."**claroline_end**"; |
||
529 | continue; |
||
530 | } |
||
531 | |||
532 | if ('textEntryInteraction' === $childNode->nodeName) { |
||
533 | $correct_answer_value = $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$currentAnswerId]; |
||
534 | $currentQuestionItemBody .= "[".$correct_answer_value."]"; |
||
535 | |||
536 | continue; |
||
537 | } |
||
538 | |||
539 | if ('br' === $childNode->nodeName) { |
||
540 | $currentQuestionItemBody .= '<br>'; |
||
541 | } |
||
542 | } |
||
543 | |||
544 | // Replace relative links by links to the documents in the course |
||
545 | // $resourcesLinks is only defined by qtiProcessManifest() |
||
546 | if (isset($resourcesLinks) && isset($resourcesLinks['manifest']) && isset($resourcesLinks['web'])) { |
||
547 | foreach ($resourcesLinks['manifest'] as $key => $value) { |
||
548 | $nodeValue = preg_replace('|'.$value.'|', $resourcesLinks['web'][$key], $nodeValue); |
||
549 | } |
||
550 | } |
||
551 | |||
552 | $currentQuestionItemBody .= $node->firstChild->nodeValue; |
||
553 | |||
554 | if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FIB) { |
||
555 | $exerciseInfo['question'][$currentQuestionIdent]['response_text'] = $currentQuestionItemBody; |
||
556 | } else { |
||
557 | if ($exerciseInfo['question'][$currentQuestionIdent]['type'] == FREE_ANSWER) { |
||
558 | $currentQuestionItemBody = trim($currentQuestionItemBody); |
||
559 | |||
560 | if (!empty($currentQuestionItemBody)) { |
||
561 | $exerciseInfo['question'][$currentQuestionIdent]['description'] = $currentQuestionItemBody; |
||
562 | } |
||
563 | } else { |
||
564 | $exerciseInfo['question'][$currentQuestionIdent]['statement'] = $currentQuestionItemBody; |
||
565 | } |
||
566 | } |
||
567 | break; |
||
568 | case 'img': |
||
569 | $exerciseInfo['question'][$currentQuestionIdent]['attached_file_url'] = $node->getAttribute('src'); |
||
570 | break; |
||
571 | case 'order': |
||
572 | $orderType = $node->getAttribute('order_type'); |
||
573 | |||
574 | if (!empty($orderType)) { |
||
575 | $exerciseInfo['order_type'] = $orderType; |
||
576 | } |
||
577 | break; |
||
578 | case 'feedbackInline': |
||
579 | if (!isset($exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'])) { |
||
580 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] = trim( |
||
581 | $node->nodeValue |
||
582 | ); |
||
583 | } else { |
||
584 | $exerciseInfo['question'][$currentQuestionIdent]['answer'][$currentAnswerId]['feedback'] .= trim( |
||
585 | $node->nodeValue |
||
586 | ); |
||
587 | } |
||
588 | break; |
||
589 | case 'value': |
||
590 | if ('correctResponse' === $node->parentNode->nodeName) { |
||
591 | $nodeValue = trim($node->nodeValue); |
||
592 | |||
593 | if ('single' === $cardinality) { |
||
594 | $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][$nodeValue] = $nodeValue; |
||
595 | } else { |
||
596 | $exerciseInfo['question'][$currentQuestionIdent]['correct_answers'][] = $nodeValue; |
||
597 | } |
||
598 | } |
||
599 | |||
600 | if ('outcomeDeclaration' === $node->parentNode->parentNode->nodeName) { |
||
601 | $nodeValue = trim($node->nodeValue); |
||
602 | |||
603 | if (!empty($nodeValue)) { |
||
604 | $exerciseInfo['question'][$currentQuestionIdent]['weighting'][0] = $nodeValue; |
||
605 | } |
||
606 | } |
||
607 | break; |
||
608 | case 'mattext': |
||
609 | if ('flow_mat' === $node->parentNode->parentNode->nodeName && |
||
610 | ('presentation_material' === $node->parentNode->parentNode->parentNode->nodeName || |
||
611 | 'section' === $node->parentNode->parentNode->parentNode->nodeName |
||
612 | ) |
||
613 | ) { |
||
614 | $nodeValue = trim($node->nodeValue); |
||
615 | |||
616 | if (!empty($nodeValue)) { |
||
617 | $exerciseInfo['description'] = $node->nodeValue; |
||
618 | } |
||
619 | } |
||
620 | break; |
||
621 | case 'prompt': |
||
622 | $description = trim($node->nodeValue); |
||
623 | $description = htmlspecialchars_decode($description); |
||
624 | $description = Security::remove_XSS($description); |
||
625 | |||
626 | if (!empty($description)) { |
||
627 | $exerciseInfo['question'][$currentQuestionIdent]['description'] = $description; |
||
628 | } |
||
629 | break; |
||
630 | } |
||
631 | } |
||
632 | } |
||
633 | |||
634 | /** |
||
635 | * Check if a given file is an IMS/QTI question bank file. |
||
636 | * |
||
637 | * @param string $filePath The absolute filepath |
||
638 | * |
||
639 | * @return bool Whether it is an IMS/QTI question bank or not |
||
640 | */ |
||
641 | function isQtiQuestionBank($filePath) |
||
642 | { |
||
643 | $data = file_get_contents($filePath); |
||
644 | if (!empty($data)) { |
||
645 | $match = preg_match('/ims_qtiasiv(\d)p(\d)/', $data); |
||
646 | // @todo allow other types |
||
647 | //$match2 = preg_match('/imsqti_v(\d)p(\d)/', $data); |
||
648 | |||
649 | if ($match) { |
||
650 | return true; |
||
651 | } |
||
652 | } |
||
653 | |||
654 | return false; |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Check if a given file is an IMS/QTI manifest file (listing of extra files). |
||
659 | * |
||
660 | * @param string $filePath The absolute filepath |
||
661 | * |
||
662 | * @return bool Whether it is an IMS/QTI manifest file or not |
||
663 | */ |
||
664 | function isQtiManifest($filePath) |
||
665 | { |
||
666 | $data = file_get_contents($filePath); |
||
667 | if (!empty($data)) { |
||
668 | $match = preg_match('/imsccv(\d)p(\d)/', $data); |
||
669 | if ($match) { |
||
670 | return true; |
||
671 | } |
||
672 | } |
||
673 | |||
674 | return false; |
||
675 | } |
||
676 | |||
677 | /** |
||
678 | * Processes an IMS/QTI manifest file: store links to new files |
||
679 | * to be able to transform them into the questions text. |
||
680 | * |
||
681 | * @param string $filePath The absolute filepath |
||
682 | * |
||
683 | * @return bool |
||
684 | */ |
||
685 | function qtiProcessManifest($filePath) |
||
686 | { |
||
687 | $xml = simplexml_load_file($filePath); |
||
688 | $course = api_get_course_info(); |
||
689 | $sessionId = api_get_session_id(); |
||
690 | $courseDir = $course['path']; |
||
691 | $sysPath = api_get_path(SYS_COURSE_PATH); |
||
692 | $exercisesSysPath = $sysPath.$courseDir.'/document/'; |
||
693 | $webPath = api_get_path(WEB_CODE_PATH); |
||
694 | $exercisesWebPath = $webPath.'document/document.php?'.api_get_cidreq().'&action=download&id='; |
||
695 | $links = [ |
||
696 | 'manifest' => [], |
||
697 | 'system' => [], |
||
698 | 'web' => [], |
||
699 | ]; |
||
700 | $tableDocuments = Database::get_course_table(TABLE_DOCUMENT); |
||
701 | $countResources = count($xml->resources->resource->file); |
||
702 | for ($i = 0; $i < $countResources; $i++) { |
||
703 | $file = $xml->resources->resource->file[$i]; |
||
704 | $href = ''; |
||
705 | foreach ($file->attributes() as $key => $value) { |
||
706 | if ('href' == $key) { |
||
707 | if ('xml' != substr($value, -3, 3)) { |
||
708 | $href = $value; |
||
709 | } |
||
710 | } |
||
711 | } |
||
712 | if (!empty($href)) { |
||
713 | $links['manifest'][] = (string) $href; |
||
714 | $links['system'][] = $exercisesSysPath.strtolower($href); |
||
715 | $specialHref = Database::escape_string(preg_replace('/_/', '-', strtolower($href))); |
||
716 | $specialHref = preg_replace('/(-){2,8}/', '-', $specialHref); |
||
717 | |||
718 | $sql = "SELECT iid FROM $tableDocuments |
||
719 | WHERE |
||
720 | c_id = ".$course['real_id']." AND |
||
721 | session_id = $sessionId AND |
||
722 | path = '/".$specialHref."'"; |
||
723 | $result = Database::query($sql); |
||
724 | $documentId = 0; |
||
725 | while ($row = Database::fetch_assoc($result)) { |
||
726 | $documentId = $row['iid']; |
||
727 | } |
||
728 | $links['web'][] = $exercisesWebPath.$documentId; |
||
729 | } |
||
730 | } |
||
731 | |||
732 | return $links; |
||
733 | } |
||
734 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.