1 | <?php |
||
2 | |||
3 | /* For licensing terms, see /license.txt */ |
||
4 | |||
5 | use Chamilo\CoreBundle\Component\Utils\ChamiloApi; |
||
6 | use Chamilo\CoreBundle\Entity\GradebookLink; |
||
7 | use Chamilo\CoreBundle\Entity\TrackEExerciseConfirmation; |
||
8 | use Chamilo\CoreBundle\Entity\TrackEHotspot; |
||
9 | use Chamilo\CourseBundle\Entity\CExerciseCategory; |
||
10 | use ChamiloSession as Session; |
||
11 | use Doctrine\DBAL\Types\Type; |
||
12 | |||
13 | /** |
||
14 | * Class Exercise. |
||
15 | * |
||
16 | * Allows to instantiate an object of type Exercise |
||
17 | * |
||
18 | * @todo use getters and setters correctly |
||
19 | * |
||
20 | * @author Olivier Brouckaert |
||
21 | * @author Julio Montoya Cleaning exercises |
||
22 | * Modified by Hubert Borderiou #294 |
||
23 | */ |
||
24 | class Exercise |
||
25 | { |
||
26 | public const PAGINATION_ITEMS_PER_PAGE = 20; |
||
27 | public $iid; |
||
28 | public $name; |
||
29 | public $title; |
||
30 | public $exercise; |
||
31 | public $description; |
||
32 | public $sound; |
||
33 | public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE |
||
34 | public $random; |
||
35 | public $random_answers; |
||
36 | public $active; |
||
37 | public $timeLimit; |
||
38 | public $attempts; |
||
39 | public $feedback_type; |
||
40 | public $end_time; |
||
41 | public $start_time; |
||
42 | public $questionList; // array with the list of this exercise's questions |
||
43 | /* including question list of the media */ |
||
44 | public $questionListUncompressed; |
||
45 | public $results_disabled; |
||
46 | public $expired_time; |
||
47 | public $course; |
||
48 | public $course_id; |
||
49 | public $propagate_neg; |
||
50 | public $saveCorrectAnswers; |
||
51 | public $review_answers; |
||
52 | public $randomByCat; |
||
53 | public $text_when_finished; |
||
54 | public $text_when_finished_failure; |
||
55 | public $display_category_name; |
||
56 | public $pass_percentage; |
||
57 | public $edit_exercise_in_lp = false; |
||
58 | public $is_gradebook_locked = false; |
||
59 | public $exercise_was_added_in_lp = false; |
||
60 | public $lpList = []; |
||
61 | public $force_edit_exercise_in_lp = false; |
||
62 | public $categories; |
||
63 | public $categories_grouping = true; |
||
64 | public $endButton = 0; |
||
65 | public $categoryWithQuestionList; |
||
66 | public $mediaList; |
||
67 | public $loadQuestionAJAX = false; |
||
68 | // Notification send to the teacher. |
||
69 | public $emailNotificationTemplate = null; |
||
70 | // Notification send to the student. |
||
71 | public $emailNotificationTemplateToUser = null; |
||
72 | public $countQuestions = 0; |
||
73 | public $fastEdition = false; |
||
74 | public $modelType = 1; |
||
75 | public $questionSelectionType = EX_Q_SELECTION_ORDERED; |
||
76 | public $hideQuestionTitle = 0; |
||
77 | public $scoreTypeModel = 0; |
||
78 | public $categoryMinusOne = true; // Shows the category -1: See BT#6540 |
||
79 | public $globalCategoryId = null; |
||
80 | public $onSuccessMessage = null; |
||
81 | public $onFailedMessage = null; |
||
82 | public $emailAlert; |
||
83 | public $notifyUserByEmail = ''; |
||
84 | public $sessionId = 0; |
||
85 | public $questionFeedbackEnabled = false; |
||
86 | public $questionTypeWithFeedback; |
||
87 | public $showPreviousButton; |
||
88 | public $notifications; |
||
89 | public $export = false; |
||
90 | public $autolaunch; |
||
91 | public $exerciseCategoryId; |
||
92 | public $pageResultConfiguration; |
||
93 | public $hideQuestionNumber; |
||
94 | public $preventBackwards; |
||
95 | public $currentQuestion; |
||
96 | public $hideComment; |
||
97 | public $hideNoAnswer; |
||
98 | public $hideExpectedAnswer; |
||
99 | public $forceShowExpectedChoiceColumn; |
||
100 | public $disableHideCorrectAnsweredQuestions; |
||
101 | public $hideAttemptsTableOnStartPage; |
||
102 | |||
103 | /** |
||
104 | * Constructor of the class. |
||
105 | * |
||
106 | * @param int $courseId |
||
107 | * |
||
108 | * @author Olivier Brouckaert |
||
109 | */ |
||
110 | public function __construct($courseId = 0) |
||
111 | { |
||
112 | $this->iid = 0; |
||
113 | $this->exercise = ''; |
||
114 | $this->description = ''; |
||
115 | $this->sound = ''; |
||
116 | $this->type = ALL_ON_ONE_PAGE; |
||
117 | $this->random = 0; |
||
118 | $this->random_answers = 0; |
||
119 | $this->active = 1; |
||
120 | $this->questionList = []; |
||
121 | $this->timeLimit = 0; |
||
122 | $this->end_time = ''; |
||
123 | $this->start_time = ''; |
||
124 | $this->results_disabled = 1; |
||
125 | $this->expired_time = 0; |
||
126 | $this->propagate_neg = 0; |
||
127 | $this->saveCorrectAnswers = 0; |
||
128 | $this->review_answers = false; |
||
129 | $this->randomByCat = 0; |
||
130 | $this->text_when_finished = ''; |
||
131 | $this->text_when_finished_failure = ''; |
||
132 | $this->display_category_name = 0; |
||
133 | $this->pass_percentage = 0; |
||
134 | $this->modelType = 1; |
||
135 | $this->questionSelectionType = EX_Q_SELECTION_ORDERED; |
||
136 | $this->endButton = 0; |
||
137 | $this->scoreTypeModel = 0; |
||
138 | $this->globalCategoryId = null; |
||
139 | $this->notifications = []; |
||
140 | $this->exerciseCategoryId = null; |
||
141 | $this->pageResultConfiguration; |
||
142 | $this->hideQuestionNumber = 0; |
||
143 | $this->preventBackwards = 0; |
||
144 | $this->hideComment = false; |
||
145 | $this->hideNoAnswer = false; |
||
146 | $this->hideExpectedAnswer = false; |
||
147 | $this->disableHideCorrectAnsweredQuestions = false; |
||
148 | $this->hideAttemptsTableOnStartPage = 0; |
||
149 | |||
150 | if (!empty($courseId)) { |
||
151 | $courseInfo = api_get_course_info_by_id($courseId); |
||
152 | } else { |
||
153 | $courseInfo = api_get_course_info(); |
||
154 | } |
||
155 | $this->course_id = $courseInfo['real_id']; |
||
156 | $this->course = $courseInfo; |
||
157 | $this->sessionId = api_get_session_id(); |
||
158 | |||
159 | // ALTER TABLE c_quiz_question ADD COLUMN feedback text; |
||
160 | $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback'); |
||
161 | $this->showPreviousButton = true; |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Reads exercise information from the data base. |
||
166 | * |
||
167 | * @author Olivier Brouckaert |
||
168 | * |
||
169 | * @param int $id - exercise Id |
||
170 | * @param bool $parseQuestionList |
||
171 | * |
||
172 | * @return bool - true if exercise exists, otherwise false |
||
173 | */ |
||
174 | public function read($id, $parseQuestionList = true) |
||
175 | { |
||
176 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
177 | |||
178 | $id = (int) $id; |
||
179 | if (empty($this->course_id)) { |
||
180 | return false; |
||
181 | } |
||
182 | |||
183 | $sql = "SELECT * FROM $table WHERE iid = ".$id; |
||
184 | $result = Database::query($sql); |
||
185 | |||
186 | // if the exercise has been found |
||
187 | if ($object = Database::fetch_object($result)) { |
||
188 | $this->iid = $object->iid; |
||
189 | $this->exercise = $object->title; |
||
190 | $this->name = $object->title; |
||
191 | $this->title = $object->title; |
||
192 | $this->description = $object->description; |
||
193 | $this->sound = $object->sound; |
||
194 | $this->type = $object->type; |
||
195 | if (empty($this->type)) { |
||
196 | $this->type = ONE_PER_PAGE; |
||
197 | } |
||
198 | $this->random = $object->random; |
||
199 | $this->random_answers = $object->random_answers; |
||
200 | $this->active = $object->active; |
||
201 | $this->results_disabled = $object->results_disabled; |
||
202 | $this->attempts = $object->max_attempt; |
||
203 | $this->feedback_type = $object->feedback_type; |
||
204 | $this->sessionId = $object->session_id; |
||
205 | $this->propagate_neg = $object->propagate_neg; |
||
206 | $this->saveCorrectAnswers = $object->save_correct_answers; |
||
207 | $this->randomByCat = $object->random_by_category; |
||
208 | $this->text_when_finished = $object->text_when_finished; |
||
209 | $this->text_when_finished_failure = isset($object->text_when_finished_failure) ? $object->text_when_finished_failure : null; |
||
210 | $this->display_category_name = $object->display_category_name; |
||
211 | $this->pass_percentage = $object->pass_percentage; |
||
212 | $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE); |
||
213 | $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false; |
||
214 | $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null; |
||
215 | $this->questionSelectionType = isset($object->question_selection_type) ? (int) $object->question_selection_type : null; |
||
216 | $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0; |
||
217 | $this->autolaunch = isset($object->autolaunch) ? (int) $object->autolaunch : 0; |
||
218 | $this->exerciseCategoryId = isset($object->exercise_category_id) ? (int) $object->exercise_category_id : null; |
||
219 | $this->preventBackwards = isset($object->prevent_backwards) ? (int) $object->prevent_backwards : 0; |
||
220 | $this->exercise_was_added_in_lp = false; |
||
221 | $this->lpList = []; |
||
222 | $this->notifications = []; |
||
223 | if (!empty($object->notifications)) { |
||
224 | $this->notifications = explode(',', $object->notifications); |
||
225 | } |
||
226 | |||
227 | if (!empty($object->page_result_configuration)) { |
||
228 | $this->pageResultConfiguration = $object->page_result_configuration; |
||
229 | } |
||
230 | |||
231 | if (isset($object->hide_question_number)) { |
||
232 | $this->hideQuestionNumber = $object->hide_question_number == 1; |
||
233 | } |
||
234 | |||
235 | if (isset($object->hide_attempts_table)) { |
||
236 | $this->hideAttemptsTableOnStartPage = $object->hide_attempts_table == 1; |
||
237 | } |
||
238 | |||
239 | if (isset($object->show_previous_button)) { |
||
240 | $this->showPreviousButton = $object->show_previous_button == 1; |
||
241 | } |
||
242 | |||
243 | $list = self::getLpListFromExercise($id, $this->course_id); |
||
244 | if (!empty($list)) { |
||
245 | $this->exercise_was_added_in_lp = true; |
||
246 | $this->lpList = $list; |
||
247 | } |
||
248 | |||
249 | $this->force_edit_exercise_in_lp = api_get_configuration_value('force_edit_exercise_in_lp'); |
||
250 | $this->edit_exercise_in_lp = true; |
||
251 | if ($this->exercise_was_added_in_lp) { |
||
252 | $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true; |
||
253 | } |
||
254 | |||
255 | if (!empty($object->end_time)) { |
||
256 | $this->end_time = $object->end_time; |
||
257 | } |
||
258 | if (!empty($object->start_time)) { |
||
259 | $this->start_time = $object->start_time; |
||
260 | } |
||
261 | |||
262 | // Control time |
||
263 | $this->expired_time = $object->expired_time; |
||
264 | |||
265 | // Checking if question_order is correctly set |
||
266 | if ($parseQuestionList) { |
||
267 | $this->setQuestionList(true); |
||
268 | } |
||
269 | |||
270 | //overload questions list with recorded questions list |
||
271 | //load questions only for exercises of type 'one question per page' |
||
272 | //this is needed only is there is no questions |
||
273 | |||
274 | // @todo not sure were in the code this is used somebody mess with the exercise tool |
||
275 | // @todo don't know who add that config and why $_configuration['live_exercise_tracking'] |
||
276 | /*global $_configuration, $questionList; |
||
277 | if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' |
||
278 | && defined('QUESTION_LIST_ALREADY_LOGGED') && |
||
279 | isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking'] |
||
280 | ) { |
||
281 | $this->questionList = $questionList; |
||
282 | }*/ |
||
283 | return true; |
||
284 | } |
||
285 | |||
286 | return false; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * @return string |
||
291 | */ |
||
292 | public function getCutTitle() |
||
293 | { |
||
294 | $title = $this->getUnformattedTitle(); |
||
295 | |||
296 | return cut($title, EXERCISE_MAX_NAME_SIZE); |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * returns the exercise ID. |
||
301 | * |
||
302 | * @author Olivier Brouckaert |
||
303 | * |
||
304 | * @return int - exercise ID |
||
305 | */ |
||
306 | public function selectId() |
||
307 | { |
||
308 | return $this->iid; |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * returns the exercise title. |
||
313 | * |
||
314 | * @author Olivier Brouckaert |
||
315 | * |
||
316 | * @param bool $unformattedText Optional. Get the title without HTML tags |
||
317 | * |
||
318 | * @return string - exercise title |
||
319 | */ |
||
320 | public function selectTitle($unformattedText = false) |
||
321 | { |
||
322 | if ($unformattedText) { |
||
323 | return $this->getUnformattedTitle(); |
||
324 | } |
||
325 | |||
326 | return $this->exercise; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Returns the maximum number of attempts set in the exercise configuration. |
||
331 | * |
||
332 | * @return int Maximum attempts allowed (0 if no limit) |
||
333 | */ |
||
334 | public function selectAttempts() |
||
335 | { |
||
336 | return $this->attempts; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Returns the number of FeedbackType |
||
341 | * 0: Feedback , 1: DirectFeedback, 2: NoFeedback. |
||
342 | * |
||
343 | * @return int - exercise attempts |
||
344 | */ |
||
345 | public function getFeedbackType() |
||
346 | { |
||
347 | return (int) $this->feedback_type; |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * returns the time limit. |
||
352 | * |
||
353 | * @return int |
||
354 | */ |
||
355 | public function selectTimeLimit() |
||
356 | { |
||
357 | return $this->timeLimit; |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * returns the exercise description. |
||
362 | * |
||
363 | * @author Olivier Brouckaert |
||
364 | * |
||
365 | * @return string - exercise description |
||
366 | */ |
||
367 | public function selectDescription() |
||
368 | { |
||
369 | return $this->description; |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * returns the exercise sound file. |
||
374 | * |
||
375 | * @author Olivier Brouckaert |
||
376 | * |
||
377 | * @return string - exercise description |
||
378 | */ |
||
379 | public function selectSound() |
||
380 | { |
||
381 | return $this->sound; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * returns the exercise type. |
||
386 | * |
||
387 | * @author Olivier Brouckaert |
||
388 | * |
||
389 | * @return int - exercise type |
||
390 | */ |
||
391 | public function selectType() |
||
392 | { |
||
393 | return $this->type; |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * @return int |
||
398 | */ |
||
399 | public function getModelType() |
||
400 | { |
||
401 | return $this->modelType; |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * @return int |
||
406 | */ |
||
407 | public function selectEndButton() |
||
408 | { |
||
409 | return $this->endButton; |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * @author hubert borderiou 30-11-11 |
||
414 | * |
||
415 | * @return int : do we display the question category name for students |
||
416 | */ |
||
417 | public function selectDisplayCategoryName() |
||
418 | { |
||
419 | return $this->display_category_name; |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * @return int |
||
424 | */ |
||
425 | public function selectPassPercentage() |
||
426 | { |
||
427 | return $this->pass_percentage; |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Modify object to update the switch display_category_name. |
||
432 | * |
||
433 | * @author hubert borderiou 30-11-11 |
||
434 | * |
||
435 | * @param int $value is an integer 0 or 1 |
||
436 | */ |
||
437 | public function updateDisplayCategoryName($value) |
||
438 | { |
||
439 | $this->display_category_name = $value; |
||
440 | } |
||
441 | |||
442 | /** |
||
443 | * @author hubert borderiou 28-11-11 |
||
444 | * |
||
445 | * @return string html text : the text to display ay the end of the test |
||
446 | */ |
||
447 | public function getTextWhenFinished() |
||
448 | { |
||
449 | return $this->text_when_finished; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * @param string $text |
||
454 | * |
||
455 | * @author hubert borderiou 28-11-11 |
||
456 | */ |
||
457 | public function updateTextWhenFinished($text) |
||
458 | { |
||
459 | $this->text_when_finished = $text; |
||
460 | } |
||
461 | |||
462 | /** |
||
463 | * Get the text to display when the user has failed the test. |
||
464 | * |
||
465 | * @return string html text : the text to display ay the end of the test |
||
466 | */ |
||
467 | public function getTextWhenFinishedFailure(): string |
||
468 | { |
||
469 | if (empty($this->text_when_finished_failure)) { |
||
470 | return ''; |
||
471 | } |
||
472 | |||
473 | return $this->text_when_finished_failure; |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Set the text to display when the user has succeeded in the test. |
||
478 | */ |
||
479 | public function setTextWhenFinishedFailure(string $text): void |
||
480 | { |
||
481 | $this->text_when_finished_failure = $text; |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * return 1 or 2 if randomByCat. |
||
486 | * |
||
487 | * @author hubert borderiou |
||
488 | * |
||
489 | * @return int - quiz random by category |
||
490 | */ |
||
491 | public function getRandomByCategory() |
||
492 | { |
||
493 | return $this->randomByCat; |
||
494 | } |
||
495 | |||
496 | /** |
||
497 | * return 0 if no random by cat |
||
498 | * return 1 if random by cat, categories shuffled |
||
499 | * return 2 if random by cat, categories sorted by alphabetic order. |
||
500 | * |
||
501 | * @author hubert borderiou |
||
502 | * |
||
503 | * @return int - quiz random by category |
||
504 | */ |
||
505 | public function isRandomByCat() |
||
506 | { |
||
507 | $res = EXERCISE_CATEGORY_RANDOM_DISABLED; |
||
508 | if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) { |
||
509 | $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED; |
||
510 | } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) { |
||
511 | $res = EXERCISE_CATEGORY_RANDOM_ORDERED; |
||
512 | } |
||
513 | |||
514 | return $res; |
||
515 | } |
||
516 | |||
517 | /** |
||
518 | * return nothing |
||
519 | * update randomByCat value for object. |
||
520 | * |
||
521 | * @param int $random |
||
522 | * |
||
523 | * @author hubert borderiou |
||
524 | */ |
||
525 | public function updateRandomByCat($random) |
||
526 | { |
||
527 | $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED; |
||
528 | if (in_array( |
||
529 | $random, |
||
530 | [ |
||
531 | EXERCISE_CATEGORY_RANDOM_SHUFFLED, |
||
532 | EXERCISE_CATEGORY_RANDOM_ORDERED, |
||
533 | EXERCISE_CATEGORY_RANDOM_DISABLED, |
||
534 | ] |
||
535 | )) { |
||
536 | $this->randomByCat = $random; |
||
537 | } |
||
538 | } |
||
539 | |||
540 | /** |
||
541 | * Tells if questions are selected randomly, and if so returns the draws. |
||
542 | * |
||
543 | * @author Carlos Vargas |
||
544 | * |
||
545 | * @return int - results disabled exercise |
||
546 | */ |
||
547 | public function selectResultsDisabled() |
||
548 | { |
||
549 | return $this->results_disabled; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * tells if questions are selected randomly, and if so returns the draws. |
||
554 | * |
||
555 | * @author Olivier Brouckaert |
||
556 | * |
||
557 | * @return bool |
||
558 | */ |
||
559 | public function isRandom() |
||
560 | { |
||
561 | $isRandom = false; |
||
562 | // "-1" means all questions will be random |
||
563 | if ($this->random > 0 || $this->random == -1) { |
||
564 | $isRandom = true; |
||
565 | } |
||
566 | |||
567 | return $isRandom; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * returns random answers status. |
||
572 | * |
||
573 | * @author Juan Carlos Rana |
||
574 | */ |
||
575 | public function getRandomAnswers() |
||
576 | { |
||
577 | return $this->random_answers; |
||
578 | } |
||
579 | |||
580 | /** |
||
581 | * Same as isRandom() but has a name applied to values different than 0 or 1. |
||
582 | * |
||
583 | * @return int |
||
584 | */ |
||
585 | public function getShuffle() |
||
586 | { |
||
587 | return $this->random; |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * returns the exercise status (1 = enabled ; 0 = disabled). |
||
592 | * |
||
593 | * @author Olivier Brouckaert |
||
594 | * |
||
595 | * @return int - 1 if enabled, otherwise 0 |
||
596 | */ |
||
597 | public function selectStatus() |
||
598 | { |
||
599 | return $this->active; |
||
600 | } |
||
601 | |||
602 | /** |
||
603 | * If false the question list will be managed as always if true |
||
604 | * the question will be filtered |
||
605 | * depending of the exercise settings (table c_quiz_rel_category). |
||
606 | * |
||
607 | * @param bool $status active or inactive grouping |
||
608 | */ |
||
609 | public function setCategoriesGrouping($status) |
||
610 | { |
||
611 | $this->categories_grouping = (bool) $status; |
||
612 | } |
||
613 | |||
614 | /** |
||
615 | * @return int |
||
616 | */ |
||
617 | public function getHideQuestionTitle() |
||
618 | { |
||
619 | return $this->hideQuestionTitle; |
||
620 | } |
||
621 | |||
622 | /** |
||
623 | * @param $value |
||
624 | */ |
||
625 | public function setHideQuestionTitle($value) |
||
626 | { |
||
627 | $this->hideQuestionTitle = (int) $value; |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * @return int |
||
632 | */ |
||
633 | public function getScoreTypeModel() |
||
634 | { |
||
635 | return $this->scoreTypeModel; |
||
636 | } |
||
637 | |||
638 | /** |
||
639 | * @param int $value |
||
640 | */ |
||
641 | public function setScoreTypeModel($value) |
||
642 | { |
||
643 | $this->scoreTypeModel = (int) $value; |
||
644 | } |
||
645 | |||
646 | /** |
||
647 | * @return int |
||
648 | */ |
||
649 | public function getGlobalCategoryId() |
||
650 | { |
||
651 | return $this->globalCategoryId; |
||
652 | } |
||
653 | |||
654 | /** |
||
655 | * @param int $value |
||
656 | */ |
||
657 | public function setGlobalCategoryId($value) |
||
658 | { |
||
659 | if (is_array($value) && isset($value[0])) { |
||
660 | $value = $value[0]; |
||
661 | } |
||
662 | $this->globalCategoryId = (int) $value; |
||
663 | } |
||
664 | |||
665 | /** |
||
666 | * @param int $start |
||
667 | * @param int $limit |
||
668 | * @param int $sidx |
||
669 | * @param string $sord |
||
670 | * @param array $whereCondition |
||
671 | * @param array $extraFields |
||
672 | * |
||
673 | * @return array |
||
674 | */ |
||
675 | public function getQuestionListPagination( |
||
676 | $start, |
||
677 | $limit, |
||
678 | $sidx, |
||
679 | $sord, |
||
680 | $whereCondition = [], |
||
681 | $extraFields = [] |
||
682 | ) { |
||
683 | if (!empty($this->iid)) { |
||
684 | $category_list = TestCategory::getListOfCategoriesNameForTest( |
||
685 | $this->iid, |
||
686 | false |
||
687 | ); |
||
688 | $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
689 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
690 | |||
691 | $sql = "SELECT q.iid |
||
692 | FROM $TBL_EXERCICE_QUESTION e |
||
693 | INNER JOIN $TBL_QUESTIONS q ON e.question_id = q.iid |
||
694 | WHERE e.exercice_id = ".$this->iid." AND e.c_id = {$this->course_id}"; |
||
695 | |||
696 | $orderCondition = ' ORDER BY question_order '; |
||
697 | |||
698 | if (!empty($sidx) && !empty($sord)) { |
||
699 | if ('question' === $sidx) { |
||
700 | if (in_array(strtolower($sord), ['desc', 'asc'])) { |
||
701 | $orderCondition = " ORDER BY q.question $sord"; |
||
702 | } |
||
703 | } |
||
704 | } |
||
705 | |||
706 | $sql .= $orderCondition; |
||
707 | $limitCondition = null; |
||
708 | if (isset($start) && isset($limit)) { |
||
709 | $start = (int) $start; |
||
710 | $limit = (int) $limit; |
||
711 | $limitCondition = " LIMIT $start, $limit"; |
||
712 | } |
||
713 | $sql .= $limitCondition; |
||
714 | $result = Database::query($sql); |
||
715 | $questions = []; |
||
716 | if (Database::num_rows($result)) { |
||
717 | if (!empty($extraFields)) { |
||
718 | $extraFieldValue = new ExtraFieldValue('question'); |
||
719 | } |
||
720 | while ($question = Database::fetch_array($result, 'ASSOC')) { |
||
721 | /** @var Question $objQuestionTmp */ |
||
722 | $objQuestionTmp = Question::read($question['iid']); |
||
723 | $category_labels = TestCategory::return_category_labels( |
||
724 | $objQuestionTmp->category_list, |
||
725 | $category_list |
||
726 | ); |
||
727 | |||
728 | if (empty($category_labels)) { |
||
729 | $category_labels = '-'; |
||
730 | } |
||
731 | |||
732 | // Question type |
||
733 | $typeImg = $objQuestionTmp->getTypePicture(); |
||
734 | $typeExpl = $objQuestionTmp->getExplanation(); |
||
735 | |||
736 | $question_media = null; |
||
737 | if (!empty($objQuestionTmp->parent_id)) { |
||
738 | $objQuestionMedia = Question::read($objQuestionTmp->parent_id); |
||
739 | $question_media = Question::getMediaLabel($objQuestionMedia->question); |
||
740 | } |
||
741 | |||
742 | $questionType = Display::tag( |
||
743 | 'div', |
||
744 | Display::return_icon($typeImg, $typeExpl, [], ICON_SIZE_MEDIUM).$question_media |
||
745 | ); |
||
746 | |||
747 | $question = [ |
||
748 | 'iid' => $question['iid'], |
||
749 | 'question' => $objQuestionTmp->selectTitle(), |
||
750 | 'type' => $questionType, |
||
751 | 'category' => Display::tag( |
||
752 | 'div', |
||
753 | '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>' |
||
754 | ), |
||
755 | 'score' => $objQuestionTmp->selectWeighting(), |
||
756 | 'level' => $objQuestionTmp->level, |
||
757 | ]; |
||
758 | |||
759 | if (!empty($extraFields)) { |
||
760 | foreach ($extraFields as $extraField) { |
||
761 | $value = $extraFieldValue->get_values_by_handler_and_field_id( |
||
762 | $question['iid'], |
||
763 | $extraField['id'] |
||
764 | ); |
||
765 | $stringValue = null; |
||
766 | if ($value) { |
||
767 | $stringValue = $value['field_value']; |
||
768 | } |
||
769 | $question[$extraField['field_variable']] = $stringValue; |
||
770 | } |
||
771 | } |
||
772 | $questions[] = $question; |
||
773 | } |
||
774 | } |
||
775 | |||
776 | return $questions; |
||
777 | } |
||
778 | } |
||
779 | |||
780 | /** |
||
781 | * Get question count per exercise from DB (any special treatment). |
||
782 | * |
||
783 | * @return int |
||
784 | */ |
||
785 | public function getQuestionCount() |
||
786 | { |
||
787 | $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
788 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
789 | $sql = "SELECT count(q.iid) as count |
||
790 | FROM $TBL_EXERCICE_QUESTION e |
||
791 | INNER JOIN $TBL_QUESTIONS q |
||
792 | ON e.question_id = q.iid |
||
793 | WHERE |
||
794 | e.c_id = {$this->course_id} AND |
||
795 | e.exercice_id = {$this->iid}"; |
||
796 | $result = Database::query($sql); |
||
797 | |||
798 | $count = 0; |
||
799 | if (Database::num_rows($result)) { |
||
800 | $row = Database::fetch_array($result); |
||
801 | $count = (int) $row['count']; |
||
802 | } |
||
803 | |||
804 | return $count; |
||
805 | } |
||
806 | |||
807 | /** |
||
808 | * @return array |
||
809 | */ |
||
810 | public function getQuestionOrderedListByName() |
||
811 | { |
||
812 | if (empty($this->course_id) || empty($this->iid)) { |
||
813 | return []; |
||
814 | } |
||
815 | |||
816 | $exerciseQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
817 | $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
818 | |||
819 | // Getting question list from the order (question list drag n drop interface ). |
||
820 | $sql = "SELECT e.question_id |
||
821 | FROM $exerciseQuestionTable e |
||
822 | INNER JOIN $questionTable q |
||
823 | ON e.question_id= q.iid |
||
824 | WHERE |
||
825 | e.c_id = {$this->course_id} AND |
||
826 | e.exercice_id = {$this->iid} |
||
827 | ORDER BY q.question"; |
||
828 | $result = Database::query($sql); |
||
829 | $list = []; |
||
830 | if (Database::num_rows($result)) { |
||
831 | $list = Database::store_result($result, 'ASSOC'); |
||
832 | } |
||
833 | |||
834 | return $list; |
||
835 | } |
||
836 | |||
837 | /** |
||
838 | * Selecting question list depending in the exercise-category |
||
839 | * relationship (category table in exercise settings). |
||
840 | * |
||
841 | * @param array $question_list |
||
842 | * @param int $questionSelectionType |
||
843 | * |
||
844 | * @return array |
||
845 | */ |
||
846 | public function getQuestionListWithCategoryListFilteredByCategorySettings( |
||
847 | $question_list, |
||
848 | $questionSelectionType |
||
849 | ) { |
||
850 | $result = [ |
||
851 | 'question_list' => [], |
||
852 | 'category_with_questions_list' => [], |
||
853 | ]; |
||
854 | |||
855 | // Order/random categories |
||
856 | $cat = new TestCategory(); |
||
857 | // Setting category order. |
||
858 | switch ($questionSelectionType) { |
||
859 | case EX_Q_SELECTION_ORDERED: // 1 |
||
860 | case EX_Q_SELECTION_RANDOM: // 2 |
||
861 | // This options are not allowed here. |
||
862 | break; |
||
863 | case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3 |
||
864 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
865 | $this, |
||
866 | $this->course['real_id'], |
||
867 | 'title ASC', |
||
868 | false, |
||
869 | true |
||
870 | ); |
||
871 | |||
872 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
873 | $this->iid, |
||
874 | $question_list, |
||
875 | $categoriesAddedInExercise |
||
876 | ); |
||
877 | |||
878 | $question_list = $this->pickQuestionsPerCategory( |
||
879 | $categoriesAddedInExercise, |
||
880 | $question_list, |
||
881 | $questions_by_category, |
||
882 | true, |
||
883 | false |
||
884 | ); |
||
885 | break; |
||
886 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4 |
||
887 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7 |
||
888 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
889 | $this, |
||
890 | $this->course['real_id'], |
||
891 | null, |
||
892 | true, |
||
893 | true |
||
894 | ); |
||
895 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
896 | $this->iid, |
||
897 | $question_list, |
||
898 | $categoriesAddedInExercise |
||
899 | ); |
||
900 | $question_list = $this->pickQuestionsPerCategory( |
||
901 | $categoriesAddedInExercise, |
||
902 | $question_list, |
||
903 | $questions_by_category, |
||
904 | true, |
||
905 | false |
||
906 | ); |
||
907 | break; |
||
908 | case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5 |
||
909 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
910 | $this, |
||
911 | $this->course['real_id'], |
||
912 | 'title ASC', |
||
913 | false, |
||
914 | true |
||
915 | ); |
||
916 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
917 | $this->iid, |
||
918 | $question_list, |
||
919 | $categoriesAddedInExercise |
||
920 | ); |
||
921 | |||
922 | $questionsByCategoryMandatory = []; |
||
923 | if (EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM == $this->getQuestionSelectionType() && |
||
924 | api_get_configuration_value('allow_mandatory_question_in_category') |
||
925 | ) { |
||
926 | $questionsByCategoryMandatory = TestCategory::getQuestionsByCat( |
||
927 | $this->iid, |
||
928 | $question_list, |
||
929 | $categoriesAddedInExercise, |
||
930 | true |
||
931 | ); |
||
932 | } |
||
933 | |||
934 | $question_list = $this->pickQuestionsPerCategory( |
||
935 | $categoriesAddedInExercise, |
||
936 | $question_list, |
||
937 | $questions_by_category, |
||
938 | true, |
||
939 | true, |
||
940 | $questionsByCategoryMandatory |
||
941 | ); |
||
942 | break; |
||
943 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6 |
||
944 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: |
||
945 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
946 | $this, |
||
947 | $this->course['real_id'], |
||
948 | null, |
||
949 | true, |
||
950 | true |
||
951 | ); |
||
952 | |||
953 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
954 | $this->iid, |
||
955 | $question_list, |
||
956 | $categoriesAddedInExercise |
||
957 | ); |
||
958 | |||
959 | $question_list = $this->pickQuestionsPerCategory( |
||
960 | $categoriesAddedInExercise, |
||
961 | $question_list, |
||
962 | $questions_by_category, |
||
963 | true, |
||
964 | true |
||
965 | ); |
||
966 | break; |
||
967 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7 |
||
968 | break; |
||
969 | case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8 |
||
970 | break; |
||
971 | case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9 |
||
972 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
973 | $this, |
||
974 | $this->course['real_id'], |
||
975 | 'root ASC, lft ASC', |
||
976 | false, |
||
977 | true |
||
978 | ); |
||
979 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
980 | $this->iid, |
||
981 | $question_list, |
||
982 | $categoriesAddedInExercise |
||
983 | ); |
||
984 | $question_list = $this->pickQuestionsPerCategory( |
||
985 | $categoriesAddedInExercise, |
||
986 | $question_list, |
||
987 | $questions_by_category, |
||
988 | true, |
||
989 | false |
||
990 | ); |
||
991 | break; |
||
992 | case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10 |
||
993 | $categoriesAddedInExercise = $cat->getCategoryExerciseTree( |
||
994 | $this, |
||
995 | $this->course['real_id'], |
||
996 | 'root, lft ASC', |
||
997 | false, |
||
998 | true |
||
999 | ); |
||
1000 | $questions_by_category = TestCategory::getQuestionsByCat( |
||
1001 | $this->iid, |
||
1002 | $question_list, |
||
1003 | $categoriesAddedInExercise |
||
1004 | ); |
||
1005 | $question_list = $this->pickQuestionsPerCategory( |
||
1006 | $categoriesAddedInExercise, |
||
1007 | $question_list, |
||
1008 | $questions_by_category, |
||
1009 | true, |
||
1010 | true |
||
1011 | ); |
||
1012 | break; |
||
1013 | } |
||
1014 | |||
1015 | $result['question_list'] = isset($question_list) ? $question_list : []; |
||
1016 | $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : []; |
||
1017 | $parentsLoaded = []; |
||
1018 | // Adding category info in the category list with question list: |
||
1019 | if (!empty($questions_by_category)) { |
||
1020 | $newCategoryList = []; |
||
1021 | $em = Database::getManager(); |
||
1022 | $repo = $em->getRepository('ChamiloCourseBundle:CQuizCategory'); |
||
1023 | |||
1024 | foreach ($questions_by_category as $categoryId => $questionList) { |
||
1025 | $cat = new TestCategory(); |
||
1026 | $cat = $cat->getCategory($categoryId); |
||
1027 | if ($cat) { |
||
1028 | $cat = (array) $cat; |
||
1029 | } |
||
1030 | |||
1031 | $categoryParentInfo = null; |
||
1032 | // Parent is not set no loop here |
||
1033 | if (isset($cat['parent_id']) && !empty($cat['parent_id'])) { |
||
1034 | /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryEntity */ |
||
1035 | if (!isset($parentsLoaded[$cat['parent_id']])) { |
||
1036 | $categoryEntity = $em->find('ChamiloCourseBundle:CQuizCategory', $cat['parent_id']); |
||
1037 | $parentsLoaded[$cat['parent_id']] = $categoryEntity; |
||
1038 | } else { |
||
1039 | $categoryEntity = $parentsLoaded[$cat['parent_id']]; |
||
1040 | } |
||
1041 | $path = $repo->getPath($categoryEntity); |
||
1042 | |||
1043 | $index = 0; |
||
1044 | if ($this->categoryMinusOne) { |
||
1045 | //$index = 1; |
||
1046 | } |
||
1047 | |||
1048 | /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent */ |
||
1049 | foreach ($path as $categoryParent) { |
||
1050 | $visibility = $categoryParent->getVisibility(); |
||
1051 | if (0 == $visibility) { |
||
1052 | $categoryParentId = $categoryId; |
||
1053 | $categoryTitle = $cat['title']; |
||
1054 | if (count($path) > 1) { |
||
1055 | continue; |
||
1056 | } |
||
1057 | } else { |
||
1058 | $categoryParentId = $categoryParent->getIid(); |
||
1059 | $categoryTitle = $categoryParent->getTitle(); |
||
1060 | } |
||
1061 | |||
1062 | $categoryParentInfo['id'] = $categoryParentId; |
||
1063 | $categoryParentInfo['iid'] = $categoryParentId; |
||
1064 | $categoryParentInfo['parent_path'] = null; |
||
1065 | $categoryParentInfo['title'] = $categoryTitle; |
||
1066 | $categoryParentInfo['name'] = $categoryTitle; |
||
1067 | $categoryParentInfo['parent_id'] = null; |
||
1068 | break; |
||
1069 | } |
||
1070 | } |
||
1071 | $cat['parent_info'] = $categoryParentInfo; |
||
1072 | $newCategoryList[$categoryId] = [ |
||
1073 | 'category' => $cat, |
||
1074 | 'question_list' => $questionList, |
||
1075 | ]; |
||
1076 | } |
||
1077 | |||
1078 | $result['category_with_questions_list'] = $newCategoryList; |
||
1079 | } |
||
1080 | |||
1081 | return $result; |
||
1082 | } |
||
1083 | |||
1084 | /** |
||
1085 | * returns the array with the question ID list. |
||
1086 | * |
||
1087 | * @param bool $fromDatabase Whether the results should be fetched in the database or just from memory |
||
1088 | * @param bool $adminView Whether we should return all questions (admin view) or |
||
1089 | * just a list limited by the max number of random questions |
||
1090 | * |
||
1091 | * @author Olivier Brouckaert |
||
1092 | * |
||
1093 | * @return array - question ID list |
||
1094 | */ |
||
1095 | public function selectQuestionList($fromDatabase = false, $adminView = false) |
||
1096 | { |
||
1097 | if ($fromDatabase && !empty($this->iid)) { |
||
1098 | $nbQuestions = $this->getQuestionCount(); |
||
1099 | $questionSelectionType = $this->getQuestionSelectionType(); |
||
1100 | |||
1101 | switch ($questionSelectionType) { |
||
1102 | case EX_Q_SELECTION_ORDERED: |
||
1103 | $questionList = $this->getQuestionOrderedList($adminView); |
||
1104 | break; |
||
1105 | case EX_Q_SELECTION_RANDOM: |
||
1106 | // Not a random exercise, or if there are not at least 2 questions |
||
1107 | if ($this->random == 0 || $nbQuestions < 2) { |
||
1108 | $questionList = $this->getQuestionOrderedList($adminView); |
||
1109 | } else { |
||
1110 | $questionList = $this->getRandomList($adminView); |
||
1111 | } |
||
1112 | break; |
||
1113 | default: |
||
1114 | $questionList = $this->getQuestionOrderedList($adminView); |
||
1115 | $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings( |
||
1116 | $questionList, |
||
1117 | $questionSelectionType |
||
1118 | ); |
||
1119 | $this->categoryWithQuestionList = $result['category_with_questions_list']; |
||
1120 | $questionList = $result['question_list']; |
||
1121 | break; |
||
1122 | } |
||
1123 | |||
1124 | return $questionList; |
||
1125 | } |
||
1126 | |||
1127 | return $this->questionList; |
||
1128 | } |
||
1129 | |||
1130 | /** |
||
1131 | * returns the number of questions in this exercise. |
||
1132 | * |
||
1133 | * @author Olivier Brouckaert |
||
1134 | * |
||
1135 | * @return int - number of questions |
||
1136 | */ |
||
1137 | public function selectNbrQuestions() |
||
1138 | { |
||
1139 | return count($this->questionList); |
||
1140 | } |
||
1141 | |||
1142 | /** |
||
1143 | * @return int |
||
1144 | */ |
||
1145 | public function selectPropagateNeg() |
||
1146 | { |
||
1147 | return $this->propagate_neg; |
||
1148 | } |
||
1149 | |||
1150 | /** |
||
1151 | * @return int |
||
1152 | */ |
||
1153 | public function getSaveCorrectAnswers() |
||
1154 | { |
||
1155 | return $this->saveCorrectAnswers; |
||
1156 | } |
||
1157 | |||
1158 | /** |
||
1159 | * Selects questions randomly in the question list. |
||
1160 | * |
||
1161 | * @author Olivier Brouckaert |
||
1162 | * @author Hubert Borderiou 15 nov 2011 |
||
1163 | * |
||
1164 | * @param bool $adminView Whether we should return all |
||
1165 | * questions (admin view) or just a list limited by the max number of random questions |
||
1166 | * |
||
1167 | * @return array - if the exercise is not set to take questions randomly, returns the question list |
||
1168 | * without randomizing, otherwise, returns the list with questions selected randomly |
||
1169 | */ |
||
1170 | public function getRandomList($adminView = false) |
||
1171 | { |
||
1172 | $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
1173 | $question = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
1174 | $random = isset($this->random) && !empty($this->random) ? $this->random : 0; |
||
1175 | |||
1176 | // Random with limit |
||
1177 | $randomLimit = " ORDER BY RAND() LIMIT $random"; |
||
1178 | |||
1179 | // Random with no limit |
||
1180 | if (-1 == $random) { |
||
1181 | $randomLimit = ' ORDER BY RAND() '; |
||
1182 | } |
||
1183 | |||
1184 | // Admin see the list in default order |
||
1185 | if (true === $adminView) { |
||
1186 | // If viewing it as admin for edition, don't show it randomly, use title + id |
||
1187 | $randomLimit = 'ORDER BY e.question_order'; |
||
1188 | } |
||
1189 | |||
1190 | $sql = "SELECT e.question_id |
||
1191 | FROM $quizRelQuestion e |
||
1192 | INNER JOIN $question q |
||
1193 | ON e.question_id= q.iid |
||
1194 | WHERE |
||
1195 | e.c_id = {$this->course_id} AND |
||
1196 | e.exercice_id = '".Database::escape_string($this->iid)."' |
||
1197 | $randomLimit "; |
||
1198 | $result = Database::query($sql); |
||
1199 | $questionList = []; |
||
1200 | while ($row = Database::fetch_object($result)) { |
||
1201 | $questionList[] = $row->question_id; |
||
1202 | } |
||
1203 | |||
1204 | return $questionList; |
||
1205 | } |
||
1206 | |||
1207 | /** |
||
1208 | * returns 'true' if the question ID is in the question list. |
||
1209 | * |
||
1210 | * @author Olivier Brouckaert |
||
1211 | * |
||
1212 | * @param int $questionId - question ID |
||
1213 | * |
||
1214 | * @return bool - true if in the list, otherwise false |
||
1215 | */ |
||
1216 | public function isInList($questionId) |
||
1217 | { |
||
1218 | $inList = false; |
||
1219 | if (is_array($this->questionList)) { |
||
1220 | $inList = in_array($questionId, $this->questionList); |
||
1221 | } |
||
1222 | |||
1223 | return $inList; |
||
1224 | } |
||
1225 | |||
1226 | /** |
||
1227 | * If current exercise has a question. |
||
1228 | * |
||
1229 | * @param int $questionId |
||
1230 | * |
||
1231 | * @return int |
||
1232 | */ |
||
1233 | public function hasQuestion($questionId) |
||
1234 | { |
||
1235 | $questionId = (int) $questionId; |
||
1236 | |||
1237 | $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
1238 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
1239 | $sql = "SELECT q.iid |
||
1240 | FROM $TBL_EXERCICE_QUESTION e |
||
1241 | INNER JOIN $TBL_QUESTIONS q |
||
1242 | ON e.question_id = q.iid |
||
1243 | WHERE |
||
1244 | q.iid = $questionId AND |
||
1245 | e.c_id = {$this->course_id} AND |
||
1246 | e.exercice_id = ".$this->iid; |
||
1247 | |||
1248 | $result = Database::query($sql); |
||
1249 | |||
1250 | return Database::num_rows($result) > 0; |
||
1251 | } |
||
1252 | |||
1253 | public function hasQuestionWithType($type) |
||
1254 | { |
||
1255 | $type = (int) $type; |
||
1256 | |||
1257 | $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
1258 | $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
1259 | $sql = "SELECT q.iid |
||
1260 | FROM $table e |
||
1261 | INNER JOIN $tableQuestion q |
||
1262 | ON e.question_id = q.iid |
||
1263 | WHERE |
||
1264 | q.type = $type AND |
||
1265 | e.c_id = {$this->course_id} AND |
||
1266 | e.exercice_id = ".$this->iid; |
||
1267 | |||
1268 | $result = Database::query($sql); |
||
1269 | |||
1270 | return Database::num_rows($result) > 0; |
||
1271 | } |
||
1272 | |||
1273 | public function hasQuestionWithTypeNotInList(array $questionTypeList) |
||
1274 | { |
||
1275 | if (empty($questionTypeList)) { |
||
1276 | return false; |
||
1277 | } |
||
1278 | |||
1279 | $questionTypeToString = implode("','", array_map('intval', $questionTypeList)); |
||
1280 | |||
1281 | $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
1282 | $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
1283 | $sql = "SELECT q.iid |
||
1284 | FROM $table e |
||
1285 | INNER JOIN $tableQuestion q |
||
1286 | ON e.question_id = q.iid |
||
1287 | WHERE |
||
1288 | q.type NOT IN ('$questionTypeToString') AND |
||
1289 | e.c_id = {$this->course_id} AND |
||
1290 | e.exercice_id = ".$this->iid; |
||
1291 | |||
1292 | $result = Database::query($sql); |
||
1293 | |||
1294 | return Database::num_rows($result) > 0; |
||
1295 | } |
||
1296 | |||
1297 | /** |
||
1298 | * changes the exercise title. |
||
1299 | * |
||
1300 | * @author Olivier Brouckaert |
||
1301 | * |
||
1302 | * @param string $title - exercise title |
||
1303 | */ |
||
1304 | public function updateTitle($title) |
||
1305 | { |
||
1306 | $this->title = $this->exercise = $title; |
||
1307 | } |
||
1308 | |||
1309 | /** |
||
1310 | * changes the exercise max attempts. |
||
1311 | * |
||
1312 | * @param int $attempts - exercise max attempts |
||
1313 | */ |
||
1314 | public function updateAttempts($attempts) |
||
1315 | { |
||
1316 | $this->attempts = $attempts; |
||
1317 | } |
||
1318 | |||
1319 | /** |
||
1320 | * changes the exercise feedback type. |
||
1321 | * |
||
1322 | * @param int $feedback_type |
||
1323 | */ |
||
1324 | public function updateFeedbackType($feedback_type) |
||
1325 | { |
||
1326 | $this->feedback_type = $feedback_type; |
||
1327 | } |
||
1328 | |||
1329 | /** |
||
1330 | * changes the exercise description. |
||
1331 | * |
||
1332 | * @author Olivier Brouckaert |
||
1333 | * |
||
1334 | * @param string $description - exercise description |
||
1335 | */ |
||
1336 | public function updateDescription($description) |
||
1337 | { |
||
1338 | $this->description = $description; |
||
1339 | } |
||
1340 | |||
1341 | /** |
||
1342 | * changes the exercise expired_time. |
||
1343 | * |
||
1344 | * @author Isaac flores |
||
1345 | * |
||
1346 | * @param int $expired_time The expired time of the quiz |
||
1347 | */ |
||
1348 | public function updateExpiredTime($expired_time) |
||
1349 | { |
||
1350 | $this->expired_time = $expired_time; |
||
1351 | } |
||
1352 | |||
1353 | /** |
||
1354 | * @param $value |
||
1355 | */ |
||
1356 | public function updatePropagateNegative($value) |
||
1357 | { |
||
1358 | $this->propagate_neg = $value; |
||
1359 | } |
||
1360 | |||
1361 | /** |
||
1362 | * @param int $value |
||
1363 | */ |
||
1364 | public function updateSaveCorrectAnswers($value) |
||
1365 | { |
||
1366 | $this->saveCorrectAnswers = (int) $value; |
||
1367 | } |
||
1368 | |||
1369 | /** |
||
1370 | * @param $value |
||
1371 | */ |
||
1372 | public function updateReviewAnswers($value) |
||
1373 | { |
||
1374 | $this->review_answers = isset($value) && $value ? true : false; |
||
1375 | } |
||
1376 | |||
1377 | /** |
||
1378 | * @param $value |
||
1379 | */ |
||
1380 | public function updatePassPercentage($value) |
||
1381 | { |
||
1382 | $this->pass_percentage = $value; |
||
1383 | } |
||
1384 | |||
1385 | /** |
||
1386 | * @param string $text |
||
1387 | */ |
||
1388 | public function updateEmailNotificationTemplate($text) |
||
1389 | { |
||
1390 | $this->emailNotificationTemplate = $text; |
||
1391 | } |
||
1392 | |||
1393 | /** |
||
1394 | * @param string $text |
||
1395 | */ |
||
1396 | public function setEmailNotificationTemplateToUser($text) |
||
1397 | { |
||
1398 | $this->emailNotificationTemplateToUser = $text; |
||
1399 | } |
||
1400 | |||
1401 | /** |
||
1402 | * @param string $value |
||
1403 | */ |
||
1404 | public function setNotifyUserByEmail($value) |
||
1405 | { |
||
1406 | $this->notifyUserByEmail = $value; |
||
1407 | } |
||
1408 | |||
1409 | /** |
||
1410 | * @param int $value |
||
1411 | */ |
||
1412 | public function updateEndButton($value) |
||
1413 | { |
||
1414 | $this->endButton = (int) $value; |
||
1415 | } |
||
1416 | |||
1417 | /** |
||
1418 | * @param string $value |
||
1419 | */ |
||
1420 | public function setOnSuccessMessage($value) |
||
1421 | { |
||
1422 | $this->onSuccessMessage = $value; |
||
1423 | } |
||
1424 | |||
1425 | /** |
||
1426 | * @param string $value |
||
1427 | */ |
||
1428 | public function setOnFailedMessage($value) |
||
1429 | { |
||
1430 | $this->onFailedMessage = $value; |
||
1431 | } |
||
1432 | |||
1433 | /** |
||
1434 | * @param $value |
||
1435 | */ |
||
1436 | public function setModelType($value) |
||
1437 | { |
||
1438 | $this->modelType = (int) $value; |
||
1439 | } |
||
1440 | |||
1441 | /** |
||
1442 | * @param int $value |
||
1443 | */ |
||
1444 | public function setQuestionSelectionType($value) |
||
1445 | { |
||
1446 | $this->questionSelectionType = (int) $value; |
||
1447 | } |
||
1448 | |||
1449 | /** |
||
1450 | * @return int |
||
1451 | */ |
||
1452 | public function getQuestionSelectionType() |
||
1453 | { |
||
1454 | return (int) $this->questionSelectionType; |
||
1455 | } |
||
1456 | |||
1457 | /** |
||
1458 | * @param array $categories |
||
1459 | */ |
||
1460 | public function updateCategories($categories) |
||
1461 | { |
||
1462 | if (!empty($categories)) { |
||
1463 | $categories = array_map('intval', $categories); |
||
1464 | $this->categories = $categories; |
||
1465 | } |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * changes the exercise sound file. |
||
1470 | * |
||
1471 | * @author Olivier Brouckaert |
||
1472 | * |
||
1473 | * @param string $sound - exercise sound file |
||
1474 | * @param string $delete - ask to delete the file |
||
1475 | */ |
||
1476 | public function updateSound($sound, $delete) |
||
1477 | { |
||
1478 | global $audioPath, $documentPath; |
||
1479 | $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT); |
||
1480 | |||
1481 | if ($sound['size'] && (strstr($sound['type'], 'audio') || strstr($sound['type'], 'video'))) { |
||
1482 | $this->sound = $sound['name']; |
||
1483 | |||
1484 | if (@move_uploaded_file($sound['tmp_name'], $audioPath.'/'.$this->sound)) { |
||
1485 | $sql = "SELECT 1 FROM $TBL_DOCUMENT |
||
1486 | WHERE |
||
1487 | c_id = ".$this->course_id." AND |
||
1488 | path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'"; |
||
1489 | $result = Database::query($sql); |
||
1490 | |||
1491 | if (!Database::num_rows($result)) { |
||
1492 | $id = add_document( |
||
1493 | $this->course, |
||
1494 | str_replace($documentPath, '', $audioPath).'/'.$this->sound, |
||
1495 | 'file', |
||
1496 | $sound['size'], |
||
1497 | $sound['name'] |
||
1498 | ); |
||
1499 | api_item_property_update( |
||
1500 | $this->course, |
||
1501 | TOOL_DOCUMENT, |
||
1502 | $id, |
||
1503 | 'DocumentAdded', |
||
1504 | api_get_user_id() |
||
1505 | ); |
||
1506 | item_property_update_on_folder( |
||
1507 | $this->course, |
||
1508 | str_replace($documentPath, '', $audioPath), |
||
1509 | api_get_user_id() |
||
1510 | ); |
||
1511 | } |
||
1512 | } |
||
1513 | } elseif ($delete && is_file($audioPath.'/'.$this->sound)) { |
||
1514 | $this->sound = ''; |
||
1515 | } |
||
1516 | } |
||
1517 | |||
1518 | /** |
||
1519 | * changes the exercise type. |
||
1520 | * |
||
1521 | * @author Olivier Brouckaert |
||
1522 | * |
||
1523 | * @param int $type - exercise type |
||
1524 | */ |
||
1525 | public function updateType($type) |
||
1526 | { |
||
1527 | $this->type = $type; |
||
1528 | } |
||
1529 | |||
1530 | /** |
||
1531 | * sets to 0 if questions are not selected randomly |
||
1532 | * if questions are selected randomly, sets the draws. |
||
1533 | * |
||
1534 | * @author Olivier Brouckaert |
||
1535 | * |
||
1536 | * @param int $random - 0 if not random, otherwise the draws |
||
1537 | */ |
||
1538 | public function setRandom($random) |
||
1539 | { |
||
1540 | $this->random = $random; |
||
1541 | } |
||
1542 | |||
1543 | /** |
||
1544 | * sets to 0 if answers are not selected randomly |
||
1545 | * if answers are selected randomly. |
||
1546 | * |
||
1547 | * @author Juan Carlos Rana |
||
1548 | * |
||
1549 | * @param int $random_answers - random answers |
||
1550 | */ |
||
1551 | public function updateRandomAnswers($random_answers) |
||
1552 | { |
||
1553 | $this->random_answers = $random_answers; |
||
1554 | } |
||
1555 | |||
1556 | /** |
||
1557 | * enables the exercise. |
||
1558 | * |
||
1559 | * @author Olivier Brouckaert |
||
1560 | */ |
||
1561 | public function enable() |
||
1562 | { |
||
1563 | $this->active = 1; |
||
1564 | } |
||
1565 | |||
1566 | /** |
||
1567 | * disables the exercise. |
||
1568 | * |
||
1569 | * @author Olivier Brouckaert |
||
1570 | */ |
||
1571 | public function disable() |
||
1572 | { |
||
1573 | $this->active = 0; |
||
1574 | } |
||
1575 | |||
1576 | /** |
||
1577 | * Set disable results. |
||
1578 | */ |
||
1579 | public function disable_results() |
||
1580 | { |
||
1581 | $this->results_disabled = true; |
||
1582 | } |
||
1583 | |||
1584 | /** |
||
1585 | * Enable results. |
||
1586 | */ |
||
1587 | public function enable_results() |
||
1588 | { |
||
1589 | $this->results_disabled = false; |
||
1590 | } |
||
1591 | |||
1592 | /** |
||
1593 | * @param int $results_disabled |
||
1594 | */ |
||
1595 | public function updateResultsDisabled($results_disabled) |
||
1596 | { |
||
1597 | $this->results_disabled = (int) $results_disabled; |
||
1598 | } |
||
1599 | |||
1600 | /** |
||
1601 | * updates the exercise in the data base. |
||
1602 | * |
||
1603 | * @param string $type_e |
||
1604 | * |
||
1605 | * @author Olivier Brouckaert |
||
1606 | */ |
||
1607 | public function save($type_e = '') |
||
1608 | { |
||
1609 | $_course = $this->course; |
||
1610 | $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); |
||
1611 | |||
1612 | $id = $this->iid; |
||
1613 | $exercise = $this->exercise; |
||
1614 | $description = $this->description; |
||
1615 | $sound = $this->sound; |
||
1616 | $type = $this->type; |
||
1617 | $attempts = isset($this->attempts) ? $this->attempts : 0; |
||
1618 | $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0; |
||
1619 | $random = $this->random; |
||
1620 | $random_answers = $this->random_answers; |
||
1621 | $active = $this->active; |
||
1622 | $propagate_neg = (int) $this->propagate_neg; |
||
1623 | $saveCorrectAnswers = isset($this->saveCorrectAnswers) ? (int) $this->saveCorrectAnswers : 0; |
||
1624 | $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0; |
||
1625 | $randomByCat = (int) $this->randomByCat; |
||
1626 | $text_when_finished = $this->text_when_finished; |
||
1627 | $text_when_finished_failure = $this->text_when_finished_failure; |
||
1628 | $display_category_name = (int) $this->display_category_name; |
||
1629 | $pass_percentage = (int) $this->pass_percentage; |
||
1630 | $session_id = $this->sessionId; |
||
1631 | |||
1632 | // If direct we do not show results |
||
1633 | $results_disabled = (int) $this->results_disabled; |
||
1634 | if (in_array($feedback_type, [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) { |
||
1635 | $results_disabled = 0; |
||
1636 | } |
||
1637 | $expired_time = (int) $this->expired_time; |
||
1638 | $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number'); |
||
1639 | |||
1640 | // Exercise already exists |
||
1641 | if ($id) { |
||
1642 | // we prepare date in the database using the api_get_utc_datetime() function |
||
1643 | $start_time = null; |
||
1644 | if (!empty($this->start_time)) { |
||
1645 | $start_time = $this->start_time; |
||
1646 | } |
||
1647 | |||
1648 | $end_time = null; |
||
1649 | if (!empty($this->end_time)) { |
||
1650 | $end_time = $this->end_time; |
||
1651 | } |
||
1652 | |||
1653 | $params = [ |
||
1654 | 'title' => $exercise, |
||
1655 | 'description' => $description, |
||
1656 | ]; |
||
1657 | |||
1658 | $paramsExtra = []; |
||
1659 | if ($type_e != 'simple') { |
||
1660 | $paramsExtra = [ |
||
1661 | 'sound' => $sound, |
||
1662 | 'type' => $type, |
||
1663 | 'random' => $random, |
||
1664 | 'random_answers' => $random_answers, |
||
1665 | 'active' => $active, |
||
1666 | 'feedback_type' => $feedback_type, |
||
1667 | 'start_time' => $start_time, |
||
1668 | 'end_time' => $end_time, |
||
1669 | 'max_attempt' => $attempts, |
||
1670 | 'expired_time' => $expired_time, |
||
1671 | 'propagate_neg' => $propagate_neg, |
||
1672 | 'save_correct_answers' => $saveCorrectAnswers, |
||
1673 | 'review_answers' => $review_answers, |
||
1674 | 'random_by_category' => $randomByCat, |
||
1675 | 'text_when_finished' => $text_when_finished, |
||
1676 | 'display_category_name' => $display_category_name, |
||
1677 | 'pass_percentage' => $pass_percentage, |
||
1678 | 'results_disabled' => $results_disabled, |
||
1679 | 'question_selection_type' => $this->getQuestionSelectionType(), |
||
1680 | 'hide_question_title' => $this->getHideQuestionTitle(), |
||
1681 | ]; |
||
1682 | |||
1683 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
1684 | $paramsExtra['text_when_finished_failure'] = $text_when_finished_failure; |
||
1685 | } |
||
1686 | |||
1687 | $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting'); |
||
1688 | if ($allow === true) { |
||
1689 | $paramsExtra['show_previous_button'] = $this->showPreviousButton(); |
||
1690 | } |
||
1691 | |||
1692 | if (api_get_configuration_value('quiz_prevent_backwards_move')) { |
||
1693 | $paramsExtra['prevent_backwards'] = $this->getPreventBackwards(); |
||
1694 | } |
||
1695 | |||
1696 | $allow = api_get_configuration_value('allow_exercise_categories'); |
||
1697 | if ($allow === true) { |
||
1698 | if (!empty($this->getExerciseCategoryId())) { |
||
1699 | $paramsExtra['exercise_category_id'] = $this->getExerciseCategoryId(); |
||
1700 | } |
||
1701 | } |
||
1702 | |||
1703 | $allow = api_get_configuration_value('allow_notification_setting_per_exercise'); |
||
1704 | if ($allow === true) { |
||
1705 | $notifications = $this->getNotifications(); |
||
1706 | $notifications = implode(',', $notifications); |
||
1707 | $paramsExtra['notifications'] = $notifications; |
||
1708 | } |
||
1709 | |||
1710 | $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); |
||
1711 | if ($pageConfig && !empty($this->pageResultConfiguration)) { |
||
1712 | $paramsExtra['page_result_configuration'] = $this->pageResultConfiguration; |
||
1713 | } |
||
1714 | } |
||
1715 | |||
1716 | if ($showHideConfiguration) { |
||
1717 | $paramsExtra['hide_question_number'] = $this->hideQuestionNumber; |
||
1718 | } |
||
1719 | if (true === api_get_configuration_value('quiz_hide_attempts_table_on_start_page')) { |
||
1720 | $paramsExtra['hide_attempts_table'] = $this->getHideAttemptsTableOnStartPage(); |
||
1721 | } |
||
1722 | |||
1723 | $params = array_merge($params, $paramsExtra); |
||
1724 | |||
1725 | Database::update( |
||
1726 | $TBL_EXERCISES, |
||
1727 | $params, |
||
1728 | ['c_id = ? AND iid = ?' => [$this->course_id, $id]] |
||
1729 | ); |
||
1730 | |||
1731 | // update into the item_property table |
||
1732 | api_item_property_update( |
||
1733 | $_course, |
||
1734 | TOOL_QUIZ, |
||
1735 | $id, |
||
1736 | 'QuizUpdated', |
||
1737 | api_get_user_id() |
||
1738 | ); |
||
1739 | |||
1740 | if (api_get_setting('search_enabled') === 'true') { |
||
1741 | $this->search_engine_edit(); |
||
1742 | } |
||
1743 | Event::addEvent( |
||
1744 | LOG_EXERCISE_UPDATE, |
||
1745 | LOG_EXERCISE_ID, |
||
1746 | $id |
||
1747 | ); |
||
1748 | } else { |
||
1749 | // Creates a new exercise |
||
1750 | // In this case of new exercise, we don't do the api_get_utc_datetime() |
||
1751 | // for date because, bellow, we call function api_set_default_visibility() |
||
1752 | // In this function, api_set_default_visibility, |
||
1753 | // the Quiz is saved too, with an $id and api_get_utc_datetime() is done. |
||
1754 | // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586) |
||
1755 | $start_time = null; |
||
1756 | if (!empty($this->start_time)) { |
||
1757 | $start_time = $this->start_time; |
||
1758 | } |
||
1759 | |||
1760 | $end_time = null; |
||
1761 | if (!empty($this->end_time)) { |
||
1762 | $end_time = $this->end_time; |
||
1763 | } |
||
1764 | |||
1765 | $params = [ |
||
1766 | 'c_id' => $this->course_id, |
||
1767 | 'start_time' => $start_time, |
||
1768 | 'end_time' => $end_time, |
||
1769 | 'title' => $exercise, |
||
1770 | 'description' => $description, |
||
1771 | 'sound' => $sound, |
||
1772 | 'type' => $type, |
||
1773 | 'random' => $random, |
||
1774 | 'random_answers' => $random_answers, |
||
1775 | 'active' => $active, |
||
1776 | 'results_disabled' => $results_disabled, |
||
1777 | 'max_attempt' => $attempts, |
||
1778 | 'feedback_type' => $feedback_type, |
||
1779 | 'expired_time' => $expired_time, |
||
1780 | 'session_id' => $session_id, |
||
1781 | 'review_answers' => $review_answers, |
||
1782 | 'random_by_category' => $randomByCat, |
||
1783 | 'text_when_finished' => $text_when_finished, |
||
1784 | 'display_category_name' => $display_category_name, |
||
1785 | 'pass_percentage' => $pass_percentage, |
||
1786 | 'save_correct_answers' => $saveCorrectAnswers, |
||
1787 | 'propagate_neg' => $propagate_neg, |
||
1788 | 'hide_question_title' => $this->getHideQuestionTitle(), |
||
1789 | ]; |
||
1790 | |||
1791 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
1792 | $params['text_when_finished_failure'] = $text_when_finished_failure; |
||
1793 | } |
||
1794 | |||
1795 | $allow = api_get_configuration_value('allow_exercise_categories'); |
||
1796 | if (true === $allow) { |
||
1797 | if (!empty($this->getExerciseCategoryId())) { |
||
1798 | $params['exercise_category_id'] = $this->getExerciseCategoryId(); |
||
1799 | } |
||
1800 | } |
||
1801 | |||
1802 | if (api_get_configuration_value('quiz_prevent_backwards_move')) { |
||
1803 | $params['prevent_backwards'] = $this->getPreventBackwards(); |
||
1804 | } |
||
1805 | |||
1806 | $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting'); |
||
1807 | if (true === $allow) { |
||
1808 | $params['show_previous_button'] = $this->showPreviousButton(); |
||
1809 | } |
||
1810 | |||
1811 | $allow = api_get_configuration_value('allow_notification_setting_per_exercise'); |
||
1812 | if (true === $allow) { |
||
1813 | $notifications = $this->getNotifications(); |
||
1814 | $params['notifications'] = ''; |
||
1815 | if (!empty($notifications)) { |
||
1816 | $notifications = implode(',', $notifications); |
||
1817 | $params['notifications'] = $notifications; |
||
1818 | } |
||
1819 | } |
||
1820 | |||
1821 | $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); |
||
1822 | if ($pageConfig && !empty($this->pageResultConfiguration)) { |
||
1823 | $params['page_result_configuration'] = $this->pageResultConfiguration; |
||
1824 | } |
||
1825 | if ($showHideConfiguration) { |
||
1826 | $params['hide_question_number'] = $this->hideQuestionNumber; |
||
1827 | } |
||
1828 | |||
1829 | $this->iid = Database::insert($TBL_EXERCISES, $params); |
||
1830 | |||
1831 | if ($this->iid) { |
||
1832 | $sql = "UPDATE $TBL_EXERCISES |
||
1833 | SET question_selection_type= ".$this->getQuestionSelectionType()." |
||
1834 | WHERE iid = ".$this->iid; |
||
1835 | Database::query($sql); |
||
1836 | |||
1837 | // insert into the item_property table |
||
1838 | api_item_property_update( |
||
1839 | $this->course, |
||
1840 | TOOL_QUIZ, |
||
1841 | $this->iid, |
||
1842 | 'QuizAdded', |
||
1843 | api_get_user_id() |
||
1844 | ); |
||
1845 | |||
1846 | // This function save the quiz again, carefull about start_time |
||
1847 | // and end_time if you remove this line (see above) |
||
1848 | api_set_default_visibility( |
||
1849 | $this->iid, |
||
1850 | TOOL_QUIZ, |
||
1851 | null, |
||
1852 | $this->course |
||
1853 | ); |
||
1854 | |||
1855 | if (api_get_setting('search_enabled') === 'true' && extension_loaded('xapian')) { |
||
1856 | $this->search_engine_save(); |
||
1857 | } |
||
1858 | Event::addEvent( |
||
1859 | LOG_EXERCISE_CREATE, |
||
1860 | LOG_EXERCISE_ID, |
||
1861 | $this->iid |
||
1862 | ); |
||
1863 | } |
||
1864 | } |
||
1865 | |||
1866 | $this->save_categories_in_exercise($this->categories); |
||
1867 | |||
1868 | return $this->iid; |
||
1869 | } |
||
1870 | |||
1871 | /** |
||
1872 | * Updates question position. |
||
1873 | * |
||
1874 | * @return bool |
||
1875 | */ |
||
1876 | public function update_question_positions() |
||
1877 | { |
||
1878 | $table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
1879 | // Fixes #3483 when updating order |
||
1880 | $questionList = $this->selectQuestionList(true); |
||
1881 | |||
1882 | if (empty($this->iid)) { |
||
1883 | return false; |
||
1884 | } |
||
1885 | |||
1886 | if (!empty($questionList)) { |
||
1887 | foreach ($questionList as $position => $questionId) { |
||
1888 | $position = (int) $position; |
||
1889 | $questionId = (int) $questionId; |
||
1890 | $sql = "UPDATE $table SET |
||
1891 | question_order ='".$position."' |
||
1892 | WHERE |
||
1893 | question_id = ".$questionId." AND |
||
1894 | exercice_id=".$this->iid; |
||
1895 | Database::query($sql); |
||
1896 | } |
||
1897 | } |
||
1898 | |||
1899 | return true; |
||
1900 | } |
||
1901 | |||
1902 | /** |
||
1903 | * Adds a question into the question list. |
||
1904 | * |
||
1905 | * @author Olivier Brouckaert |
||
1906 | * |
||
1907 | * @param int $questionId - question ID |
||
1908 | * |
||
1909 | * @return bool - true if the question has been added, otherwise false |
||
1910 | */ |
||
1911 | public function addToList($questionId) |
||
1912 | { |
||
1913 | // checks if the question ID is not in the list |
||
1914 | if (!$this->isInList($questionId)) { |
||
1915 | // selects the max position |
||
1916 | if (!$this->selectNbrQuestions()) { |
||
1917 | $pos = 1; |
||
1918 | } else { |
||
1919 | if (is_array($this->questionList)) { |
||
1920 | $pos = max(array_keys($this->questionList)) + 1; |
||
1921 | } |
||
1922 | } |
||
1923 | $this->questionList[$pos] = $questionId; |
||
1924 | |||
1925 | return true; |
||
1926 | } |
||
1927 | |||
1928 | return false; |
||
1929 | } |
||
1930 | |||
1931 | /** |
||
1932 | * removes a question from the question list. |
||
1933 | * |
||
1934 | * @author Olivier Brouckaert |
||
1935 | * |
||
1936 | * @param int $questionId - question ID |
||
1937 | * |
||
1938 | * @return bool - true if the question has been removed, otherwise false |
||
1939 | */ |
||
1940 | public function removeFromList($questionId) |
||
1941 | { |
||
1942 | // searches the position of the question ID in the list |
||
1943 | $pos = array_search($questionId, $this->questionList); |
||
1944 | // question not found |
||
1945 | if (false === $pos) { |
||
1946 | return false; |
||
1947 | } else { |
||
1948 | // dont reduce the number of random question if we use random by category option, or if |
||
1949 | // random all questions |
||
1950 | if ($this->isRandom() && 0 == $this->isRandomByCat()) { |
||
1951 | if (count($this->questionList) >= $this->random && $this->random > 0) { |
||
1952 | $this->random--; |
||
1953 | $this->save(); |
||
1954 | } |
||
1955 | } |
||
1956 | // deletes the position from the array containing the wanted question ID |
||
1957 | unset($this->questionList[$pos]); |
||
1958 | |||
1959 | return true; |
||
1960 | } |
||
1961 | } |
||
1962 | |||
1963 | /** |
||
1964 | * Marks the exercise as deleted. |
||
1965 | * If $delete argument set, completely deletes it from the database. |
||
1966 | * Note: leaves the questions in the database as "orphan" questions |
||
1967 | * (unless used by other tests). |
||
1968 | * |
||
1969 | * @param bool $delete Whether to really delete the test (true) or only mark it (false = default) |
||
1970 | * @param bool $deleteQuestions Whether to delete the test questions (true) |
||
1971 | * |
||
1972 | * @return bool Whether the operation was successful or not |
||
1973 | * |
||
1974 | * @author Olivier Brouckaert |
||
1975 | * @author Yannick Warnier |
||
1976 | */ |
||
1977 | public function delete(bool $delete = false, bool $deleteQuestions = false): bool |
||
1978 | { |
||
1979 | $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access'); |
||
1980 | |||
1981 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
1982 | return false; |
||
1983 | } |
||
1984 | |||
1985 | $locked = api_resource_is_locked_by_gradebook( |
||
1986 | $this->iid, |
||
1987 | LINK_EXERCISE |
||
1988 | ); |
||
1989 | |||
1990 | if ($locked) { |
||
1991 | return false; |
||
1992 | } |
||
1993 | |||
1994 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
1995 | $sql = "UPDATE $table SET active='-1' |
||
1996 | WHERE iid = ".$this->iid; |
||
1997 | Database::query($sql); |
||
1998 | |||
1999 | api_item_property_update( |
||
2000 | $this->course, |
||
2001 | TOOL_QUIZ, |
||
2002 | $this->iid, |
||
2003 | 'QuizDeleted', |
||
2004 | api_get_user_id() |
||
2005 | ); |
||
2006 | api_item_property_update( |
||
2007 | $this->course, |
||
2008 | TOOL_QUIZ, |
||
2009 | $this->iid, |
||
2010 | 'delete', |
||
2011 | api_get_user_id() |
||
2012 | ); |
||
2013 | |||
2014 | Skill::deleteSkillsFromItem($this->iid, ITEM_TYPE_EXERCISE); |
||
2015 | |||
2016 | if (api_get_setting('search_enabled') === 'true' && |
||
2017 | extension_loaded('xapian') |
||
2018 | ) { |
||
2019 | $this->search_engine_delete(); |
||
2020 | } |
||
2021 | |||
2022 | $linkInfo = GradebookUtils::isResourceInCourseGradebook( |
||
2023 | $this->course['code'], |
||
2024 | LINK_EXERCISE, |
||
2025 | $this->iid, |
||
2026 | $this->sessionId |
||
2027 | ); |
||
2028 | |||
2029 | if ($linkInfo !== false) { |
||
2030 | GradebookUtils::remove_resource_from_course_gradebook($linkInfo['id']); |
||
2031 | } |
||
2032 | |||
2033 | $questions = []; |
||
2034 | |||
2035 | if ($delete || $deleteQuestions) { |
||
2036 | $questions = $this->getQuestionOrderedList(true); |
||
2037 | } |
||
2038 | |||
2039 | if ($deleteQuestions) { |
||
2040 | // Delete the questions of the test (this could delete questions reused on other tests) |
||
2041 | foreach ($questions as $order => $questionId) { |
||
2042 | $masterExerciseId = Question::getMasterQuizForQuestion($questionId); |
||
2043 | if ($masterExerciseId == $this->iid) { |
||
2044 | $objQuestionTmp = Question::read($questionId); |
||
2045 | $objQuestionTmp->delete(); |
||
2046 | $this->removeFromList($questionId); |
||
2047 | } |
||
2048 | } |
||
2049 | } |
||
2050 | |||
2051 | if ($delete) { |
||
2052 | // Really delete the test (if questions were not previously deleted these will be orphaned) |
||
2053 | foreach ($questions as $order => $questionId) { |
||
2054 | $question = Question::read($questionId, $this->course); |
||
2055 | $question->delete($this->course_id); |
||
2056 | } |
||
2057 | $sql = "DELETE FROM $table |
||
2058 | WHERE iid = ".$this->iid; |
||
2059 | Database::query($sql); |
||
2060 | } |
||
2061 | Event::addEvent( |
||
2062 | LOG_EXERCISE_DELETE, |
||
2063 | LOG_EXERCISE_ID, |
||
2064 | $this->iid |
||
2065 | ); |
||
2066 | |||
2067 | return true; |
||
2068 | } |
||
2069 | |||
2070 | /** |
||
2071 | * Creates the form to create / edit an exercise. |
||
2072 | * |
||
2073 | * @param FormValidator $form |
||
2074 | * @param string $type |
||
2075 | */ |
||
2076 | public function createForm($form, $type = 'full') |
||
2077 | { |
||
2078 | if (empty($type)) { |
||
2079 | $type = 'full'; |
||
2080 | } |
||
2081 | |||
2082 | // Form title |
||
2083 | $form_title = get_lang('NewEx'); |
||
2084 | if (!empty($_GET['exerciseId'])) { |
||
2085 | $form_title = get_lang('ModifyExercise'); |
||
2086 | } |
||
2087 | |||
2088 | $form->addHeader($form_title); |
||
2089 | $form->protect(); |
||
2090 | |||
2091 | // Title. |
||
2092 | if (api_get_configuration_value('save_titles_as_html')) { |
||
2093 | $form->addHtmlEditor( |
||
2094 | 'exerciseTitle', |
||
2095 | get_lang('ExerciseName'), |
||
2096 | false, |
||
2097 | false, |
||
2098 | ['ToolbarSet' => 'TitleAsHtml'] |
||
2099 | ); |
||
2100 | } else { |
||
2101 | $form->addElement( |
||
2102 | 'text', |
||
2103 | 'exerciseTitle', |
||
2104 | get_lang('ExerciseName'), |
||
2105 | ['id' => 'exercise_title'] |
||
2106 | ); |
||
2107 | } |
||
2108 | |||
2109 | $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters')); |
||
2110 | $form->addElement('html', '<div id="advanced_params_options" style="display:none">'); |
||
2111 | |||
2112 | if (api_get_configuration_value('allow_exercise_categories')) { |
||
2113 | $categoryManager = new ExerciseCategoryManager(); |
||
2114 | $categories = $categoryManager->getCategories(api_get_course_int_id()); |
||
2115 | $options = []; |
||
2116 | if (!empty($categories)) { |
||
2117 | /** @var CExerciseCategory $category */ |
||
2118 | foreach ($categories as $category) { |
||
2119 | $options[$category->getId()] = $category->getName(); |
||
2120 | } |
||
2121 | } |
||
2122 | |||
2123 | $form->addSelect( |
||
2124 | 'exercise_category_id', |
||
2125 | get_lang('Category'), |
||
2126 | $options, |
||
2127 | ['placeholder' => get_lang('SelectAnOption')] |
||
2128 | ); |
||
2129 | } |
||
2130 | |||
2131 | $editor_config = [ |
||
2132 | 'ToolbarSet' => 'TestQuestionDescription', |
||
2133 | 'Width' => '100%', |
||
2134 | 'Height' => '150', |
||
2135 | ]; |
||
2136 | |||
2137 | if (is_array($type)) { |
||
2138 | $editor_config = array_merge($editor_config, $type); |
||
2139 | } |
||
2140 | |||
2141 | $form->addHtmlEditor( |
||
2142 | 'exerciseDescription', |
||
2143 | get_lang('ExerciseDescription'), |
||
2144 | false, |
||
2145 | false, |
||
2146 | $editor_config |
||
2147 | ); |
||
2148 | |||
2149 | $skillList = []; |
||
2150 | if ('full' === $type) { |
||
2151 | // Can't modify a DirectFeedback question. |
||
2152 | if (!in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) { |
||
2153 | $this->setResultFeedbackGroup($form); |
||
2154 | |||
2155 | // Type of results display on the final page |
||
2156 | $this->setResultDisabledGroup($form); |
||
2157 | |||
2158 | // Type of questions disposition on page |
||
2159 | $radios = []; |
||
2160 | $radios[] = $form->createElement( |
||
2161 | 'radio', |
||
2162 | 'exerciseType', |
||
2163 | null, |
||
2164 | get_lang('SimpleExercise'), |
||
2165 | '1', |
||
2166 | [ |
||
2167 | 'onclick' => 'check_per_page_all()', |
||
2168 | 'id' => 'option_page_all', |
||
2169 | ] |
||
2170 | ); |
||
2171 | $radios[] = $form->createElement( |
||
2172 | 'radio', |
||
2173 | 'exerciseType', |
||
2174 | null, |
||
2175 | get_lang('SequentialExercise'), |
||
2176 | '2', |
||
2177 | [ |
||
2178 | 'onclick' => 'check_per_page_one()', |
||
2179 | 'id' => 'option_page_one', |
||
2180 | ] |
||
2181 | ); |
||
2182 | |||
2183 | $form->addGroup($radios, null, get_lang('QuestionsPerPage')); |
||
2184 | } else { |
||
2185 | // if is Direct feedback but has not questions we can allow to modify the question type |
||
2186 | if (empty($this->iid) || 0 === $this->getQuestionCount()) { |
||
2187 | $this->setResultFeedbackGroup($form); |
||
2188 | $this->setResultDisabledGroup($form); |
||
2189 | |||
2190 | // Type of questions disposition on page |
||
2191 | $radios = []; |
||
2192 | $radios[] = $form->createElement('radio', 'exerciseType', null, get_lang('SimpleExercise'), '1'); |
||
2193 | $radios[] = $form->createElement( |
||
2194 | 'radio', |
||
2195 | 'exerciseType', |
||
2196 | null, |
||
2197 | get_lang('SequentialExercise'), |
||
2198 | '2' |
||
2199 | ); |
||
2200 | $form->addGroup($radios, null, get_lang('ExerciseType')); |
||
2201 | } else { |
||
2202 | $this->setResultFeedbackGroup($form, true); |
||
2203 | $group = $this->setResultDisabledGroup($form); |
||
2204 | $group->freeze(); |
||
2205 | |||
2206 | // we force the options to the DirectFeedback exercisetype |
||
2207 | //$form->addElement('hidden', 'exerciseFeedbackType', $this->getFeedbackType()); |
||
2208 | //$form->addElement('hidden', 'exerciseType', ONE_PER_PAGE); |
||
2209 | |||
2210 | // Type of questions disposition on page |
||
2211 | $radios[] = $form->createElement( |
||
2212 | 'radio', |
||
2213 | 'exerciseType', |
||
2214 | null, |
||
2215 | get_lang('SimpleExercise'), |
||
2216 | '1', |
||
2217 | [ |
||
2218 | 'onclick' => 'check_per_page_all()', |
||
2219 | 'id' => 'option_page_all', |
||
2220 | ] |
||
2221 | ); |
||
2222 | $radios[] = $form->createElement( |
||
2223 | 'radio', |
||
2224 | 'exerciseType', |
||
2225 | null, |
||
2226 | get_lang('SequentialExercise'), |
||
2227 | '2', |
||
2228 | [ |
||
2229 | 'onclick' => 'check_per_page_one()', |
||
2230 | 'id' => 'option_page_one', |
||
2231 | ] |
||
2232 | ); |
||
2233 | |||
2234 | $type_group = $form->addGroup($radios, null, get_lang('QuestionsPerPage')); |
||
2235 | $type_group->freeze(); |
||
2236 | } |
||
2237 | } |
||
2238 | |||
2239 | $option = [ |
||
2240 | EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'), |
||
2241 | // Defined by user |
||
2242 | EX_Q_SELECTION_RANDOM => get_lang('Random'), |
||
2243 | // 1-10, All |
||
2244 | 'per_categories' => '--------'.get_lang('UsingCategories').'----------', |
||
2245 | // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0} |
||
2246 | EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'), |
||
2247 | // A 123 B 456 C 78 (0, 1, all) |
||
2248 | EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'), |
||
2249 | // C 78 B 456 A 123 |
||
2250 | EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'), |
||
2251 | // A 321 B 654 C 87 |
||
2252 | EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'), |
||
2253 | // C 87 B 654 A 321 |
||
2254 | //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'), |
||
2255 | /* B 456 C 78 A 123 |
||
2256 | 456 78 123 |
||
2257 | 123 456 78 |
||
2258 | */ |
||
2259 | //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'), |
||
2260 | /* |
||
2261 | A 123 B 456 C 78 |
||
2262 | B 456 C 78 A 123 |
||
2263 | B 654 C 87 A 321 |
||
2264 | 654 87 321 |
||
2265 | 165 842 73 |
||
2266 | */ |
||
2267 | //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'), |
||
2268 | //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'), |
||
2269 | ]; |
||
2270 | |||
2271 | $form->addElement( |
||
2272 | 'select', |
||
2273 | 'question_selection_type', |
||
2274 | [get_lang('QuestionSelection')], |
||
2275 | $option, |
||
2276 | [ |
||
2277 | 'id' => 'questionSelection', |
||
2278 | 'onchange' => 'checkQuestionSelection()', |
||
2279 | ] |
||
2280 | ); |
||
2281 | |||
2282 | $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); |
||
2283 | if ($pageConfig) { |
||
2284 | $group = [ |
||
2285 | $form->createElement( |
||
2286 | 'checkbox', |
||
2287 | 'hide_expected_answer', |
||
2288 | null, |
||
2289 | get_lang('HideExpectedAnswer') |
||
2290 | ), |
||
2291 | $form->createElement( |
||
2292 | 'checkbox', |
||
2293 | 'hide_total_score', |
||
2294 | null, |
||
2295 | get_lang('HideTotalScore') |
||
2296 | ), |
||
2297 | $form->createElement( |
||
2298 | 'checkbox', |
||
2299 | 'hide_question_score', |
||
2300 | null, |
||
2301 | get_lang('HideQuestionScore') |
||
2302 | ), |
||
2303 | $form->createElement( |
||
2304 | 'checkbox', |
||
2305 | 'hide_category_table', |
||
2306 | null, |
||
2307 | get_lang('HideCategoryTable') |
||
2308 | ), |
||
2309 | $form->createElement( |
||
2310 | 'checkbox', |
||
2311 | 'hide_correct_answered_questions', |
||
2312 | null, |
||
2313 | get_lang('HideCorrectAnsweredQuestions') |
||
2314 | ), |
||
2315 | $form->createElement( |
||
2316 | 'checkbox', |
||
2317 | 'hide_comment', |
||
2318 | null, |
||
2319 | get_lang('HideComment') |
||
2320 | ), |
||
2321 | ]; |
||
2322 | $form->addGroup($group, null, get_lang('ResultsConfigurationPage')); |
||
2323 | } |
||
2324 | $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number'); |
||
2325 | if ($showHideConfiguration) { |
||
2326 | $group = [ |
||
2327 | $form->createElement('radio', 'hide_question_number', null, get_lang('Yes'), '1'), |
||
2328 | $form->createElement('radio', 'hide_question_number', null, get_lang('No'), '0'), |
||
2329 | ]; |
||
2330 | $form->addGroup($group, null, get_lang('HideQuestionNumber')); |
||
2331 | } |
||
2332 | |||
2333 | $displayMatrix = 'none'; |
||
2334 | $displayRandom = 'none'; |
||
2335 | $selectionType = $this->getQuestionSelectionType(); |
||
2336 | switch ($selectionType) { |
||
2337 | case EX_Q_SELECTION_RANDOM: |
||
2338 | $displayRandom = 'block'; |
||
2339 | break; |
||
2340 | case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: |
||
2341 | $displayMatrix = 'block'; |
||
2342 | break; |
||
2343 | } |
||
2344 | |||
2345 | $form->addHtml('<div id="hidden_random" style="display:'.$displayRandom.'">'); |
||
2346 | // Number of random question. |
||
2347 | $max = ($this->iid > 0) ? $this->getQuestionCount() : 10; |
||
2348 | $option = range(0, $max); |
||
2349 | $option[0] = get_lang('No'); |
||
2350 | $option[-1] = get_lang('AllQuestionsShort'); |
||
2351 | $form->addElement( |
||
2352 | 'select', |
||
2353 | 'randomQuestions', |
||
2354 | [ |
||
2355 | get_lang('RandomQuestions'), |
||
2356 | get_lang('RandomQuestionsHelp'), |
||
2357 | ], |
||
2358 | $option, |
||
2359 | ['id' => 'randomQuestions'] |
||
2360 | ); |
||
2361 | $form->addHtml('</div>'); |
||
2362 | $form->addHtml('<div id="hidden_matrix" style="display:'.$displayMatrix.'">'); |
||
2363 | |||
2364 | // Category selection. |
||
2365 | $cat = new TestCategory(); |
||
2366 | $cat_form = $cat->returnCategoryForm($this); |
||
2367 | if (empty($cat_form)) { |
||
2368 | $cat_form = '<span class="label label-warning">'.get_lang('NoCategoriesDefined').'</span>'; |
||
2369 | } |
||
2370 | $form->addElement('label', null, $cat_form); |
||
2371 | $form->addHtml('</div>'); |
||
2372 | |||
2373 | // Random answers. |
||
2374 | $radios_random_answers = [ |
||
2375 | $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'), |
||
2376 | $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'), |
||
2377 | ]; |
||
2378 | $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers')); |
||
2379 | |||
2380 | // Category name. |
||
2381 | $radio_display_cat_name = [ |
||
2382 | $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'), |
||
2383 | $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'), |
||
2384 | ]; |
||
2385 | $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName')); |
||
2386 | |||
2387 | // Hide question title. |
||
2388 | $group = [ |
||
2389 | $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'), |
||
2390 | $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'), |
||
2391 | ]; |
||
2392 | $form->addGroup($group, null, get_lang('HideQuestionTitle')); |
||
2393 | |||
2394 | $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting'); |
||
2395 | if (true === $allow) { |
||
2396 | // Hide question title. |
||
2397 | $group = [ |
||
2398 | $form->createElement( |
||
2399 | 'radio', |
||
2400 | 'show_previous_button', |
||
2401 | null, |
||
2402 | get_lang('Yes'), |
||
2403 | '1' |
||
2404 | ), |
||
2405 | $form->createElement( |
||
2406 | 'radio', |
||
2407 | 'show_previous_button', |
||
2408 | null, |
||
2409 | get_lang('No'), |
||
2410 | '0' |
||
2411 | ), |
||
2412 | ]; |
||
2413 | $form->addGroup($group, null, get_lang('ShowPreviousButton')); |
||
2414 | } |
||
2415 | |||
2416 | $form->addElement( |
||
2417 | 'number', |
||
2418 | 'exerciseAttempts', |
||
2419 | get_lang('ExerciseAttempts'), |
||
2420 | null, |
||
2421 | ['id' => 'exerciseAttempts'] |
||
2422 | ); |
||
2423 | |||
2424 | // Exercise time limit |
||
2425 | $form->addElement( |
||
2426 | 'checkbox', |
||
2427 | 'activate_start_date_check', |
||
2428 | null, |
||
2429 | get_lang('EnableStartTime'), |
||
2430 | ['onclick' => 'activate_start_date()'] |
||
2431 | ); |
||
2432 | |||
2433 | if (!empty($this->start_time)) { |
||
2434 | $form->addElement('html', '<div id="start_date_div" style="display:block;">'); |
||
2435 | } else { |
||
2436 | $form->addElement('html', '<div id="start_date_div" style="display:none;">'); |
||
2437 | } |
||
2438 | |||
2439 | $form->addElement('date_time_picker', 'start_time'); |
||
2440 | $form->addElement('html', '</div>'); |
||
2441 | $form->addElement( |
||
2442 | 'checkbox', |
||
2443 | 'activate_end_date_check', |
||
2444 | null, |
||
2445 | get_lang('EnableEndTime'), |
||
2446 | ['onclick' => 'activate_end_date()'] |
||
2447 | ); |
||
2448 | |||
2449 | if (!empty($this->end_time)) { |
||
2450 | $form->addHtml('<div id="end_date_div" style="display:block;">'); |
||
2451 | } else { |
||
2452 | $form->addHtml('<div id="end_date_div" style="display:none;">'); |
||
2453 | } |
||
2454 | |||
2455 | $form->addElement('date_time_picker', 'end_time'); |
||
2456 | $form->addElement('html', '</div>'); |
||
2457 | |||
2458 | $display = 'block'; |
||
2459 | $form->addElement( |
||
2460 | 'checkbox', |
||
2461 | 'propagate_neg', |
||
2462 | null, |
||
2463 | get_lang('PropagateNegativeResults') |
||
2464 | ); |
||
2465 | |||
2466 | if (api_get_configuration_value('allow_quiz_save_correct_options')) { |
||
2467 | $options = [ |
||
2468 | '' => get_lang('SelectAnOption'), |
||
2469 | 1 => get_lang('SaveTheCorrectAnswersForTheNextAttempt'), |
||
2470 | 2 => get_lang('SaveAllAnswers'), |
||
2471 | ]; |
||
2472 | $form->addSelect( |
||
2473 | 'save_correct_answers', |
||
2474 | get_lang('SaveAnswers'), |
||
2475 | $options |
||
2476 | ); |
||
2477 | } else { |
||
2478 | $form->addCheckBox( |
||
2479 | 'save_correct_answers', |
||
2480 | null, |
||
2481 | get_lang('SaveTheCorrectAnswersForTheNextAttempt') |
||
2482 | ); |
||
2483 | } |
||
2484 | |||
2485 | $form->addElement('html', '<div class="clear"> </div>'); |
||
2486 | $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers')); |
||
2487 | $form->addElement('html', '<div id="divtimecontrol" style="display:'.$display.';">'); |
||
2488 | |||
2489 | // Timer control |
||
2490 | $form->addElement( |
||
2491 | 'checkbox', |
||
2492 | 'enabletimercontrol', |
||
2493 | null, |
||
2494 | get_lang('EnableTimerControl'), |
||
2495 | [ |
||
2496 | 'onclick' => 'option_time_expired()', |
||
2497 | 'id' => 'enabletimercontrol', |
||
2498 | 'onload' => 'check_load_time()', |
||
2499 | ] |
||
2500 | ); |
||
2501 | |||
2502 | $expired_date = (int) $this->selectExpiredTime(); |
||
2503 | |||
2504 | if (($expired_date != '0')) { |
||
2505 | $form->addElement('html', '<div id="timercontrol" style="display:block;">'); |
||
2506 | } else { |
||
2507 | $form->addElement('html', '<div id="timercontrol" style="display:none;">'); |
||
2508 | } |
||
2509 | $form->addText( |
||
2510 | 'enabletimercontroltotalminutes', |
||
2511 | get_lang('ExerciseTotalDurationInMinutes'), |
||
2512 | false, |
||
2513 | [ |
||
2514 | 'id' => 'enabletimercontroltotalminutes', |
||
2515 | 'cols-size' => [2, 2, 8], |
||
2516 | ] |
||
2517 | ); |
||
2518 | $form->addElement('html', '</div>'); |
||
2519 | |||
2520 | if (api_get_configuration_value('quiz_prevent_backwards_move')) { |
||
2521 | $form->addCheckBox( |
||
2522 | 'prevent_backwards', |
||
2523 | null, |
||
2524 | get_lang('QuizPreventBackwards') |
||
2525 | ); |
||
2526 | } |
||
2527 | |||
2528 | $form->addElement( |
||
2529 | 'text', |
||
2530 | 'pass_percentage', |
||
2531 | [get_lang('PassPercentage'), null, '%'], |
||
2532 | ['id' => 'pass_percentage'] |
||
2533 | ); |
||
2534 | |||
2535 | $form->addRule('pass_percentage', get_lang('Numeric'), 'numeric'); |
||
2536 | $form->addRule('pass_percentage', get_lang('ValueTooSmall'), 'min_numeric_length', 0); |
||
2537 | $form->addRule('pass_percentage', get_lang('ValueTooBig'), 'max_numeric_length', 100); |
||
2538 | |||
2539 | // add the text_when_finished textbox |
||
2540 | $form->addHtmlEditor( |
||
2541 | 'text_when_finished', |
||
2542 | get_lang('TextWhenFinished'), |
||
2543 | false, |
||
2544 | false, |
||
2545 | $editor_config |
||
2546 | ); |
||
2547 | |||
2548 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
2549 | $form->addHtmlEditor( |
||
2550 | 'text_when_finished_failure', |
||
2551 | get_lang('TextAppearingAtTheEndOfTheTestWhenTheUserHasFailed'), |
||
2552 | false, |
||
2553 | false, |
||
2554 | $editor_config |
||
2555 | ); |
||
2556 | } |
||
2557 | |||
2558 | $allow = api_get_configuration_value('allow_notification_setting_per_exercise'); |
||
2559 | if ($allow === true) { |
||
2560 | $settings = ExerciseLib::getNotificationSettings(); |
||
2561 | $group = []; |
||
2562 | foreach ($settings as $itemId => $label) { |
||
2563 | $group[] = $form->createElement( |
||
2564 | 'checkbox', |
||
2565 | 'notifications[]', |
||
2566 | null, |
||
2567 | $label, |
||
2568 | ['value' => $itemId] |
||
2569 | ); |
||
2570 | } |
||
2571 | $form->addGroup($group, '', [get_lang('EmailNotifications')]); |
||
2572 | } |
||
2573 | |||
2574 | $form->addCheckBox( |
||
2575 | 'update_title_in_lps', |
||
2576 | null, |
||
2577 | get_lang('UpdateTitleInLps') |
||
2578 | ); |
||
2579 | |||
2580 | $allowHideAttempts = api_get_configuration_value('quiz_hide_attempts_table_on_start_page'); |
||
2581 | if ($allowHideAttempts) { |
||
2582 | $group = [ |
||
2583 | $form->createElement('radio', 'hide_attempts_table', null, get_lang('Yes'), '1'), |
||
2584 | $form->createElement('radio', 'hide_attempts_table', null, get_lang('No'), '0'), |
||
2585 | ]; |
||
2586 | $form->addGroup($group, null, get_lang('HideAttemptsTableOnStartPage')); |
||
2587 | } |
||
2588 | |||
2589 | $defaults = []; |
||
2590 | if (api_get_setting('search_enabled') === 'true') { |
||
2591 | require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; |
||
2592 | $form->addElement('checkbox', 'index_document', '', get_lang('SearchFeatureDoIndexDocument')); |
||
2593 | $form->addSelectLanguage('language', get_lang('SearchFeatureDocumentLanguage')); |
||
2594 | $specific_fields = get_specific_field_list(); |
||
2595 | |||
2596 | foreach ($specific_fields as $specific_field) { |
||
2597 | $form->addElement('text', $specific_field['code'], $specific_field['name']); |
||
2598 | $filter = [ |
||
2599 | 'c_id' => api_get_course_int_id(), |
||
2600 | 'field_id' => $specific_field['id'], |
||
2601 | 'ref_id' => $this->iid, |
||
2602 | 'tool_id' => "'".TOOL_QUIZ."'", |
||
2603 | ]; |
||
2604 | $values = get_specific_field_values_list($filter, ['value']); |
||
2605 | if (!empty($values)) { |
||
2606 | $arr_str_values = []; |
||
2607 | foreach ($values as $value) { |
||
2608 | $arr_str_values[] = $value['value']; |
||
2609 | } |
||
2610 | $defaults[$specific_field['code']] = implode(', ', $arr_str_values); |
||
2611 | } |
||
2612 | } |
||
2613 | } |
||
2614 | |||
2615 | Skill::addSkillsToForm($form, api_get_course_int_id(), api_get_session_id(), ITEM_TYPE_EXERCISE, $this->iid); |
||
2616 | |||
2617 | $extraField = new ExtraField('exercise'); |
||
2618 | $extraField->addElements( |
||
2619 | $form, |
||
2620 | $this->iid, |
||
2621 | [ |
||
2622 | 'notifications', |
||
2623 | 'remedialcourselist', |
||
2624 | 'advancedcourselist', |
||
2625 | 'subscribe_session_when_finished_failure', |
||
2626 | ], //exclude |
||
2627 | false, // filter |
||
2628 | false, // tag as select |
||
2629 | [], //show only fields |
||
2630 | [], // order fields |
||
2631 | [] // extra data |
||
2632 | ); |
||
2633 | |||
2634 | // See BT#18165 |
||
2635 | $remedialList = [ |
||
2636 | 'remedialcourselist' => 'RemedialCourses', |
||
2637 | 'advancedcourselist' => 'AdvancedCourses', |
||
2638 | ]; |
||
2639 | $extraFieldExercice = new ExtraField('exercise'); |
||
2640 | $extraFieldExerciceValue = new ExtraFieldValue('exercise'); |
||
2641 | $pluginRemedial = api_get_plugin_setting('remedial_course', 'enabled') === 'true'; |
||
2642 | if ($pluginRemedial) { |
||
2643 | $sessionId = api_get_session_id(); |
||
2644 | $userId = api_get_user_id(); |
||
2645 | foreach ($remedialList as $item => $label) { |
||
2646 | $remedialField = $extraFieldExercice->get_handler_field_info_by_field_variable($item); |
||
2647 | $optionRemedial = []; |
||
2648 | $defaults[$item] = []; |
||
2649 | $remedialExtraValue = $extraFieldExerciceValue->get_values_by_handler_and_field_id($this->iid, $remedialField['id']); |
||
2650 | $defaults[$item] = isset($remedialExtraValue['value']) ? explode(';', $remedialExtraValue['value']) : []; |
||
2651 | if ($sessionId != 0) { |
||
2652 | $courseList = SessionManager::getCoursesInSession($sessionId); |
||
2653 | foreach ($courseList as $course) { |
||
2654 | $courseSession = api_get_course_info_by_id($course); |
||
2655 | if (!empty($courseSession) && isset($courseSession['real_id'])) { |
||
2656 | $courseId = $courseSession['real_id']; |
||
2657 | if (api_get_course_int_id() != $courseId) { |
||
2658 | $optionRemedial[$courseId] = $courseSession['title']; |
||
2659 | } |
||
2660 | } |
||
2661 | } |
||
2662 | } else { |
||
2663 | $courseList = CourseManager::get_course_list(); |
||
2664 | foreach ($courseList as $course) { |
||
2665 | if (!empty($course) && isset($course['real_id'])) { |
||
2666 | $courseId = $course['real_id']; |
||
2667 | if (api_get_course_int_id() != $courseId) { |
||
2668 | $optionRemedial[$courseId] = $course['title']; |
||
2669 | } |
||
2670 | } |
||
2671 | } |
||
2672 | } |
||
2673 | unset($optionRemedial[0]); |
||
2674 | $form->addSelect( |
||
2675 | "extra_".$item, |
||
2676 | get_plugin_lang($label, RemedialCoursePlugin::class), |
||
2677 | $optionRemedial, |
||
2678 | [ |
||
2679 | 'placeholder' => get_lang('SelectAnOption'), |
||
2680 | 'multiple' => 'multiple', |
||
2681 | ] |
||
2682 | ); |
||
2683 | } |
||
2684 | } |
||
2685 | |||
2686 | if (true === api_get_configuration_value('exercise_subscribe_session_when_finished_failure')) { |
||
2687 | $optionSessionWhenFailure = []; |
||
2688 | |||
2689 | if ($failureSession = ExerciseLib::getSessionWhenFinishedFailure($this->iid)) { |
||
2690 | $defaults['subscribe_session_when_finished_failure'] = $failureSession->getId(); |
||
2691 | $optionSessionWhenFailure[$failureSession->getId()] = $failureSession->getName(); |
||
2692 | } |
||
2693 | |||
2694 | $form->addSelectAjax( |
||
2695 | 'extra_subscribe_session_when_finished_failure', |
||
2696 | get_lang('SubscribeSessionWhenFinishedFailure'), |
||
2697 | $optionSessionWhenFailure, |
||
2698 | [ |
||
2699 | 'url' => api_get_path(WEB_AJAX_PATH).'session.ajax.php?' |
||
2700 | .http_build_query(['a' => 'search_session']), |
||
2701 | ] |
||
2702 | ); |
||
2703 | } |
||
2704 | |||
2705 | $settings = api_get_configuration_value('exercise_finished_notification_settings'); |
||
2706 | if (!empty($settings)) { |
||
2707 | $options = []; |
||
2708 | foreach ($settings as $name => $data) { |
||
2709 | $options[$name] = $name; |
||
2710 | } |
||
2711 | $form->addSelect( |
||
2712 | 'extra_notifications', |
||
2713 | get_lang('Notifications'), |
||
2714 | $options, |
||
2715 | ['placeholder' => get_lang('SelectAnOption')] |
||
2716 | ); |
||
2717 | } |
||
2718 | $form->addElement('html', '</div>'); //End advanced setting |
||
2719 | $form->addElement('html', '</div>'); |
||
2720 | } |
||
2721 | |||
2722 | // submit |
||
2723 | if (isset($_GET['exerciseId'])) { |
||
2724 | $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise'); |
||
2725 | } else { |
||
2726 | $form->addButtonUpdate(get_lang('ProcedToQuestions'), 'submitExercise'); |
||
2727 | } |
||
2728 | |||
2729 | $form->addRule('exerciseTitle', get_lang('GiveExerciseName'), 'required'); |
||
2730 | |||
2731 | // defaults |
||
2732 | if ($type == 'full') { |
||
2733 | // rules |
||
2734 | $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric'); |
||
2735 | $form->addRule('start_time', get_lang('InvalidDate'), 'datetime'); |
||
2736 | $form->addRule('end_time', get_lang('InvalidDate'), 'datetime'); |
||
2737 | |||
2738 | if ($this->iid > 0) { |
||
2739 | $defaults['randomQuestions'] = $this->random; |
||
2740 | $defaults['randomAnswers'] = $this->getRandomAnswers(); |
||
2741 | $defaults['exerciseType'] = $this->selectType(); |
||
2742 | $defaults['exerciseTitle'] = $this->get_formated_title(); |
||
2743 | $defaults['exerciseDescription'] = $this->selectDescription(); |
||
2744 | $defaults['exerciseAttempts'] = $this->selectAttempts(); |
||
2745 | $defaults['exerciseFeedbackType'] = $this->getFeedbackType(); |
||
2746 | $defaults['results_disabled'] = $this->selectResultsDisabled(); |
||
2747 | $defaults['propagate_neg'] = $this->selectPropagateNeg(); |
||
2748 | $defaults['save_correct_answers'] = $this->getSaveCorrectAnswers(); |
||
2749 | $defaults['review_answers'] = $this->review_answers; |
||
2750 | $defaults['randomByCat'] = $this->getRandomByCategory(); |
||
2751 | $defaults['text_when_finished'] = $this->getTextWhenFinished(); |
||
2752 | $defaults['display_category_name'] = $this->selectDisplayCategoryName(); |
||
2753 | $defaults['pass_percentage'] = $this->selectPassPercentage(); |
||
2754 | $defaults['question_selection_type'] = $this->getQuestionSelectionType(); |
||
2755 | $defaults['hide_question_title'] = $this->getHideQuestionTitle(); |
||
2756 | $defaults['show_previous_button'] = $this->showPreviousButton(); |
||
2757 | $defaults['exercise_category_id'] = $this->getExerciseCategoryId(); |
||
2758 | $defaults['prevent_backwards'] = $this->getPreventBackwards(); |
||
2759 | |||
2760 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
2761 | $defaults['text_when_finished_failure'] = $this->getTextWhenFinishedFailure(); |
||
2762 | } |
||
2763 | |||
2764 | if (!empty($this->start_time)) { |
||
2765 | $defaults['activate_start_date_check'] = 1; |
||
2766 | } |
||
2767 | if (!empty($this->end_time)) { |
||
2768 | $defaults['activate_end_date_check'] = 1; |
||
2769 | } |
||
2770 | |||
2771 | $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00'); |
||
2772 | $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600); |
||
2773 | |||
2774 | // Get expired time |
||
2775 | if ($this->expired_time != '0') { |
||
2776 | $defaults['enabletimercontrol'] = 1; |
||
2777 | $defaults['enabletimercontroltotalminutes'] = $this->expired_time; |
||
2778 | } else { |
||
2779 | $defaults['enabletimercontroltotalminutes'] = 0; |
||
2780 | } |
||
2781 | $defaults['notifications'] = $this->getNotifications(); |
||
2782 | } else { |
||
2783 | $defaults['exerciseType'] = 2; |
||
2784 | $defaults['exerciseAttempts'] = 0; |
||
2785 | $defaults['randomQuestions'] = 0; |
||
2786 | $defaults['randomAnswers'] = 0; |
||
2787 | $defaults['exerciseDescription'] = ''; |
||
2788 | $defaults['exerciseFeedbackType'] = 0; |
||
2789 | $defaults['results_disabled'] = 0; |
||
2790 | $defaults['randomByCat'] = 0; |
||
2791 | $defaults['text_when_finished'] = ''; |
||
2792 | |||
2793 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
2794 | $defaults['text_when_finished_failure'] = ''; |
||
2795 | } |
||
2796 | |||
2797 | $defaults['start_time'] = date('Y-m-d 12:00:00'); |
||
2798 | $defaults['display_category_name'] = 1; |
||
2799 | $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600); |
||
2800 | $defaults['pass_percentage'] = ''; |
||
2801 | $defaults['end_button'] = $this->selectEndButton(); |
||
2802 | $defaults['question_selection_type'] = 1; |
||
2803 | $defaults['hide_question_title'] = 0; |
||
2804 | $defaults['show_previous_button'] = 1; |
||
2805 | $defaults['on_success_message'] = null; |
||
2806 | $defaults['on_failed_message'] = null; |
||
2807 | } |
||
2808 | } else { |
||
2809 | $defaults['exerciseTitle'] = $this->selectTitle(); |
||
2810 | $defaults['exerciseDescription'] = $this->selectDescription(); |
||
2811 | } |
||
2812 | |||
2813 | if (api_get_setting('search_enabled') === 'true') { |
||
2814 | $defaults['index_document'] = 'checked="checked"'; |
||
2815 | } |
||
2816 | |||
2817 | $this->setPageResultConfigurationDefaults($defaults); |
||
2818 | $this->setHideQuestionNumberDefaults($defaults); |
||
2819 | $this->setHideAttemptsTableOnStartPageDefaults($defaults); |
||
2820 | $form->setDefaults($defaults); |
||
2821 | |||
2822 | // Freeze some elements. |
||
2823 | if ($this->iid != 0 && $this->edit_exercise_in_lp == false) { |
||
2824 | $elementsToFreeze = [ |
||
2825 | 'randomQuestions', |
||
2826 | //'randomByCat', |
||
2827 | 'exerciseAttempts', |
||
2828 | 'propagate_neg', |
||
2829 | 'enabletimercontrol', |
||
2830 | 'review_answers', |
||
2831 | ]; |
||
2832 | |||
2833 | foreach ($elementsToFreeze as $elementName) { |
||
2834 | /** @var HTML_QuickForm_element $element */ |
||
2835 | $element = $form->getElement($elementName); |
||
2836 | $element->freeze(); |
||
2837 | } |
||
2838 | } |
||
2839 | } |
||
2840 | |||
2841 | public function setResultFeedbackGroup(FormValidator $form, $checkFreeze = true) |
||
2842 | { |
||
2843 | // Feedback type. |
||
2844 | $feedback = []; |
||
2845 | $warning = sprintf( |
||
2846 | get_lang('TheSettingXWillChangeToX'), |
||
2847 | get_lang('ShowResultsToStudents'), |
||
2848 | get_lang('ShowScoreAndRightAnswer') |
||
2849 | ); |
||
2850 | $endTest = $form->createElement( |
||
2851 | 'radio', |
||
2852 | 'exerciseFeedbackType', |
||
2853 | null, |
||
2854 | get_lang('ExerciseAtTheEndOfTheTest'), |
||
2855 | EXERCISE_FEEDBACK_TYPE_END, |
||
2856 | [ |
||
2857 | 'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_END, |
||
2858 | //'onclick' => 'if confirm() check_feedback()', |
||
2859 | 'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_feedback(); } else { return false;} ', |
||
2860 | ] |
||
2861 | ); |
||
2862 | |||
2863 | $noFeedBack = $form->createElement( |
||
2864 | 'radio', |
||
2865 | 'exerciseFeedbackType', |
||
2866 | null, |
||
2867 | get_lang('NoFeedback'), |
||
2868 | EXERCISE_FEEDBACK_TYPE_EXAM, |
||
2869 | [ |
||
2870 | 'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_EXAM, |
||
2871 | ] |
||
2872 | ); |
||
2873 | |||
2874 | $feedback[] = $endTest; |
||
2875 | $feedback[] = $noFeedBack; |
||
2876 | |||
2877 | $scenarioEnabled = 'true' === api_get_setting('enable_quiz_scenario'); |
||
2878 | $freeze = true; |
||
2879 | if ($scenarioEnabled) { |
||
2880 | if ($this->getQuestionCount() > 0) { |
||
2881 | $hasDifferentQuestion = $this->hasQuestionWithTypeNotInList([UNIQUE_ANSWER, HOT_SPOT_DELINEATION]); |
||
2882 | |||
2883 | if (false === $hasDifferentQuestion) { |
||
2884 | $freeze = false; |
||
2885 | } |
||
2886 | } else { |
||
2887 | $freeze = false; |
||
2888 | } |
||
2889 | |||
2890 | $direct = $form->createElement( |
||
2891 | 'radio', |
||
2892 | 'exerciseFeedbackType', |
||
2893 | null, |
||
2894 | get_lang('DirectFeedback'), |
||
2895 | EXERCISE_FEEDBACK_TYPE_DIRECT, |
||
2896 | [ |
||
2897 | 'id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_DIRECT, |
||
2898 | 'onclick' => 'check_direct_feedback()', |
||
2899 | ] |
||
2900 | ); |
||
2901 | |||
2902 | $directPopUp = $form->createElement( |
||
2903 | 'radio', |
||
2904 | 'exerciseFeedbackType', |
||
2905 | null, |
||
2906 | get_lang('ExerciseDirectPopUp'), |
||
2907 | EXERCISE_FEEDBACK_TYPE_POPUP, |
||
2908 | ['id' => 'exerciseType_'.EXERCISE_FEEDBACK_TYPE_POPUP, 'onclick' => 'check_direct_feedback()'] |
||
2909 | ); |
||
2910 | |||
2911 | if ($freeze) { |
||
2912 | $direct->freeze(); |
||
2913 | $directPopUp->freeze(); |
||
2914 | } |
||
2915 | |||
2916 | // If has delineation freeze all. |
||
2917 | $hasDelineation = $this->hasQuestionWithType(HOT_SPOT_DELINEATION); |
||
2918 | if ($hasDelineation) { |
||
2919 | $endTest->freeze(); |
||
2920 | $noFeedBack->freeze(); |
||
2921 | $direct->freeze(); |
||
2922 | $directPopUp->freeze(); |
||
2923 | } |
||
2924 | |||
2925 | $feedback[] = $direct; |
||
2926 | $feedback[] = $directPopUp; |
||
2927 | } |
||
2928 | |||
2929 | $form->addGroup( |
||
2930 | $feedback, |
||
2931 | null, |
||
2932 | [ |
||
2933 | get_lang('FeedbackType'), |
||
2934 | get_lang('FeedbackDisplayOptions'), |
||
2935 | ] |
||
2936 | ); |
||
2937 | } |
||
2938 | |||
2939 | /** |
||
2940 | * function which process the creation of exercises. |
||
2941 | * |
||
2942 | * @param FormValidator $form |
||
2943 | * @param string |
||
2944 | * |
||
2945 | * @return int c_quiz.iid |
||
2946 | */ |
||
2947 | public function processCreation($form, $type = '') |
||
2948 | { |
||
2949 | $this->updateTitle(self::format_title_variable($form->getSubmitValue('exerciseTitle'))); |
||
2950 | $this->updateDescription($form->getSubmitValue('exerciseDescription')); |
||
2951 | $this->updateAttempts($form->getSubmitValue('exerciseAttempts')); |
||
2952 | $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType')); |
||
2953 | $this->updateType($form->getSubmitValue('exerciseType')); |
||
2954 | |||
2955 | // If direct feedback then force to One per page |
||
2956 | if (EXERCISE_FEEDBACK_TYPE_DIRECT == $form->getSubmitValue('exerciseFeedbackType')) { |
||
2957 | $this->updateType(ONE_PER_PAGE); |
||
2958 | } |
||
2959 | |||
2960 | $this->setRandom($form->getSubmitValue('randomQuestions')); |
||
2961 | $this->updateRandomAnswers($form->getSubmitValue('randomAnswers')); |
||
2962 | $this->updateResultsDisabled($form->getSubmitValue('results_disabled')); |
||
2963 | $this->updateExpiredTime($form->getSubmitValue('enabletimercontroltotalminutes')); |
||
2964 | $this->updatePropagateNegative($form->getSubmitValue('propagate_neg')); |
||
2965 | $this->updateSaveCorrectAnswers($form->getSubmitValue('save_correct_answers')); |
||
2966 | $this->updateRandomByCat($form->getSubmitValue('randomByCat')); |
||
2967 | $this->updateTextWhenFinished($form->getSubmitValue('text_when_finished')); |
||
2968 | |||
2969 | if (true === api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
2970 | $this->setTextWhenFinishedFailure($form->getSubmitValue('text_when_finished_failure')); |
||
2971 | } |
||
2972 | |||
2973 | $this->updateDisplayCategoryName($form->getSubmitValue('display_category_name')); |
||
2974 | $this->updateReviewAnswers($form->getSubmitValue('review_answers')); |
||
2975 | $this->updatePassPercentage($form->getSubmitValue('pass_percentage')); |
||
2976 | $this->updateCategories($form->getSubmitValue('category')); |
||
2977 | $this->updateEndButton($form->getSubmitValue('end_button')); |
||
2978 | $this->setOnSuccessMessage($form->getSubmitValue('on_success_message')); |
||
2979 | $this->setOnFailedMessage($form->getSubmitValue('on_failed_message')); |
||
2980 | $this->updateEmailNotificationTemplate($form->getSubmitValue('email_notification_template')); |
||
2981 | $this->setEmailNotificationTemplateToUser($form->getSubmitValue('email_notification_template_to_user')); |
||
2982 | $this->setNotifyUserByEmail($form->getSubmitValue('notify_user_by_email')); |
||
2983 | $this->setModelType($form->getSubmitValue('model_type')); |
||
2984 | $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type')); |
||
2985 | $this->setHideQuestionTitle($form->getSubmitValue('hide_question_title')); |
||
2986 | $this->sessionId = api_get_session_id(); |
||
2987 | $this->setQuestionSelectionType($form->getSubmitValue('question_selection_type')); |
||
2988 | $this->setScoreTypeModel($form->getSubmitValue('score_type_model')); |
||
2989 | $this->setGlobalCategoryId($form->getSubmitValue('global_category_id')); |
||
2990 | $this->setShowPreviousButton($form->getSubmitValue('show_previous_button')); |
||
2991 | $this->setNotifications($form->getSubmitValue('notifications')); |
||
2992 | $this->setExerciseCategoryId($form->getSubmitValue('exercise_category_id')); |
||
2993 | $this->setPageResultConfiguration($form->getSubmitValues()); |
||
2994 | $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number'); |
||
2995 | if ($showHideConfiguration) { |
||
2996 | $this->setHideQuestionNumber($form->getSubmitValue('hide_question_number')); |
||
2997 | } |
||
2998 | |||
2999 | $this->setHideAttemptsTableOnStartPage($form->getSubmitValue('hide_attempts_table')); |
||
3000 | |||
3001 | $this->preventBackwards = (int) $form->getSubmitValue('prevent_backwards'); |
||
3002 | |||
3003 | $this->start_time = null; |
||
3004 | if ($form->getSubmitValue('activate_start_date_check') == 1) { |
||
3005 | $start_time = $form->getSubmitValue('start_time'); |
||
3006 | $this->start_time = api_get_utc_datetime($start_time); |
||
3007 | } |
||
3008 | |||
3009 | $this->end_time = null; |
||
3010 | if ($form->getSubmitValue('activate_end_date_check') == 1) { |
||
3011 | $end_time = $form->getSubmitValue('end_time'); |
||
3012 | $this->end_time = api_get_utc_datetime($end_time); |
||
3013 | } |
||
3014 | |||
3015 | $this->expired_time = 0; |
||
3016 | if ($form->getSubmitValue('enabletimercontrol') == 1) { |
||
3017 | $expired_total_time = $form->getSubmitValue('enabletimercontroltotalminutes'); |
||
3018 | if ($this->expired_time == 0) { |
||
3019 | $this->expired_time = $expired_total_time; |
||
3020 | } |
||
3021 | } |
||
3022 | |||
3023 | $this->random_answers = 0; |
||
3024 | if ($form->getSubmitValue('randomAnswers') == 1) { |
||
3025 | $this->random_answers = 1; |
||
3026 | } |
||
3027 | |||
3028 | // Update title in all LPs that have this quiz added |
||
3029 | if ($form->getSubmitValue('update_title_in_lps') == 1) { |
||
3030 | $courseId = api_get_course_int_id(); |
||
3031 | $table = Database::get_course_table(TABLE_LP_ITEM); |
||
3032 | $sql = "SELECT * FROM $table |
||
3033 | WHERE |
||
3034 | c_id = $courseId AND |
||
3035 | item_type = 'quiz' AND |
||
3036 | path = '".$this->iid."' |
||
3037 | "; |
||
3038 | $result = Database::query($sql); |
||
3039 | $items = Database::store_result($result); |
||
3040 | if (!empty($items)) { |
||
3041 | foreach ($items as $item) { |
||
3042 | $itemId = $item['iid']; |
||
3043 | $sql = "UPDATE $table SET title = '".$this->title."' |
||
3044 | WHERE iid = $itemId AND c_id = $courseId "; |
||
3045 | Database::query($sql); |
||
3046 | } |
||
3047 | } |
||
3048 | } |
||
3049 | |||
3050 | $iid = $this->save($type); |
||
3051 | if (!empty($iid)) { |
||
3052 | $values = $form->getSubmitValues(); |
||
3053 | $values['item_id'] = $iid; |
||
3054 | $extraFieldValue = new ExtraFieldValue('exercise'); |
||
3055 | $extraFieldValue->saveFieldValues($values); |
||
3056 | |||
3057 | Skill::saveSkills($form, ITEM_TYPE_EXERCISE, $iid); |
||
3058 | } |
||
3059 | } |
||
3060 | |||
3061 | public function search_engine_save() |
||
3062 | { |
||
3063 | if ($_POST['index_document'] != 1) { |
||
3064 | return; |
||
3065 | } |
||
3066 | $course_id = api_get_course_id(); |
||
3067 | |||
3068 | require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; |
||
3069 | |||
3070 | $specific_fields = get_specific_field_list(); |
||
3071 | $ic_slide = new IndexableChunk(); |
||
3072 | |||
3073 | $all_specific_terms = ''; |
||
3074 | foreach ($specific_fields as $specific_field) { |
||
3075 | if (isset($_REQUEST[$specific_field['code']])) { |
||
3076 | $sterms = trim($_REQUEST[$specific_field['code']]); |
||
3077 | if (!empty($sterms)) { |
||
3078 | $all_specific_terms .= ' '.$sterms; |
||
3079 | $sterms = explode(',', $sterms); |
||
3080 | foreach ($sterms as $sterm) { |
||
3081 | $ic_slide->addTerm(trim($sterm), $specific_field['code']); |
||
3082 | add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->iid, $sterm); |
||
3083 | } |
||
3084 | } |
||
3085 | } |
||
3086 | } |
||
3087 | |||
3088 | // build the chunk to index |
||
3089 | $ic_slide->addValue("title", $this->exercise); |
||
3090 | $ic_slide->addCourseId($course_id); |
||
3091 | $ic_slide->addToolId(TOOL_QUIZ); |
||
3092 | $xapian_data = [ |
||
3093 | SE_COURSE_ID => $course_id, |
||
3094 | SE_TOOL_ID => TOOL_QUIZ, |
||
3095 | SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => (int) $this->iid], |
||
3096 | SE_USER => (int) api_get_user_id(), |
||
3097 | ]; |
||
3098 | $ic_slide->xapian_data = serialize($xapian_data); |
||
3099 | $exercise_description = $all_specific_terms.' '.$this->description; |
||
3100 | $ic_slide->addValue("content", $exercise_description); |
||
3101 | |||
3102 | $di = new ChamiloIndexer(); |
||
3103 | isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english'; |
||
3104 | $di->connectDb(null, null, $lang); |
||
3105 | $di->addChunk($ic_slide); |
||
3106 | |||
3107 | //index and return search engine document id |
||
3108 | $did = $di->index(); |
||
3109 | if ($did) { |
||
3110 | // save it to db |
||
3111 | $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); |
||
3112 | $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did) |
||
3113 | VALUES (NULL , \'%s\', \'%s\', %s, %s)'; |
||
3114 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid, $did); |
||
3115 | Database::query($sql); |
||
3116 | } |
||
3117 | } |
||
3118 | |||
3119 | public function search_engine_edit() |
||
3120 | { |
||
3121 | // update search enchine and its values table if enabled |
||
3122 | if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) { |
||
3123 | $course_id = api_get_course_id(); |
||
3124 | |||
3125 | // actually, it consists on delete terms from db, |
||
3126 | // insert new ones, create a new search engine document, and remove the old one |
||
3127 | // get search_did |
||
3128 | $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); |
||
3129 | $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s LIMIT 1'; |
||
3130 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid); |
||
3131 | $res = Database::query($sql); |
||
3132 | |||
3133 | if (Database::num_rows($res) > 0) { |
||
3134 | require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; |
||
3135 | |||
3136 | $se_ref = Database::fetch_array($res); |
||
3137 | $specific_fields = get_specific_field_list(); |
||
3138 | $ic_slide = new IndexableChunk(); |
||
3139 | |||
3140 | $all_specific_terms = ''; |
||
3141 | foreach ($specific_fields as $specific_field) { |
||
3142 | delete_all_specific_field_value($course_id, $specific_field['id'], TOOL_QUIZ, $this->iid); |
||
3143 | if (isset($_REQUEST[$specific_field['code']])) { |
||
3144 | $sterms = trim($_REQUEST[$specific_field['code']]); |
||
3145 | $all_specific_terms .= ' '.$sterms; |
||
3146 | $sterms = explode(',', $sterms); |
||
3147 | foreach ($sterms as $sterm) { |
||
3148 | $ic_slide->addTerm(trim($sterm), $specific_field['code']); |
||
3149 | add_specific_field_value($specific_field['id'], $course_id, TOOL_QUIZ, $this->iid, $sterm); |
||
3150 | } |
||
3151 | } |
||
3152 | } |
||
3153 | |||
3154 | // build the chunk to index |
||
3155 | $ic_slide->addValue('title', $this->exercise); |
||
3156 | $ic_slide->addCourseId($course_id); |
||
3157 | $ic_slide->addToolId(TOOL_QUIZ); |
||
3158 | $xapian_data = [ |
||
3159 | SE_COURSE_ID => $course_id, |
||
3160 | SE_TOOL_ID => TOOL_QUIZ, |
||
3161 | SE_DATA => ['type' => SE_DOCTYPE_EXERCISE_EXERCISE, 'exercise_id' => $this->iid], |
||
3162 | SE_USER => api_get_user_id(), |
||
3163 | ]; |
||
3164 | $ic_slide->xapian_data = serialize($xapian_data); |
||
3165 | $exercise_description = $all_specific_terms.' '.$this->description; |
||
3166 | $ic_slide->addValue('content', $exercise_description); |
||
3167 | |||
3168 | $di = new ChamiloIndexer(); |
||
3169 | isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english'; |
||
3170 | $di->connectDb(null, null, $lang); |
||
3171 | $di->remove_document($se_ref['search_did']); |
||
3172 | $di->addChunk($ic_slide); |
||
3173 | |||
3174 | //index and return search engine document id |
||
3175 | $did = $di->index(); |
||
3176 | if ($did) { |
||
3177 | // save it to db |
||
3178 | $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=\'%s\''; |
||
3179 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid); |
||
3180 | Database::query($sql); |
||
3181 | $sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, search_did) |
||
3182 | VALUES (NULL , \'%s\', \'%s\', %s, %s)'; |
||
3183 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid, $did); |
||
3184 | Database::query($sql); |
||
3185 | } |
||
3186 | } else { |
||
3187 | $this->search_engine_save(); |
||
3188 | } |
||
3189 | } |
||
3190 | } |
||
3191 | |||
3192 | public function search_engine_delete() |
||
3193 | { |
||
3194 | // remove from search engine if enabled |
||
3195 | if ('true' == api_get_setting('search_enabled') && extension_loaded('xapian')) { |
||
3196 | $course_id = api_get_course_id(); |
||
3197 | $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); |
||
3198 | $sql = 'SELECT * FROM %s |
||
3199 | WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL |
||
3200 | LIMIT 1'; |
||
3201 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid); |
||
3202 | $res = Database::query($sql); |
||
3203 | if (Database::num_rows($res) > 0) { |
||
3204 | $row = Database::fetch_array($res); |
||
3205 | $di = new ChamiloIndexer(); |
||
3206 | $di->remove_document($row['search_did']); |
||
3207 | unset($di); |
||
3208 | $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
3209 | foreach ($this->questionList as $question_i) { |
||
3210 | $sql = 'SELECT type FROM %s WHERE iid = %s'; |
||
3211 | $sql = sprintf($sql, $tbl_quiz_question, $question_i); |
||
3212 | $qres = Database::query($sql); |
||
3213 | if (Database::num_rows($qres) > 0) { |
||
3214 | $qrow = Database::fetch_array($qres); |
||
3215 | $objQuestion = Question::getInstance($qrow['type']); |
||
3216 | $objQuestion = Question::read((int) $question_i); |
||
3217 | $objQuestion->search_engine_edit($this->iid, false, true); |
||
3218 | unset($objQuestion); |
||
3219 | } |
||
3220 | } |
||
3221 | } |
||
3222 | $sql = 'DELETE FROM %s |
||
3223 | WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level IS NULL |
||
3224 | LIMIT 1'; |
||
3225 | $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->iid); |
||
3226 | Database::query($sql); |
||
3227 | |||
3228 | // remove terms from db |
||
3229 | require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; |
||
3230 | delete_all_values_for_item($course_id, TOOL_QUIZ, $this->iid); |
||
3231 | } |
||
3232 | } |
||
3233 | |||
3234 | public function selectExpiredTime() |
||
3235 | { |
||
3236 | return $this->expired_time; |
||
3237 | } |
||
3238 | |||
3239 | /** |
||
3240 | * Cleans the student's results only for the Exercise tool (Not from the LP) |
||
3241 | * The LP results are NOT deleted by default, otherwise put $cleanLpTests = true |
||
3242 | * Works with exercises in sessions. |
||
3243 | * |
||
3244 | * @param bool $cleanLpTests |
||
3245 | * @param string $cleanResultBeforeDate |
||
3246 | * |
||
3247 | * @return int quantity of user's exercises deleted |
||
3248 | */ |
||
3249 | public function cleanResults($cleanLpTests = false, $cleanResultBeforeDate = null) |
||
3250 | { |
||
3251 | $sessionId = api_get_session_id(); |
||
3252 | $table_track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
3253 | $table_track_e_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
3254 | |||
3255 | $sql_where = ' AND |
||
3256 | orig_lp_id = 0 AND |
||
3257 | orig_lp_item_id = 0'; |
||
3258 | |||
3259 | // if we want to delete results from LP too |
||
3260 | if ($cleanLpTests) { |
||
3261 | $sql_where = ''; |
||
3262 | } |
||
3263 | |||
3264 | // if we want to delete attempts before date $cleanResultBeforeDate |
||
3265 | // $cleanResultBeforeDate must be a valid UTC-0 date yyyy-mm-dd |
||
3266 | |||
3267 | if (!empty($cleanResultBeforeDate)) { |
||
3268 | $cleanResultBeforeDate = Database::escape_string($cleanResultBeforeDate); |
||
3269 | if (api_is_valid_date($cleanResultBeforeDate)) { |
||
3270 | $sql_where .= " AND exe_date <= '$cleanResultBeforeDate' "; |
||
3271 | } else { |
||
3272 | return 0; |
||
3273 | } |
||
3274 | } |
||
3275 | |||
3276 | $sql = "SELECT exe_id |
||
3277 | FROM $table_track_e_exercises |
||
3278 | WHERE |
||
3279 | c_id = ".api_get_course_int_id()." AND |
||
3280 | exe_exo_id = ".$this->iid." AND |
||
3281 | session_id = ".$sessionId." ". |
||
3282 | $sql_where; |
||
3283 | |||
3284 | $result = Database::query($sql); |
||
3285 | $exe_list = Database::store_result($result); |
||
3286 | |||
3287 | // deleting TRACK_E_ATTEMPT table |
||
3288 | // check if exe in learning path or not |
||
3289 | $i = 0; |
||
3290 | if (is_array($exe_list) && count($exe_list) > 0) { |
||
3291 | foreach ($exe_list as $item) { |
||
3292 | $sql = "DELETE FROM $table_track_e_attempt |
||
3293 | WHERE exe_id = '".$item['exe_id']."'"; |
||
3294 | Database::query($sql); |
||
3295 | $i++; |
||
3296 | } |
||
3297 | } |
||
3298 | |||
3299 | // delete TRACK_E_EXERCISES table |
||
3300 | $sql = "DELETE FROM $table_track_e_exercises |
||
3301 | WHERE |
||
3302 | c_id = ".api_get_course_int_id()." AND |
||
3303 | exe_exo_id = ".$this->iid." $sql_where AND |
||
3304 | session_id = ".$sessionId; |
||
3305 | Database::query($sql); |
||
3306 | |||
3307 | $this->generateStats($this->iid, api_get_course_info(), $sessionId); |
||
3308 | |||
3309 | Event::addEvent( |
||
3310 | LOG_EXERCISE_RESULT_DELETE, |
||
3311 | LOG_EXERCISE_ID, |
||
3312 | $this->iid, |
||
3313 | null, |
||
3314 | null, |
||
3315 | api_get_course_int_id(), |
||
3316 | $sessionId |
||
3317 | ); |
||
3318 | |||
3319 | return $i; |
||
3320 | } |
||
3321 | |||
3322 | /** |
||
3323 | * Copies an exercise (duplicate all questions and answers). |
||
3324 | */ |
||
3325 | public function copyExercise() |
||
3326 | { |
||
3327 | $exerciseObject = $this; |
||
3328 | $categories = $exerciseObject->getCategoriesInExercise(true); |
||
3329 | // Get all questions no matter the order/category settings |
||
3330 | $questionList = $exerciseObject->getQuestionOrderedList(); |
||
3331 | $sourceId = $exerciseObject->iid; |
||
3332 | // Force the creation of a new exercise |
||
3333 | $exerciseObject->updateTitle($exerciseObject->selectTitle().' - '.get_lang('Copy')); |
||
3334 | // Hides the new exercise |
||
3335 | $exerciseObject->updateStatus(false); |
||
3336 | $exerciseObject->updateId(0); |
||
3337 | $exerciseObject->sessionId = api_get_session_id(); |
||
3338 | $courseId = api_get_course_int_id(); |
||
3339 | $exerciseObject->save(); |
||
3340 | $newId = $exerciseObject->selectId(); |
||
3341 | $exerciseRelQuestionTable = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
3342 | |||
3343 | $count = 1; |
||
3344 | $batchSize = 20; |
||
3345 | $em = Database::getManager(); |
||
3346 | |||
3347 | if ($newId && !empty($questionList)) { |
||
3348 | $extraField = new ExtraFieldValue('exercise'); |
||
3349 | $extraField->copy($sourceId, $newId); |
||
3350 | |||
3351 | // Question creation |
||
3352 | foreach ($questionList as $oldQuestionId) { |
||
3353 | $oldQuestionObj = Question::read($oldQuestionId, null, false); |
||
3354 | $newQuestionId = $oldQuestionObj->duplicate(); |
||
3355 | if ($newQuestionId) { |
||
3356 | $newQuestionObj = Question::read($newQuestionId, null, false); |
||
3357 | if (isset($newQuestionObj) && $newQuestionObj) { |
||
3358 | $sql = "INSERT INTO $exerciseRelQuestionTable (c_id, question_id, exercice_id, question_order) |
||
3359 | VALUES ($courseId, ".$newQuestionId.", ".$newId.", '$count')"; |
||
3360 | Database::query($sql); |
||
3361 | $count++; |
||
3362 | |||
3363 | if (!empty($oldQuestionObj->category)) { |
||
3364 | $newQuestionObj->saveCategory($oldQuestionObj->category); |
||
3365 | } |
||
3366 | |||
3367 | // This should be moved to the duplicate function |
||
3368 | $newAnswerObj = new Answer($oldQuestionId, $courseId, $exerciseObject); |
||
3369 | $newAnswerObj->read(); |
||
3370 | $newAnswerObj->duplicate($newQuestionObj); |
||
3371 | |||
3372 | if (($count % $batchSize) === 0) { |
||
3373 | $em->clear(); // Detaches all objects from Doctrine! |
||
3374 | } |
||
3375 | } |
||
3376 | } |
||
3377 | } |
||
3378 | if (!empty($categories)) { |
||
3379 | $newCategoryList = []; |
||
3380 | foreach ($categories as $category) { |
||
3381 | $newCategoryList[$category['category_id']] = $category['count_questions']; |
||
3382 | } |
||
3383 | $exerciseObject->save_categories_in_exercise($newCategoryList); |
||
3384 | } |
||
3385 | } |
||
3386 | } |
||
3387 | |||
3388 | /** |
||
3389 | * Changes the exercise status. |
||
3390 | * |
||
3391 | * @param string $status - exercise status |
||
3392 | */ |
||
3393 | public function updateStatus($status) |
||
3394 | { |
||
3395 | $this->active = $status; |
||
3396 | } |
||
3397 | |||
3398 | /** |
||
3399 | * Get the contents of the track_e_exercises table for the current |
||
3400 | * exercise object, in the specific context (if defined) of a |
||
3401 | * learning path and optionally a current progress status. |
||
3402 | * |
||
3403 | * @param int $lp_id |
||
3404 | * @param int $lp_item_id |
||
3405 | * @param int $lp_item_view_id |
||
3406 | * @param string $status |
||
3407 | * |
||
3408 | * @return array |
||
3409 | */ |
||
3410 | public function get_stat_track_exercise_info( |
||
3411 | $lp_id = 0, |
||
3412 | $lp_item_id = 0, |
||
3413 | $lp_item_view_id = 0, |
||
3414 | $status = 'incomplete' |
||
3415 | ) { |
||
3416 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
3417 | if (empty($lp_id)) { |
||
3418 | $lp_id = 0; |
||
3419 | } |
||
3420 | if (empty($lp_item_id)) { |
||
3421 | $lp_item_id = 0; |
||
3422 | } |
||
3423 | if (empty($lp_item_view_id)) { |
||
3424 | $lp_item_view_id = 0; |
||
3425 | } |
||
3426 | $condition = ' WHERE exe_exo_id = '.$this->iid.' AND |
||
3427 | exe_user_id = '.api_get_user_id().' AND |
||
3428 | c_id = '.api_get_course_int_id().' AND |
||
3429 | status = \''.Database::escape_string($status).'\' AND |
||
3430 | orig_lp_id = \''.$lp_id.'\' AND |
||
3431 | orig_lp_item_id = \''.$lp_item_id.'\' AND |
||
3432 | orig_lp_item_view_id = \''.$lp_item_view_id.'\' AND |
||
3433 | session_id = \''.api_get_session_id().'\' LIMIT 1'; //Adding limit 1 just in case |
||
3434 | |||
3435 | $sql_track = 'SELECT * FROM '.$track_exercises.$condition; |
||
3436 | |||
3437 | $result = Database::query($sql_track); |
||
3438 | $new_array = []; |
||
3439 | if (Database::num_rows($result) > 0) { |
||
3440 | $new_array = Database::fetch_array($result, 'ASSOC'); |
||
3441 | $new_array['num_exe'] = Database::num_rows($result); |
||
3442 | } |
||
3443 | |||
3444 | return $new_array; |
||
3445 | } |
||
3446 | |||
3447 | /** |
||
3448 | * Saves a test attempt. |
||
3449 | * |
||
3450 | * @param int $clock_expired_time clock_expired_time |
||
3451 | * @param int int lp id |
||
3452 | * @param int int lp item id |
||
3453 | * @param int int lp item_view id |
||
3454 | * @param array $questionList |
||
3455 | * @param float $weight |
||
3456 | * |
||
3457 | * @return int |
||
3458 | */ |
||
3459 | public function save_stat_track_exercise_info( |
||
3460 | $clock_expired_time = 0, |
||
3461 | $safe_lp_id = 0, |
||
3462 | $safe_lp_item_id = 0, |
||
3463 | $safe_lp_item_view_id = 0, |
||
3464 | $questionList = [], |
||
3465 | $weight = 0 |
||
3466 | ) { |
||
3467 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
3468 | $safe_lp_id = (int) $safe_lp_id; |
||
3469 | $safe_lp_item_id = (int) $safe_lp_item_id; |
||
3470 | $safe_lp_item_view_id = (int) $safe_lp_item_view_id; |
||
3471 | |||
3472 | if (empty($clock_expired_time)) { |
||
3473 | $clock_expired_time = null; |
||
3474 | } |
||
3475 | |||
3476 | $questionList = array_map('intval', $questionList); |
||
3477 | |||
3478 | $params = [ |
||
3479 | 'exe_exo_id' => $this->iid, |
||
3480 | 'exe_user_id' => api_get_user_id(), |
||
3481 | 'c_id' => api_get_course_int_id(), |
||
3482 | 'status' => 'incomplete', |
||
3483 | 'session_id' => api_get_session_id(), |
||
3484 | 'data_tracking' => implode(',', $questionList), |
||
3485 | 'start_date' => api_get_utc_datetime(), |
||
3486 | 'orig_lp_id' => $safe_lp_id, |
||
3487 | 'orig_lp_item_id' => $safe_lp_item_id, |
||
3488 | 'orig_lp_item_view_id' => $safe_lp_item_view_id, |
||
3489 | 'exe_weighting' => $weight, |
||
3490 | 'user_ip' => Database::escape_string(api_get_real_ip()), |
||
3491 | 'exe_date' => api_get_utc_datetime(), |
||
3492 | 'exe_result' => 0, |
||
3493 | 'steps_counter' => 0, |
||
3494 | 'exe_duration' => 0, |
||
3495 | 'expired_time_control' => $clock_expired_time, |
||
3496 | 'questions_to_check' => '', |
||
3497 | ]; |
||
3498 | |||
3499 | return Database::insert($track_exercises, $params); |
||
3500 | } |
||
3501 | |||
3502 | /** |
||
3503 | * @param int $question_id |
||
3504 | * @param int $questionNum |
||
3505 | * @param array $questions_in_media |
||
3506 | * @param string $currentAnswer |
||
3507 | * @param array $myRemindList |
||
3508 | * @param bool $showPreviousButton |
||
3509 | * |
||
3510 | * @return string |
||
3511 | */ |
||
3512 | public function show_button( |
||
3513 | $question_id, |
||
3514 | $questionNum, |
||
3515 | $questions_in_media = [], |
||
3516 | $currentAnswer = '', |
||
3517 | $myRemindList = [], |
||
3518 | $showPreviousButton = true, |
||
3519 | int $lpId = 0, |
||
3520 | int $lpItemId = 0, |
||
3521 | int $lpItemViewId = 0 |
||
3522 | ) { |
||
3523 | $nbrQuestions = $this->countQuestionsInExercise(); |
||
3524 | $buttonList = []; |
||
3525 | $html = $label = ''; |
||
3526 | $hotspotGet = isset($_POST['hotspot']) ? Security::remove_XSS($_POST['hotspot']) : null; |
||
3527 | |||
3528 | if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]) && |
||
3529 | $this->type == ONE_PER_PAGE |
||
3530 | ) { |
||
3531 | $urlTitle = get_lang('ContinueTest'); |
||
3532 | if ($questionNum == count($this->questionList)) { |
||
3533 | $urlTitle = get_lang('EndTest'); |
||
3534 | } |
||
3535 | |||
3536 | $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_submit_modal.php?'.api_get_cidreq(); |
||
3537 | $url .= '&'.http_build_query([ |
||
3538 | 'learnpath_id' => $lpId, |
||
3539 | 'learnpath_item_id' => $lpItemId, |
||
3540 | 'learnpath_item_view_id' => $lpItemViewId, |
||
3541 | 'hotspot' => $hotspotGet, |
||
3542 | 'nbrQuestions' => $nbrQuestions, |
||
3543 | 'num' => $questionNum, |
||
3544 | 'exerciseType' => $this->type, |
||
3545 | 'exerciseId' => $this->iid, |
||
3546 | 'reminder' => empty($myRemindList) ? null : 2, |
||
3547 | 'tryagain' => isset($_REQUEST['tryagain']) && 1 === (int) $_REQUEST['tryagain'] ? 1 : 0, |
||
3548 | ]); |
||
3549 | |||
3550 | $params = [ |
||
3551 | 'class' => 'ajax btn btn-default no-close-button', |
||
3552 | 'data-title' => Security::remove_XSS(get_lang('Comment')), |
||
3553 | 'data-size' => 'md', |
||
3554 | 'id' => "button_$question_id", |
||
3555 | ]; |
||
3556 | |||
3557 | if ($this->getFeedbackType() === EXERCISE_FEEDBACK_TYPE_POPUP) { |
||
3558 | $params['data-block-closing'] = 'true'; |
||
3559 | $params['class'] .= ' no-header '; |
||
3560 | } |
||
3561 | |||
3562 | $html .= Display::url($urlTitle, $url, $params); |
||
3563 | $html .= '<br />'; |
||
3564 | |||
3565 | return $html; |
||
3566 | } |
||
3567 | |||
3568 | if (!api_is_allowed_to_session_edit()) { |
||
3569 | return ''; |
||
3570 | } |
||
3571 | |||
3572 | $isReviewingAnswers = isset($_REQUEST['reminder']) && 2 === (int) $_REQUEST['reminder']; |
||
3573 | $endReminderValue = false; |
||
3574 | if (!empty($myRemindList) && $isReviewingAnswers) { |
||
3575 | $endValue = end($myRemindList); |
||
3576 | if ($endValue == $question_id) { |
||
3577 | $endReminderValue = true; |
||
3578 | } |
||
3579 | } |
||
3580 | $endTest = false; |
||
3581 | if ($this->type == ALL_ON_ONE_PAGE || $nbrQuestions == $questionNum || $endReminderValue) { |
||
3582 | if ($this->review_answers) { |
||
3583 | $label = get_lang('ReviewQuestions'); |
||
3584 | $class = 'btn btn-success'; |
||
3585 | } else { |
||
3586 | $endTest = true; |
||
3587 | $label = get_lang('EndTest'); |
||
3588 | $class = 'btn btn-warning'; |
||
3589 | } |
||
3590 | } else { |
||
3591 | $label = get_lang('NextQuestion'); |
||
3592 | $class = 'btn btn-primary'; |
||
3593 | } |
||
3594 | // used to select it with jquery |
||
3595 | $class .= ' question-validate-btn'; |
||
3596 | if ($this->type == ONE_PER_PAGE) { |
||
3597 | if ($questionNum != 1 && $this->showPreviousButton()) { |
||
3598 | $prev_question = $questionNum - 2; |
||
3599 | $showPreview = true; |
||
3600 | if (!empty($myRemindList) && $isReviewingAnswers) { |
||
3601 | $beforeId = null; |
||
3602 | for ($i = 0; $i < count($myRemindList); $i++) { |
||
0 ignored issues
–
show
|
|||
3603 | if (isset($myRemindList[$i]) && $myRemindList[$i] == $question_id) { |
||
3604 | $beforeId = isset($myRemindList[$i - 1]) ? $myRemindList[$i - 1] : null; |
||
3605 | break; |
||
3606 | } |
||
3607 | } |
||
3608 | |||
3609 | if (empty($beforeId)) { |
||
3610 | $showPreview = false; |
||
3611 | } else { |
||
3612 | $num = 0; |
||
3613 | foreach ($this->questionList as $originalQuestionId) { |
||
3614 | if ($originalQuestionId == $beforeId) { |
||
3615 | break; |
||
3616 | } |
||
3617 | $num++; |
||
3618 | } |
||
3619 | $prev_question = $num; |
||
3620 | } |
||
3621 | } |
||
3622 | |||
3623 | if ($showPreviousButton && $showPreview && 0 === $this->getPreventBackwards()) { |
||
3624 | $buttonList[] = Display::button( |
||
3625 | 'previous_question_and_save', |
||
3626 | get_lang('PreviousQuestion'), |
||
3627 | [ |
||
3628 | 'type' => 'button', |
||
3629 | 'class' => 'btn btn-default', |
||
3630 | 'data-prev' => $prev_question, |
||
3631 | 'data-question' => $question_id, |
||
3632 | ] |
||
3633 | ); |
||
3634 | } |
||
3635 | } |
||
3636 | |||
3637 | // Next question |
||
3638 | if (!empty($questions_in_media)) { |
||
3639 | $buttonList[] = Display::button( |
||
3640 | 'save_question_list', |
||
3641 | $label, |
||
3642 | [ |
||
3643 | 'type' => 'button', |
||
3644 | 'class' => $class, |
||
3645 | 'data-list' => implode(",", $questions_in_media), |
||
3646 | ] |
||
3647 | ); |
||
3648 | } else { |
||
3649 | $attributes = ['type' => 'button', 'class' => $class, 'data-question' => $question_id]; |
||
3650 | $name = 'save_now'; |
||
3651 | if ($endTest && api_get_configuration_value('quiz_check_all_answers_before_end_test')) { |
||
3652 | $name = 'check_answers'; |
||
3653 | } |
||
3654 | $buttonList[] = Display::button( |
||
3655 | $name, |
||
3656 | $label, |
||
3657 | $attributes |
||
3658 | ); |
||
3659 | } |
||
3660 | $buttonList[] = '<span id="save_for_now_'.$question_id.'" class="exercise_save_mini_message"></span>'; |
||
3661 | |||
3662 | $html .= implode(PHP_EOL, $buttonList).PHP_EOL; |
||
3663 | |||
3664 | return $html; |
||
3665 | } |
||
3666 | |||
3667 | if ($this->review_answers) { |
||
3668 | $all_label = get_lang('ReviewQuestions'); |
||
3669 | $class = 'btn btn-success'; |
||
3670 | } else { |
||
3671 | $all_label = get_lang('EndTest'); |
||
3672 | $class = 'btn btn-warning'; |
||
3673 | } |
||
3674 | // used to select it with jquery |
||
3675 | $class .= ' question-validate-btn'; |
||
3676 | $buttonList[] = Display::button( |
||
3677 | 'validate_all', |
||
3678 | $all_label, |
||
3679 | ['type' => 'button', 'class' => $class] |
||
3680 | ); |
||
3681 | $buttonList[] = Display::span(null, ['id' => 'save_all_response']); |
||
3682 | $html .= implode(PHP_EOL, $buttonList).PHP_EOL; |
||
3683 | |||
3684 | return $html; |
||
3685 | } |
||
3686 | |||
3687 | /** |
||
3688 | * @param int $timeLeft in seconds |
||
3689 | * @param string $url |
||
3690 | * |
||
3691 | * @return string |
||
3692 | */ |
||
3693 | public function showSimpleTimeControl($timeLeft, $url = '') |
||
3694 | { |
||
3695 | $timeLeft = (int) $timeLeft; |
||
3696 | |||
3697 | return "<script> |
||
3698 | function openClockWarning() { |
||
3699 | $('#clock_warning').dialog({ |
||
3700 | modal:true, |
||
3701 | height:320, |
||
3702 | width:550, |
||
3703 | closeOnEscape: false, |
||
3704 | resizable: false, |
||
3705 | buttons: { |
||
3706 | '".addslashes(get_lang('Close'))."': function() { |
||
3707 | $('#clock_warning').dialog('close'); |
||
3708 | } |
||
3709 | }, |
||
3710 | close: function() { |
||
3711 | window.location.href = '$url'; |
||
3712 | } |
||
3713 | }); |
||
3714 | $('#clock_warning').dialog('open'); |
||
3715 | $('#counter_to_redirect').epiclock({ |
||
3716 | mode: $.epiclock.modes.countdown, |
||
3717 | offset: {seconds: 5}, |
||
3718 | format: 's' |
||
3719 | }).bind('timer', function () { |
||
3720 | window.location.href = '$url'; |
||
3721 | }); |
||
3722 | } |
||
3723 | |||
3724 | function onExpiredTimeExercise() { |
||
3725 | $('#wrapper-clock').hide(); |
||
3726 | $('#expired-message-id').show(); |
||
3727 | // Fixes bug #5263 |
||
3728 | $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."'); |
||
3729 | openClockWarning(); |
||
3730 | } |
||
3731 | |||
3732 | $(function() { |
||
3733 | // time in seconds when using minutes there are some seconds lost |
||
3734 | var time_left = parseInt(".$timeLeft."); |
||
3735 | $('#exercise_clock_warning').epiclock({ |
||
3736 | mode: $.epiclock.modes.countdown, |
||
3737 | offset: {seconds: time_left}, |
||
3738 | format: 'x:i:s', |
||
3739 | renderer: 'minute' |
||
3740 | }).bind('timer', function () { |
||
3741 | onExpiredTimeExercise(); |
||
3742 | }); |
||
3743 | $('#submit_save').click(function () {}); |
||
3744 | }); |
||
3745 | </script>"; |
||
3746 | } |
||
3747 | |||
3748 | /** |
||
3749 | * So the time control will work. |
||
3750 | * |
||
3751 | * @param int $timeLeft |
||
3752 | * @param string $redirectToUrl |
||
3753 | * |
||
3754 | * @return string |
||
3755 | */ |
||
3756 | public function showTimeControlJS($timeLeft, $redirectToUrl = '') |
||
3757 | { |
||
3758 | $timeLeft = (int) $timeLeft; |
||
3759 | $script = 'redirectExerciseToResult();'; |
||
3760 | if (ALL_ON_ONE_PAGE == $this->type) { |
||
3761 | $script = "save_now_all('validate');"; |
||
3762 | } elseif (ONE_PER_PAGE == $this->type) { |
||
3763 | $script = 'window.quizTimeEnding = true; |
||
3764 | $(\'[name="save_now"]\').trigger(\'click\');'; |
||
3765 | } |
||
3766 | |||
3767 | $exerciseSubmitRedirect = ''; |
||
3768 | if (!empty($redirectToUrl)) { |
||
3769 | $exerciseSubmitRedirect = "window.location = '$redirectToUrl'"; |
||
3770 | } |
||
3771 | |||
3772 | return "<script> |
||
3773 | function openClockWarning() { |
||
3774 | $('#clock_warning').dialog({ |
||
3775 | modal:true, |
||
3776 | height:320, |
||
3777 | width:550, |
||
3778 | closeOnEscape: false, |
||
3779 | resizable: false, |
||
3780 | buttons: { |
||
3781 | '".addslashes(get_lang('EndTest'))."': function() { |
||
3782 | $('#clock_warning').dialog('close'); |
||
3783 | } |
||
3784 | }, |
||
3785 | close: function() { |
||
3786 | send_form(); |
||
3787 | } |
||
3788 | }); |
||
3789 | |||
3790 | $('#clock_warning').dialog('open'); |
||
3791 | $('#counter_to_redirect').epiclock({ |
||
3792 | mode: $.epiclock.modes.countdown, |
||
3793 | offset: {seconds: 5}, |
||
3794 | format: 's' |
||
3795 | }).bind('timer', function () { |
||
3796 | send_form(); |
||
3797 | }); |
||
3798 | } |
||
3799 | |||
3800 | function send_form() { |
||
3801 | if ($('#exercise_form').length) { |
||
3802 | $script |
||
3803 | } else { |
||
3804 | $exerciseSubmitRedirect |
||
3805 | // In exercise_reminder.php |
||
3806 | final_submit(); |
||
3807 | } |
||
3808 | } |
||
3809 | |||
3810 | function onExpiredTimeExercise() { |
||
3811 | $('#wrapper-clock').hide(); |
||
3812 | $('#expired-message-id').show(); |
||
3813 | // Fixes bug #5263 |
||
3814 | $('#num_current_id').attr('value', '".$this->selectNbrQuestions()."'); |
||
3815 | openClockWarning(); |
||
3816 | } |
||
3817 | |||
3818 | $(function() { |
||
3819 | // time in seconds when using minutes there are some seconds lost |
||
3820 | var time_left = parseInt(".$timeLeft."); |
||
3821 | $('#exercise_clock_warning').epiclock({ |
||
3822 | mode: $.epiclock.modes.countdown, |
||
3823 | offset: {seconds: time_left}, |
||
3824 | format: 'x:C:s', |
||
3825 | renderer: 'minute' |
||
3826 | }).bind('timer', function () { |
||
3827 | onExpiredTimeExercise(); |
||
3828 | }); |
||
3829 | $('#submit_save').click(function () {}); |
||
3830 | }); |
||
3831 | </script>"; |
||
3832 | } |
||
3833 | |||
3834 | /** |
||
3835 | * Prepare, calculate result and save answer to the database by calling |
||
3836 | * Event::saveQuestionAttempt() once everything is ready. |
||
3837 | * |
||
3838 | * @param int $exeId |
||
3839 | * @param int $questionId |
||
3840 | * @param mixed $choice the user-selected option |
||
3841 | * @param string $from function is called from 'exercise_show' or 'exercise_result' |
||
3842 | * @param array $exerciseResultCoordinates the hotspot coordinates $hotspot[$question_id] = coordinates |
||
3843 | * @param bool $save_results save results in the DB or just show the response |
||
3844 | * @param bool $from_database gets information from DB or from the current selection |
||
3845 | * @param bool $show_result show results or not |
||
3846 | * @param int $propagate_neg |
||
3847 | * @param array $hotspot_delineation_result |
||
3848 | * @param bool $showTotalScoreAndUserChoicesInLastAttempt |
||
3849 | * @param bool $updateResults |
||
3850 | * @param bool $showHotSpotDelineationTable |
||
3851 | * @param int $questionDuration seconds |
||
3852 | * |
||
3853 | * @return array|false |
||
3854 | */ |
||
3855 | public function manage_answer( |
||
3856 | $exeId, |
||
3857 | $questionId, |
||
3858 | $choice, |
||
3859 | $from = 'exercise_show', |
||
3860 | $exerciseResultCoordinates = [], |
||
3861 | $save_results = true, |
||
3862 | $from_database = false, |
||
3863 | $show_result = true, |
||
3864 | $propagate_neg = 0, |
||
3865 | $hotspot_delineation_result = [], |
||
3866 | $showTotalScoreAndUserChoicesInLastAttempt = true, |
||
3867 | $updateResults = false, |
||
3868 | $showHotSpotDelineationTable = false, |
||
3869 | $questionDuration = 0 |
||
3870 | ) { |
||
3871 | $debug = false; |
||
3872 | //needed in order to use in the exercise_attempt() for the time |
||
3873 | global $learnpath_id, $learnpath_item_id; |
||
3874 | require_once api_get_path(LIBRARY_PATH).'geometry.lib.php'; |
||
3875 | $em = Database::getManager(); |
||
3876 | $feedback_type = $this->getFeedbackType(); |
||
3877 | $results_disabled = $this->selectResultsDisabled(); |
||
3878 | $questionDuration = (int) $questionDuration; |
||
3879 | |||
3880 | if ($debug) { |
||
3881 | error_log("<------ manage_answer ------> "); |
||
3882 | error_log('exe_id: '.$exeId); |
||
3883 | error_log('$from: '.$from); |
||
3884 | error_log('$save_results: '.intval($save_results)); |
||
3885 | error_log('$from_database: '.intval($from_database)); |
||
3886 | error_log('$show_result: '.intval($show_result)); |
||
3887 | error_log('$propagate_neg: '.$propagate_neg); |
||
3888 | error_log('$exerciseResultCoordinates: '.print_r($exerciseResultCoordinates, 1)); |
||
3889 | error_log('$hotspot_delineation_result: '.print_r($hotspot_delineation_result, 1)); |
||
3890 | error_log('$learnpath_id: '.$learnpath_id); |
||
3891 | error_log('$learnpath_item_id: '.$learnpath_item_id); |
||
3892 | error_log('$choice: '.print_r($choice, 1)); |
||
3893 | error_log('-----------------------------'); |
||
3894 | } |
||
3895 | |||
3896 | $final_overlap = 0; |
||
3897 | $final_missing = 0; |
||
3898 | $final_excess = 0; |
||
3899 | $overlap_color = 0; |
||
3900 | $missing_color = 0; |
||
3901 | $excess_color = 0; |
||
3902 | $threadhold1 = 0; |
||
3903 | $threadhold2 = 0; |
||
3904 | $threadhold3 = 0; |
||
3905 | $arrques = null; |
||
3906 | $arrans = null; |
||
3907 | $studentChoice = null; |
||
3908 | $expectedAnswer = ''; |
||
3909 | $calculatedChoice = ''; |
||
3910 | $calculatedStatus = ''; |
||
3911 | $questionId = (int) $questionId; |
||
3912 | $exeId = (int) $exeId; |
||
3913 | $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
3914 | $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER); |
||
3915 | $studentChoiceDegree = null; |
||
3916 | |||
3917 | // Creates a temporary Question object |
||
3918 | $course_id = $this->course_id; |
||
3919 | $objQuestionTmp = Question::read($questionId, $this->course); |
||
3920 | |||
3921 | if (false === $objQuestionTmp) { |
||
3922 | return false; |
||
3923 | } |
||
3924 | |||
3925 | $questionName = $objQuestionTmp->selectTitle(); |
||
3926 | $questionWeighting = $objQuestionTmp->selectWeighting(); |
||
3927 | $answerType = $objQuestionTmp->selectType(); |
||
3928 | $quesId = $objQuestionTmp->selectId(); |
||
3929 | $extra = $objQuestionTmp->extra; |
||
3930 | $next = 1; //not for now |
||
3931 | $totalWeighting = 0; |
||
3932 | $totalScore = 0; |
||
3933 | |||
3934 | // Extra information of the question |
||
3935 | if (( |
||
3936 | $answerType == MULTIPLE_ANSWER_TRUE_FALSE || |
||
3937 | $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY |
||
3938 | ) |
||
3939 | && !empty($extra) |
||
3940 | ) { |
||
3941 | $extra = explode(':', $extra); |
||
3942 | // Fixes problems with negatives values using intval |
||
3943 | $true_score = (float) trim($extra[0]); |
||
3944 | $false_score = (float) trim($extra[1]); |
||
3945 | $doubt_score = (float) trim($extra[2]); |
||
3946 | } |
||
3947 | |||
3948 | // Construction of the Answer object |
||
3949 | $objAnswerTmp = new Answer($questionId, $course_id); |
||
3950 | $nbrAnswers = $objAnswerTmp->selectNbrAnswers(); |
||
3951 | |||
3952 | if ($debug) { |
||
3953 | error_log('Count of possible answers: '.$nbrAnswers); |
||
3954 | error_log('$answerType: '.$answerType); |
||
3955 | } |
||
3956 | |||
3957 | if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
3958 | $choiceTmp = $choice; |
||
3959 | $choice = $choiceTmp['choice'] ?? ''; |
||
3960 | $choiceDegreeCertainty = $choiceTmp['choiceDegreeCertainty'] ?? ''; |
||
3961 | } |
||
3962 | |||
3963 | if ($answerType == FREE_ANSWER || |
||
3964 | $answerType == ORAL_EXPRESSION || |
||
3965 | $answerType == CALCULATED_ANSWER || |
||
3966 | $answerType == ANNOTATION || |
||
3967 | $answerType == UPLOAD_ANSWER || |
||
3968 | $answerType == ANSWER_IN_OFFICE_DOC |
||
3969 | ) { |
||
3970 | $nbrAnswers = 1; |
||
3971 | } |
||
3972 | |||
3973 | $generatedFile = ''; |
||
3974 | if ($answerType == ORAL_EXPRESSION) { |
||
3975 | $exe_info = Event::get_exercise_results_by_attempt($exeId); |
||
3976 | $exe_info = $exe_info[$exeId] ?? null; |
||
3977 | $objQuestionTmp->initFile( |
||
3978 | api_get_session_id(), |
||
3979 | $exe_info['exe_user_id'] ?? api_get_user_id(), |
||
3980 | $exe_info['exe_exo_id'] ?? $this->iid, |
||
3981 | $exe_info['exe_id'] ?? $exeId |
||
3982 | ); |
||
3983 | |||
3984 | // Probably this attempt came in an exercise all question by page |
||
3985 | if ($feedback_type == 0) { |
||
3986 | $objQuestionTmp->replaceWithRealExe($exeId); |
||
3987 | } |
||
3988 | $generatedFile = $objQuestionTmp->getFileUrl(); |
||
3989 | } |
||
3990 | |||
3991 | $user_answer = ''; |
||
3992 | // Get answer list for matching. |
||
3993 | $sql = "SELECT iid, answer |
||
3994 | FROM $table_ans |
||
3995 | WHERE question_id = $questionId"; |
||
3996 | $res_answer = Database::query($sql); |
||
3997 | |||
3998 | $answerMatching = []; |
||
3999 | while ($real_answer = Database::fetch_array($res_answer)) { |
||
4000 | $answerMatching[$real_answer['iid']] = $real_answer['answer']; |
||
4001 | } |
||
4002 | |||
4003 | // Get first answer needed for global question, no matter the answer shuffle option; |
||
4004 | $firstAnswer = []; |
||
4005 | if ($answerType == MULTIPLE_ANSWER_COMBINATION || |
||
4006 | $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE |
||
4007 | ) { |
||
4008 | $sql = "SELECT * |
||
4009 | FROM $table_ans |
||
4010 | WHERE question_id = $questionId |
||
4011 | ORDER BY position |
||
4012 | LIMIT 1"; |
||
4013 | $result = Database::query($sql); |
||
4014 | if (Database::num_rows($result)) { |
||
4015 | $firstAnswer = Database::fetch_array($result); |
||
4016 | } |
||
4017 | } |
||
4018 | |||
4019 | $real_answers = []; |
||
4020 | $quiz_question_options = Question::readQuestionOption($questionId, $course_id); |
||
4021 | $organs_at_risk_hit = 0; |
||
4022 | $questionScore = 0; |
||
4023 | $orderedHotSpots = []; |
||
4024 | if (in_array($answerType, [HOT_SPOT_COMBINATION, HOT_SPOT, ANNOTATION])) { |
||
4025 | $orderedHotSpots = $em->getRepository('ChamiloCoreBundle:TrackEHotspot')->findBy( |
||
4026 | [ |
||
4027 | 'hotspotQuestionId' => $questionId, |
||
4028 | 'cId' => $course_id, |
||
4029 | 'hotspotExeId' => $exeId, |
||
4030 | ], |
||
4031 | ['hotspotAnswerId' => 'ASC'] |
||
4032 | ); |
||
4033 | } |
||
4034 | |||
4035 | if (in_array($answerType, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) { |
||
4036 | if (MULTIPLE_ANSWER_DROPDOWN_COMBINATION == $answerType) { |
||
4037 | $questionScore = $questionWeighting; |
||
4038 | } |
||
4039 | |||
4040 | if ($from_database) { |
||
4041 | $studentChoices = Database::store_result( |
||
4042 | Database::query( |
||
4043 | "SELECT answer FROM $TBL_TRACK_ATTEMPT WHERE exe_id = $exeId AND question_id = $questionId" |
||
4044 | ), |
||
4045 | 'ASSOC' |
||
4046 | ); |
||
4047 | $studentChoices = array_column($studentChoices, 'answer'); |
||
4048 | } else { |
||
4049 | $studentChoices = array_values($choice); |
||
4050 | } |
||
4051 | |||
4052 | $correctChoices = array_filter( |
||
4053 | $answerMatching, |
||
4054 | function ($answerId) use ($objAnswerTmp) { |
||
4055 | $index = array_search($answerId, $objAnswerTmp->iid); |
||
4056 | |||
4057 | return true === (bool) $objAnswerTmp->correct[$index]; |
||
4058 | }, |
||
4059 | ARRAY_FILTER_USE_KEY |
||
4060 | ); |
||
4061 | |||
4062 | $correctChoices = array_keys($correctChoices); |
||
4063 | |||
4064 | if (MULTIPLE_ANSWER_DROPDOWN_COMBINATION == $answerType |
||
4065 | && (array_diff($studentChoices, $correctChoices) || array_diff($correctChoices, $studentChoices)) |
||
4066 | ) { |
||
4067 | $questionScore = 0; |
||
4068 | } |
||
4069 | |||
4070 | if ($show_result) { |
||
4071 | echo ExerciseShowFunctions::displayMultipleAnswerDropdown( |
||
4072 | $this, |
||
4073 | $objAnswerTmp, |
||
4074 | $correctChoices, |
||
4075 | $studentChoices, |
||
4076 | $showTotalScoreAndUserChoicesInLastAttempt |
||
4077 | ); |
||
4078 | } |
||
4079 | } |
||
4080 | |||
4081 | if ($debug) { |
||
4082 | error_log('-- Start answer loop --'); |
||
4083 | } |
||
4084 | |||
4085 | $answerDestination = null; |
||
4086 | $userAnsweredQuestion = false; |
||
4087 | $correctAnswerId = []; |
||
4088 | $matchingCorrectAnswers = []; |
||
4089 | for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { |
||
4090 | $answer = $objAnswerTmp->selectAnswer($answerId); |
||
4091 | $hideComment = (int) $this->getPageConfigurationAttribute('hide_comment'); |
||
4092 | if (1 === $hideComment) { |
||
4093 | $answerComment = null; |
||
4094 | } else { |
||
4095 | $answerComment = $objAnswerTmp->selectComment($answerId); |
||
4096 | } |
||
4097 | $answerCorrect = $objAnswerTmp->isCorrect($answerId); |
||
4098 | $answerWeighting = (float) $objAnswerTmp->selectWeighting($answerId); |
||
4099 | $answerAutoId = $objAnswerTmp->selectId($answerId); |
||
4100 | $answerIid = isset($objAnswerTmp->iid[$answerId]) ? (int) $objAnswerTmp->iid[$answerId] : 0; |
||
4101 | |||
4102 | if ($debug) { |
||
4103 | error_log("c_quiz_answer.iid: $answerAutoId "); |
||
4104 | error_log("Answer marked as correct in db (0/1)?: $answerCorrect "); |
||
4105 | error_log("answerWeighting: $answerWeighting"); |
||
4106 | } |
||
4107 | |||
4108 | // Delineation. |
||
4109 | $delineation_cord = $objAnswerTmp->selectHotspotCoordinates(1); |
||
4110 | $answer_delineation_destination = $objAnswerTmp->selectDestination(1); |
||
4111 | |||
4112 | switch ($answerType) { |
||
4113 | case UNIQUE_ANSWER: |
||
4114 | case UNIQUE_ANSWER_IMAGE: |
||
4115 | case UNIQUE_ANSWER_NO_OPTION: |
||
4116 | case READING_COMPREHENSION: |
||
4117 | if ($from_database) { |
||
4118 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4119 | WHERE |
||
4120 | exe_id = $exeId AND |
||
4121 | question_id = $questionId"; |
||
4122 | $result = Database::query($sql); |
||
4123 | $choice = Database::result($result, 0, 'answer'); |
||
4124 | |||
4125 | if (false === $userAnsweredQuestion) { |
||
4126 | $userAnsweredQuestion = !empty($choice); |
||
4127 | } |
||
4128 | $studentChoice = $choice == $answerAutoId ? 1 : 0; |
||
4129 | if ($studentChoice) { |
||
4130 | $questionScore += $answerWeighting; |
||
4131 | $answerDestination = $objAnswerTmp->selectDestination($answerId); |
||
4132 | $correctAnswerId[] = $answerId; |
||
4133 | } |
||
4134 | } else { |
||
4135 | $studentChoice = $choice == $answerAutoId ? 1 : 0; |
||
4136 | if ($studentChoice) { |
||
4137 | $questionScore += $answerWeighting; |
||
4138 | $answerDestination = $objAnswerTmp->selectDestination($answerId); |
||
4139 | $correctAnswerId[] = $answerAutoId; |
||
4140 | } |
||
4141 | } |
||
4142 | break; |
||
4143 | case MULTIPLE_ANSWER_TRUE_FALSE: |
||
4144 | if ($from_database) { |
||
4145 | $choice = []; |
||
4146 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4147 | WHERE |
||
4148 | exe_id = $exeId AND |
||
4149 | question_id = ".$questionId; |
||
4150 | |||
4151 | $result = Database::query($sql); |
||
4152 | while ($row = Database::fetch_array($result)) { |
||
4153 | $values = explode(':', $row['answer']); |
||
4154 | $my_answer_id = isset($values[0]) ? $values[0] : ''; |
||
4155 | $option = isset($values[1]) ? $values[1] : ''; |
||
4156 | $choice[$my_answer_id] = $option; |
||
4157 | } |
||
4158 | $userAnsweredQuestion = !empty($choice); |
||
4159 | } |
||
4160 | |||
4161 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4162 | if (isset($studentChoice)) { |
||
4163 | $correctAnswerId[] = $answerAutoId; |
||
4164 | if ($studentChoice == $answerCorrect) { |
||
4165 | $questionScore += $true_score; |
||
4166 | } else { |
||
4167 | if ($quiz_question_options[$studentChoice]['name'] === "Don't know" || |
||
4168 | $quiz_question_options[$studentChoice]['name'] === "DoubtScore" |
||
4169 | ) { |
||
4170 | $questionScore += $doubt_score; |
||
4171 | } else { |
||
4172 | $questionScore += $false_score; |
||
4173 | } |
||
4174 | } |
||
4175 | } else { |
||
4176 | // If no result then the user just hit don't know |
||
4177 | $studentChoice = 3; |
||
4178 | $questionScore += $doubt_score; |
||
4179 | } |
||
4180 | $totalScore = $questionScore; |
||
4181 | break; |
||
4182 | case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY: |
||
4183 | if ($from_database) { |
||
4184 | $choice = []; |
||
4185 | $choiceDegreeCertainty = []; |
||
4186 | $sql = "SELECT answer |
||
4187 | FROM $TBL_TRACK_ATTEMPT |
||
4188 | WHERE |
||
4189 | exe_id = $exeId AND question_id = $questionId"; |
||
4190 | |||
4191 | $result = Database::query($sql); |
||
4192 | while ($row = Database::fetch_array($result)) { |
||
4193 | $ind = $row['answer']; |
||
4194 | $values = explode(':', $ind); |
||
4195 | $myAnswerId = $values[0]; |
||
4196 | $option = $values[1]; |
||
4197 | $percent = $values[2]; |
||
4198 | $choice[$myAnswerId] = $option; |
||
4199 | $choiceDegreeCertainty[$myAnswerId] = $percent; |
||
4200 | } |
||
4201 | } |
||
4202 | |||
4203 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4204 | $studentChoiceDegree = $choiceDegreeCertainty[$answerAutoId] ?? null; |
||
4205 | |||
4206 | // student score update |
||
4207 | if (!empty($studentChoice)) { |
||
4208 | if ($studentChoice == $answerCorrect) { |
||
4209 | // correct answer and student is Unsure or PrettySur |
||
4210 | if (isset($quiz_question_options[$studentChoiceDegree]) && |
||
4211 | $quiz_question_options[$studentChoiceDegree]['position'] >= 3 && |
||
4212 | $quiz_question_options[$studentChoiceDegree]['position'] < 9 |
||
4213 | ) { |
||
4214 | $questionScore += $true_score; |
||
4215 | } else { |
||
4216 | // student ignore correct answer |
||
4217 | $questionScore += $doubt_score; |
||
4218 | } |
||
4219 | } else { |
||
4220 | // false answer and student is Unsure or PrettySur |
||
4221 | if ($quiz_question_options[$studentChoiceDegree]['position'] >= 3 |
||
4222 | && $quiz_question_options[$studentChoiceDegree]['position'] < 9) { |
||
4223 | $questionScore += $false_score; |
||
4224 | } else { |
||
4225 | // student ignore correct answer |
||
4226 | $questionScore += $doubt_score; |
||
4227 | } |
||
4228 | } |
||
4229 | } |
||
4230 | $totalScore = $questionScore; |
||
4231 | break; |
||
4232 | case MULTIPLE_ANSWER: |
||
4233 | case MULTIPLE_ANSWER_DROPDOWN: |
||
4234 | if ($from_database) { |
||
4235 | $choice = []; |
||
4236 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4237 | WHERE exe_id = $exeId AND question_id = $questionId "; |
||
4238 | $resultans = Database::query($sql); |
||
4239 | while ($row = Database::fetch_array($resultans)) { |
||
4240 | $choice[$row['answer']] = 1; |
||
4241 | } |
||
4242 | |||
4243 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4244 | $real_answers[$answerId] = (bool) $studentChoice; |
||
4245 | |||
4246 | if ($studentChoice) { |
||
4247 | $questionScore += $answerWeighting; |
||
4248 | } |
||
4249 | } else { |
||
4250 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4251 | $real_answers[$answerId] = (bool) $studentChoice; |
||
4252 | |||
4253 | if (isset($studentChoice) |
||
4254 | || (MULTIPLE_ANSWER_DROPDOWN == $answerType && in_array($answerAutoId, $choice)) |
||
4255 | ) { |
||
4256 | $correctAnswerId[] = $answerAutoId; |
||
4257 | $questionScore += $answerWeighting; |
||
4258 | } |
||
4259 | } |
||
4260 | $totalScore += $answerWeighting; |
||
4261 | break; |
||
4262 | case GLOBAL_MULTIPLE_ANSWER: |
||
4263 | if ($from_database) { |
||
4264 | $choice = []; |
||
4265 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4266 | WHERE exe_id = $exeId AND question_id = $questionId "; |
||
4267 | $resultans = Database::query($sql); |
||
4268 | while ($row = Database::fetch_array($resultans)) { |
||
4269 | $choice[$row['answer']] = 1; |
||
4270 | } |
||
4271 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4272 | $real_answers[$answerId] = (bool) $studentChoice; |
||
4273 | if ($studentChoice) { |
||
4274 | $questionScore += $answerWeighting; |
||
4275 | } |
||
4276 | } else { |
||
4277 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4278 | if (isset($studentChoice)) { |
||
4279 | $questionScore += $answerWeighting; |
||
4280 | } |
||
4281 | $real_answers[$answerId] = (bool) $studentChoice; |
||
4282 | } |
||
4283 | $totalScore += $answerWeighting; |
||
4284 | if ($debug) { |
||
4285 | error_log("studentChoice: $studentChoice"); |
||
4286 | } |
||
4287 | break; |
||
4288 | case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE: |
||
4289 | if ($from_database) { |
||
4290 | $choice = []; |
||
4291 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4292 | WHERE exe_id = $exeId AND question_id = $questionId"; |
||
4293 | $resultans = Database::query($sql); |
||
4294 | while ($row = Database::fetch_array($resultans)) { |
||
4295 | $result = explode(':', $row['answer']); |
||
4296 | if (isset($result[0])) { |
||
4297 | $my_answer_id = isset($result[0]) ? $result[0] : ''; |
||
4298 | $option = isset($result[1]) ? $result[1] : ''; |
||
4299 | $choice[$my_answer_id] = $option; |
||
4300 | } |
||
4301 | } |
||
4302 | $studentChoice = $choice[$answerAutoId] ?? ''; |
||
4303 | $real_answers[$answerId] = false; |
||
4304 | if ($answerCorrect == $studentChoice) { |
||
4305 | $real_answers[$answerId] = true; |
||
4306 | } |
||
4307 | } else { |
||
4308 | $studentChoice = $choice[$answerAutoId] ?? ''; |
||
4309 | $real_answers[$answerId] = false; |
||
4310 | if ($answerCorrect == $studentChoice) { |
||
4311 | $real_answers[$answerId] = true; |
||
4312 | } |
||
4313 | } |
||
4314 | break; |
||
4315 | case MULTIPLE_ANSWER_COMBINATION: |
||
4316 | if ($from_database) { |
||
4317 | $choice = []; |
||
4318 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4319 | WHERE exe_id = $exeId AND question_id = $questionId"; |
||
4320 | $resultans = Database::query($sql); |
||
4321 | while ($row = Database::fetch_array($resultans)) { |
||
4322 | $choice[$row['answer']] = 1; |
||
4323 | } |
||
4324 | |||
4325 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4326 | if (1 == $answerCorrect) { |
||
4327 | $real_answers[$answerId] = false; |
||
4328 | if ($studentChoice) { |
||
4329 | $real_answers[$answerId] = true; |
||
4330 | } |
||
4331 | } else { |
||
4332 | $real_answers[$answerId] = true; |
||
4333 | if ($studentChoice) { |
||
4334 | $real_answers[$answerId] = false; |
||
4335 | } |
||
4336 | } |
||
4337 | } else { |
||
4338 | $studentChoice = $choice[$answerAutoId] ?? null; |
||
4339 | if (1 == $answerCorrect) { |
||
4340 | $real_answers[$answerId] = false; |
||
4341 | if ($studentChoice) { |
||
4342 | $real_answers[$answerId] = true; |
||
4343 | } |
||
4344 | } else { |
||
4345 | $real_answers[$answerId] = true; |
||
4346 | if ($studentChoice) { |
||
4347 | $real_answers[$answerId] = false; |
||
4348 | } |
||
4349 | } |
||
4350 | } |
||
4351 | break; |
||
4352 | case FILL_IN_BLANKS: |
||
4353 | case FILL_IN_BLANKS_COMBINATION: |
||
4354 | $str = ''; |
||
4355 | $answerFromDatabase = ''; |
||
4356 | if ($from_database) { |
||
4357 | $sql = "SELECT answer |
||
4358 | FROM $TBL_TRACK_ATTEMPT |
||
4359 | WHERE |
||
4360 | exe_id = $exeId AND |
||
4361 | question_id= $questionId "; |
||
4362 | $result = Database::query($sql); |
||
4363 | $str = $answerFromDatabase = Database::result($result, 0, 'answer'); |
||
4364 | } |
||
4365 | |||
4366 | // if ($save_results == false && strpos($answerFromDatabase, 'font color') !== false) { |
||
4367 | if (false) { |
||
4368 | // the question is encoded like this |
||
4369 | // [A] B [C] D [E] F::10,10,10@1 |
||
4370 | // number 1 before the "@" means that is a switchable fill in blank question |
||
4371 | // [A] B [C] D [E] F::10,10,10@ or [A] B [C] D [E] F::10,10,10 |
||
4372 | // means that is a normal fill blank question |
||
4373 | // first we explode the "::" |
||
4374 | $pre_array = explode('::', $answer); |
||
4375 | |||
4376 | // is switchable fill blank or not |
||
4377 | $last = count($pre_array) - 1; |
||
4378 | $is_set_switchable = explode('@', $pre_array[$last]); |
||
4379 | $switchable_answer_set = false; |
||
4380 | if (isset($is_set_switchable[1]) && 1 == $is_set_switchable[1]) { |
||
4381 | $switchable_answer_set = true; |
||
4382 | } |
||
4383 | $answer = ''; |
||
4384 | for ($k = 0; $k < $last; $k++) { |
||
4385 | $answer .= $pre_array[$k]; |
||
4386 | } |
||
4387 | // splits weightings that are joined with a comma |
||
4388 | $answerWeighting = explode(',', $is_set_switchable[0]); |
||
4389 | // we save the answer because it will be modified |
||
4390 | $temp = $answer; |
||
4391 | $answer = ''; |
||
4392 | $j = 0; |
||
4393 | //initialise answer tags |
||
4394 | $user_tags = $correct_tags = $real_text = []; |
||
4395 | // the loop will stop at the end of the text |
||
4396 | while (1) { |
||
4397 | // quits the loop if there are no more blanks (detect '[') |
||
4398 | if ($temp == false || ($pos = api_strpos($temp, '[')) === false) { |
||
4399 | // adds the end of the text |
||
4400 | $answer = $temp; |
||
4401 | $real_text[] = $answer; |
||
4402 | break; //no more "blanks", quit the loop |
||
4403 | } |
||
4404 | // adds the piece of text that is before the blank |
||
4405 | //and ends with '[' into a general storage array |
||
4406 | $real_text[] = api_substr($temp, 0, $pos + 1); |
||
4407 | $answer .= api_substr($temp, 0, $pos + 1); |
||
4408 | //take the string remaining (after the last "[" we found) |
||
4409 | $temp = api_substr($temp, $pos + 1); |
||
4410 | // quit the loop if there are no more blanks, and update $pos to the position of next ']' |
||
4411 | if (($pos = api_strpos($temp, ']')) === false) { |
||
4412 | // adds the end of the text |
||
4413 | $answer .= $temp; |
||
4414 | break; |
||
4415 | } |
||
4416 | if ($from_database) { |
||
4417 | $str = $answerFromDatabase; |
||
4418 | api_preg_match_all('#\[([^[]*)\]#', $str, $arr); |
||
4419 | $str = str_replace('\r\n', '', $str); |
||
4420 | |||
4421 | $choice = $arr[1]; |
||
4422 | if (isset($choice[$j])) { |
||
4423 | $tmp = api_strrpos($choice[$j], ' / '); |
||
4424 | $choice[$j] = api_substr($choice[$j], 0, $tmp); |
||
4425 | $choice[$j] = trim($choice[$j]); |
||
4426 | // Needed to let characters ' and " to work as part of an answer |
||
4427 | $choice[$j] = stripslashes($choice[$j]); |
||
4428 | } else { |
||
4429 | $choice[$j] = null; |
||
4430 | } |
||
4431 | } else { |
||
4432 | // This value is the user input, not escaped while correct answer is escaped by ckeditor |
||
4433 | $choice[$j] = api_htmlentities(trim($choice[$j])); |
||
4434 | } |
||
4435 | |||
4436 | $user_tags[] = $choice[$j]; |
||
4437 | // Put the contents of the [] answer tag into correct_tags[] |
||
4438 | $correct_tags[] = api_substr($temp, 0, $pos); |
||
4439 | $j++; |
||
4440 | $temp = api_substr($temp, $pos + 1); |
||
4441 | } |
||
4442 | $answer = ''; |
||
4443 | $real_correct_tags = $correct_tags; |
||
4444 | $chosen_list = []; |
||
4445 | |||
4446 | for ($i = 0; $i < count($real_correct_tags); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
4447 | if (0 == $i) { |
||
4448 | $answer .= $real_text[0]; |
||
4449 | } |
||
4450 | if (!$switchable_answer_set) { |
||
4451 | // Needed to parse ' and " characters |
||
4452 | $user_tags[$i] = stripslashes($user_tags[$i]); |
||
4453 | if ($correct_tags[$i] == $user_tags[$i]) { |
||
4454 | // gives the related weighting to the student |
||
4455 | $questionScore += $answerWeighting[$i]; |
||
4456 | // increments total score |
||
4457 | $totalScore += $answerWeighting[$i]; |
||
4458 | // adds the word in green at the end of the string |
||
4459 | $answer .= $correct_tags[$i]; |
||
4460 | } elseif (!empty($user_tags[$i])) { |
||
4461 | // else if the word entered by the student IS NOT the same as |
||
4462 | // the one defined by the professor |
||
4463 | // adds the word in red at the end of the string, and strikes it |
||
4464 | $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>'; |
||
4465 | } else { |
||
4466 | // adds a tabulation if no word has been typed by the student |
||
4467 | $answer .= ''; // remove that causes issue |
||
4468 | } |
||
4469 | } else { |
||
4470 | // switchable fill in the blanks |
||
4471 | if (in_array($user_tags[$i], $correct_tags)) { |
||
4472 | $chosen_list[] = $user_tags[$i]; |
||
4473 | $correct_tags = array_diff($correct_tags, $chosen_list); |
||
4474 | // gives the related weighting to the student |
||
4475 | $questionScore += $answerWeighting[$i]; |
||
4476 | // increments total score |
||
4477 | $totalScore += $answerWeighting[$i]; |
||
4478 | // adds the word in green at the end of the string |
||
4479 | $answer .= $user_tags[$i]; |
||
4480 | } elseif (!empty($user_tags[$i])) { |
||
4481 | // else if the word entered by the student IS NOT the same |
||
4482 | // as the one defined by the professor |
||
4483 | // adds the word in red at the end of the string, and strikes it |
||
4484 | $answer .= '<font color="red"><s>'.$user_tags[$i].'</s></font>'; |
||
4485 | } else { |
||
4486 | // adds a tabulation if no word has been typed by the student |
||
4487 | $answer .= ''; // remove that causes issue |
||
4488 | } |
||
4489 | } |
||
4490 | |||
4491 | // adds the correct word, followed by ] to close the blank |
||
4492 | $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]'; |
||
4493 | if (isset($real_text[$i + 1])) { |
||
4494 | $answer .= $real_text[$i + 1]; |
||
4495 | } |
||
4496 | } |
||
4497 | } else { |
||
4498 | // insert the student result in the track_e_attempt table, field answer |
||
4499 | // $answer is the answer like in the c_quiz_answer table for the question |
||
4500 | // student data are choice[] |
||
4501 | $listCorrectAnswers = FillBlanks::getAnswerInfo($answer); |
||
4502 | $switchableAnswerSet = $listCorrectAnswers['switchable']; |
||
4503 | $answerWeighting = $listCorrectAnswers['weighting']; |
||
4504 | // user choices is an array $choice |
||
4505 | |||
4506 | // get existing user data in n the BDD |
||
4507 | if ($from_database) { |
||
4508 | $listStudentResults = FillBlanks::getAnswerInfo( |
||
4509 | $answerFromDatabase, |
||
4510 | true |
||
4511 | ); |
||
4512 | $choice = $listStudentResults['student_answer']; |
||
4513 | } |
||
4514 | |||
4515 | // loop other all blanks words |
||
4516 | if (!$switchableAnswerSet) { |
||
4517 | // not switchable answer, must be in the same place than teacher order |
||
4518 | for ($i = 0; $i < count($listCorrectAnswers['words']); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
4519 | $studentAnswer = isset($choice[$i]) ? $choice[$i] : ''; |
||
4520 | $correctAnswer = $listCorrectAnswers['words'][$i]; |
||
4521 | |||
4522 | if ($debug) { |
||
4523 | error_log("Student answer: $i"); |
||
4524 | error_log($studentAnswer); |
||
4525 | } |
||
4526 | |||
4527 | // This value is the user input, not escaped while correct answer is escaped by ckeditor |
||
4528 | // Works with cyrillic alphabet and when using ">" chars see #7718 #7610 #7618 |
||
4529 | // ENT_QUOTES is used in order to transform ' to ' |
||
4530 | if (!$from_database) { |
||
4531 | $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer); |
||
4532 | if ($debug) { |
||
4533 | error_log('Student answer cleaned:'); |
||
4534 | error_log($studentAnswer); |
||
4535 | } |
||
4536 | } |
||
4537 | |||
4538 | $isAnswerCorrect = 0; |
||
4539 | if (FillBlanks::isStudentAnswerGood($studentAnswer, $correctAnswer, $from_database)) { |
||
4540 | // gives the related weighting to the student |
||
4541 | $questionScore += $answerWeighting[$i]; |
||
4542 | // increments total score |
||
4543 | $totalScore += $answerWeighting[$i]; |
||
4544 | $isAnswerCorrect = 1; |
||
4545 | } |
||
4546 | if ($debug) { |
||
4547 | error_log("isAnswerCorrect $i: $isAnswerCorrect"); |
||
4548 | } |
||
4549 | |||
4550 | $studentAnswerToShow = $studentAnswer; |
||
4551 | $type = FillBlanks::getFillTheBlankAnswerType($correctAnswer); |
||
4552 | if ($debug) { |
||
4553 | error_log("Fill in blank type: $type"); |
||
4554 | } |
||
4555 | if ($type == FillBlanks::FILL_THE_BLANK_MENU) { |
||
4556 | $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false); |
||
4557 | if ($studentAnswer != '') { |
||
4558 | foreach ($listMenu as $item) { |
||
4559 | if (sha1($item) == $studentAnswer) { |
||
4560 | $studentAnswerToShow = $item; |
||
4561 | } |
||
4562 | } |
||
4563 | } |
||
4564 | } |
||
4565 | $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow; |
||
4566 | $listCorrectAnswers['student_score'][$i] = $isAnswerCorrect; |
||
4567 | } |
||
4568 | } else { |
||
4569 | // switchable answer |
||
4570 | $listStudentAnswerTemp = $choice; |
||
4571 | $listTeacherAnswerTemp = $listCorrectAnswers['words']; |
||
4572 | |||
4573 | // for every teacher answer, check if there is a student answer |
||
4574 | for ($i = 0; $i < count($listStudentAnswerTemp); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
4575 | $studentAnswer = trim($listStudentAnswerTemp[$i]); |
||
4576 | $studentAnswerToShow = $studentAnswer; |
||
4577 | |||
4578 | if (empty($studentAnswer)) { |
||
4579 | break; |
||
4580 | } |
||
4581 | |||
4582 | if ($debug) { |
||
4583 | error_log("Student answer: $i"); |
||
4584 | error_log($studentAnswer); |
||
4585 | } |
||
4586 | |||
4587 | if (!$from_database) { |
||
4588 | $studentAnswer = FillBlanks::clearStudentAnswer($studentAnswer); |
||
4589 | if ($debug) { |
||
4590 | error_log("Student answer cleaned:"); |
||
4591 | error_log($studentAnswer); |
||
4592 | } |
||
4593 | } |
||
4594 | |||
4595 | $found = false; |
||
4596 | for ($j = 0; $j < count($listTeacherAnswerTemp); $j++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
4597 | $correctAnswer = isset($listTeacherAnswerTemp[$j]) ? $listTeacherAnswerTemp[$j] : ''; |
||
4598 | if (is_array($listTeacherAnswerTemp)) { |
||
4599 | $correctAnswer = implode('||', $listTeacherAnswerTemp); |
||
4600 | } |
||
4601 | |||
4602 | if (empty($correctAnswer)) { |
||
4603 | break; |
||
4604 | } |
||
4605 | |||
4606 | if (FillBlanks::isStudentAnswerGood( |
||
4607 | $studentAnswer, |
||
4608 | $correctAnswer, |
||
4609 | $from_database, |
||
4610 | true |
||
4611 | )) { |
||
4612 | $questionScore += $answerWeighting[$i]; |
||
4613 | $totalScore += $answerWeighting[$i]; |
||
4614 | if (is_array($listTeacherAnswerTemp)) { |
||
4615 | $searchAnswer = array_search(api_htmlentities($studentAnswer), $listTeacherAnswerTemp); |
||
4616 | if (false !== $searchAnswer) { |
||
4617 | // Remove from array |
||
4618 | unset($listTeacherAnswerTemp[$searchAnswer]); |
||
4619 | } |
||
4620 | } else { |
||
4621 | $listTeacherAnswerTemp[$j] = null; |
||
4622 | } |
||
4623 | $found = true; |
||
4624 | } |
||
4625 | |||
4626 | if (FillBlanks::FILL_THE_BLANK_MENU != $listCorrectAnswers['words_types'][$j]) { |
||
4627 | break; |
||
4628 | } |
||
4629 | |||
4630 | $listMenu = FillBlanks::getFillTheBlankMenuAnswers($correctAnswer, false); |
||
4631 | foreach ($listMenu as $item) { |
||
4632 | if (sha1($item) === $studentAnswer) { |
||
4633 | $studentAnswerToShow = $item; |
||
4634 | } |
||
4635 | } |
||
4636 | } |
||
4637 | $listCorrectAnswers['student_answer'][$i] = $studentAnswerToShow; |
||
4638 | $listCorrectAnswers['student_score'][$i] = $found ? 1 : 0; |
||
4639 | } |
||
4640 | } |
||
4641 | $answer = FillBlanks::getAnswerInStudentAttempt($listCorrectAnswers); |
||
4642 | } |
||
4643 | break; |
||
4644 | case CALCULATED_ANSWER: |
||
4645 | $calculatedAnswerList = Session::read('calculatedAnswerId'); |
||
4646 | $calculatedAnswerId = null; |
||
4647 | if (!empty($calculatedAnswerList) && isset($calculatedAnswerList[$questionId])) { |
||
4648 | $calculatedAnswerId = $calculatedAnswerList[$questionId]; |
||
4649 | $answer = $objAnswerTmp->selectAnswer($calculatedAnswerId); |
||
4650 | $preArray = explode('@@', $answer); |
||
4651 | $last = count($preArray) - 1; |
||
4652 | $answer = ''; |
||
4653 | for ($k = 0; $k < $last; $k++) { |
||
4654 | $answer .= $preArray[$k]; |
||
4655 | } |
||
4656 | $answerWeighting = [$answerWeighting]; |
||
4657 | // we save the answer because it will be modified |
||
4658 | $temp = $answer; |
||
4659 | $answer = ''; |
||
4660 | $j = 0; |
||
4661 | // initialise answer tags |
||
4662 | $userTags = $correctTags = $realText = []; |
||
4663 | // the loop will stop at the end of the text |
||
4664 | while (1) { |
||
4665 | // quits the loop if there are no more blanks (detect '[') |
||
4666 | if ($temp == false || ($pos = api_strpos($temp, '[')) === false) { |
||
4667 | // adds the end of the text |
||
4668 | $answer = $temp; |
||
4669 | $realText[] = $answer; |
||
4670 | break; //no more "blanks", quit the loop |
||
4671 | } |
||
4672 | // adds the piece of text that is before the blank |
||
4673 | // and ends with '[' into a general storage array |
||
4674 | $realText[] = api_substr($temp, 0, $pos + 1); |
||
4675 | $answer .= api_substr($temp, 0, $pos + 1); |
||
4676 | // take the string remaining (after the last "[" we found) |
||
4677 | $temp = api_substr($temp, $pos + 1); |
||
4678 | // quit the loop if there are no more blanks, and update $pos to the position of next ']' |
||
4679 | if (false === ($pos = api_strpos($temp, ']'))) { |
||
4680 | // adds the end of the text |
||
4681 | $answer .= $temp; |
||
4682 | break; |
||
4683 | } |
||
4684 | |||
4685 | if ($from_database) { |
||
4686 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4687 | WHERE |
||
4688 | exe_id = $exeId AND |
||
4689 | question_id = $questionId "; |
||
4690 | $result = Database::query($sql); |
||
4691 | $str = Database::result($result, 0, 'answer'); |
||
4692 | |||
4693 | api_preg_match_all('#\[([^[]*)\]#', $str, $arr); |
||
4694 | $str = str_replace('\r\n', '', $str); |
||
4695 | $choice = $arr[1]; |
||
4696 | if (isset($choice[$j])) { |
||
4697 | $tmp = api_strrpos($choice[$j], ' / '); |
||
4698 | if ($tmp) { |
||
4699 | $choice[$j] = api_substr($choice[$j], 0, $tmp); |
||
4700 | } else { |
||
4701 | $tmp = ltrim($tmp, '['); |
||
4702 | $tmp = rtrim($tmp, ']'); |
||
4703 | } |
||
4704 | $choice[$j] = trim($choice[$j]); |
||
4705 | // Needed to let characters ' and " to work as part of an answer |
||
4706 | $choice[$j] = stripslashes($choice[$j]); |
||
4707 | } else { |
||
4708 | $choice[$j] = null; |
||
4709 | } |
||
4710 | } else { |
||
4711 | // This value is the user input not escaped while correct answer is escaped by ckeditor |
||
4712 | $choice[$j] = api_htmlentities(trim($choice[$j])); |
||
4713 | } |
||
4714 | $userTags[] = $choice[$j]; |
||
4715 | // put the contents of the [] answer tag into correct_tags[] |
||
4716 | $correctTags[] = api_substr($temp, 0, $pos); |
||
4717 | $j++; |
||
4718 | $temp = api_substr($temp, $pos + 1); |
||
4719 | } |
||
4720 | $answer = ''; |
||
4721 | $realCorrectTags = $correctTags; |
||
4722 | $calculatedStatus = Display::label(get_lang('Incorrect'), 'danger'); |
||
4723 | $expectedAnswer = ''; |
||
4724 | $calculatedChoice = ''; |
||
4725 | |||
4726 | for ($i = 0; $i < count($realCorrectTags); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
4727 | if (0 == $i) { |
||
4728 | $answer .= $realText[0]; |
||
4729 | } |
||
4730 | // Needed to parse ' and " characters |
||
4731 | $userTags[$i] = stripslashes($userTags[$i]); |
||
4732 | if ($correctTags[$i] == $userTags[$i]) { |
||
4733 | // gives the related weighting to the student |
||
4734 | $questionScore += $answerWeighting[$i]; |
||
4735 | // increments total score |
||
4736 | $totalScore += $answerWeighting[$i]; |
||
4737 | // adds the word in green at the end of the string |
||
4738 | $answer .= $correctTags[$i]; |
||
4739 | $calculatedChoice = $correctTags[$i]; |
||
4740 | } elseif (!empty($userTags[$i])) { |
||
4741 | // else if the word entered by the student IS NOT the same as |
||
4742 | // the one defined by the professor |
||
4743 | // adds the word in red at the end of the string, and strikes it |
||
4744 | $answer .= '<font color="red"><s>'.$userTags[$i].'</s></font>'; |
||
4745 | $calculatedChoice = $userTags[$i]; |
||
4746 | } else { |
||
4747 | // adds a tabulation if no word has been typed by the student |
||
4748 | $answer .= ''; // remove that causes issue |
||
4749 | } |
||
4750 | // adds the correct word, followed by ] to close the blank |
||
4751 | if (EXERCISE_FEEDBACK_TYPE_EXAM != $this->results_disabled) { |
||
4752 | $answer .= ' / <font color="green"><b>'.$realCorrectTags[$i].'</b></font>'; |
||
4753 | $calculatedStatus = Display::label(get_lang('Correct'), 'success'); |
||
4754 | $expectedAnswer = $realCorrectTags[$i]; |
||
4755 | } |
||
4756 | $answer .= ']'; |
||
4757 | if (isset($realText[$i + 1])) { |
||
4758 | $answer .= $realText[$i + 1]; |
||
4759 | } |
||
4760 | } |
||
4761 | } else { |
||
4762 | if ($from_database) { |
||
4763 | $sql = "SELECT * |
||
4764 | FROM $TBL_TRACK_ATTEMPT |
||
4765 | WHERE |
||
4766 | exe_id = $exeId AND |
||
4767 | question_id = $questionId "; |
||
4768 | $result = Database::query($sql); |
||
4769 | $resultData = Database::fetch_array($result, 'ASSOC'); |
||
4770 | $answer = $resultData['answer']; |
||
4771 | $questionScore = $resultData['marks']; |
||
4772 | } |
||
4773 | } |
||
4774 | break; |
||
4775 | case UPLOAD_ANSWER: |
||
4776 | case FREE_ANSWER: |
||
4777 | case ANSWER_IN_OFFICE_DOC: |
||
4778 | if ($from_database) { |
||
4779 | $sql = "SELECT answer, marks FROM $TBL_TRACK_ATTEMPT |
||
4780 | WHERE |
||
4781 | exe_id = $exeId AND |
||
4782 | question_id= ".$questionId; |
||
4783 | $result = Database::query($sql); |
||
4784 | $data = Database::fetch_array($result); |
||
4785 | $choice = ''; |
||
4786 | $questionScore = 0; |
||
4787 | if ($data) { |
||
4788 | $choice = $data['answer']; |
||
4789 | $questionScore = $data['marks']; |
||
4790 | } |
||
4791 | $choice = str_replace('\r\n', '', $choice); |
||
4792 | $choice = stripslashes($choice); |
||
4793 | if (-1 == $questionScore) { |
||
4794 | $totalScore += 0; |
||
4795 | } else { |
||
4796 | $totalScore += $questionScore; |
||
4797 | } |
||
4798 | if ('' == $questionScore) { |
||
4799 | $questionScore = 0; |
||
4800 | } |
||
4801 | $arrques = $questionName; |
||
4802 | $arrans = $choice; |
||
4803 | } else { |
||
4804 | $studentChoice = $choice; |
||
4805 | if ($studentChoice) { |
||
4806 | //Fixing negative puntation see #2193 |
||
4807 | $questionScore = 0; |
||
4808 | $totalScore += 0; |
||
4809 | } |
||
4810 | } |
||
4811 | break; |
||
4812 | case ORAL_EXPRESSION: |
||
4813 | if ($from_database) { |
||
4814 | $query = "SELECT answer, marks |
||
4815 | FROM $TBL_TRACK_ATTEMPT |
||
4816 | WHERE |
||
4817 | exe_id = $exeId AND |
||
4818 | question_id = $questionId |
||
4819 | "; |
||
4820 | $resq = Database::query($query); |
||
4821 | $row = Database::fetch_assoc($resq); |
||
4822 | |||
4823 | $choice = [ |
||
4824 | 'answer' => '', |
||
4825 | 'marks' => 0, |
||
4826 | ]; |
||
4827 | $questionScore = 0; |
||
4828 | |||
4829 | if (is_array($row)) { |
||
4830 | $choice = $row['answer']; |
||
4831 | $choice = str_replace('\r\n', '', $choice); |
||
4832 | $choice = stripslashes($choice); |
||
4833 | $questionScore = $row['marks']; |
||
4834 | } |
||
4835 | |||
4836 | if ($questionScore == -1) { |
||
4837 | $totalScore += 0; |
||
4838 | } else { |
||
4839 | $totalScore += $questionScore; |
||
4840 | } |
||
4841 | $arrques = $questionName; |
||
4842 | $arrans = $choice; |
||
4843 | } else { |
||
4844 | $studentChoice = $choice; |
||
4845 | if ($studentChoice) { |
||
4846 | //Fixing negative puntation see #2193 |
||
4847 | $questionScore = 0; |
||
4848 | $totalScore += 0; |
||
4849 | } |
||
4850 | } |
||
4851 | break; |
||
4852 | case DRAGGABLE: |
||
4853 | case MATCHING_DRAGGABLE: |
||
4854 | case MATCHING_DRAGGABLE_COMBINATION: |
||
4855 | case MATCHING_COMBINATION: |
||
4856 | case MATCHING: |
||
4857 | if ($from_database) { |
||
4858 | $sql = "SELECT iid, answer, id_auto |
||
4859 | FROM $table_ans |
||
4860 | WHERE |
||
4861 | question_id = $questionId AND |
||
4862 | correct = 0 |
||
4863 | "; |
||
4864 | $result = Database::query($sql); |
||
4865 | // Getting the real answer |
||
4866 | $real_list = []; |
||
4867 | while ($realAnswer = Database::fetch_array($result)) { |
||
4868 | $real_list[$realAnswer['iid']] = $realAnswer['answer']; |
||
4869 | } |
||
4870 | |||
4871 | $orderBy = ' ORDER BY iid '; |
||
4872 | if (DRAGGABLE == $answerType) { |
||
4873 | $orderBy = ' ORDER BY correct '; |
||
4874 | } |
||
4875 | |||
4876 | $sql = "SELECT iid, answer, correct, id_auto, ponderation |
||
4877 | FROM $table_ans |
||
4878 | WHERE |
||
4879 | question_id = $questionId AND |
||
4880 | correct <> 0 |
||
4881 | $orderBy"; |
||
4882 | $result = Database::query($sql); |
||
4883 | $options = []; |
||
4884 | $correctAnswers = []; |
||
4885 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
4886 | $options[] = $row; |
||
4887 | $correctAnswers[$row['correct']] = $row['answer']; |
||
4888 | } |
||
4889 | |||
4890 | $questionScore = 0; |
||
4891 | $counterAnswer = 1; |
||
4892 | foreach ($options as $a_answers) { |
||
4893 | $i_answer_id = $a_answers['iid']; //3 |
||
4894 | $s_answer_label = $a_answers['answer']; // your daddy - your mother |
||
4895 | $i_answer_correct_answer = $a_answers['correct']; //1 - 2 |
||
4896 | |||
4897 | $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT |
||
4898 | WHERE |
||
4899 | exe_id = '$exeId' AND |
||
4900 | question_id = '$questionId' AND |
||
4901 | position = '$i_answer_id'"; |
||
4902 | $result = Database::query($sql); |
||
4903 | $s_user_answer = 0; |
||
4904 | if (Database::num_rows($result) > 0) { |
||
4905 | // rich - good looking |
||
4906 | $s_user_answer = Database::result($result, 0, 0); |
||
4907 | } |
||
4908 | $i_answerWeighting = $a_answers['ponderation']; |
||
4909 | $user_answer = ''; |
||
4910 | $status = Display::label(get_lang('Incorrect'), 'danger'); |
||
4911 | |||
4912 | if (!empty($s_user_answer)) { |
||
4913 | if (DRAGGABLE == $answerType) { |
||
4914 | $sql = "SELECT answer FROM $table_ans WHERE iid = $s_user_answer"; |
||
4915 | $rsDragAnswer = Database::query($sql); |
||
4916 | $dragAns = Database::result($rsDragAnswer, 0, 0); |
||
4917 | if ($s_user_answer == $i_answer_correct_answer) { |
||
4918 | $questionScore += $i_answerWeighting; |
||
4919 | $totalScore += $i_answerWeighting; |
||
4920 | $user_answer = Display::label(get_lang('Correct'), 'success'); |
||
4921 | if ($this->showExpectedChoice() && !empty($i_answer_id)) { |
||
4922 | $user_answer = $answerMatching[$i_answer_id]; |
||
4923 | } else { |
||
4924 | $user_answer = $dragAns; |
||
4925 | } |
||
4926 | $status = Display::label(get_lang('Correct'), 'success'); |
||
4927 | } else { |
||
4928 | $user_answer = Display::label(get_lang('Incorrect'), 'danger'); |
||
4929 | if ($this->showExpectedChoice() && !empty($s_user_answer)) { |
||
4930 | /*$data = $options[$real_list[$s_user_answer] - 1]; |
||
4931 | $user_answer = $data['answer'];*/ |
||
4932 | $user_answer = $correctAnswers[$s_user_answer] ?? ''; |
||
4933 | } else { |
||
4934 | $user_answer = $dragAns; |
||
4935 | } |
||
4936 | } |
||
4937 | } else { |
||
4938 | if ($s_user_answer == $i_answer_correct_answer) { |
||
4939 | $questionScore += $i_answerWeighting; |
||
4940 | $totalScore += $i_answerWeighting; |
||
4941 | $status = Display::label(get_lang('Correct'), 'success'); |
||
4942 | // Try with id |
||
4943 | if (isset($real_list[$i_answer_id])) { |
||
4944 | $user_answer = Display::span( |
||
4945 | $real_list[$i_answer_id], |
||
4946 | ['style' => 'color: #008000; font-weight: bold;'] |
||
4947 | ); |
||
4948 | } |
||
4949 | |||
4950 | // Try with $i_answer_id_auto |
||
4951 | if (empty($user_answer)) { |
||
4952 | if (isset($real_list[$i_answer_id])) { |
||
4953 | $user_answer = Display::span( |
||
4954 | $real_list[$i_answer_id], |
||
4955 | ['style' => 'color: #008000; font-weight: bold;'] |
||
4956 | ); |
||
4957 | } |
||
4958 | } |
||
4959 | |||
4960 | if (isset($real_list[$i_answer_correct_answer])) { |
||
4961 | $matchingCorrectAnswers[$questionId]['from_database']['correct'][$i_answer_correct_answer] = $real_list[$i_answer_correct_answer]; |
||
4962 | $user_answer = Display::span( |
||
4963 | $real_list[$i_answer_correct_answer], |
||
4964 | ['style' => 'color: #008000; font-weight: bold;'] |
||
4965 | ); |
||
4966 | } |
||
4967 | } else { |
||
4968 | $user_answer = Display::span( |
||
4969 | $real_list[$s_user_answer], |
||
4970 | ['style' => 'color: #FF0000; text-decoration: line-through;'] |
||
4971 | ); |
||
4972 | if ($this->showExpectedChoice()) { |
||
4973 | if (isset($real_list[$s_user_answer])) { |
||
4974 | $user_answer = Display::span($real_list[$s_user_answer]); |
||
4975 | } |
||
4976 | } |
||
4977 | } |
||
4978 | } |
||
4979 | } elseif (DRAGGABLE == $answerType) { |
||
4980 | $user_answer = Display::label(get_lang('Incorrect'), 'danger'); |
||
4981 | if ($this->showExpectedChoice()) { |
||
4982 | $user_answer = ''; |
||
4983 | } |
||
4984 | } else { |
||
4985 | $user_answer = Display::span( |
||
4986 | get_lang('Incorrect').' ', |
||
4987 | ['style' => 'color: #FF0000; text-decoration: line-through;'] |
||
4988 | ); |
||
4989 | if ($this->showExpectedChoice()) { |
||
4990 | $user_answer = ''; |
||
4991 | } |
||
4992 | } |
||
4993 | |||
4994 | if ($show_result) { |
||
4995 | if (false === $this->showExpectedChoice() && |
||
4996 | false === $showTotalScoreAndUserChoicesInLastAttempt |
||
4997 | ) { |
||
4998 | $user_answer = ''; |
||
4999 | } |
||
5000 | switch ($answerType) { |
||
5001 | case MATCHING: |
||
5002 | case MATCHING_COMBINATION: |
||
5003 | case MATCHING_DRAGGABLE_COMBINATION: |
||
5004 | case MATCHING_DRAGGABLE: |
||
5005 | if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $this->results_disabled) { |
||
5006 | if (false === $showTotalScoreAndUserChoicesInLastAttempt && empty($s_user_answer)) { |
||
5007 | break; |
||
5008 | } |
||
5009 | } |
||
5010 | |||
5011 | echo '<tr>'; |
||
5012 | if (!in_array( |
||
5013 | $this->results_disabled, |
||
5014 | [ |
||
5015 | RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, |
||
5016 | ] |
||
5017 | ) |
||
5018 | ) { |
||
5019 | echo '<td>'.$s_answer_label.'</td>'; |
||
5020 | echo '<td>'.$user_answer.'</td>'; |
||
5021 | } else { |
||
5022 | echo '<td>'.$s_answer_label.'</td>'; |
||
5023 | $status = Display::label(get_lang('Correct'), 'success'); |
||
5024 | } |
||
5025 | |||
5026 | if ($this->showExpectedChoice()) { |
||
5027 | if ($this->showExpectedChoiceColumn()) { |
||
5028 | echo '<td>'; |
||
5029 | if (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
5030 | if (isset($real_list[$i_answer_correct_answer]) && |
||
5031 | $showTotalScoreAndUserChoicesInLastAttempt == true |
||
5032 | ) { |
||
5033 | echo Display::span( |
||
5034 | $real_list[$i_answer_correct_answer] |
||
5035 | ); |
||
5036 | } |
||
5037 | } |
||
5038 | echo '</td>'; |
||
5039 | } |
||
5040 | echo '<td>'.$status.'</td>'; |
||
5041 | } else { |
||
5042 | if (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
5043 | if (isset($real_list[$i_answer_correct_answer]) && |
||
5044 | $showTotalScoreAndUserChoicesInLastAttempt === true |
||
5045 | ) { |
||
5046 | if ($this->showExpectedChoiceColumn()) { |
||
5047 | echo '<td>'; |
||
5048 | echo Display::span( |
||
5049 | $real_list[$i_answer_correct_answer], |
||
5050 | ['style' => 'color: #008000; font-weight: bold;'] |
||
5051 | ); |
||
5052 | echo '</td>'; |
||
5053 | } |
||
5054 | } |
||
5055 | } |
||
5056 | } |
||
5057 | echo '</tr>'; |
||
5058 | break; |
||
5059 | case DRAGGABLE: |
||
5060 | if (false == $showTotalScoreAndUserChoicesInLastAttempt) { |
||
5061 | $s_answer_label = ''; |
||
5062 | } |
||
5063 | if (RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK == $this->results_disabled) { |
||
5064 | if (false === $showTotalScoreAndUserChoicesInLastAttempt && empty($s_user_answer)) { |
||
5065 | break; |
||
5066 | } |
||
5067 | } |
||
5068 | |||
5069 | echo '<tr>'; |
||
5070 | if ($this->showExpectedChoice()) { |
||
5071 | if (!in_array($this->results_disabled, [ |
||
5072 | RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, |
||
5073 | //RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, |
||
5074 | ]) |
||
5075 | ) { |
||
5076 | echo '<td>'.$user_answer.'</td>'; |
||
5077 | } else { |
||
5078 | $status = Display::label(get_lang('Correct'), 'success'); |
||
5079 | } |
||
5080 | echo '<td>'.$s_answer_label.'</td>'; |
||
5081 | echo '<td>'.$status.'</td>'; |
||
5082 | } else { |
||
5083 | echo '<td>'.$s_answer_label.'</td>'; |
||
5084 | echo '<td>'.$user_answer.'</td>'; |
||
5085 | echo '<td>'.$counterAnswer.'</td>'; |
||
5086 | echo '<td>'.$status.'</td>'; |
||
5087 | echo '<td>'; |
||
5088 | if (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
5089 | if (isset($real_list[$i_answer_correct_answer]) && |
||
5090 | $showTotalScoreAndUserChoicesInLastAttempt === true |
||
5091 | ) { |
||
5092 | echo Display::span( |
||
5093 | $real_list[$i_answer_correct_answer], |
||
5094 | ['style' => 'color: #008000; font-weight: bold;'] |
||
5095 | ); |
||
5096 | } |
||
5097 | } |
||
5098 | echo '</td>'; |
||
5099 | } |
||
5100 | echo '</tr>'; |
||
5101 | break; |
||
5102 | } |
||
5103 | } |
||
5104 | $counterAnswer++; |
||
5105 | } |
||
5106 | $matchingCorrectAnswers[$questionId]['from_database']['count_options'] = count($options); |
||
5107 | break 2; // break the switch and the "for" condition |
||
5108 | } else { |
||
5109 | if ($answerCorrect) { |
||
5110 | if (isset($choice[$answerAutoId]) && |
||
5111 | $answerCorrect == $choice[$answerAutoId] |
||
5112 | ) { |
||
5113 | $matchingCorrectAnswers[$questionId]['form_values']['correct'][$answerAutoId] = $choice[$answerAutoId]; |
||
5114 | $correctAnswerId[] = $answerAutoId; |
||
5115 | $questionScore += $answerWeighting; |
||
5116 | $totalScore += $answerWeighting; |
||
5117 | $user_answer = Display::span($answerMatching[$choice[$answerAutoId]]); |
||
5118 | } else { |
||
5119 | if (isset($answerMatching[$choice[$answerAutoId]])) { |
||
5120 | $user_answer = Display::span( |
||
5121 | $answerMatching[$choice[$answerAutoId]], |
||
5122 | ['style' => 'color: #FF0000; text-decoration: line-through;'] |
||
5123 | ); |
||
5124 | } |
||
5125 | } |
||
5126 | $matching[$answerAutoId] = $choice[$answerAutoId]; |
||
5127 | } |
||
5128 | $matchingCorrectAnswers[$questionId]['form_values']['count_options'] = count($choice); |
||
5129 | } |
||
5130 | break; |
||
5131 | case HOT_SPOT: |
||
5132 | case HOT_SPOT_COMBINATION: |
||
5133 | if ($from_database) { |
||
5134 | $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT); |
||
5135 | // Check auto id |
||
5136 | $foundAnswerId = $answerAutoId; |
||
5137 | $sql = "SELECT hotspot_correct |
||
5138 | FROM $TBL_TRACK_HOTSPOT |
||
5139 | WHERE |
||
5140 | hotspot_exe_id = $exeId AND |
||
5141 | hotspot_question_id= $questionId AND |
||
5142 | hotspot_answer_id = $answerAutoId |
||
5143 | ORDER BY hotspot_id ASC"; |
||
5144 | $result = Database::query($sql); |
||
5145 | if (Database::num_rows($result)) { |
||
5146 | $studentChoice = Database::result( |
||
5147 | $result, |
||
5148 | 0, |
||
5149 | 'hotspot_correct' |
||
5150 | ); |
||
5151 | |||
5152 | if ($studentChoice) { |
||
5153 | $questionScore += $answerWeighting; |
||
5154 | $totalScore += $answerWeighting; |
||
5155 | } |
||
5156 | } else { |
||
5157 | // If answer.id is different: |
||
5158 | $sql = "SELECT hotspot_correct |
||
5159 | FROM $TBL_TRACK_HOTSPOT |
||
5160 | WHERE |
||
5161 | hotspot_exe_id = $exeId AND |
||
5162 | hotspot_question_id= $questionId AND |
||
5163 | hotspot_answer_id = ".intval($answerId)." |
||
5164 | ORDER BY hotspot_id ASC"; |
||
5165 | $result = Database::query($sql); |
||
5166 | $foundAnswerId = $answerId; |
||
5167 | if (Database::num_rows($result)) { |
||
5168 | $studentChoice = Database::result( |
||
5169 | $result, |
||
5170 | 0, |
||
5171 | 'hotspot_correct' |
||
5172 | ); |
||
5173 | |||
5174 | if ($studentChoice) { |
||
5175 | $questionScore += $answerWeighting; |
||
5176 | $totalScore += $answerWeighting; |
||
5177 | } |
||
5178 | } else { |
||
5179 | // check answer.iid |
||
5180 | if (!empty($answerIid)) { |
||
5181 | $sql = "SELECT hotspot_correct |
||
5182 | FROM $TBL_TRACK_HOTSPOT |
||
5183 | WHERE |
||
5184 | hotspot_exe_id = $exeId AND |
||
5185 | hotspot_question_id= $questionId AND |
||
5186 | hotspot_answer_id = $answerIid |
||
5187 | ORDER BY hotspot_id ASC"; |
||
5188 | $result = Database::query($sql); |
||
5189 | $foundAnswerId = $answerIid; |
||
5190 | $studentChoice = Database::result( |
||
5191 | $result, |
||
5192 | 0, |
||
5193 | 'hotspot_correct' |
||
5194 | ); |
||
5195 | |||
5196 | if ($studentChoice) { |
||
5197 | $questionScore += $answerWeighting; |
||
5198 | $totalScore += $answerWeighting; |
||
5199 | } |
||
5200 | } |
||
5201 | } |
||
5202 | } |
||
5203 | } else { |
||
5204 | if (!isset($choice[$answerAutoId]) && !isset($choice[$answerIid])) { |
||
5205 | $choice[$answerAutoId] = 0; |
||
5206 | $choice[$answerIid] = 0; |
||
5207 | } else { |
||
5208 | $studentChoice = $choice[$answerAutoId]; |
||
5209 | if (empty($studentChoice)) { |
||
5210 | $studentChoice = $choice[$answerIid]; |
||
5211 | } |
||
5212 | $choiceIsValid = false; |
||
5213 | if (!empty($studentChoice)) { |
||
5214 | $hotspotType = $objAnswerTmp->selectHotspotType($answerId); |
||
5215 | $hotspotCoordinates = $objAnswerTmp->selectHotspotCoordinates($answerId); |
||
5216 | $choicePoint = Geometry::decodePoint($studentChoice); |
||
5217 | |||
5218 | switch ($hotspotType) { |
||
5219 | case 'square': |
||
5220 | $hotspotProperties = Geometry::decodeSquare($hotspotCoordinates); |
||
5221 | $choiceIsValid = Geometry::pointIsInSquare($hotspotProperties, $choicePoint); |
||
5222 | break; |
||
5223 | case 'circle': |
||
5224 | $hotspotProperties = Geometry::decodeEllipse($hotspotCoordinates); |
||
5225 | $choiceIsValid = Geometry::pointIsInEllipse($hotspotProperties, $choicePoint); |
||
5226 | break; |
||
5227 | case 'poly': |
||
5228 | $hotspotProperties = Geometry::decodePolygon($hotspotCoordinates); |
||
5229 | $choiceIsValid = Geometry::pointIsInPolygon($hotspotProperties, $choicePoint); |
||
5230 | break; |
||
5231 | } |
||
5232 | } |
||
5233 | |||
5234 | $choice[$answerAutoId] = 0; |
||
5235 | if ($choiceIsValid) { |
||
5236 | $questionScore += $answerWeighting; |
||
5237 | $totalScore += $answerWeighting; |
||
5238 | $choice[$answerAutoId] = 1; |
||
5239 | $choice[$answerIid] = 1; |
||
5240 | } |
||
5241 | } |
||
5242 | } |
||
5243 | break; |
||
5244 | case HOT_SPOT_ORDER: |
||
5245 | // @todo never added to chamilo |
||
5246 | // for hotspot with fixed order |
||
5247 | $studentChoice = $choice['order'][$answerId]; |
||
5248 | if ($studentChoice == $answerId) { |
||
5249 | $questionScore += $answerWeighting; |
||
5250 | $totalScore += $answerWeighting; |
||
5251 | $studentChoice = true; |
||
5252 | } else { |
||
5253 | $studentChoice = false; |
||
5254 | } |
||
5255 | break; |
||
5256 | case HOT_SPOT_DELINEATION: |
||
5257 | // for hotspot with delineation |
||
5258 | if ($from_database) { |
||
5259 | // getting the user answer |
||
5260 | $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT); |
||
5261 | $query = "SELECT hotspot_correct, hotspot_coordinate |
||
5262 | FROM $TBL_TRACK_HOTSPOT |
||
5263 | WHERE |
||
5264 | hotspot_exe_id = $exeId AND |
||
5265 | hotspot_question_id= $questionId AND |
||
5266 | hotspot_answer_id = '1'"; |
||
5267 | // By default we take 1 because it's a delineation |
||
5268 | $resq = Database::query($query); |
||
5269 | $row = Database::fetch_array($resq, 'ASSOC'); |
||
5270 | |||
5271 | $choice = $row['hotspot_correct']; |
||
5272 | $user_answer = $row['hotspot_coordinate']; |
||
5273 | |||
5274 | // THIS is very important otherwise the poly_compile will throw an error!! |
||
5275 | // round-up the coordinates |
||
5276 | $coords = explode('/', $user_answer); |
||
5277 | $coords = array_filter($coords); |
||
5278 | $user_array = ''; |
||
5279 | foreach ($coords as $coord) { |
||
5280 | [$x, $y] = explode(';', $coord); |
||
5281 | $user_array .= round($x).';'.round($y).'/'; |
||
5282 | } |
||
5283 | $user_array = substr($user_array, 0, -1) ?: ''; |
||
5284 | } else { |
||
5285 | if (!empty($studentChoice)) { |
||
5286 | $correctAnswerId[] = $answerAutoId; |
||
5287 | $newquestionList[] = $questionId; |
||
5288 | } |
||
5289 | |||
5290 | if (1 === $answerId) { |
||
5291 | $studentChoice = $choice[$answerId]; |
||
5292 | $questionScore += $answerWeighting; |
||
5293 | } |
||
5294 | if (isset($_SESSION['exerciseResultCoordinates'][$questionId])) { |
||
5295 | $user_array = $_SESSION['exerciseResultCoordinates'][$questionId]; |
||
5296 | } |
||
5297 | } |
||
5298 | $_SESSION['hotspot_coord'][$questionId][1] = $delineation_cord; |
||
5299 | $_SESSION['hotspot_dest'][$questionId][1] = $answer_delineation_destination; |
||
5300 | break; |
||
5301 | case ANNOTATION: |
||
5302 | if ($from_database) { |
||
5303 | $sql = "SELECT answer, marks |
||
5304 | FROM $TBL_TRACK_ATTEMPT |
||
5305 | WHERE |
||
5306 | exe_id = $exeId AND |
||
5307 | question_id = $questionId "; |
||
5308 | $resq = Database::query($sql); |
||
5309 | $data = Database::fetch_array($resq); |
||
5310 | |||
5311 | $questionScore = empty($data['marks']) ? 0 : $data['marks']; |
||
5312 | $arrques = $questionName; |
||
5313 | break; |
||
5314 | } |
||
5315 | $studentChoice = $choice; |
||
5316 | if ($studentChoice) { |
||
5317 | $questionScore = 0; |
||
5318 | } |
||
5319 | break; |
||
5320 | } |
||
5321 | |||
5322 | if ($show_result) { |
||
5323 | if ('exercise_result' === $from) { |
||
5324 | // Display answers (if not matching type, or if the answer is correct) |
||
5325 | if (!in_array($answerType, [MATCHING, MATCHING_COMBINATION, DRAGGABLE, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION]) || |
||
5326 | $answerCorrect |
||
5327 | ) { |
||
5328 | if (in_array( |
||
5329 | $answerType, |
||
5330 | [ |
||
5331 | UNIQUE_ANSWER, |
||
5332 | UNIQUE_ANSWER_IMAGE, |
||
5333 | UNIQUE_ANSWER_NO_OPTION, |
||
5334 | MULTIPLE_ANSWER, |
||
5335 | MULTIPLE_ANSWER_COMBINATION, |
||
5336 | GLOBAL_MULTIPLE_ANSWER, |
||
5337 | READING_COMPREHENSION, |
||
5338 | ] |
||
5339 | )) { |
||
5340 | ExerciseShowFunctions::display_unique_or_multiple_answer( |
||
5341 | $this, |
||
5342 | $feedback_type, |
||
5343 | $answerType, |
||
5344 | $studentChoice, |
||
5345 | $answer, |
||
5346 | $answerComment, |
||
5347 | $answerCorrect, |
||
5348 | 0, |
||
5349 | 0, |
||
5350 | 0, |
||
5351 | $results_disabled, |
||
5352 | $showTotalScoreAndUserChoicesInLastAttempt, |
||
5353 | $this->export |
||
5354 | ); |
||
5355 | } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) { |
||
5356 | ExerciseShowFunctions::display_multiple_answer_true_false( |
||
5357 | $this, |
||
5358 | $feedback_type, |
||
5359 | $answerType, |
||
5360 | $studentChoice, |
||
5361 | $answer, |
||
5362 | $answerComment, |
||
5363 | $answerCorrect, |
||
5364 | 0, |
||
5365 | $questionId, |
||
5366 | 0, |
||
5367 | $results_disabled, |
||
5368 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5369 | ); |
||
5370 | } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
5371 | ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty( |
||
5372 | $this, |
||
5373 | $feedback_type, |
||
5374 | $studentChoice, |
||
5375 | $studentChoiceDegree, |
||
5376 | $answer, |
||
5377 | $answerComment, |
||
5378 | $answerCorrect, |
||
5379 | $questionId, |
||
5380 | $results_disabled |
||
5381 | ); |
||
5382 | } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE) { |
||
5383 | ExerciseShowFunctions::display_multiple_answer_combination_true_false( |
||
5384 | $this, |
||
5385 | $feedback_type, |
||
5386 | $answerType, |
||
5387 | $studentChoice, |
||
5388 | $answer, |
||
5389 | $answerComment, |
||
5390 | $answerCorrect, |
||
5391 | 0, |
||
5392 | 0, |
||
5393 | 0, |
||
5394 | $results_disabled, |
||
5395 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5396 | ); |
||
5397 | } elseif (in_array($answerType, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||
5398 | ExerciseShowFunctions::display_fill_in_blanks_answer( |
||
5399 | $this, |
||
5400 | $feedback_type, |
||
5401 | $answer, |
||
5402 | 0, |
||
5403 | 0, |
||
5404 | $results_disabled, |
||
5405 | '', |
||
5406 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5407 | ); |
||
5408 | } elseif ($answerType == CALCULATED_ANSWER) { |
||
5409 | ExerciseShowFunctions::display_calculated_answer( |
||
5410 | $this, |
||
5411 | $feedback_type, |
||
5412 | $answer, |
||
5413 | 0, |
||
5414 | 0, |
||
5415 | $results_disabled, |
||
5416 | $showTotalScoreAndUserChoicesInLastAttempt, |
||
5417 | $expectedAnswer, |
||
5418 | $calculatedChoice, |
||
5419 | $calculatedStatus |
||
5420 | ); |
||
5421 | } elseif ($answerType == FREE_ANSWER) { |
||
5422 | ExerciseShowFunctions::display_free_answer( |
||
5423 | $feedback_type, |
||
5424 | $choice, |
||
5425 | $exeId, |
||
5426 | $questionId, |
||
5427 | $questionScore, |
||
5428 | $results_disabled |
||
5429 | ); |
||
5430 | } elseif ($answerType == UPLOAD_ANSWER) { |
||
5431 | ExerciseShowFunctions::displayUploadAnswer( |
||
5432 | $feedback_type, |
||
5433 | $choice, |
||
5434 | $exeId, |
||
5435 | $questionId, |
||
5436 | $questionScore, |
||
5437 | $results_disabled |
||
5438 | ); |
||
5439 | } elseif ($answerType == ANSWER_IN_OFFICE_DOC) { |
||
5440 | $exe_info = Event::get_exercise_results_by_attempt($exeId); |
||
5441 | $exe_info = $exe_info[$exeId] ?? null; |
||
5442 | ExerciseShowFunctions::displayOnlyOfficeAnswer( |
||
5443 | $feedback_type, |
||
5444 | $exeId, |
||
5445 | $exe_info['exe_user_id'] ?? api_get_user_id(), |
||
5446 | $this->iid, |
||
5447 | $questionId, |
||
5448 | $questionScore, |
||
5449 | true |
||
5450 | ); |
||
5451 | } elseif ($answerType == ORAL_EXPRESSION) { |
||
5452 | // to store the details of open questions in an array to be used in mail |
||
5453 | /** @var OralExpression $objQuestionTmp */ |
||
5454 | ExerciseShowFunctions::display_oral_expression_answer( |
||
5455 | $feedback_type, |
||
5456 | $choice, |
||
5457 | 0, |
||
5458 | 0, |
||
5459 | $objQuestionTmp->getFileUrl(true), |
||
5460 | $results_disabled, |
||
5461 | $questionScore |
||
5462 | ); |
||
5463 | } elseif (in_array($answerType, [HOT_SPOT, HOT_SPOT_COMBINATION])) { |
||
5464 | $correctAnswerId = 0; |
||
5465 | /** @var TrackEHotspot $hotspot */ |
||
5466 | foreach ($orderedHotSpots as $correctAnswerId => $hotspot) { |
||
5467 | if ($hotspot->getHotspotAnswerId() == $answerIid) { |
||
5468 | break; |
||
5469 | } |
||
5470 | } |
||
5471 | |||
5472 | // force to show whether the choice is correct or not |
||
5473 | $showTotalScoreAndUserChoicesInLastAttempt = true; |
||
5474 | ExerciseShowFunctions::display_hotspot_answer( |
||
5475 | $this, |
||
5476 | $feedback_type, |
||
5477 | $answerId, |
||
5478 | $answer, |
||
5479 | $studentChoice, |
||
5480 | $answerComment, |
||
5481 | $results_disabled, |
||
5482 | $answerId, |
||
5483 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5484 | ); |
||
5485 | } elseif ($answerType == HOT_SPOT_ORDER) { |
||
5486 | ExerciseShowFunctions::display_hotspot_order_answer( |
||
5487 | $feedback_type, |
||
5488 | $answerId, |
||
5489 | $answer, |
||
5490 | $studentChoice, |
||
5491 | $answerComment |
||
5492 | ); |
||
5493 | } elseif ($answerType == HOT_SPOT_DELINEATION) { |
||
5494 | $user_answer = $_SESSION['exerciseResultCoordinates'][$questionId]; |
||
5495 | |||
5496 | // Round-up the coordinates |
||
5497 | $coords = explode('/', $user_answer); |
||
5498 | $coords = array_filter($coords); |
||
5499 | $user_array = ''; |
||
5500 | foreach ($coords as $coord) { |
||
5501 | if (!empty($coord)) { |
||
5502 | $parts = explode(';', $coord); |
||
5503 | if (!empty($parts)) { |
||
5504 | $user_array .= round($parts[0]).';'.round($parts[1]).'/'; |
||
5505 | } |
||
5506 | } |
||
5507 | } |
||
5508 | $user_array = substr($user_array, 0, -1) ?: ''; |
||
5509 | if ($next) { |
||
5510 | $user_answer = $user_array; |
||
5511 | // We compare only the delineation not the other points |
||
5512 | $answer_question = $_SESSION['hotspot_coord'][$questionId][1]; |
||
5513 | $answerDestination = $_SESSION['hotspot_dest'][$questionId][1]; |
||
5514 | |||
5515 | // Calculating the area |
||
5516 | $poly_user = convert_coordinates($user_answer, '/'); |
||
5517 | $poly_answer = convert_coordinates($answer_question, '|'); |
||
5518 | $max_coord = poly_get_max($poly_user, $poly_answer); |
||
5519 | $poly_user_compiled = poly_compile($poly_user, $max_coord); |
||
5520 | $poly_answer_compiled = poly_compile($poly_answer, $max_coord); |
||
5521 | $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord); |
||
5522 | |||
5523 | $overlap = $poly_results['both']; |
||
5524 | $poly_answer_area = $poly_results['s1']; |
||
5525 | $poly_user_area = $poly_results['s2']; |
||
5526 | $missing = $poly_results['s1Only']; |
||
5527 | $excess = $poly_results['s2Only']; |
||
5528 | |||
5529 | // //this is an area in pixels |
||
5530 | if ($debug > 0) { |
||
5531 | error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0); |
||
5532 | } |
||
5533 | |||
5534 | if ($overlap < 1) { |
||
5535 | // Shortcut to avoid complicated calculations |
||
5536 | $final_overlap = 0; |
||
5537 | $final_missing = 100; |
||
5538 | $final_excess = 100; |
||
5539 | } else { |
||
5540 | // the final overlap is the percentage of the initial polygon |
||
5541 | // that is overlapped by the user's polygon |
||
5542 | $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100); |
||
5543 | if ($debug > 1) { |
||
5544 | error_log(__LINE__.' - Final overlap is '.$final_overlap, 0); |
||
5545 | } |
||
5546 | // the final missing area is the percentage of the initial polygon |
||
5547 | // that is not overlapped by the user's polygon |
||
5548 | $final_missing = 100 - $final_overlap; |
||
5549 | if ($debug > 1) { |
||
5550 | error_log(__LINE__.' - Final missing is '.$final_missing, 0); |
||
5551 | } |
||
5552 | // the final excess area is the percentage of the initial polygon's size |
||
5553 | // that is covered by the user's polygon outside of the initial polygon |
||
5554 | $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100); |
||
5555 | if ($debug > 1) { |
||
5556 | error_log(__LINE__.' - Final excess is '.$final_excess, 0); |
||
5557 | } |
||
5558 | } |
||
5559 | |||
5560 | // Checking the destination parameters parsing the "@@" |
||
5561 | $destination_items = explode('@@', $answerDestination); |
||
5562 | $threadhold_total = $destination_items[0]; |
||
5563 | $threadhold_items = explode(';', $threadhold_total); |
||
5564 | $threadhold1 = $threadhold_items[0]; // overlap |
||
5565 | $threadhold2 = $threadhold_items[1]; // excess |
||
5566 | $threadhold3 = $threadhold_items[2]; // missing |
||
5567 | |||
5568 | // if is delineation |
||
5569 | if ($answerId === 1) { |
||
5570 | //setting colors |
||
5571 | if ($final_overlap >= $threadhold1) { |
||
5572 | $overlap_color = true; |
||
5573 | } |
||
5574 | if ($final_excess <= $threadhold2) { |
||
5575 | $excess_color = true; |
||
5576 | } |
||
5577 | if ($final_missing <= $threadhold3) { |
||
5578 | $missing_color = true; |
||
5579 | } |
||
5580 | |||
5581 | // if pass |
||
5582 | if ($final_overlap >= $threadhold1 && |
||
5583 | $final_missing <= $threadhold3 && |
||
5584 | $final_excess <= $threadhold2 |
||
5585 | ) { |
||
5586 | $next = 1; //go to the oars |
||
5587 | $result_comment = get_lang('Acceptable'); |
||
5588 | $final_answer = 1; // do not update with update_exercise_attempt |
||
5589 | } else { |
||
5590 | $next = 0; |
||
5591 | $result_comment = get_lang('Unacceptable'); |
||
5592 | $comment = $answerDestination = $objAnswerTmp->selectComment(1); |
||
5593 | $answerDestination = $objAnswerTmp->selectDestination(1); |
||
5594 | // checking the destination parameters parsing the "@@" |
||
5595 | $destination_items = explode('@@', $answerDestination); |
||
5596 | } |
||
5597 | } elseif ($answerId > 1) { |
||
5598 | if ($objAnswerTmp->selectHotspotType($answerId) == 'noerror') { |
||
5599 | if ($debug > 0) { |
||
5600 | error_log(__LINE__.' - answerId is of type noerror', 0); |
||
5601 | } |
||
5602 | //type no error shouldn't be treated |
||
5603 | $next = 1; |
||
5604 | continue; |
||
5605 | } |
||
5606 | if ($debug > 0) { |
||
5607 | error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0); |
||
5608 | } |
||
5609 | $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId); |
||
5610 | $poly_answer = convert_coordinates($delineation_cord, '|'); |
||
5611 | $max_coord = poly_get_max($poly_user, $poly_answer); |
||
5612 | $poly_answer_compiled = poly_compile($poly_answer, $max_coord); |
||
5613 | $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord); |
||
5614 | |||
5615 | if ($overlap == false) { |
||
5616 | //all good, no overlap |
||
5617 | $next = 1; |
||
5618 | continue; |
||
5619 | } else { |
||
5620 | if ($debug > 0) { |
||
5621 | error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0); |
||
5622 | } |
||
5623 | $organs_at_risk_hit++; |
||
5624 | //show the feedback |
||
5625 | $next = 0; |
||
5626 | $comment = $answerDestination = $objAnswerTmp->selectComment($answerId); |
||
5627 | $answerDestination = $objAnswerTmp->selectDestination($answerId); |
||
5628 | |||
5629 | $destination_items = explode('@@', $answerDestination); |
||
5630 | $try_hotspot = $destination_items[1]; |
||
5631 | $lp_hotspot = $destination_items[2]; |
||
5632 | $select_question_hotspot = $destination_items[3]; |
||
5633 | $url_hotspot = $destination_items[4]; |
||
5634 | } |
||
5635 | } |
||
5636 | } else { |
||
5637 | // the first delineation feedback |
||
5638 | if ($debug > 0) { |
||
5639 | error_log(__LINE__.' first', 0); |
||
5640 | } |
||
5641 | } |
||
5642 | } elseif (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
5643 | echo '<tr>'; |
||
5644 | echo Display::tag('td', $answerMatching[$answerId]); |
||
5645 | echo Display::tag( |
||
5646 | 'td', |
||
5647 | "$user_answer / ".Display::tag( |
||
5648 | 'strong', |
||
5649 | $answerMatching[$answerCorrect], |
||
5650 | ['style' => 'color: #008000; font-weight: bold;'] |
||
5651 | ) |
||
5652 | ); |
||
5653 | echo '</tr>'; |
||
5654 | } elseif ($answerType == ANNOTATION) { |
||
5655 | ExerciseShowFunctions::displayAnnotationAnswer( |
||
5656 | $feedback_type, |
||
5657 | $exeId, |
||
5658 | $questionId, |
||
5659 | $questionScore, |
||
5660 | $results_disabled |
||
5661 | ); |
||
5662 | } |
||
5663 | } |
||
5664 | } else { |
||
5665 | if ($debug) { |
||
5666 | error_log('Showing questions $from '.$from); |
||
5667 | } |
||
5668 | |||
5669 | switch ($answerType) { |
||
5670 | case UNIQUE_ANSWER: |
||
5671 | case UNIQUE_ANSWER_IMAGE: |
||
5672 | case UNIQUE_ANSWER_NO_OPTION: |
||
5673 | case MULTIPLE_ANSWER: |
||
5674 | case GLOBAL_MULTIPLE_ANSWER: |
||
5675 | case MULTIPLE_ANSWER_COMBINATION: |
||
5676 | case READING_COMPREHENSION: |
||
5677 | if ($answerId == 1) { |
||
5678 | ExerciseShowFunctions::display_unique_or_multiple_answer( |
||
5679 | $this, |
||
5680 | $feedback_type, |
||
5681 | $answerType, |
||
5682 | $studentChoice, |
||
5683 | $answer, |
||
5684 | $answerComment, |
||
5685 | $answerCorrect, |
||
5686 | $exeId, |
||
5687 | $questionId, |
||
5688 | $answerId, |
||
5689 | $results_disabled, |
||
5690 | $showTotalScoreAndUserChoicesInLastAttempt, |
||
5691 | $this->export |
||
5692 | ); |
||
5693 | } else { |
||
5694 | ExerciseShowFunctions::display_unique_or_multiple_answer( |
||
5695 | $this, |
||
5696 | $feedback_type, |
||
5697 | $answerType, |
||
5698 | $studentChoice, |
||
5699 | $answer, |
||
5700 | $answerComment, |
||
5701 | $answerCorrect, |
||
5702 | $exeId, |
||
5703 | $questionId, |
||
5704 | '', |
||
5705 | $results_disabled, |
||
5706 | $showTotalScoreAndUserChoicesInLastAttempt, |
||
5707 | $this->export |
||
5708 | ); |
||
5709 | } |
||
5710 | break; |
||
5711 | case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE: |
||
5712 | if ($answerId == 1) { |
||
5713 | ExerciseShowFunctions::display_multiple_answer_combination_true_false( |
||
5714 | $this, |
||
5715 | $feedback_type, |
||
5716 | $answerType, |
||
5717 | $studentChoice, |
||
5718 | $answer, |
||
5719 | $answerComment, |
||
5720 | $answerCorrect, |
||
5721 | $exeId, |
||
5722 | $questionId, |
||
5723 | $answerId, |
||
5724 | $results_disabled, |
||
5725 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5726 | ); |
||
5727 | } else { |
||
5728 | ExerciseShowFunctions::display_multiple_answer_combination_true_false( |
||
5729 | $this, |
||
5730 | $feedback_type, |
||
5731 | $answerType, |
||
5732 | $studentChoice, |
||
5733 | $answer, |
||
5734 | $answerComment, |
||
5735 | $answerCorrect, |
||
5736 | $exeId, |
||
5737 | $questionId, |
||
5738 | '', |
||
5739 | $results_disabled, |
||
5740 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5741 | ); |
||
5742 | } |
||
5743 | break; |
||
5744 | case MULTIPLE_ANSWER_TRUE_FALSE: |
||
5745 | if ($answerId == 1) { |
||
5746 | ExerciseShowFunctions::display_multiple_answer_true_false( |
||
5747 | $this, |
||
5748 | $feedback_type, |
||
5749 | $answerType, |
||
5750 | $studentChoice, |
||
5751 | $answer, |
||
5752 | $answerComment, |
||
5753 | $answerCorrect, |
||
5754 | $exeId, |
||
5755 | $questionId, |
||
5756 | $answerId, |
||
5757 | $results_disabled, |
||
5758 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5759 | ); |
||
5760 | } else { |
||
5761 | ExerciseShowFunctions::display_multiple_answer_true_false( |
||
5762 | $this, |
||
5763 | $feedback_type, |
||
5764 | $answerType, |
||
5765 | $studentChoice, |
||
5766 | $answer, |
||
5767 | $answerComment, |
||
5768 | $answerCorrect, |
||
5769 | $exeId, |
||
5770 | $questionId, |
||
5771 | '', |
||
5772 | $results_disabled, |
||
5773 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5774 | ); |
||
5775 | } |
||
5776 | break; |
||
5777 | case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY: |
||
5778 | if ($answerId == 1) { |
||
5779 | ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty( |
||
5780 | $this, |
||
5781 | $feedback_type, |
||
5782 | $studentChoice, |
||
5783 | $studentChoiceDegree, |
||
5784 | $answer, |
||
5785 | $answerComment, |
||
5786 | $answerCorrect, |
||
5787 | $questionId, |
||
5788 | $results_disabled |
||
5789 | ); |
||
5790 | } else { |
||
5791 | ExerciseShowFunctions::displayMultipleAnswerTrueFalseDegreeCertainty( |
||
5792 | $this, |
||
5793 | $feedback_type, |
||
5794 | $studentChoice, |
||
5795 | $studentChoiceDegree, |
||
5796 | $answer, |
||
5797 | $answerComment, |
||
5798 | $answerCorrect, |
||
5799 | $questionId, |
||
5800 | $results_disabled |
||
5801 | ); |
||
5802 | } |
||
5803 | break; |
||
5804 | case FILL_IN_BLANKS: |
||
5805 | case FILL_IN_BLANKS_COMBINATION: |
||
5806 | ExerciseShowFunctions::display_fill_in_blanks_answer( |
||
5807 | $this, |
||
5808 | $feedback_type, |
||
5809 | $answer, |
||
5810 | $exeId, |
||
5811 | $questionId, |
||
5812 | $results_disabled, |
||
5813 | $str, |
||
5814 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5815 | ); |
||
5816 | break; |
||
5817 | case CALCULATED_ANSWER: |
||
5818 | ExerciseShowFunctions::display_calculated_answer( |
||
5819 | $this, |
||
5820 | $feedback_type, |
||
5821 | $answer, |
||
5822 | $exeId, |
||
5823 | $questionId, |
||
5824 | $results_disabled, |
||
5825 | '', |
||
5826 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5827 | ); |
||
5828 | break; |
||
5829 | case FREE_ANSWER: |
||
5830 | echo ExerciseShowFunctions::display_free_answer( |
||
5831 | $feedback_type, |
||
5832 | $choice, |
||
5833 | $exeId, |
||
5834 | $questionId, |
||
5835 | $questionScore, |
||
5836 | $results_disabled |
||
5837 | ); |
||
5838 | break; |
||
5839 | case UPLOAD_ANSWER: |
||
5840 | echo ExerciseShowFunctions::displayUploadAnswer( |
||
5841 | $feedback_type, |
||
5842 | $choice, |
||
5843 | $exeId, |
||
5844 | $questionId, |
||
5845 | $questionScore, |
||
5846 | $results_disabled |
||
5847 | ); |
||
5848 | break; |
||
5849 | case ANSWER_IN_OFFICE_DOC: |
||
5850 | $exe_info = Event::get_exercise_results_by_attempt($exeId); |
||
5851 | $exe_info = $exe_info[$exeId] ?? null; |
||
5852 | ExerciseShowFunctions::displayOnlyOfficeAnswer( |
||
5853 | $feedback_type, |
||
5854 | $exeId, |
||
5855 | $exe_info['exe_user_id'] ?? api_get_user_id(), |
||
5856 | $this->iid, |
||
5857 | $questionId, |
||
5858 | $questionScore |
||
5859 | ); |
||
5860 | break; |
||
5861 | case ORAL_EXPRESSION: |
||
5862 | echo '<tr> |
||
5863 | <td>'. |
||
5864 | ExerciseShowFunctions::display_oral_expression_answer( |
||
5865 | $feedback_type, |
||
5866 | $choice, |
||
5867 | $exeId, |
||
5868 | $questionId, |
||
5869 | $objQuestionTmp->getFileUrl(), |
||
5870 | $results_disabled, |
||
5871 | $questionScore |
||
5872 | ).'</td> |
||
5873 | </tr> |
||
5874 | </table>'; |
||
5875 | break; |
||
5876 | case HOT_SPOT: |
||
5877 | case HOT_SPOT_COMBINATION: |
||
5878 | $correctAnswerId = 0; |
||
5879 | /** @var TrackEHotspot $hotspot */ |
||
5880 | foreach ($orderedHotSpots as $correctAnswerId => $hotspot) { |
||
5881 | if ($hotspot->getHotspotAnswerId() == $foundAnswerId) { |
||
5882 | break; |
||
5883 | } |
||
5884 | } |
||
5885 | |||
5886 | ExerciseShowFunctions::display_hotspot_answer( |
||
5887 | $this, |
||
5888 | $feedback_type, |
||
5889 | $answerId, |
||
5890 | $answer, |
||
5891 | $studentChoice, |
||
5892 | $answerComment, |
||
5893 | $results_disabled, |
||
5894 | $answerId, |
||
5895 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5896 | ); |
||
5897 | break; |
||
5898 | case HOT_SPOT_DELINEATION: |
||
5899 | $user_answer = $user_array; |
||
5900 | if ($next) { |
||
5901 | $user_answer = $user_array; |
||
5902 | // we compare only the delineation not the other points |
||
5903 | $answer_question = $_SESSION['hotspot_coord'][$questionId][1]; |
||
5904 | $answerDestination = $_SESSION['hotspot_dest'][$questionId][1]; |
||
5905 | |||
5906 | // calculating the area |
||
5907 | $poly_user = convert_coordinates($user_answer, '/'); |
||
5908 | $poly_answer = convert_coordinates($answer_question, '|'); |
||
5909 | $max_coord = poly_get_max($poly_user, $poly_answer); |
||
5910 | $poly_user_compiled = poly_compile($poly_user, $max_coord); |
||
5911 | $poly_answer_compiled = poly_compile($poly_answer, $max_coord); |
||
5912 | $poly_results = poly_result($poly_answer_compiled, $poly_user_compiled, $max_coord); |
||
5913 | |||
5914 | $overlap = $poly_results['both']; |
||
5915 | $poly_answer_area = $poly_results['s1']; |
||
5916 | $poly_user_area = $poly_results['s2']; |
||
5917 | $missing = $poly_results['s1Only']; |
||
5918 | $excess = $poly_results['s2Only']; |
||
5919 | if ($debug > 0) { |
||
5920 | error_log(__LINE__.' - Polygons results are '.print_r($poly_results, 1), 0); |
||
5921 | } |
||
5922 | if ($overlap < 1) { |
||
5923 | //shortcut to avoid complicated calculations |
||
5924 | $final_overlap = 0; |
||
5925 | $final_missing = 100; |
||
5926 | $final_excess = 100; |
||
5927 | } else { |
||
5928 | // the final overlap is the percentage of the initial polygon |
||
5929 | // that is overlapped by the user's polygon |
||
5930 | $final_overlap = round(((float) $overlap / (float) $poly_answer_area) * 100); |
||
5931 | |||
5932 | // the final missing area is the percentage of the initial polygon that |
||
5933 | // is not overlapped by the user's polygon |
||
5934 | $final_missing = 100 - $final_overlap; |
||
5935 | // the final excess area is the percentage of the initial polygon's size that is |
||
5936 | // covered by the user's polygon outside of the initial polygon |
||
5937 | $final_excess = round((((float) $poly_user_area - (float) $overlap) / (float) $poly_answer_area) * 100); |
||
5938 | |||
5939 | if ($debug > 1) { |
||
5940 | error_log(__LINE__.' - Final overlap is '.$final_overlap); |
||
5941 | error_log(__LINE__.' - Final excess is '.$final_excess); |
||
5942 | error_log(__LINE__.' - Final missing is '.$final_missing); |
||
5943 | } |
||
5944 | } |
||
5945 | |||
5946 | // Checking the destination parameters parsing the "@@" |
||
5947 | $destination_items = explode('@@', $answerDestination); |
||
5948 | $threadhold_total = $destination_items[0]; |
||
5949 | $threadhold_items = explode(';', $threadhold_total); |
||
5950 | $threadhold1 = $threadhold_items[0]; // overlap |
||
5951 | $threadhold2 = $threadhold_items[1]; // excess |
||
5952 | $threadhold3 = $threadhold_items[2]; //missing |
||
5953 | // if is delineation |
||
5954 | if ($answerId === 1) { |
||
5955 | //setting colors |
||
5956 | if ($final_overlap >= $threadhold1) { |
||
5957 | $overlap_color = true; |
||
5958 | } |
||
5959 | if ($final_excess <= $threadhold2) { |
||
5960 | $excess_color = true; |
||
5961 | } |
||
5962 | if ($final_missing <= $threadhold3) { |
||
5963 | $missing_color = true; |
||
5964 | } |
||
5965 | |||
5966 | // if pass |
||
5967 | if ($final_overlap >= $threadhold1 && |
||
5968 | $final_missing <= $threadhold3 && |
||
5969 | $final_excess <= $threadhold2 |
||
5970 | ) { |
||
5971 | $next = 1; //go to the oars |
||
5972 | $result_comment = get_lang('Acceptable'); |
||
5973 | $final_answer = 1; // do not update with update_exercise_attempt |
||
5974 | } else { |
||
5975 | $next = 0; |
||
5976 | $result_comment = get_lang('Unacceptable'); |
||
5977 | $comment = $answerDestination = $objAnswerTmp->selectComment(1); |
||
5978 | $answerDestination = $objAnswerTmp->selectDestination(1); |
||
5979 | //checking the destination parameters parsing the "@@" |
||
5980 | $destination_items = explode('@@', $answerDestination); |
||
5981 | } |
||
5982 | } elseif ($answerId > 1) { |
||
5983 | if ($objAnswerTmp->selectHotspotType($answerId) === 'noerror') { |
||
5984 | if ($debug > 0) { |
||
5985 | error_log(__LINE__.' - answerId is of type noerror', 0); |
||
5986 | } |
||
5987 | //type no error shouldn't be treated |
||
5988 | $next = 1; |
||
5989 | break; |
||
5990 | } |
||
5991 | if ($debug > 0) { |
||
5992 | error_log(__LINE__.' - answerId is >1 so we\'re probably in OAR', 0); |
||
5993 | } |
||
5994 | $delineation_cord = $objAnswerTmp->selectHotspotCoordinates($answerId); |
||
5995 | $poly_answer = convert_coordinates($delineation_cord, '|'); |
||
5996 | $max_coord = poly_get_max($poly_user, $poly_answer); |
||
5997 | $poly_answer_compiled = poly_compile($poly_answer, $max_coord); |
||
5998 | $overlap = poly_touch($poly_user_compiled, $poly_answer_compiled, $max_coord); |
||
5999 | |||
6000 | if ($overlap == false) { |
||
6001 | //all good, no overlap |
||
6002 | $next = 1; |
||
6003 | break; |
||
6004 | } else { |
||
6005 | if ($debug > 0) { |
||
6006 | error_log(__LINE__.' - Overlap is '.$overlap.': OAR hit', 0); |
||
6007 | } |
||
6008 | $organs_at_risk_hit++; |
||
6009 | //show the feedback |
||
6010 | $next = 0; |
||
6011 | $comment = $answerDestination = $objAnswerTmp->selectComment($answerId); |
||
6012 | $answerDestination = $objAnswerTmp->selectDestination($answerId); |
||
6013 | |||
6014 | $destination_items = explode('@@', $answerDestination); |
||
6015 | $try_hotspot = $destination_items[1]; |
||
6016 | $lp_hotspot = $destination_items[2]; |
||
6017 | $select_question_hotspot = $destination_items[3]; |
||
6018 | $url_hotspot = $destination_items[4]; |
||
6019 | } |
||
6020 | } |
||
6021 | } |
||
6022 | break; |
||
6023 | case HOT_SPOT_ORDER: |
||
6024 | ExerciseShowFunctions::display_hotspot_order_answer( |
||
6025 | $feedback_type, |
||
6026 | $answerId, |
||
6027 | $answer, |
||
6028 | $studentChoice, |
||
6029 | $answerComment |
||
6030 | ); |
||
6031 | break; |
||
6032 | case DRAGGABLE: |
||
6033 | case MATCHING_DRAGGABLE: |
||
6034 | case MATCHING_DRAGGABLE_COMBINATION: |
||
6035 | case MATCHING_COMBINATION: |
||
6036 | case MATCHING: |
||
6037 | echo '<tr>'; |
||
6038 | echo Display::tag('td', $answerMatching[$answerId]); |
||
6039 | echo Display::tag( |
||
6040 | 'td', |
||
6041 | "$user_answer / ".Display::tag( |
||
6042 | 'strong', |
||
6043 | $answerMatching[$answerCorrect], |
||
6044 | ['style' => 'color: #008000; font-weight: bold;'] |
||
6045 | ) |
||
6046 | ); |
||
6047 | echo '</tr>'; |
||
6048 | break; |
||
6049 | case ANNOTATION: |
||
6050 | ExerciseShowFunctions::displayAnnotationAnswer( |
||
6051 | $feedback_type, |
||
6052 | $exeId, |
||
6053 | $questionId, |
||
6054 | $questionScore, |
||
6055 | $results_disabled |
||
6056 | ); |
||
6057 | break; |
||
6058 | } |
||
6059 | } |
||
6060 | } |
||
6061 | } // end for that loops over all answers of the current question |
||
6062 | |||
6063 | // It validates unique score when all answers are correct for global questions |
||
6064 | if (FILL_IN_BLANKS_COMBINATION === $answerType) { |
||
6065 | $questionScore = ExerciseLib::getUserQuestionScoreGlobal( |
||
6066 | $answerType, |
||
6067 | $listCorrectAnswers, |
||
6068 | $exeId, |
||
6069 | $questionId, |
||
6070 | $questionWeighting |
||
6071 | ); |
||
6072 | } |
||
6073 | if (HOT_SPOT_COMBINATION === $answerType) { |
||
6074 | $listCorrectAnswers = isset($exerciseResultCoordinates[$questionId]) ? $exerciseResultCoordinates[$questionId] : []; |
||
6075 | $questionScore = ExerciseLib::getUserQuestionScoreGlobal( |
||
6076 | $answerType, |
||
6077 | $listCorrectAnswers, |
||
6078 | $exeId, |
||
6079 | $questionId, |
||
6080 | $questionWeighting, |
||
6081 | $choice, |
||
6082 | $nbrAnswers |
||
6083 | ); |
||
6084 | } |
||
6085 | if (in_array($answerType, [MATCHING_COMBINATION, MATCHING_DRAGGABLE_COMBINATION])) { |
||
6086 | $questionScore = ExerciseLib::getUserQuestionScoreGlobal( |
||
6087 | $answerType, |
||
6088 | $matchingCorrectAnswers[$questionId], |
||
6089 | $exeId, |
||
6090 | $questionId, |
||
6091 | $questionWeighting |
||
6092 | ); |
||
6093 | } |
||
6094 | |||
6095 | if ($debug) { |
||
6096 | error_log('-- End answer loop --'); |
||
6097 | } |
||
6098 | |||
6099 | $final_answer = true; |
||
6100 | |||
6101 | foreach ($real_answers as $my_answer) { |
||
6102 | if (!$my_answer) { |
||
6103 | $final_answer = false; |
||
6104 | } |
||
6105 | } |
||
6106 | |||
6107 | // We add the total score after dealing with the answers. |
||
6108 | if ($answerType == MULTIPLE_ANSWER_COMBINATION || |
||
6109 | $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE |
||
6110 | ) { |
||
6111 | if ($final_answer) { |
||
6112 | //getting only the first score where we save the weight of all the question |
||
6113 | $answerWeighting = $objAnswerTmp->selectWeighting(1); |
||
6114 | if (empty($answerWeighting) && !empty($firstAnswer) && isset($firstAnswer['ponderation'])) { |
||
6115 | $answerWeighting = $firstAnswer['ponderation']; |
||
6116 | } |
||
6117 | $questionScore += $answerWeighting; |
||
6118 | } |
||
6119 | } |
||
6120 | |||
6121 | $extra_data = [ |
||
6122 | 'final_overlap' => $final_overlap, |
||
6123 | 'final_missing' => $final_missing, |
||
6124 | 'final_excess' => $final_excess, |
||
6125 | 'overlap_color' => $overlap_color, |
||
6126 | 'missing_color' => $missing_color, |
||
6127 | 'excess_color' => $excess_color, |
||
6128 | 'threadhold1' => $threadhold1, |
||
6129 | 'threadhold2' => $threadhold2, |
||
6130 | 'threadhold3' => $threadhold3, |
||
6131 | ]; |
||
6132 | |||
6133 | if ($from === 'exercise_result') { |
||
6134 | // if answer is hotspot. To the difference of exercise_show.php, |
||
6135 | // we use the results from the session (from_db=0) |
||
6136 | // TODO Change this, because it is wrong to show the user |
||
6137 | // some results that haven't been stored in the database yet |
||
6138 | if (in_array($answerType, [HOT_SPOT, HOT_SPOT_ORDER, HOT_SPOT_DELINEATION, HOT_SPOT_COMBINATION])) { |
||
6139 | if ($debug) { |
||
6140 | error_log('$from AND this is a hotspot kind of question '); |
||
6141 | } |
||
6142 | if ($answerType === HOT_SPOT_DELINEATION) { |
||
6143 | if ($showHotSpotDelineationTable) { |
||
6144 | if (!is_numeric($final_overlap)) { |
||
6145 | $final_overlap = 0; |
||
6146 | } |
||
6147 | if (!is_numeric($final_missing)) { |
||
6148 | $final_missing = 0; |
||
6149 | } |
||
6150 | if (!is_numeric($final_excess)) { |
||
6151 | $final_excess = 0; |
||
6152 | } |
||
6153 | |||
6154 | if ($final_overlap > 100) { |
||
6155 | $final_overlap = 100; |
||
6156 | } |
||
6157 | |||
6158 | $table_resume = '<table class="table table-hover table-striped data_table"> |
||
6159 | <tr class="row_odd" > |
||
6160 | <td></td> |
||
6161 | <td ><b>'.get_lang('Requirements').'</b></td> |
||
6162 | <td><b>'.get_lang('YourAnswer').'</b></td> |
||
6163 | </tr> |
||
6164 | <tr class="row_even"> |
||
6165 | <td><b>'.get_lang('Overlap').'</b></td> |
||
6166 | <td>'.get_lang('Min').' '.$threadhold1.'</td> |
||
6167 | <td class="text-right '.($overlap_color ? 'text-success' : 'text-danger').'">' |
||
6168 | .(($final_overlap < 0) ? 0 : intval($final_overlap)).'</td> |
||
6169 | </tr> |
||
6170 | <tr> |
||
6171 | <td><b>'.get_lang('Excess').'</b></td> |
||
6172 | <td>'.get_lang('Max').' '.$threadhold2.'</td> |
||
6173 | <td class="text-right '.($excess_color ? 'text-success' : 'text-danger').'">' |
||
6174 | .(($final_excess < 0) ? 0 : intval($final_excess)).'</td> |
||
6175 | </tr> |
||
6176 | <tr class="row_even"> |
||
6177 | <td><b>'.get_lang('Missing').'</b></td> |
||
6178 | <td>'.get_lang('Max').' '.$threadhold3.'</td> |
||
6179 | <td class="text-right '.($missing_color ? 'text-success' : 'text-danger').'">' |
||
6180 | .(($final_missing < 0) ? 0 : intval($final_missing)).'</td> |
||
6181 | </tr> |
||
6182 | </table>'; |
||
6183 | if ($next == 0) { |
||
6184 | } else { |
||
6185 | $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers); |
||
6186 | $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers); |
||
6187 | } |
||
6188 | |||
6189 | $message = '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1> |
||
6190 | <p style="text-align:center">'; |
||
6191 | $message .= '<p>'.get_lang('YourDelineation').'</p>'; |
||
6192 | $message .= $table_resume; |
||
6193 | $message .= '<br />'.get_lang('ResultIs').' '.$result_comment.'<br />'; |
||
6194 | if ($organs_at_risk_hit > 0) { |
||
6195 | $message .= '<p><b>'.get_lang('OARHit').'</b></p>'; |
||
6196 | } |
||
6197 | $message .= '<p>'.$comment.'</p>'; |
||
6198 | echo $message; |
||
6199 | |||
6200 | $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][0] = $message; |
||
6201 | $_SESSION['hotspot_delineation_result'][$this->selectId()][$questionId][1] = $_SESSION['exerciseResultCoordinates'][$questionId]; |
||
6202 | } else { |
||
6203 | echo $hotspot_delineation_result[0]; |
||
6204 | } |
||
6205 | |||
6206 | // Save the score attempts |
||
6207 | if (1) { |
||
6208 | //getting the answer 1 or 0 comes from exercise_submit_modal.php |
||
6209 | $final_answer = isset($hotspot_delineation_result[1]) ? $hotspot_delineation_result[1] : ''; |
||
6210 | if ($final_answer == 0) { |
||
6211 | $questionScore = 0; |
||
6212 | } |
||
6213 | // we always insert the answer_id 1 = delineation |
||
6214 | Event::saveQuestionAttempt($questionScore, 1, $quesId, $exeId, 0); |
||
6215 | //in delineation mode, get the answer from $hotspot_delineation_result[1] |
||
6216 | $hotspotValue = isset($hotspot_delineation_result[1]) ? (int) $hotspot_delineation_result[1] === 1 ? 1 : 0 : 0; |
||
6217 | Event::saveExerciseAttemptHotspot( |
||
6218 | $exeId, |
||
6219 | $quesId, |
||
6220 | 1, |
||
6221 | $hotspotValue, |
||
6222 | $exerciseResultCoordinates[$quesId], |
||
6223 | false, |
||
6224 | 0, |
||
6225 | $learnpath_id, |
||
6226 | $learnpath_item_id |
||
6227 | ); |
||
6228 | } else { |
||
6229 | if ($final_answer == 0) { |
||
6230 | $questionScore = 0; |
||
6231 | $answer = 0; |
||
6232 | Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0); |
||
6233 | if (is_array($exerciseResultCoordinates[$quesId])) { |
||
6234 | foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) { |
||
6235 | Event::saveExerciseAttemptHotspot( |
||
6236 | $exeId, |
||
6237 | $quesId, |
||
6238 | $idx, |
||
6239 | 0, |
||
6240 | $val, |
||
6241 | false, |
||
6242 | 0, |
||
6243 | $learnpath_id, |
||
6244 | $learnpath_item_id |
||
6245 | ); |
||
6246 | } |
||
6247 | } |
||
6248 | } else { |
||
6249 | Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0); |
||
6250 | if (is_array($exerciseResultCoordinates[$quesId])) { |
||
6251 | foreach ($exerciseResultCoordinates[$quesId] as $idx => $val) { |
||
6252 | $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0; |
||
6253 | Event::saveExerciseAttemptHotspot( |
||
6254 | $exeId, |
||
6255 | $quesId, |
||
6256 | $idx, |
||
6257 | $hotspotValue, |
||
6258 | $val, |
||
6259 | false, |
||
6260 | 0, |
||
6261 | $learnpath_id, |
||
6262 | $learnpath_item_id |
||
6263 | ); |
||
6264 | } |
||
6265 | } |
||
6266 | } |
||
6267 | } |
||
6268 | } |
||
6269 | } |
||
6270 | |||
6271 | $relPath = api_get_path(WEB_CODE_PATH); |
||
6272 | |||
6273 | if (in_array($answerType, [HOT_SPOT, HOT_SPOT_COMBINATION, HOT_SPOT_ORDER])) { |
||
6274 | // We made an extra table for the answers |
||
6275 | if ($show_result) { |
||
6276 | echo '</table></td></tr>'; |
||
6277 | echo " |
||
6278 | <tr> |
||
6279 | <td> |
||
6280 | <p><em>".get_lang('HotSpot')."</em></p> |
||
6281 | <div id=\"hotspot-solution-$questionId\"></div> |
||
6282 | <script> |
||
6283 | $(function() { |
||
6284 | new HotspotQuestion({ |
||
6285 | questionId: $questionId, |
||
6286 | exerciseId: {$this->iid}, |
||
6287 | exeId: $exeId, |
||
6288 | selector: '#hotspot-solution-$questionId', |
||
6289 | for: 'solution', |
||
6290 | relPath: '$relPath' |
||
6291 | }); |
||
6292 | }); |
||
6293 | </script> |
||
6294 | </td> |
||
6295 | </tr> |
||
6296 | "; |
||
6297 | } |
||
6298 | } elseif ($answerType == ANNOTATION) { |
||
6299 | if ($show_result) { |
||
6300 | echo ' |
||
6301 | <p><em>'.get_lang('Annotation').'</em></p> |
||
6302 | <div id="annotation-canvas-'.$questionId.'"></div> |
||
6303 | <script> |
||
6304 | AnnotationQuestion({ |
||
6305 | questionId: parseInt('.$questionId.'), |
||
6306 | exerciseId: parseInt('.$exeId.'), |
||
6307 | relPath: \''.$relPath.'\', |
||
6308 | courseId: parseInt('.$course_id.') |
||
6309 | }); |
||
6310 | </script> |
||
6311 | '; |
||
6312 | } |
||
6313 | } |
||
6314 | |||
6315 | if ($show_result && $answerType != ANNOTATION) { |
||
6316 | echo '</table>'; |
||
6317 | } |
||
6318 | } |
||
6319 | unset($objAnswerTmp); |
||
6320 | |||
6321 | $totalWeighting += $questionWeighting; |
||
6322 | // Store results directly in the database |
||
6323 | // For all in one page exercises, the results will be |
||
6324 | // stored by exercise_results.php (using the session) |
||
6325 | if ($save_results) { |
||
6326 | if ($debug) { |
||
6327 | error_log("Save question results $save_results"); |
||
6328 | error_log("Question score: $questionScore"); |
||
6329 | error_log('choice: '); |
||
6330 | error_log(print_r($choice, 1)); |
||
6331 | } |
||
6332 | |||
6333 | if (empty($choice)) { |
||
6334 | $choice = 0; |
||
6335 | } |
||
6336 | // with certainty degree |
||
6337 | if (empty($choiceDegreeCertainty)) { |
||
6338 | $choiceDegreeCertainty = 0; |
||
6339 | } |
||
6340 | if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || |
||
6341 | $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE || |
||
6342 | $answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY |
||
6343 | ) { |
||
6344 | if ($choice != 0) { |
||
6345 | $reply = array_keys($choice); |
||
6346 | $countReply = count($reply); |
||
6347 | for ($i = 0; $i < $countReply; $i++) { |
||
6348 | $chosenAnswer = $reply[$i]; |
||
6349 | if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
6350 | if ($choiceDegreeCertainty != 0) { |
||
6351 | $replyDegreeCertainty = array_keys($choiceDegreeCertainty); |
||
6352 | $answerDegreeCertainty = isset($replyDegreeCertainty[$i]) ? $replyDegreeCertainty[$i] : ''; |
||
6353 | $answerValue = isset($choiceDegreeCertainty[$answerDegreeCertainty]) ? $choiceDegreeCertainty[$answerDegreeCertainty] : ''; |
||
6354 | Event::saveQuestionAttempt( |
||
6355 | $questionScore, |
||
6356 | $chosenAnswer.':'.$choice[$chosenAnswer].':'.$answerValue, |
||
6357 | $quesId, |
||
6358 | $exeId, |
||
6359 | $i, |
||
6360 | $this->iid, |
||
6361 | $updateResults, |
||
6362 | $questionDuration |
||
6363 | ); |
||
6364 | } |
||
6365 | } else { |
||
6366 | Event::saveQuestionAttempt( |
||
6367 | $questionScore, |
||
6368 | $chosenAnswer.':'.$choice[$chosenAnswer], |
||
6369 | $quesId, |
||
6370 | $exeId, |
||
6371 | $i, |
||
6372 | $this->iid, |
||
6373 | $updateResults, |
||
6374 | $questionDuration |
||
6375 | ); |
||
6376 | } |
||
6377 | if ($debug) { |
||
6378 | error_log('result =>'.$questionScore.' '.$chosenAnswer.':'.$choice[$chosenAnswer]); |
||
6379 | } |
||
6380 | } |
||
6381 | } else { |
||
6382 | Event::saveQuestionAttempt( |
||
6383 | $questionScore, |
||
6384 | 0, |
||
6385 | $quesId, |
||
6386 | $exeId, |
||
6387 | 0, |
||
6388 | $this->iid, |
||
6389 | false, |
||
6390 | $questionDuration |
||
6391 | ); |
||
6392 | } |
||
6393 | } elseif ( |
||
6394 | in_array( |
||
6395 | $answerType, |
||
6396 | [ |
||
6397 | MULTIPLE_ANSWER, |
||
6398 | GLOBAL_MULTIPLE_ANSWER, |
||
6399 | MULTIPLE_ANSWER_DROPDOWN, |
||
6400 | MULTIPLE_ANSWER_DROPDOWN_COMBINATION, |
||
6401 | ] |
||
6402 | ) |
||
6403 | ) { |
||
6404 | if ($choice != 0) { |
||
6405 | if (in_array($answerType, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION])) { |
||
6406 | $reply = array_values($choice); |
||
6407 | } else { |
||
6408 | $reply = array_keys($choice); |
||
6409 | } |
||
6410 | |||
6411 | for ($i = 0; $i < count($reply); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
6412 | $ans = $reply[$i]; |
||
6413 | Event::saveQuestionAttempt( |
||
6414 | $questionScore, |
||
6415 | $ans, |
||
6416 | $quesId, |
||
6417 | $exeId, |
||
6418 | $i, |
||
6419 | $this->iid, |
||
6420 | false, |
||
6421 | $questionDuration |
||
6422 | ); |
||
6423 | } |
||
6424 | } else { |
||
6425 | Event::saveQuestionAttempt( |
||
6426 | $questionScore, |
||
6427 | 0, |
||
6428 | $quesId, |
||
6429 | $exeId, |
||
6430 | 0, |
||
6431 | $this->iid, |
||
6432 | false, |
||
6433 | $questionDuration |
||
6434 | ); |
||
6435 | } |
||
6436 | } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) { |
||
6437 | if ($choice != 0) { |
||
6438 | $reply = array_keys($choice); |
||
6439 | for ($i = 0; $i < count($reply); $i++) { |
||
0 ignored issues
–
show
It seems like you are calling the size function
count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}
// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
![]() |
|||
6440 | $ans = $reply[$i]; |
||
6441 | Event::saveQuestionAttempt( |
||
6442 | $questionScore, |
||
6443 | $ans, |
||
6444 | $quesId, |
||
6445 | $exeId, |
||
6446 | $i, |
||
6447 | $this->iid, |
||
6448 | false, |
||
6449 | $questionDuration |
||
6450 | ); |
||
6451 | } |
||
6452 | } else { |
||
6453 | Event::saveQuestionAttempt( |
||
6454 | $questionScore, |
||
6455 | 0, |
||
6456 | $quesId, |
||
6457 | $exeId, |
||
6458 | 0, |
||
6459 | $this->iid, |
||
6460 | false, |
||
6461 | $questionDuration |
||
6462 | ); |
||
6463 | } |
||
6464 | } elseif (in_array($answerType, [MATCHING, MATCHING_COMBINATION, DRAGGABLE, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
6465 | if (isset($matching)) { |
||
6466 | foreach ($matching as $j => $val) { |
||
6467 | Event::saveQuestionAttempt( |
||
6468 | $questionScore, |
||
6469 | $val, |
||
6470 | $quesId, |
||
6471 | $exeId, |
||
6472 | $j, |
||
6473 | $this->iid, |
||
6474 | false, |
||
6475 | $questionDuration |
||
6476 | ); |
||
6477 | } |
||
6478 | } |
||
6479 | } elseif ($answerType == FREE_ANSWER) { |
||
6480 | $answer = $choice; |
||
6481 | Event::saveQuestionAttempt( |
||
6482 | $questionScore, |
||
6483 | $answer, |
||
6484 | $quesId, |
||
6485 | $exeId, |
||
6486 | 0, |
||
6487 | $this->iid, |
||
6488 | false, |
||
6489 | $questionDuration |
||
6490 | ); |
||
6491 | } elseif ($answerType == UPLOAD_ANSWER) { |
||
6492 | $answer = $choice; |
||
6493 | Event::saveQuestionAttempt( |
||
6494 | $questionScore, |
||
6495 | $answer, |
||
6496 | $quesId, |
||
6497 | $exeId, |
||
6498 | 0, |
||
6499 | $this->iid, |
||
6500 | false, |
||
6501 | $questionDuration |
||
6502 | ); |
||
6503 | } elseif ($answerType == ANSWER_IN_OFFICE_DOC) { |
||
6504 | $answer = $choice; |
||
6505 | $exerciseId = $this->iid; |
||
6506 | $questionId = $quesId; |
||
6507 | $originalFilePath = $objQuestionTmp->getFileUrl(); |
||
6508 | $originalExtension = !empty($originalFilePath) ? pathinfo($originalFilePath, PATHINFO_EXTENSION) : 'docx'; |
||
6509 | $fileName = "response_{$exeId}.{$originalExtension}"; |
||
6510 | Event::saveQuestionAttempt( |
||
6511 | $questionScore, |
||
6512 | $answer, |
||
6513 | $questionId, |
||
6514 | $exeId, |
||
6515 | 0, |
||
6516 | $exerciseId, |
||
6517 | false, |
||
6518 | $questionDuration, |
||
6519 | $fileName |
||
6520 | ); |
||
6521 | } elseif ($answerType == ORAL_EXPRESSION) { |
||
6522 | $answer = $choice; |
||
6523 | $absFilePath = $objQuestionTmp->getAbsoluteFilePath(); |
||
6524 | if (empty($answer) && !empty($absFilePath)) { |
||
6525 | // it takes the filename as answer to recognise has been saved |
||
6526 | $answer = basename($absFilePath); |
||
6527 | } |
||
6528 | Event::saveQuestionAttempt( |
||
6529 | $questionScore, |
||
6530 | $answer, |
||
6531 | $quesId, |
||
6532 | $exeId, |
||
6533 | 0, |
||
6534 | $this->iid, |
||
6535 | false, |
||
6536 | $questionDuration, |
||
6537 | $absFilePath |
||
6538 | ); |
||
6539 | } elseif ( |
||
6540 | in_array( |
||
6541 | $answerType, |
||
6542 | [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION] |
||
6543 | ) |
||
6544 | ) { |
||
6545 | $answer = $choice; |
||
6546 | Event::saveQuestionAttempt($questionScore, $answer, $quesId, $exeId, 0, $this->iid, false, $questionDuration); |
||
6547 | } elseif (in_array($answerType, [HOT_SPOT, HOT_SPOT_COMBINATION, ANNOTATION])) { |
||
6548 | $answer = []; |
||
6549 | if (isset($exerciseResultCoordinates[$questionId]) && !empty($exerciseResultCoordinates[$questionId])) { |
||
6550 | if ($debug) { |
||
6551 | error_log('Checking result coordinates'); |
||
6552 | } |
||
6553 | Database::delete( |
||
6554 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT), |
||
6555 | [ |
||
6556 | 'hotspot_exe_id = ? AND hotspot_question_id = ? AND c_id = ?' => [ |
||
6557 | $exeId, |
||
6558 | $questionId, |
||
6559 | api_get_course_int_id(), |
||
6560 | ], |
||
6561 | ] |
||
6562 | ); |
||
6563 | |||
6564 | foreach ($exerciseResultCoordinates[$questionId] as $idx => $val) { |
||
6565 | $answer[] = $val; |
||
6566 | $hotspotValue = (int) $choice[$idx] === 1 ? 1 : 0; |
||
6567 | if ($debug) { |
||
6568 | error_log('Hotspot value: '.$hotspotValue); |
||
6569 | } |
||
6570 | Event::saveExerciseAttemptHotspot( |
||
6571 | $exeId, |
||
6572 | $quesId, |
||
6573 | $idx, |
||
6574 | $hotspotValue, |
||
6575 | $val, |
||
6576 | false, |
||
6577 | $this->iid, |
||
6578 | $learnpath_id, |
||
6579 | $learnpath_item_id |
||
6580 | ); |
||
6581 | } |
||
6582 | } else { |
||
6583 | if ($debug) { |
||
6584 | error_log('Empty: exerciseResultCoordinates'); |
||
6585 | } |
||
6586 | } |
||
6587 | Event::saveQuestionAttempt( |
||
6588 | $questionScore, |
||
6589 | implode('|', $answer), |
||
6590 | $quesId, |
||
6591 | $exeId, |
||
6592 | 0, |
||
6593 | $this->iid, |
||
6594 | false, |
||
6595 | $questionDuration |
||
6596 | ); |
||
6597 | } else { |
||
6598 | if ($answerType === CALCULATED_ANSWER && !empty($calculatedAnswerId)) { |
||
6599 | $answer .= ':::'.$calculatedAnswerId; |
||
6600 | } |
||
6601 | |||
6602 | Event::saveQuestionAttempt( |
||
6603 | $questionScore, |
||
6604 | $answer, |
||
6605 | $quesId, |
||
6606 | $exeId, |
||
6607 | 0, |
||
6608 | $this->iid, |
||
6609 | false, |
||
6610 | $questionDuration |
||
6611 | ); |
||
6612 | } |
||
6613 | } |
||
6614 | |||
6615 | if ($propagate_neg == 0 && $questionScore < 0) { |
||
6616 | $questionScore = 0; |
||
6617 | } |
||
6618 | |||
6619 | if ($save_results) { |
||
6620 | $statsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
6621 | $sql = "UPDATE $statsTable SET |
||
6622 | exe_result = exe_result + ".floatval($questionScore)." |
||
6623 | WHERE exe_id = $exeId"; |
||
6624 | Database::query($sql); |
||
6625 | } |
||
6626 | |||
6627 | return [ |
||
6628 | 'score' => $questionScore, |
||
6629 | 'weight' => $questionWeighting, |
||
6630 | 'extra' => $extra_data, |
||
6631 | 'open_question' => $arrques, |
||
6632 | 'open_answer' => $arrans, |
||
6633 | 'answer_type' => $answerType, |
||
6634 | 'generated_oral_file' => $generatedFile, |
||
6635 | 'user_answered' => $userAnsweredQuestion, |
||
6636 | 'correct_answer_id' => $correctAnswerId, |
||
6637 | 'answer_destination' => $answerDestination, |
||
6638 | ]; |
||
6639 | } |
||
6640 | |||
6641 | /** |
||
6642 | * Sends a notification when a user ends an examn. |
||
6643 | * |
||
6644 | * @param string $type 'start' or 'end' of an exercise |
||
6645 | * @param array $question_list_answers |
||
6646 | * @param string $origin |
||
6647 | * @param int $exe_id |
||
6648 | * @param float $score |
||
6649 | * @param float $weight |
||
6650 | * |
||
6651 | * @return bool |
||
6652 | */ |
||
6653 | public function send_mail_notification_for_exam( |
||
6654 | $type, |
||
6655 | $question_list_answers, |
||
6656 | $origin, |
||
6657 | $exe_id, |
||
6658 | $score = null, |
||
6659 | $weight = null |
||
6660 | ) { |
||
6661 | $setting = api_get_course_setting('email_alert_manager_on_new_quiz'); |
||
6662 | |||
6663 | if (empty($setting) && empty($this->getNotifications())) { |
||
6664 | return false; |
||
6665 | } |
||
6666 | |||
6667 | $settingFromExercise = $this->getNotifications(); |
||
6668 | if (!empty($settingFromExercise)) { |
||
6669 | $setting = $settingFromExercise; |
||
6670 | } |
||
6671 | |||
6672 | // Email configuration settings |
||
6673 | $courseCode = api_get_course_id(); |
||
6674 | $courseInfo = api_get_course_info($courseCode); |
||
6675 | |||
6676 | if (empty($courseInfo)) { |
||
6677 | return false; |
||
6678 | } |
||
6679 | |||
6680 | $sessionId = api_get_session_id(); |
||
6681 | |||
6682 | $sessionData = ''; |
||
6683 | if (!empty($sessionId)) { |
||
6684 | $sessionInfo = api_get_session_info($sessionId); |
||
6685 | if (!empty($sessionInfo)) { |
||
6686 | $sessionData = '<tr>' |
||
6687 | .'<td>'.get_lang('SessionName').'</td>' |
||
6688 | .'<td>'.$sessionInfo['name'].'</td>' |
||
6689 | .'</tr>'; |
||
6690 | } |
||
6691 | } |
||
6692 | |||
6693 | $sendStart = false; |
||
6694 | $sendEnd = false; |
||
6695 | $sendEndOpenQuestion = false; |
||
6696 | $sendEndOralQuestion = false; |
||
6697 | |||
6698 | foreach ($setting as $option) { |
||
6699 | switch ($option) { |
||
6700 | case 0: |
||
6701 | return false; |
||
6702 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other ![]() |
|||
6703 | case 1: // End |
||
6704 | if ($type === 'end') { |
||
6705 | $sendEnd = true; |
||
6706 | } |
||
6707 | break; |
||
6708 | case 2: // start |
||
6709 | if ($type === 'start') { |
||
6710 | $sendStart = true; |
||
6711 | } |
||
6712 | break; |
||
6713 | case 3: // end + open |
||
6714 | if ($type === 'end') { |
||
6715 | $sendEndOpenQuestion = true; |
||
6716 | } |
||
6717 | break; |
||
6718 | case 4: // end + oral |
||
6719 | if ($type === 'end') { |
||
6720 | $sendEndOralQuestion = true; |
||
6721 | } |
||
6722 | break; |
||
6723 | } |
||
6724 | } |
||
6725 | |||
6726 | $user_info = api_get_user_info(api_get_user_id()); |
||
6727 | $url = api_get_path(WEB_CODE_PATH).'exercise/exercise_show.php?'. |
||
6728 | api_get_cidreq(true, true, 'qualify').'&id='.$exe_id.'&action=qualify'; |
||
6729 | |||
6730 | if (!empty($sessionId)) { |
||
6731 | $addGeneralCoach = true; |
||
6732 | $setting = api_get_configuration_value('block_quiz_mail_notification_general_coach'); |
||
6733 | if ($setting === true) { |
||
6734 | $addGeneralCoach = false; |
||
6735 | } |
||
6736 | $teachers = CourseManager::get_coach_list_from_course_code( |
||
6737 | $courseCode, |
||
6738 | $sessionId, |
||
6739 | $addGeneralCoach |
||
6740 | ); |
||
6741 | } else { |
||
6742 | $teachers = CourseManager::get_teacher_list_from_course_code($courseCode); |
||
6743 | } |
||
6744 | |||
6745 | if ($sendEndOpenQuestion) { |
||
6746 | $this->sendNotificationForOpenQuestions( |
||
6747 | $question_list_answers, |
||
6748 | $origin, |
||
6749 | $user_info, |
||
6750 | $url, |
||
6751 | $teachers |
||
6752 | ); |
||
6753 | } |
||
6754 | |||
6755 | if ($sendEndOralQuestion) { |
||
6756 | $this->sendNotificationForOralQuestions( |
||
6757 | $question_list_answers, |
||
6758 | $origin, |
||
6759 | $exe_id, |
||
6760 | $user_info, |
||
6761 | $url, |
||
6762 | $teachers |
||
6763 | ); |
||
6764 | } |
||
6765 | |||
6766 | if (!$sendEnd && !$sendStart) { |
||
6767 | return false; |
||
6768 | } |
||
6769 | |||
6770 | $scoreLabel = ''; |
||
6771 | if ($sendEnd && |
||
6772 | api_get_configuration_value('send_score_in_exam_notification_mail_to_manager') == true |
||
6773 | ) { |
||
6774 | $notificationPercentage = api_get_configuration_value('send_notification_score_in_percentage'); |
||
6775 | $scoreLabel = ExerciseLib::show_score($score, $weight, $notificationPercentage, true); |
||
6776 | $scoreLabel = "<tr> |
||
6777 | <td>".get_lang('Score')."</td> |
||
6778 | <td> $scoreLabel</td> |
||
6779 | </tr>"; |
||
6780 | } |
||
6781 | |||
6782 | if ($sendEnd) { |
||
6783 | $msg = get_lang('ExerciseAttempted').'<br /><br />'; |
||
6784 | } else { |
||
6785 | $msg = get_lang('StudentStartExercise').'<br /><br />'; |
||
6786 | } |
||
6787 | |||
6788 | $msg .= get_lang('AttemptDetails').' : <br /><br /> |
||
6789 | <table> |
||
6790 | <tr> |
||
6791 | <td>'.get_lang('CourseName').'</td> |
||
6792 | <td>#course#</td> |
||
6793 | </tr> |
||
6794 | '.$sessionData.' |
||
6795 | <tr> |
||
6796 | <td>'.get_lang('Exercise').'</td> |
||
6797 | <td> #exercise#</td> |
||
6798 | </tr> |
||
6799 | <tr> |
||
6800 | <td>'.get_lang('StudentName').'</td> |
||
6801 | <td> #student_complete_name#</td> |
||
6802 | </tr> |
||
6803 | <tr> |
||
6804 | <td>'.get_lang('StudentEmail').'</td> |
||
6805 | <td> #email#</td> |
||
6806 | </tr> |
||
6807 | '.$scoreLabel.' |
||
6808 | </table>'; |
||
6809 | |||
6810 | $variables = [ |
||
6811 | '#email#' => $user_info['email'], |
||
6812 | '#exercise#' => $this->exercise, |
||
6813 | '#student_complete_name#' => $user_info['complete_name'], |
||
6814 | '#course#' => Display::url( |
||
6815 | $courseInfo['title'], |
||
6816 | $courseInfo['course_public_url'].'?id_session='.$sessionId |
||
6817 | ), |
||
6818 | ]; |
||
6819 | |||
6820 | if ($sendEnd) { |
||
6821 | $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>'; |
||
6822 | $variables['#url#'] = $url; |
||
6823 | } |
||
6824 | |||
6825 | $content = str_replace(array_keys($variables), array_values($variables), $msg); |
||
6826 | |||
6827 | if ($sendEnd) { |
||
6828 | $subject = get_lang('ExerciseAttempted'); |
||
6829 | } else { |
||
6830 | $subject = get_lang('StudentStartExercise'); |
||
6831 | } |
||
6832 | |||
6833 | if (!empty($teachers)) { |
||
6834 | foreach ($teachers as $user_id => $teacher_data) { |
||
6835 | MessageManager::send_message_simple( |
||
6836 | $user_id, |
||
6837 | $subject, |
||
6838 | $content |
||
6839 | ); |
||
6840 | } |
||
6841 | } |
||
6842 | } |
||
6843 | |||
6844 | /** |
||
6845 | * @param array $user_data result of api_get_user_info() |
||
6846 | * @param array $trackExerciseInfo result of get_stat_track_exercise_info |
||
6847 | * @param bool $saveUserResult |
||
6848 | * @param bool $allowSignature |
||
6849 | * @param bool $allowExportPdf |
||
6850 | * |
||
6851 | * @return string |
||
6852 | */ |
||
6853 | public function showExerciseResultHeader( |
||
6854 | $user_data, |
||
6855 | $trackExerciseInfo, |
||
6856 | $saveUserResult, |
||
6857 | $allowSignature = false, |
||
6858 | $allowExportPdf = false |
||
6859 | ) { |
||
6860 | if (api_get_configuration_value('hide_user_info_in_quiz_result')) { |
||
6861 | return ''; |
||
6862 | } |
||
6863 | |||
6864 | $start_date = null; |
||
6865 | if (isset($trackExerciseInfo['start_date'])) { |
||
6866 | $start_date = api_convert_and_format_date($trackExerciseInfo['start_date']); |
||
6867 | } |
||
6868 | $duration = isset($trackExerciseInfo['duration_formatted']) ? $trackExerciseInfo['duration_formatted'] : null; |
||
6869 | $ip = isset($trackExerciseInfo['user_ip']) ? $trackExerciseInfo['user_ip'] : null; |
||
6870 | |||
6871 | if (!empty($user_data)) { |
||
6872 | $userFullName = $user_data['complete_name']; |
||
6873 | if (api_is_teacher() || api_is_platform_admin(true, true)) { |
||
6874 | $userFullName = '<a href="'.$user_data['profile_url'].'" title="'.get_lang('GoToStudentDetails').'">'. |
||
6875 | $user_data['complete_name'].'</a>'; |
||
6876 | } |
||
6877 | |||
6878 | $data = [ |
||
6879 | 'name_url' => $userFullName, |
||
6880 | 'complete_name' => $user_data['complete_name'], |
||
6881 | 'username' => $user_data['username'], |
||
6882 | 'avatar' => $user_data['avatar_medium'], |
||
6883 | 'url' => $user_data['profile_url'], |
||
6884 | ]; |
||
6885 | |||
6886 | if (!empty($user_data['official_code'])) { |
||
6887 | $data['code'] = $user_data['official_code']; |
||
6888 | } |
||
6889 | } |
||
6890 | // Description can be very long and is generally meant to explain |
||
6891 | // rules *before* the exam. Leaving here to make display easier if |
||
6892 | // necessary |
||
6893 | /* |
||
6894 | if (!empty($this->description)) { |
||
6895 | $array[] = array('title' => get_lang("Description"), 'content' => $this->description); |
||
6896 | } |
||
6897 | */ |
||
6898 | if (!empty($start_date)) { |
||
6899 | $data['start_date'] = $start_date; |
||
6900 | } |
||
6901 | |||
6902 | if (!empty($duration)) { |
||
6903 | $data['duration'] = $duration; |
||
6904 | } |
||
6905 | |||
6906 | if (!empty($ip)) { |
||
6907 | $data['ip'] = $ip; |
||
6908 | } |
||
6909 | |||
6910 | if (true === api_get_configuration_value('exercise_hide_ip')) { |
||
6911 | unset($data['ip']); |
||
6912 | } |
||
6913 | |||
6914 | if (api_get_configuration_value('save_titles_as_html')) { |
||
6915 | $data['title'] = Security::remove_XSS($this->get_formated_title()).get_lang('Result'); |
||
6916 | } else { |
||
6917 | $data['title'] = PHP_EOL.Security::remove_XSS($this->exercise).' : '.get_lang('Result'); |
||
6918 | } |
||
6919 | |||
6920 | $questionsCount = count(explode(',', $trackExerciseInfo['data_tracking'])); |
||
6921 | $savedAnswersCount = $this->countUserAnswersSavedInExercise($trackExerciseInfo['exe_id']); |
||
6922 | |||
6923 | $data['number_of_answers'] = $questionsCount; |
||
6924 | $data['number_of_answers_saved'] = $savedAnswersCount; |
||
6925 | $exeId = $trackExerciseInfo['exe_id']; |
||
6926 | |||
6927 | if (false !== api_get_configuration_value('quiz_confirm_saved_answers')) { |
||
6928 | $em = Database::getManager(); |
||
6929 | |||
6930 | if ($saveUserResult) { |
||
6931 | $trackConfirmation = new TrackEExerciseConfirmation(); |
||
6932 | $trackConfirmation |
||
6933 | ->setUserId($trackExerciseInfo['exe_user_id']) |
||
6934 | ->setQuizId($trackExerciseInfo['exe_exo_id']) |
||
6935 | ->setAttemptId($trackExerciseInfo['exe_id']) |
||
6936 | ->setQuestionsCount($questionsCount) |
||
6937 | ->setSavedAnswersCount($savedAnswersCount) |
||
6938 | ->setCourseId($trackExerciseInfo['c_id']) |
||
6939 | ->setSessionId($trackExerciseInfo['session_id']) |
||
6940 | ->setCreatedAt(api_get_utc_datetime(null, false, true)); |
||
6941 | |||
6942 | $em->persist($trackConfirmation); |
||
6943 | $em->flush(); |
||
6944 | } else { |
||
6945 | $trackConfirmation = $em |
||
6946 | ->getRepository('ChamiloCoreBundle:TrackEExerciseConfirmation') |
||
6947 | ->findOneBy( |
||
6948 | [ |
||
6949 | 'attemptId' => $trackExerciseInfo['exe_id'], |
||
6950 | 'quizId' => $trackExerciseInfo['exe_exo_id'], |
||
6951 | 'courseId' => $trackExerciseInfo['c_id'], |
||
6952 | 'sessionId' => $trackExerciseInfo['session_id'], |
||
6953 | ] |
||
6954 | ); |
||
6955 | } |
||
6956 | |||
6957 | $data['track_confirmation'] = $trackConfirmation; |
||
6958 | } |
||
6959 | |||
6960 | $signature = ''; |
||
6961 | if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($this)) { |
||
6962 | $signature = ExerciseSignaturePlugin::getSignature($trackExerciseInfo['exe_user_id'], $trackExerciseInfo); |
||
6963 | } |
||
6964 | |||
6965 | $tpl = new Template(null, false, false, false, false, false, false); |
||
6966 | $tpl->assign('data', $data); |
||
6967 | $tpl->assign('allow_signature', $allowSignature); |
||
6968 | $tpl->assign('signature', $signature); |
||
6969 | $tpl->assign('allow_export_pdf', $allowExportPdf); |
||
6970 | $tpl->assign('export_url', api_get_path(WEB_CODE_PATH).'exercise/result.php?action=export&id='.$exeId.'&'.api_get_cidreq()); |
||
6971 | |||
6972 | $layoutTemplate = $tpl->get_template('exercise/partials/result_exercise.tpl'); |
||
6973 | |||
6974 | return $tpl->fetch($layoutTemplate); |
||
6975 | } |
||
6976 | |||
6977 | /** |
||
6978 | * Returns the exercise result. |
||
6979 | * |
||
6980 | * @param int attempt id |
||
6981 | * |
||
6982 | * @return array |
||
6983 | */ |
||
6984 | public function get_exercise_result($exe_id) |
||
6985 | { |
||
6986 | $result = []; |
||
6987 | $track_exercise_info = ExerciseLib::get_exercise_track_exercise_info($exe_id); |
||
6988 | |||
6989 | if (!empty($track_exercise_info)) { |
||
6990 | $totalScore = 0; |
||
6991 | $objExercise = new self(); |
||
6992 | $objExercise->read($track_exercise_info['exe_exo_id']); |
||
6993 | if (!empty($track_exercise_info['data_tracking'])) { |
||
6994 | $question_list = explode(',', $track_exercise_info['data_tracking']); |
||
6995 | } |
||
6996 | foreach ($question_list as $questionId) { |
||
6997 | $question_result = $objExercise->manage_answer( |
||
6998 | $exe_id, |
||
6999 | $questionId, |
||
7000 | '', |
||
7001 | 'exercise_show', |
||
7002 | [], |
||
7003 | false, |
||
7004 | true, |
||
7005 | false, |
||
7006 | $objExercise->selectPropagateNeg() |
||
7007 | ); |
||
7008 | $totalScore += $question_result['score']; |
||
7009 | } |
||
7010 | |||
7011 | if ($objExercise->selectPropagateNeg() == 0 && $totalScore < 0) { |
||
7012 | $totalScore = 0; |
||
7013 | } |
||
7014 | $result = [ |
||
7015 | 'score' => $totalScore, |
||
7016 | 'weight' => $track_exercise_info['exe_weighting'], |
||
7017 | ]; |
||
7018 | } |
||
7019 | |||
7020 | return $result; |
||
7021 | } |
||
7022 | |||
7023 | /** |
||
7024 | * Checks if the exercise is visible due a lot of conditions |
||
7025 | * visibility, time limits, student attempts |
||
7026 | * Return associative array |
||
7027 | * value : true if exercise visible |
||
7028 | * message : HTML formatted message |
||
7029 | * rawMessage : text message. |
||
7030 | * |
||
7031 | * @param int $lpId |
||
7032 | * @param int $lpItemId |
||
7033 | * @param int $lpItemViewId |
||
7034 | * @param bool $filterByAdmin |
||
7035 | * |
||
7036 | * @return array |
||
7037 | */ |
||
7038 | public function is_visible( |
||
7039 | $lpId = 0, |
||
7040 | $lpItemId = 0, |
||
7041 | $lpItemViewId = 0, |
||
7042 | $filterByAdmin = true, |
||
7043 | $sessionId = 0 |
||
7044 | ) { |
||
7045 | $sessionId = (int) $sessionId; |
||
7046 | if ($sessionId == 0) { |
||
7047 | $sessionId = $this->sessionId; |
||
7048 | } |
||
7049 | // 1. By default the exercise is visible |
||
7050 | $isVisible = true; |
||
7051 | $message = null; |
||
7052 | |||
7053 | // 1.1 Admins, teachers and tutors can access to the exercise |
||
7054 | if ($filterByAdmin) { |
||
7055 | if (api_is_platform_admin() || api_is_course_admin() || api_is_course_tutor() || api_is_session_general_coach()) { |
||
7056 | return ['value' => true, 'message' => '']; |
||
7057 | } |
||
7058 | } |
||
7059 | |||
7060 | // Deleted exercise. |
||
7061 | if ($this->active == -1) { |
||
7062 | return [ |
||
7063 | 'value' => false, |
||
7064 | 'message' => Display::return_message( |
||
7065 | get_lang('ExerciseNotFound'), |
||
7066 | 'warning', |
||
7067 | false |
||
7068 | ), |
||
7069 | 'rawMessage' => get_lang('ExerciseNotFound'), |
||
7070 | ]; |
||
7071 | } |
||
7072 | |||
7073 | // Checking visibility in the item_property table. |
||
7074 | $visibility = api_get_item_visibility( |
||
7075 | api_get_course_info(), |
||
7076 | TOOL_QUIZ, |
||
7077 | $this->iid, |
||
7078 | api_get_session_id() |
||
7079 | ); |
||
7080 | |||
7081 | if ($visibility == 0 || $visibility == 2) { |
||
7082 | $this->active = 0; |
||
7083 | } |
||
7084 | |||
7085 | // 2. If the exercise is not active. |
||
7086 | if (empty($lpId)) { |
||
7087 | // 2.1 LP is OFF |
||
7088 | if ($this->active == 0) { |
||
7089 | return [ |
||
7090 | 'value' => false, |
||
7091 | 'message' => Display::return_message( |
||
7092 | get_lang('ExerciseNotFound'), |
||
7093 | 'warning', |
||
7094 | false |
||
7095 | ), |
||
7096 | 'rawMessage' => get_lang('ExerciseNotFound'), |
||
7097 | ]; |
||
7098 | } |
||
7099 | } else { |
||
7100 | // 2.1 LP is loaded |
||
7101 | if ($this->active == 0 && |
||
7102 | !learnpath::is_lp_visible_for_student($lpId, api_get_user_id()) |
||
7103 | ) { |
||
7104 | return [ |
||
7105 | 'value' => false, |
||
7106 | 'message' => Display::return_message( |
||
7107 | get_lang('ExerciseNotFound'), |
||
7108 | 'warning', |
||
7109 | false |
||
7110 | ), |
||
7111 | 'rawMessage' => get_lang('ExerciseNotFound'), |
||
7112 | ]; |
||
7113 | } |
||
7114 | } |
||
7115 | |||
7116 | // 3. We check if the time limits are on |
||
7117 | $limitTimeExists = false; |
||
7118 | if (!empty($this->start_time) || !empty($this->end_time)) { |
||
7119 | $limitTimeExists = true; |
||
7120 | } |
||
7121 | |||
7122 | if ($limitTimeExists) { |
||
7123 | $timeNow = time(); |
||
7124 | $existsStartDate = false; |
||
7125 | $nowIsAfterStartDate = true; |
||
7126 | $existsEndDate = false; |
||
7127 | $nowIsBeforeEndDate = true; |
||
7128 | |||
7129 | if (!empty($this->start_time)) { |
||
7130 | $existsStartDate = true; |
||
7131 | } |
||
7132 | |||
7133 | if (!empty($this->end_time)) { |
||
7134 | $existsEndDate = true; |
||
7135 | } |
||
7136 | |||
7137 | // check if we are before-or-after end-or-start date |
||
7138 | if ($existsStartDate && $timeNow < api_strtotime($this->start_time, 'UTC')) { |
||
7139 | $nowIsAfterStartDate = false; |
||
7140 | } |
||
7141 | |||
7142 | if ($existsEndDate & $timeNow >= api_strtotime($this->end_time, 'UTC')) { |
||
7143 | $nowIsBeforeEndDate = false; |
||
7144 | } |
||
7145 | |||
7146 | // lets check all cases |
||
7147 | if ($existsStartDate && !$existsEndDate) { |
||
7148 | // exists start date and dont exists end date |
||
7149 | if ($nowIsAfterStartDate) { |
||
7150 | // after start date, no end date |
||
7151 | $isVisible = true; |
||
7152 | $message = sprintf( |
||
7153 | get_lang('ExerciseAvailableSinceX'), |
||
7154 | api_convert_and_format_date($this->start_time) |
||
7155 | ); |
||
7156 | } else { |
||
7157 | // before start date, no end date |
||
7158 | $isVisible = false; |
||
7159 | $message = sprintf( |
||
7160 | get_lang('ExerciseAvailableFromX'), |
||
7161 | api_convert_and_format_date($this->start_time) |
||
7162 | ); |
||
7163 | } |
||
7164 | } elseif (!$existsStartDate && $existsEndDate) { |
||
7165 | // doesnt exist start date, exists end date |
||
7166 | if ($nowIsBeforeEndDate) { |
||
7167 | // before end date, no start date |
||
7168 | $isVisible = true; |
||
7169 | $message = sprintf( |
||
7170 | get_lang('ExerciseAvailableUntilX'), |
||
7171 | api_convert_and_format_date($this->end_time) |
||
7172 | ); |
||
7173 | } else { |
||
7174 | // after end date, no start date |
||
7175 | $isVisible = false; |
||
7176 | $message = sprintf( |
||
7177 | get_lang('ExerciseAvailableUntilX'), |
||
7178 | api_convert_and_format_date($this->end_time) |
||
7179 | ); |
||
7180 | } |
||
7181 | } elseif ($existsStartDate && $existsEndDate) { |
||
7182 | // exists start date and end date |
||
7183 | if ($nowIsAfterStartDate) { |
||
7184 | if ($nowIsBeforeEndDate) { |
||
7185 | // after start date and before end date |
||
7186 | $isVisible = true; |
||
7187 | $message = sprintf( |
||
7188 | get_lang('ExerciseIsActivatedFromXToY'), |
||
7189 | api_convert_and_format_date($this->start_time), |
||
7190 | api_convert_and_format_date($this->end_time) |
||
7191 | ); |
||
7192 | } else { |
||
7193 | // after start date and after end date |
||
7194 | $isVisible = false; |
||
7195 | $message = sprintf( |
||
7196 | get_lang('ExerciseWasActivatedFromXToY'), |
||
7197 | api_convert_and_format_date($this->start_time), |
||
7198 | api_convert_and_format_date($this->end_time) |
||
7199 | ); |
||
7200 | } |
||
7201 | } else { |
||
7202 | if ($nowIsBeforeEndDate) { |
||
7203 | // before start date and before end date |
||
7204 | $isVisible = false; |
||
7205 | $message = sprintf( |
||
7206 | get_lang('ExerciseWillBeActivatedFromXToY'), |
||
7207 | api_convert_and_format_date($this->start_time), |
||
7208 | api_convert_and_format_date($this->end_time) |
||
7209 | ); |
||
7210 | } |
||
7211 | // case before start date and after end date is impossible |
||
7212 | } |
||
7213 | } elseif (!$existsStartDate && !$existsEndDate) { |
||
7214 | // doesnt exist start date nor end date |
||
7215 | $isVisible = true; |
||
7216 | $message = ''; |
||
7217 | } |
||
7218 | } |
||
7219 | |||
7220 | $remedialCoursePlugin = RemedialCoursePlugin::create(); |
||
7221 | |||
7222 | // BT#18165 |
||
7223 | $exerciseAttempts = $this->selectAttempts(); |
||
7224 | if ($exerciseAttempts > 0) { |
||
7225 | $userId = api_get_user_id(); |
||
7226 | $attemptCount = Event::get_attempt_count_not_finished( |
||
7227 | $userId, |
||
7228 | $this->iid, |
||
7229 | $lpId, |
||
7230 | $lpItemId, |
||
7231 | $lpItemViewId |
||
7232 | ); |
||
7233 | $message .= $remedialCoursePlugin->getAdvancedCourseList( |
||
7234 | $this, |
||
7235 | $userId, |
||
7236 | api_get_session_id(), |
||
7237 | $lpId ?: 0, |
||
7238 | $lpItemId ?: 0 |
||
7239 | ); |
||
7240 | if ($attemptCount >= $exerciseAttempts) { |
||
7241 | $message .= $remedialCoursePlugin->getRemedialCourseList( |
||
7242 | $this, |
||
7243 | $userId, |
||
7244 | api_get_session_id(), |
||
7245 | false, |
||
7246 | $lpId ?: 0, |
||
7247 | $lpItemId ?: 0 |
||
7248 | ); |
||
7249 | } |
||
7250 | } |
||
7251 | // 4. We check if the student have attempts |
||
7252 | if ($isVisible) { |
||
7253 | $exerciseAttempts = $this->selectAttempts(); |
||
7254 | |||
7255 | if ($exerciseAttempts > 0) { |
||
7256 | $attemptCount = Event::get_attempt_count_not_finished( |
||
7257 | api_get_user_id(), |
||
7258 | $this->iid, |
||
7259 | $lpId, |
||
7260 | $lpItemId, |
||
7261 | $lpItemViewId |
||
7262 | ); |
||
7263 | |||
7264 | if ($attemptCount >= $exerciseAttempts) { |
||
7265 | $message = sprintf( |
||
7266 | get_lang('ReachedMaxAttempts'), |
||
7267 | $this->name, |
||
7268 | $exerciseAttempts |
||
7269 | ); |
||
7270 | $isVisible = false; |
||
7271 | } else { |
||
7272 | // Check blocking exercise. |
||
7273 | $extraFieldValue = new ExtraFieldValue('exercise'); |
||
7274 | $blockExercise = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
7275 | $this->iid, |
||
7276 | 'blocking_percentage' |
||
7277 | ); |
||
7278 | if ($blockExercise && isset($blockExercise['value']) && !empty($blockExercise['value'])) { |
||
7279 | $blockPercentage = (int) $blockExercise['value']; |
||
7280 | $userAttempts = Event::getExerciseResultsByUser( |
||
7281 | api_get_user_id(), |
||
7282 | $this->iid, |
||
7283 | $this->course_id, |
||
7284 | $sessionId, |
||
7285 | $lpId, |
||
7286 | $lpItemId |
||
7287 | ); |
||
7288 | |||
7289 | if (!empty($userAttempts)) { |
||
7290 | $currentAttempt = current($userAttempts); |
||
7291 | if ($currentAttempt['total_percentage'] <= $blockPercentage) { |
||
7292 | $message = sprintf( |
||
7293 | get_lang('ExerciseBlockBecausePercentageX'), |
||
7294 | $blockPercentage |
||
7295 | ); |
||
7296 | $isVisible = false; // See BT#18165 |
||
7297 | $message .= $remedialCoursePlugin->getRemedialCourseList( |
||
7298 | $this, |
||
7299 | api_get_user_id(), |
||
7300 | api_get_session_id(), |
||
7301 | false, |
||
7302 | $lpId, |
||
7303 | $lpItemId |
||
7304 | ); |
||
7305 | } |
||
7306 | } |
||
7307 | } |
||
7308 | } |
||
7309 | } |
||
7310 | } |
||
7311 | |||
7312 | $rawMessage = ''; |
||
7313 | if (!empty($message)) { |
||
7314 | $rawMessage = $message; |
||
7315 | $message = Display::return_message($message, 'warning', false); |
||
7316 | } |
||
7317 | |||
7318 | return [ |
||
7319 | 'value' => $isVisible, |
||
7320 | 'message' => $message, |
||
7321 | 'rawMessage' => $rawMessage, |
||
7322 | ]; |
||
7323 | } |
||
7324 | |||
7325 | /** |
||
7326 | * @return bool |
||
7327 | */ |
||
7328 | public function added_in_lp() |
||
7329 | { |
||
7330 | $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM); |
||
7331 | $sql = "SELECT max_score FROM $TBL_LP_ITEM |
||
7332 | WHERE |
||
7333 | c_id = {$this->course_id} AND |
||
7334 | item_type = '".TOOL_QUIZ."' AND |
||
7335 | path = '{$this->iid}'"; |
||
7336 | $result = Database::query($sql); |
||
7337 | if (Database::num_rows($result) > 0) { |
||
7338 | return true; |
||
7339 | } |
||
7340 | |||
7341 | return false; |
||
7342 | } |
||
7343 | |||
7344 | /** |
||
7345 | * Returns an array with this form. |
||
7346 | * |
||
7347 | * @example |
||
7348 | * <code> |
||
7349 | * array (size=3) |
||
7350 | * 999 => |
||
7351 | * array (size=3) |
||
7352 | * 0 => int 3422 |
||
7353 | * 1 => int 3423 |
||
7354 | * 2 => int 3424 |
||
7355 | * 100 => |
||
7356 | * array (size=2) |
||
7357 | * 0 => int 3469 |
||
7358 | * 1 => int 3470 |
||
7359 | * 101 => |
||
7360 | * array (size=1) |
||
7361 | * 0 => int 3482 |
||
7362 | * </code> |
||
7363 | * The array inside the key 999 means the question list that belongs to the media id = 999, |
||
7364 | * this case is special because 999 means "no media". |
||
7365 | * |
||
7366 | * @return array |
||
7367 | */ |
||
7368 | public function getMediaList() |
||
7369 | { |
||
7370 | return $this->mediaList; |
||
7371 | } |
||
7372 | |||
7373 | /** |
||
7374 | * Is media question activated? |
||
7375 | * |
||
7376 | * @return bool |
||
7377 | */ |
||
7378 | public function mediaIsActivated() |
||
7379 | { |
||
7380 | $mediaQuestions = $this->getMediaList(); |
||
7381 | $active = false; |
||
7382 | if (isset($mediaQuestions) && !empty($mediaQuestions)) { |
||
7383 | $media_count = count($mediaQuestions); |
||
7384 | if ($media_count > 1) { |
||
7385 | return true; |
||
7386 | } elseif ($media_count == 1) { |
||
7387 | if (isset($mediaQuestions[999])) { |
||
7388 | return false; |
||
7389 | } else { |
||
7390 | return true; |
||
7391 | } |
||
7392 | } |
||
7393 | } |
||
7394 | |||
7395 | return $active; |
||
7396 | } |
||
7397 | |||
7398 | /** |
||
7399 | * Gets question list from the exercise. |
||
7400 | * |
||
7401 | * @return array |
||
7402 | */ |
||
7403 | public function getQuestionList() |
||
7404 | { |
||
7405 | return $this->questionList; |
||
7406 | } |
||
7407 | |||
7408 | /** |
||
7409 | * Question list with medias compressed like this. |
||
7410 | * |
||
7411 | * @example |
||
7412 | * <code> |
||
7413 | * array( |
||
7414 | * question_id_1, |
||
7415 | * question_id_2, |
||
7416 | * media_id, <- this media id contains question ids |
||
7417 | * question_id_3, |
||
7418 | * ) |
||
7419 | * </code> |
||
7420 | * |
||
7421 | * @return array |
||
7422 | */ |
||
7423 | public function getQuestionListWithMediasCompressed() |
||
7424 | { |
||
7425 | return $this->questionList; |
||
7426 | } |
||
7427 | |||
7428 | /** |
||
7429 | * Question list with medias uncompressed like this. |
||
7430 | * |
||
7431 | * @example |
||
7432 | * <code> |
||
7433 | * array( |
||
7434 | * question_id, |
||
7435 | * question_id, |
||
7436 | * question_id, <- belongs to a media id |
||
7437 | * question_id, <- belongs to a media id |
||
7438 | * question_id, |
||
7439 | * ) |
||
7440 | * </code> |
||
7441 | * |
||
7442 | * @return array |
||
7443 | */ |
||
7444 | public function getQuestionListWithMediasUncompressed() |
||
7445 | { |
||
7446 | return $this->questionListUncompressed; |
||
7447 | } |
||
7448 | |||
7449 | /** |
||
7450 | * Sets the question list when the exercise->read() is executed. |
||
7451 | * |
||
7452 | * @param bool $adminView Whether to view the set the list of *all* questions or just the normal student view |
||
7453 | */ |
||
7454 | public function setQuestionList($adminView = false) |
||
7455 | { |
||
7456 | // Getting question list. |
||
7457 | $questionList = $this->selectQuestionList(true, $adminView); |
||
7458 | $this->setMediaList($questionList); |
||
7459 | $this->questionList = $this->transformQuestionListWithMedias($questionList, false); |
||
7460 | $this->questionListUncompressed = $this->transformQuestionListWithMedias( |
||
7461 | $questionList, |
||
7462 | true |
||
7463 | ); |
||
7464 | } |
||
7465 | |||
7466 | /** |
||
7467 | * @params array question list |
||
7468 | * @params bool expand or not question list (true show all questions, |
||
7469 | * false show media question id instead of the question ids) |
||
7470 | */ |
||
7471 | public function transformQuestionListWithMedias( |
||
7472 | $question_list, |
||
7473 | $expand_media_questions = false |
||
7474 | ) { |
||
7475 | $new_question_list = []; |
||
7476 | if (!empty($question_list)) { |
||
7477 | $media_questions = $this->getMediaList(); |
||
7478 | $media_active = $this->mediaIsActivated($media_questions); |
||
7479 | |||
7480 | if ($media_active) { |
||
7481 | $counter = 1; |
||
7482 | foreach ($question_list as $question_id) { |
||
7483 | $add_question = true; |
||
7484 | foreach ($media_questions as $media_id => $question_list_in_media) { |
||
7485 | if ($media_id != 999 && in_array($question_id, $question_list_in_media)) { |
||
7486 | $add_question = false; |
||
7487 | if (!in_array($media_id, $new_question_list)) { |
||
7488 | $new_question_list[$counter] = $media_id; |
||
7489 | $counter++; |
||
7490 | } |
||
7491 | break; |
||
7492 | } |
||
7493 | } |
||
7494 | if ($add_question) { |
||
7495 | $new_question_list[$counter] = $question_id; |
||
7496 | $counter++; |
||
7497 | } |
||
7498 | } |
||
7499 | if ($expand_media_questions) { |
||
7500 | $media_key_list = array_keys($media_questions); |
||
7501 | foreach ($new_question_list as &$question_id) { |
||
7502 | if (in_array($question_id, $media_key_list)) { |
||
7503 | $question_id = $media_questions[$question_id]; |
||
7504 | } |
||
7505 | } |
||
7506 | $new_question_list = array_flatten($new_question_list); |
||
7507 | } |
||
7508 | } else { |
||
7509 | $new_question_list = $question_list; |
||
7510 | } |
||
7511 | } |
||
7512 | |||
7513 | return $new_question_list; |
||
7514 | } |
||
7515 | |||
7516 | /** |
||
7517 | * Get sorted question list based on the random order settings. |
||
7518 | * |
||
7519 | * @return array |
||
7520 | */ |
||
7521 | public function get_validated_question_list() |
||
7522 | { |
||
7523 | $isRandomByCategory = $this->isRandomByCat(); |
||
7524 | if ($isRandomByCategory == 0) { |
||
7525 | if ($this->isRandom()) { |
||
7526 | return $this->getRandomList(); |
||
7527 | } |
||
7528 | |||
7529 | return $this->selectQuestionList(); |
||
7530 | } |
||
7531 | |||
7532 | if ($this->isRandom()) { |
||
7533 | // USE question categories |
||
7534 | // get questions by category for this exercise |
||
7535 | // we have to choice $objExercise->random question in each array values of $categoryQuestions |
||
7536 | // key of $categoryQuestions are the categopy id (0 for not in a category) |
||
7537 | // value is the array of question id of this category |
||
7538 | $questionList = []; |
||
7539 | $categoryQuestions = TestCategory::getQuestionsByCat($this->iid); |
||
7540 | $isRandomByCategory = $this->getRandomByCategory(); |
||
7541 | // We sort categories based on the term between [] in the head |
||
7542 | // of the category's description |
||
7543 | /* examples of categories : |
||
7544 | * [biologie] Maitriser les mecanismes de base de la genetique |
||
7545 | * [biologie] Relier les moyens de depenses et les agents infectieux |
||
7546 | * [biologie] Savoir ou est produite l'enrgie dans les cellules et sous quelle forme |
||
7547 | * [chimie] Classer les molles suivant leur pouvoir oxydant ou reacteur |
||
7548 | * [chimie] Connaître la denition de la theoie acide/base selon Brönsted |
||
7549 | * [chimie] Connaître les charges des particules |
||
7550 | * We want that in the order of the groups defined by the term |
||
7551 | * between brackets at the beginning of the category title |
||
7552 | */ |
||
7553 | // If test option is Grouped By Categories |
||
7554 | if ($isRandomByCategory == 2) { |
||
7555 | $categoryQuestions = TestCategory::sortTabByBracketLabel($categoryQuestions); |
||
7556 | } |
||
7557 | foreach ($categoryQuestions as $question) { |
||
7558 | $number_of_random_question = $this->random; |
||
7559 | if ($this->random == -1) { |
||
7560 | $number_of_random_question = count($this->questionList); |
||
7561 | } |
||
7562 | $questionList = array_merge( |
||
7563 | $questionList, |
||
7564 | TestCategory::getNElementsFromArray( |
||
7565 | $question, |
||
7566 | $number_of_random_question |
||
7567 | ) |
||
7568 | ); |
||
7569 | } |
||
7570 | // shuffle the question list if test is not grouped by categories |
||
7571 | if ($isRandomByCategory == 1) { |
||
7572 | shuffle($questionList); // or not |
||
7573 | } |
||
7574 | |||
7575 | return $questionList; |
||
7576 | } |
||
7577 | |||
7578 | // Problem, random by category has been selected and |
||
7579 | // we have no $this->isRandom number of question selected |
||
7580 | // Should not happened |
||
7581 | |||
7582 | return []; |
||
7583 | } |
||
7584 | |||
7585 | public function get_question_list($expand_media_questions = false) |
||
7586 | { |
||
7587 | $question_list = $this->get_validated_question_list(); |
||
7588 | $question_list = $this->transform_question_list_with_medias($question_list, $expand_media_questions); |
||
7589 | |||
7590 | return $question_list; |
||
7591 | } |
||
7592 | |||
7593 | public function transform_question_list_with_medias($question_list, $expand_media_questions = false) |
||
7594 | { |
||
7595 | $new_question_list = []; |
||
7596 | if (!empty($question_list)) { |
||
7597 | $media_questions = $this->getMediaList(); |
||
7598 | $media_active = $this->mediaIsActivated($media_questions); |
||
7599 | |||
7600 | if ($media_active) { |
||
7601 | $counter = 1; |
||
7602 | foreach ($question_list as $question_id) { |
||
7603 | $add_question = true; |
||
7604 | foreach ($media_questions as $media_id => $question_list_in_media) { |
||
7605 | if ($media_id != 999 && in_array($question_id, $question_list_in_media)) { |
||
7606 | $add_question = false; |
||
7607 | if (!in_array($media_id, $new_question_list)) { |
||
7608 | $new_question_list[$counter] = $media_id; |
||
7609 | $counter++; |
||
7610 | } |
||
7611 | break; |
||
7612 | } |
||
7613 | } |
||
7614 | if ($add_question) { |
||
7615 | $new_question_list[$counter] = $question_id; |
||
7616 | $counter++; |
||
7617 | } |
||
7618 | } |
||
7619 | if ($expand_media_questions) { |
||
7620 | $media_key_list = array_keys($media_questions); |
||
7621 | foreach ($new_question_list as &$question_id) { |
||
7622 | if (in_array($question_id, $media_key_list)) { |
||
7623 | $question_id = $media_questions[$question_id]; |
||
7624 | } |
||
7625 | } |
||
7626 | $new_question_list = array_flatten($new_question_list); |
||
7627 | } |
||
7628 | } else { |
||
7629 | $new_question_list = $question_list; |
||
7630 | } |
||
7631 | } |
||
7632 | |||
7633 | return $new_question_list; |
||
7634 | } |
||
7635 | |||
7636 | /** |
||
7637 | * @param int $exe_id |
||
7638 | * |
||
7639 | * @return array |
||
7640 | */ |
||
7641 | public function get_stat_track_exercise_info_by_exe_id($exe_id) |
||
7642 | { |
||
7643 | $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
7644 | $exe_id = (int) $exe_id; |
||
7645 | $sql_track = "SELECT * FROM $table WHERE exe_id = $exe_id "; |
||
7646 | $result = Database::query($sql_track); |
||
7647 | $new_array = []; |
||
7648 | if (Database::num_rows($result) > 0) { |
||
7649 | $new_array = Database::fetch_array($result, 'ASSOC'); |
||
7650 | $start_date = api_get_utc_datetime($new_array['start_date'], true); |
||
7651 | $end_date = api_get_utc_datetime($new_array['exe_date'], true); |
||
7652 | $new_array['duration_formatted'] = ''; |
||
7653 | if (!empty($new_array['exe_duration']) && !empty($start_date) && !empty($end_date)) { |
||
7654 | $time = api_format_time($new_array['exe_duration'], 'js'); |
||
7655 | $new_array['duration_formatted'] = $time; |
||
7656 | } |
||
7657 | } |
||
7658 | |||
7659 | return $new_array; |
||
7660 | } |
||
7661 | |||
7662 | /** |
||
7663 | * @param int $exeId |
||
7664 | * |
||
7665 | * @return bool |
||
7666 | */ |
||
7667 | public function removeAllQuestionToRemind($exeId) |
||
7668 | { |
||
7669 | $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
7670 | $exeId = (int) $exeId; |
||
7671 | if (empty($exeId)) { |
||
7672 | return false; |
||
7673 | } |
||
7674 | $sql = "UPDATE $table |
||
7675 | SET questions_to_check = '' |
||
7676 | WHERE exe_id = $exeId "; |
||
7677 | Database::query($sql); |
||
7678 | |||
7679 | return true; |
||
7680 | } |
||
7681 | |||
7682 | /** |
||
7683 | * @param int $exeId |
||
7684 | * @param array $questionList |
||
7685 | * |
||
7686 | * @return bool |
||
7687 | */ |
||
7688 | public function addAllQuestionToRemind($exeId, $questionList = []) |
||
7689 | { |
||
7690 | $exeId = (int) $exeId; |
||
7691 | if (empty($questionList)) { |
||
7692 | return false; |
||
7693 | } |
||
7694 | |||
7695 | $questionListToString = implode(',', $questionList); |
||
7696 | $questionListToString = Database::escape_string($questionListToString); |
||
7697 | |||
7698 | $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
7699 | $sql = "UPDATE $table |
||
7700 | SET questions_to_check = '$questionListToString' |
||
7701 | WHERE exe_id = $exeId"; |
||
7702 | Database::query($sql); |
||
7703 | |||
7704 | return true; |
||
7705 | } |
||
7706 | |||
7707 | /** |
||
7708 | * @param int $exeId |
||
7709 | * @param int $questionId |
||
7710 | * @param string $action |
||
7711 | */ |
||
7712 | public function editQuestionToRemind($exeId, $questionId, $action = 'add') |
||
7713 | { |
||
7714 | $exercise_info = self::get_stat_track_exercise_info_by_exe_id($exeId); |
||
7715 | $questionId = (int) $questionId; |
||
7716 | $exeId = (int) $exeId; |
||
7717 | |||
7718 | if ($exercise_info) { |
||
7719 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
7720 | if (empty($exercise_info['questions_to_check'])) { |
||
7721 | if ($action === 'add') { |
||
7722 | $sql = "UPDATE $track_exercises |
||
7723 | SET questions_to_check = '$questionId' |
||
7724 | WHERE exe_id = $exeId "; |
||
7725 | Database::query($sql); |
||
7726 | } |
||
7727 | } else { |
||
7728 | $remind_list = explode(',', $exercise_info['questions_to_check']); |
||
7729 | $remind_list_string = ''; |
||
7730 | if ($action === 'add') { |
||
7731 | if (!in_array($questionId, $remind_list)) { |
||
7732 | $newRemindList = []; |
||
7733 | $remind_list[] = $questionId; |
||
7734 | $questionListInSession = Session::read('questionList'); |
||
7735 | if (!empty($questionListInSession)) { |
||
7736 | foreach ($questionListInSession as $originalQuestionId) { |
||
7737 | if (in_array($originalQuestionId, $remind_list)) { |
||
7738 | $newRemindList[] = $originalQuestionId; |
||
7739 | } |
||
7740 | } |
||
7741 | } |
||
7742 | $remind_list_string = implode(',', $newRemindList); |
||
7743 | } |
||
7744 | } elseif ($action === 'delete') { |
||
7745 | if (!empty($remind_list)) { |
||
7746 | if (in_array($questionId, $remind_list)) { |
||
7747 | $remind_list = array_flip($remind_list); |
||
7748 | unset($remind_list[$questionId]); |
||
7749 | $remind_list = array_flip($remind_list); |
||
7750 | |||
7751 | if (!empty($remind_list)) { |
||
7752 | sort($remind_list); |
||
7753 | array_filter($remind_list); |
||
7754 | $remind_list_string = implode(',', $remind_list); |
||
7755 | } |
||
7756 | } |
||
7757 | } |
||
7758 | } |
||
7759 | $value = Database::escape_string($remind_list_string); |
||
7760 | $sql = "UPDATE $track_exercises |
||
7761 | SET questions_to_check = '$value' |
||
7762 | WHERE exe_id = $exeId "; |
||
7763 | Database::query($sql); |
||
7764 | } |
||
7765 | } |
||
7766 | } |
||
7767 | |||
7768 | /** |
||
7769 | * @param string $answer |
||
7770 | * |
||
7771 | * @return mixed |
||
7772 | */ |
||
7773 | public function fill_in_blank_answer_to_array($answer) |
||
7774 | { |
||
7775 | $listStudentResults = FillBlanks::getAnswerInfo( |
||
7776 | $answer, |
||
7777 | true |
||
7778 | ); |
||
7779 | $teacherAnswerList = $listStudentResults['student_answer']; |
||
7780 | |||
7781 | return $teacherAnswerList; |
||
7782 | } |
||
7783 | |||
7784 | /** |
||
7785 | * @param string $answer |
||
7786 | * |
||
7787 | * @return string |
||
7788 | */ |
||
7789 | public function fill_in_blank_answer_to_string($answer) |
||
7790 | { |
||
7791 | $teacher_answer_list = $this->fill_in_blank_answer_to_array($answer); |
||
7792 | $result = ''; |
||
7793 | if (!empty($teacher_answer_list)) { |
||
7794 | foreach ($teacher_answer_list as $teacher_item) { |
||
7795 | // Cleaning student answer list |
||
7796 | $value = strip_tags($teacher_item); |
||
7797 | if (strlen($value) > 2 && false !== strpos($value, '/')) { |
||
7798 | $value = api_substr($value, 1, api_strlen($value) - 2); |
||
7799 | $value = explode('/', $value); |
||
7800 | if (!empty($value[0])) { |
||
7801 | $value = trim($value[0]); |
||
7802 | $value = str_replace(' ', '', $value); |
||
7803 | $result .= $value; |
||
7804 | } |
||
7805 | } else { |
||
7806 | $result .= $value; |
||
7807 | } |
||
7808 | } |
||
7809 | } |
||
7810 | |||
7811 | return $result; |
||
7812 | } |
||
7813 | |||
7814 | /** |
||
7815 | * @return string |
||
7816 | */ |
||
7817 | public function returnTimeLeftDiv() |
||
7818 | { |
||
7819 | $html = '<div id="clock_warning" style="display:none">'; |
||
7820 | $html .= Display::return_message( |
||
7821 | get_lang('ReachedTimeLimit'), |
||
7822 | 'warning' |
||
7823 | ); |
||
7824 | $html .= ' '; |
||
7825 | $html .= sprintf( |
||
7826 | get_lang('YouWillBeRedirectedInXSeconds'), |
||
7827 | '<span id="counter_to_redirect" class="red_alert"></span>' |
||
7828 | ); |
||
7829 | $html .= '</div>'; |
||
7830 | |||
7831 | $icon = Display::returnFontAwesomeIcon('clock-o'); |
||
7832 | $html .= '<div class="count_down"> |
||
7833 | '.get_lang('RemainingTimeToFinishExercise').' |
||
7834 | '.$icon.'<span id="exercise_clock_warning"></span> |
||
7835 | </div>'; |
||
7836 | |||
7837 | return $html; |
||
7838 | } |
||
7839 | |||
7840 | /** |
||
7841 | * Get categories added in the exercise--category matrix. |
||
7842 | * |
||
7843 | * @return array |
||
7844 | */ |
||
7845 | public function getCategoriesInExercise() |
||
7846 | { |
||
7847 | $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY); |
||
7848 | if (!empty($this->iid)) { |
||
7849 | $sql = "SELECT * FROM $table |
||
7850 | WHERE exercise_id = {$this->iid} AND c_id = {$this->course_id} "; |
||
7851 | $result = Database::query($sql); |
||
7852 | $list = []; |
||
7853 | if (Database::num_rows($result)) { |
||
7854 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
7855 | $list[$row['category_id']] = $row; |
||
7856 | } |
||
7857 | |||
7858 | return $list; |
||
7859 | } |
||
7860 | } |
||
7861 | |||
7862 | return []; |
||
7863 | } |
||
7864 | |||
7865 | /** |
||
7866 | * Get total number of question that will be parsed when using the category/exercise. |
||
7867 | * |
||
7868 | * @return int |
||
7869 | */ |
||
7870 | public function getNumberQuestionExerciseCategory() |
||
7871 | { |
||
7872 | $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY); |
||
7873 | if (!empty($this->iid)) { |
||
7874 | $sql = "SELECT SUM(count_questions) count_questions |
||
7875 | FROM $table |
||
7876 | WHERE exercise_id = {$this->iid} AND c_id = {$this->course_id}"; |
||
7877 | $result = Database::query($sql); |
||
7878 | if (Database::num_rows($result)) { |
||
7879 | $row = Database::fetch_array($result); |
||
7880 | |||
7881 | return (int) $row['count_questions']; |
||
7882 | } |
||
7883 | } |
||
7884 | |||
7885 | return 0; |
||
7886 | } |
||
7887 | |||
7888 | /** |
||
7889 | * Save categories in the TABLE_QUIZ_REL_CATEGORY table. |
||
7890 | * |
||
7891 | * @param array $categories |
||
7892 | */ |
||
7893 | public function save_categories_in_exercise($categories) |
||
7894 | { |
||
7895 | if (!empty($categories) && !empty($this->iid)) { |
||
7896 | $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY); |
||
7897 | $sql = "DELETE FROM $table |
||
7898 | WHERE exercise_id = {$this->iid} AND c_id = {$this->course_id}"; |
||
7899 | Database::query($sql); |
||
7900 | if (!empty($categories)) { |
||
7901 | foreach ($categories as $categoryId => $countQuestions) { |
||
7902 | $params = [ |
||
7903 | 'c_id' => $this->course_id, |
||
7904 | 'exercise_id' => $this->iid, |
||
7905 | 'category_id' => $categoryId, |
||
7906 | 'count_questions' => $countQuestions, |
||
7907 | ]; |
||
7908 | Database::insert($table, $params); |
||
7909 | } |
||
7910 | } |
||
7911 | } |
||
7912 | } |
||
7913 | |||
7914 | /** |
||
7915 | * @param array $questionList |
||
7916 | * @param int $currentQuestion |
||
7917 | * @param array $conditions |
||
7918 | * @param string $link |
||
7919 | * |
||
7920 | * @return string |
||
7921 | */ |
||
7922 | public function progressExercisePaginationBar( |
||
7923 | $questionList, |
||
7924 | $currentQuestion, |
||
7925 | $conditions, |
||
7926 | $link |
||
7927 | ) { |
||
7928 | $mediaQuestions = $this->getMediaList(); |
||
7929 | |||
7930 | $html = '<div class="exercise_pagination pagination pagination-mini"><ul>'; |
||
7931 | $counter = 0; |
||
7932 | $nextValue = 0; |
||
7933 | $wasMedia = false; |
||
7934 | $before = 0; |
||
7935 | $counterNoMedias = 0; |
||
7936 | foreach ($questionList as $questionId) { |
||
7937 | $isCurrent = $currentQuestion == ($counterNoMedias + 1) ? true : false; |
||
7938 | |||
7939 | if (!empty($nextValue)) { |
||
7940 | if ($wasMedia) { |
||
7941 | $nextValue = $nextValue - $before + 1; |
||
7942 | } |
||
7943 | } |
||
7944 | |||
7945 | if (isset($mediaQuestions) && isset($mediaQuestions[$questionId])) { |
||
7946 | $fixedValue = $counterNoMedias; |
||
7947 | |||
7948 | $html .= Display::progressPaginationBar( |
||
7949 | $nextValue, |
||
7950 | $mediaQuestions[$questionId], |
||
7951 | $currentQuestion, |
||
7952 | $fixedValue, |
||
7953 | $conditions, |
||
7954 | $link, |
||
7955 | true, |
||
7956 | true |
||
7957 | ); |
||
7958 | |||
7959 | $counter += count($mediaQuestions[$questionId]) - 1; |
||
7960 | $before = count($questionList); |
||
7961 | $wasMedia = true; |
||
7962 | $nextValue += count($questionList); |
||
7963 | } else { |
||
7964 | $html .= Display::parsePaginationItem( |
||
7965 | $questionId, |
||
7966 | $isCurrent, |
||
7967 | $conditions, |
||
7968 | $link, |
||
7969 | $counter |
||
7970 | ); |
||
7971 | $counter++; |
||
7972 | $nextValue++; |
||
7973 | $wasMedia = false; |
||
7974 | } |
||
7975 | $counterNoMedias++; |
||
7976 | } |
||
7977 | $html .= '</ul></div>'; |
||
7978 | |||
7979 | return $html; |
||
7980 | } |
||
7981 | |||
7982 | /** |
||
7983 | * Shows a list of numbers that represents the question to answer in a exercise. |
||
7984 | * |
||
7985 | * @param array $categories |
||
7986 | * @param int $current |
||
7987 | * @param array $conditions |
||
7988 | * @param string $link |
||
7989 | * |
||
7990 | * @return string |
||
7991 | */ |
||
7992 | public function progressExercisePaginationBarWithCategories( |
||
7993 | $categories, |
||
7994 | $current, |
||
7995 | $conditions = [], |
||
7996 | $link = null |
||
7997 | ) { |
||
7998 | $html = null; |
||
7999 | $counterNoMedias = 0; |
||
8000 | $nextValue = 0; |
||
8001 | $wasMedia = false; |
||
8002 | $before = 0; |
||
8003 | |||
8004 | if (!empty($categories)) { |
||
8005 | $selectionType = $this->getQuestionSelectionType(); |
||
8006 | $useRootAsCategoryTitle = false; |
||
8007 | |||
8008 | // Grouping questions per parent category see BT#6540 |
||
8009 | if (in_array( |
||
8010 | $selectionType, |
||
8011 | [ |
||
8012 | EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED, |
||
8013 | EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM, |
||
8014 | ] |
||
8015 | )) { |
||
8016 | $useRootAsCategoryTitle = true; |
||
8017 | } |
||
8018 | |||
8019 | // If the exercise is set to only show the titles of the categories |
||
8020 | // at the root of the tree, then pre-order the categories tree by |
||
8021 | // removing children and summing their questions into the parent |
||
8022 | // categories |
||
8023 | if ($useRootAsCategoryTitle) { |
||
8024 | // The new categories list starts empty |
||
8025 | $newCategoryList = []; |
||
8026 | foreach ($categories as $category) { |
||
8027 | $rootElement = $category['root']; |
||
8028 | |||
8029 | if (isset($category['parent_info'])) { |
||
8030 | $rootElement = $category['parent_info']['iid']; |
||
8031 | } |
||
8032 | |||
8033 | //$rootElement = $category['iid']; |
||
8034 | // If the current category's ancestor was never seen |
||
8035 | // before, then declare it and assign the current |
||
8036 | // category to it. |
||
8037 | if (!isset($newCategoryList[$rootElement])) { |
||
8038 | $newCategoryList[$rootElement] = $category; |
||
8039 | } else { |
||
8040 | // If it was already seen, then merge the previous with |
||
8041 | // the current category |
||
8042 | $oldQuestionList = $newCategoryList[$rootElement]['question_list']; |
||
8043 | $category['question_list'] = array_merge($oldQuestionList, $category['question_list']); |
||
8044 | $newCategoryList[$rootElement] = $category; |
||
8045 | } |
||
8046 | } |
||
8047 | // Now use the newly built categories list, with only parents |
||
8048 | $categories = $newCategoryList; |
||
8049 | } |
||
8050 | |||
8051 | foreach ($categories as $category) { |
||
8052 | $questionList = $category['question_list']; |
||
8053 | // Check if in this category there questions added in a media |
||
8054 | $mediaQuestionId = $category['media_question']; |
||
8055 | $isMedia = false; |
||
8056 | $fixedValue = null; |
||
8057 | |||
8058 | // Media exists! |
||
8059 | if ($mediaQuestionId != 999) { |
||
8060 | $isMedia = true; |
||
8061 | $fixedValue = $counterNoMedias; |
||
8062 | } |
||
8063 | |||
8064 | //$categoryName = $category['path']; << show the path |
||
8065 | $categoryName = $category['name']; |
||
8066 | |||
8067 | if ($useRootAsCategoryTitle) { |
||
8068 | if (isset($category['parent_info'])) { |
||
8069 | $categoryName = $category['parent_info']['title']; |
||
8070 | } |
||
8071 | } |
||
8072 | $html .= '<div class="row">'; |
||
8073 | $html .= '<div class="span2">'.$categoryName.'</div>'; |
||
8074 | $html .= '<div class="span8">'; |
||
8075 | |||
8076 | if (!empty($nextValue)) { |
||
8077 | if ($wasMedia) { |
||
8078 | $nextValue = $nextValue - $before + 1; |
||
8079 | } |
||
8080 | } |
||
8081 | $html .= Display::progressPaginationBar( |
||
8082 | $nextValue, |
||
8083 | $questionList, |
||
8084 | $current, |
||
8085 | $fixedValue, |
||
8086 | $conditions, |
||
8087 | $link, |
||
8088 | $isMedia, |
||
8089 | true |
||
8090 | ); |
||
8091 | $html .= '</div>'; |
||
8092 | $html .= '</div>'; |
||
8093 | |||
8094 | if ($mediaQuestionId == 999) { |
||
8095 | $counterNoMedias += count($questionList); |
||
8096 | } else { |
||
8097 | $counterNoMedias++; |
||
8098 | } |
||
8099 | |||
8100 | $nextValue += count($questionList); |
||
8101 | $before = count($questionList); |
||
8102 | |||
8103 | if ($mediaQuestionId != 999) { |
||
8104 | $wasMedia = true; |
||
8105 | } else { |
||
8106 | $wasMedia = false; |
||
8107 | } |
||
8108 | } |
||
8109 | } |
||
8110 | |||
8111 | return $html; |
||
8112 | } |
||
8113 | |||
8114 | /** |
||
8115 | * Renders a question list. |
||
8116 | * |
||
8117 | * @param array $questionList (with media questions compressed) |
||
8118 | * @param int $currentQuestion |
||
8119 | * @param array $exerciseResult |
||
8120 | * @param array $attemptList |
||
8121 | * @param array $remindList |
||
8122 | */ |
||
8123 | public function renderQuestionList( |
||
8124 | $questionList, |
||
8125 | $currentQuestion, |
||
8126 | $exerciseResult, |
||
8127 | $attemptList, |
||
8128 | $remindList |
||
8129 | ) { |
||
8130 | $mediaQuestions = $this->getMediaList(); |
||
8131 | $i = 0; |
||
8132 | |||
8133 | // Normal question list render (medias compressed) |
||
8134 | foreach ($questionList as $questionId) { |
||
8135 | $i++; |
||
8136 | // For sequential exercises |
||
8137 | |||
8138 | if ($this->type == ONE_PER_PAGE) { |
||
8139 | // If it is not the right question, goes to the next loop iteration |
||
8140 | if ($currentQuestion != $i) { |
||
8141 | continue; |
||
8142 | } else { |
||
8143 | if (!in_array( |
||
8144 | $this->getFeedbackType(), |
||
8145 | [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP] |
||
8146 | )) { |
||
8147 | // if the user has already answered this question |
||
8148 | if (isset($exerciseResult[$questionId])) { |
||
8149 | echo Display::return_message( |
||
8150 | get_lang('AlreadyAnswered'), |
||
8151 | 'normal' |
||
8152 | ); |
||
8153 | break; |
||
8154 | } |
||
8155 | } |
||
8156 | } |
||
8157 | } |
||
8158 | |||
8159 | // The $questionList contains the media id we check |
||
8160 | // if this questionId is a media question type |
||
8161 | if (isset($mediaQuestions[$questionId]) && |
||
8162 | $mediaQuestions[$questionId] != 999 |
||
8163 | ) { |
||
8164 | // The question belongs to a media |
||
8165 | $mediaQuestionList = $mediaQuestions[$questionId]; |
||
8166 | $objQuestionTmp = Question::read($questionId); |
||
8167 | |||
8168 | $counter = 1; |
||
8169 | if ($objQuestionTmp->type == MEDIA_QUESTION) { |
||
8170 | echo $objQuestionTmp->show_media_content(); |
||
8171 | |||
8172 | $countQuestionsInsideMedia = count($mediaQuestionList); |
||
8173 | |||
8174 | // Show questions that belongs to a media |
||
8175 | if (!empty($mediaQuestionList)) { |
||
8176 | // In order to parse media questions we use letters a, b, c, etc. |
||
8177 | $letterCounter = 97; |
||
8178 | foreach ($mediaQuestionList as $questionIdInsideMedia) { |
||
8179 | $isLastQuestionInMedia = false; |
||
8180 | if ($counter == $countQuestionsInsideMedia) { |
||
8181 | $isLastQuestionInMedia = true; |
||
8182 | } |
||
8183 | $this->renderQuestion( |
||
8184 | $questionIdInsideMedia, |
||
8185 | $attemptList, |
||
8186 | $remindList, |
||
8187 | chr($letterCounter), |
||
8188 | $currentQuestion, |
||
8189 | $mediaQuestionList, |
||
8190 | $isLastQuestionInMedia, |
||
8191 | $questionList |
||
8192 | ); |
||
8193 | $letterCounter++; |
||
8194 | $counter++; |
||
8195 | } |
||
8196 | } |
||
8197 | } else { |
||
8198 | $this->renderQuestion( |
||
8199 | $questionId, |
||
8200 | $attemptList, |
||
8201 | $remindList, |
||
8202 | $i, |
||
8203 | $currentQuestion, |
||
8204 | null, |
||
8205 | null, |
||
8206 | $questionList |
||
8207 | ); |
||
8208 | $i++; |
||
8209 | } |
||
8210 | } else { |
||
8211 | // Normal question render. |
||
8212 | $this->renderQuestion( |
||
8213 | $questionId, |
||
8214 | $attemptList, |
||
8215 | $remindList, |
||
8216 | $i, |
||
8217 | $currentQuestion, |
||
8218 | null, |
||
8219 | null, |
||
8220 | $questionList |
||
8221 | ); |
||
8222 | } |
||
8223 | |||
8224 | // For sequential exercises. |
||
8225 | if ($this->type == ONE_PER_PAGE) { |
||
8226 | // quits the loop |
||
8227 | break; |
||
8228 | } |
||
8229 | } |
||
8230 | // end foreach() |
||
8231 | |||
8232 | if ($this->type == ALL_ON_ONE_PAGE) { |
||
8233 | $exercise_actions = $this->show_button($questionId, $currentQuestion); |
||
8234 | echo Display::div($exercise_actions, ['class' => 'exercise_actions']); |
||
8235 | } |
||
8236 | } |
||
8237 | |||
8238 | /** |
||
8239 | * @param int $questionId |
||
8240 | * @param array $attemptList |
||
8241 | * @param array $remindList |
||
8242 | * @param int $i |
||
8243 | * @param int $current_question |
||
8244 | * @param array $questions_in_media |
||
8245 | * @param bool $last_question_in_media |
||
8246 | * @param array $realQuestionList |
||
8247 | * @param bool $generateJS |
||
8248 | */ |
||
8249 | public function renderQuestion( |
||
8250 | $questionId, |
||
8251 | $attemptList, |
||
8252 | $remindList, |
||
8253 | $i, |
||
8254 | $current_question, |
||
8255 | $questions_in_media = [], |
||
8256 | $last_question_in_media = false, |
||
8257 | $realQuestionList = [], |
||
8258 | $generateJS = true |
||
8259 | ) { |
||
8260 | // With this option on the question is loaded via AJAX |
||
8261 | //$generateJS = true; |
||
8262 | //$this->loadQuestionAJAX = true; |
||
8263 | |||
8264 | if ($generateJS && $this->loadQuestionAJAX) { |
||
8265 | $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?a=get_question&id='.$questionId.'&'.api_get_cidreq(); |
||
8266 | $params = [ |
||
8267 | 'questionId' => $questionId, |
||
8268 | 'attemptList' => $attemptList, |
||
8269 | 'remindList' => $remindList, |
||
8270 | 'i' => $i, |
||
8271 | 'current_question' => $current_question, |
||
8272 | 'questions_in_media' => $questions_in_media, |
||
8273 | 'last_question_in_media' => $last_question_in_media, |
||
8274 | ]; |
||
8275 | $params = json_encode($params); |
||
8276 | |||
8277 | $script = '<script> |
||
8278 | $(function(){ |
||
8279 | var params = '.$params.'; |
||
8280 | $.ajax({ |
||
8281 | type: "GET", |
||
8282 | data: params, |
||
8283 | url: "'.$url.'", |
||
8284 | success: function(return_value) { |
||
8285 | $("#ajaxquestiondiv'.$questionId.'").html(return_value); |
||
8286 | } |
||
8287 | }); |
||
8288 | }); |
||
8289 | </script> |
||
8290 | <div id="ajaxquestiondiv'.$questionId.'"></div>'; |
||
8291 | echo $script; |
||
8292 | } else { |
||
8293 | global $origin; |
||
8294 | $question_obj = Question::read($questionId); |
||
8295 | $user_choice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null; |
||
8296 | $remind_highlight = null; |
||
8297 | |||
8298 | // Hides questions when reviewing a ALL_ON_ONE_PAGE exercise |
||
8299 | // see #4542 no_remind_highlight class hide with jquery |
||
8300 | if ($this->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) { |
||
8301 | $remind_highlight = 'no_remind_highlight'; |
||
8302 | if (in_array($question_obj->type, Question::question_type_no_review())) { |
||
8303 | return null; |
||
8304 | } |
||
8305 | } |
||
8306 | |||
8307 | $attributes = ['id' => 'remind_list['.$questionId.']', 'data-question-id' => $questionId]; |
||
8308 | |||
8309 | // Showing the question |
||
8310 | $exercise_actions = null; |
||
8311 | echo '<a id="questionanchor'.$questionId.'"></a><br />'; |
||
8312 | echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >'; |
||
8313 | |||
8314 | // Shows the question + possible answers |
||
8315 | $showTitle = $this->getHideQuestionTitle() == 1 ? false : true; |
||
8316 | echo ExerciseLib::showQuestion( |
||
8317 | $this, |
||
8318 | $question_obj, |
||
8319 | false, |
||
8320 | $origin, |
||
8321 | $i, |
||
8322 | $showTitle, |
||
8323 | false, |
||
8324 | $user_choice |
||
8325 | ); |
||
8326 | |||
8327 | // Button save and continue |
||
8328 | switch ($this->type) { |
||
8329 | case ONE_PER_PAGE: |
||
8330 | $exercise_actions .= $this->show_button( |
||
8331 | $questionId, |
||
8332 | $current_question, |
||
8333 | null, |
||
8334 | $remindList |
||
8335 | ); |
||
8336 | break; |
||
8337 | case ALL_ON_ONE_PAGE: |
||
8338 | if (api_is_allowed_to_session_edit()) { |
||
8339 | $button = [ |
||
8340 | Display::button( |
||
8341 | 'save_now', |
||
8342 | get_lang('SaveForNow'), |
||
8343 | ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId] |
||
8344 | ), |
||
8345 | '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span>', |
||
8346 | ]; |
||
8347 | $exercise_actions .= Display::div( |
||
8348 | implode(PHP_EOL, $button), |
||
8349 | ['class' => 'exercise_save_now_button'] |
||
8350 | ); |
||
8351 | } |
||
8352 | break; |
||
8353 | } |
||
8354 | |||
8355 | if (!empty($questions_in_media)) { |
||
8356 | $count_of_questions_inside_media = count($questions_in_media); |
||
8357 | if ($count_of_questions_inside_media > 1 && api_is_allowed_to_session_edit()) { |
||
8358 | $button = [ |
||
8359 | Display::button( |
||
8360 | 'save_now', |
||
8361 | get_lang('SaveForNow'), |
||
8362 | ['type' => 'button', 'class' => 'btn btn-primary', 'data-question' => $questionId] |
||
8363 | ), |
||
8364 | '<span id="save_for_now_'.$questionId.'" class="exercise_save_mini_message"></span> ', |
||
8365 | ]; |
||
8366 | $exercise_actions = Display::div( |
||
8367 | implode(PHP_EOL, $button), |
||
8368 | ['class' => 'exercise_save_now_button'] |
||
8369 | ); |
||
8370 | } |
||
8371 | |||
8372 | if ($last_question_in_media && $this->type == ONE_PER_PAGE) { |
||
8373 | $exercise_actions = $this->show_button($questionId, $current_question, $questions_in_media); |
||
8374 | } |
||
8375 | } |
||
8376 | |||
8377 | // Checkbox review answers |
||
8378 | if ($this->review_answers && |
||
8379 | !in_array($question_obj->type, Question::question_type_no_review()) |
||
8380 | ) { |
||
8381 | $remind_question_div = Display::tag( |
||
8382 | 'label', |
||
8383 | Display::input( |
||
8384 | 'checkbox', |
||
8385 | 'remind_list['.$questionId.']', |
||
8386 | '', |
||
8387 | $attributes |
||
8388 | ).get_lang('ReviewQuestionLater'), |
||
8389 | [ |
||
8390 | 'class' => 'checkbox', |
||
8391 | 'for' => 'remind_list['.$questionId.']', |
||
8392 | ] |
||
8393 | ); |
||
8394 | $exercise_actions .= Display::div( |
||
8395 | $remind_question_div, |
||
8396 | ['class' => 'exercise_save_now_button'] |
||
8397 | ); |
||
8398 | } |
||
8399 | |||
8400 | echo Display::div(' ', ['class' => 'clear']); |
||
8401 | |||
8402 | $paginationCounter = null; |
||
8403 | if ($this->type == ONE_PER_PAGE) { |
||
8404 | if (empty($questions_in_media)) { |
||
8405 | $paginationCounter = Display::paginationIndicator( |
||
8406 | $current_question, |
||
8407 | count($realQuestionList) |
||
8408 | ); |
||
8409 | } else { |
||
8410 | if ($last_question_in_media) { |
||
8411 | $paginationCounter = Display::paginationIndicator( |
||
8412 | $current_question, |
||
8413 | count($realQuestionList) |
||
8414 | ); |
||
8415 | } |
||
8416 | } |
||
8417 | } |
||
8418 | |||
8419 | echo '<div class="row"><div class="pull-right">'.$paginationCounter.'</div></div>'; |
||
8420 | echo Display::div($exercise_actions, ['class' => 'form-actions']); |
||
8421 | echo '</div>'; |
||
8422 | } |
||
8423 | } |
||
8424 | |||
8425 | /** |
||
8426 | * Returns an array of categories details for the questions of the current |
||
8427 | * exercise. |
||
8428 | * |
||
8429 | * @return array |
||
8430 | */ |
||
8431 | public function getQuestionWithCategories() |
||
8432 | { |
||
8433 | $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY); |
||
8434 | $categoryRelTable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY); |
||
8435 | $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
8436 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
8437 | $sql = "SELECT DISTINCT cat.* |
||
8438 | FROM $TBL_EXERCICE_QUESTION e |
||
8439 | INNER JOIN $TBL_QUESTIONS q |
||
8440 | ON e.question_id = q.iid |
||
8441 | INNER JOIN $categoryRelTable catRel |
||
8442 | ON (catRel.question_id = e.question_id AND catRel.c_id = e.c_id) |
||
8443 | INNER JOIN $categoryTable cat |
||
8444 | ON (cat.iid = catRel.category_id) |
||
8445 | WHERE |
||
8446 | e.c_id = {$this->course_id} AND |
||
8447 | e.exercice_id = ".intval($this->iid); |
||
8448 | |||
8449 | $result = Database::query($sql); |
||
8450 | $categoriesInExercise = []; |
||
8451 | if (Database::num_rows($result)) { |
||
8452 | $categoriesInExercise = Database::store_result($result, 'ASSOC'); |
||
8453 | } |
||
8454 | |||
8455 | return $categoriesInExercise; |
||
8456 | } |
||
8457 | |||
8458 | /** |
||
8459 | * Calculate the max_score of the quiz, depending of question inside, and quiz advanced option. |
||
8460 | */ |
||
8461 | public function get_max_score() |
||
8462 | { |
||
8463 | $outMaxScore = 0; |
||
8464 | // list of question's id !!! the array key start at 1 !!! |
||
8465 | $questionList = $this->selectQuestionList(true); |
||
8466 | |||
8467 | if ($this->random > 0 && $this->randomByCat > 0) { |
||
8468 | // test is random by category |
||
8469 | // get the $numberRandomQuestions best score question of each category |
||
8470 | $numberRandomQuestions = $this->random; |
||
8471 | $tab_categories_scores = []; |
||
8472 | foreach ($questionList as $questionId) { |
||
8473 | $question_category_id = TestCategory::getCategoryForQuestion($questionId); |
||
8474 | if (!is_array($tab_categories_scores[$question_category_id])) { |
||
8475 | $tab_categories_scores[$question_category_id] = []; |
||
8476 | } |
||
8477 | $tmpobj_question = Question::read($questionId); |
||
8478 | if (is_object($tmpobj_question)) { |
||
8479 | $tab_categories_scores[$question_category_id][] = $tmpobj_question->weighting; |
||
8480 | } |
||
8481 | } |
||
8482 | |||
8483 | // here we've got an array with first key, the category_id, second key, score of question for this cat |
||
8484 | foreach ($tab_categories_scores as $tab_scores) { |
||
8485 | rsort($tab_scores); |
||
8486 | for ($i = 0; $i < min($numberRandomQuestions, count($tab_scores)); $i++) { |
||
8487 | $outMaxScore += $tab_scores[$i]; |
||
8488 | } |
||
8489 | } |
||
8490 | } else { |
||
8491 | // standard test, just add each question score |
||
8492 | foreach ($questionList as $questionId) { |
||
8493 | $question = Question::read($questionId, $this->course); |
||
8494 | $outMaxScore += $question->weighting; |
||
8495 | } |
||
8496 | } |
||
8497 | |||
8498 | return $outMaxScore; |
||
8499 | } |
||
8500 | |||
8501 | /** |
||
8502 | * @return string |
||
8503 | */ |
||
8504 | public function get_formated_title() |
||
8505 | { |
||
8506 | if (api_get_configuration_value('save_titles_as_html')) { |
||
8507 | } |
||
8508 | |||
8509 | return api_html_entity_decode($this->selectTitle()); |
||
8510 | } |
||
8511 | |||
8512 | /** |
||
8513 | * @param string $title |
||
8514 | * |
||
8515 | * @return string |
||
8516 | */ |
||
8517 | public static function get_formated_title_variable($title) |
||
8518 | { |
||
8519 | return api_html_entity_decode($title); |
||
8520 | } |
||
8521 | |||
8522 | /** |
||
8523 | * @return string |
||
8524 | */ |
||
8525 | public function format_title() |
||
8526 | { |
||
8527 | return api_htmlentities($this->title); |
||
8528 | } |
||
8529 | |||
8530 | /** |
||
8531 | * @param string $title |
||
8532 | * |
||
8533 | * @return string |
||
8534 | */ |
||
8535 | public static function format_title_variable($title) |
||
8536 | { |
||
8537 | return api_htmlentities($title); |
||
8538 | } |
||
8539 | |||
8540 | /** |
||
8541 | * @param int $courseId |
||
8542 | * @param int $sessionId |
||
8543 | * |
||
8544 | * @return array exercises |
||
8545 | */ |
||
8546 | public function getExercisesByCourseSession($courseId, $sessionId) |
||
8547 | { |
||
8548 | $courseId = (int) $courseId; |
||
8549 | $sessionId = (int) $sessionId; |
||
8550 | |||
8551 | $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST); |
||
8552 | $sql = "SELECT * FROM $tbl_quiz cq |
||
8553 | WHERE |
||
8554 | cq.c_id = %s AND |
||
8555 | (cq.session_id = %s OR cq.session_id = 0) AND |
||
8556 | cq.active = 0 |
||
8557 | ORDER BY cq.iid"; |
||
8558 | $sql = sprintf($sql, $courseId, $sessionId); |
||
8559 | |||
8560 | $result = Database::query($sql); |
||
8561 | |||
8562 | $rows = []; |
||
8563 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
8564 | $rows[] = $row; |
||
8565 | } |
||
8566 | |||
8567 | return $rows; |
||
8568 | } |
||
8569 | |||
8570 | /** |
||
8571 | * Get array of exercise details and user results. |
||
8572 | * |
||
8573 | * @param int $courseId |
||
8574 | * @param int $sessionId |
||
8575 | * @param array $quizId |
||
8576 | * @param bool $checkOnlyActiveUsers |
||
8577 | * @param array $filterDates Limit the results exported to those within this range ('start_date' to 'end_date') |
||
8578 | * |
||
8579 | * @return array exercises |
||
8580 | */ |
||
8581 | public function getExerciseAndResult($courseId, $sessionId, $quizId = [], $checkOnlyActiveUsers = false, $filterDates = []) |
||
8582 | { |
||
8583 | if (empty($quizId)) { |
||
8584 | return []; |
||
8585 | } |
||
8586 | |||
8587 | $sessionId = (int) $sessionId; |
||
8588 | $courseId = (int) $courseId; |
||
8589 | |||
8590 | $ids = is_array($quizId) ? $quizId : [$quizId]; |
||
8591 | $ids = array_map('intval', $ids); |
||
8592 | $ids = implode(',', $ids); |
||
8593 | $trackExcercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
8594 | $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST); |
||
8595 | $tblUser = Database::get_main_table(TABLE_MAIN_USER); |
||
8596 | |||
8597 | $condition = ''; |
||
8598 | $innerJoinUser = ''; |
||
8599 | if ($checkOnlyActiveUsers) { |
||
8600 | $condition .= " AND te.status = '' "; |
||
8601 | $innerJoinUser .= " INNER JOIN $tblUser u ON u.user_id = te. exe_user_id"; |
||
8602 | } |
||
8603 | |||
8604 | if (!empty($filterDates)) { |
||
8605 | if (!empty($filterDates['start_date'])) { |
||
8606 | $condition .= " AND te.exe_date >= '".Database::escape_string($filterDates['start_date'])."' "; |
||
8607 | } |
||
8608 | if (!empty($filterDates['end_date'])) { |
||
8609 | $condition .= " AND te.exe_date <= '".Database::escape_string($filterDates['end_date'])."' "; |
||
8610 | } |
||
8611 | } |
||
8612 | |||
8613 | if (0 != $sessionId) { |
||
8614 | $sql = "SELECT * FROM $trackExcercises te |
||
8615 | INNER JOIN $tblQuiz cq ON cq.iid = te.exe_exo_id |
||
8616 | $innerJoinUser |
||
8617 | WHERE |
||
8618 | te.c_id = %s AND |
||
8619 | te.session_id = %s AND |
||
8620 | cq.iid IN (%s) |
||
8621 | $condition |
||
8622 | ORDER BY cq.iid"; |
||
8623 | |||
8624 | $sql = sprintf($sql, $courseId, $sessionId, $ids); |
||
8625 | } else { |
||
8626 | $sql = "SELECT * FROM $trackExcercises te |
||
8627 | INNER JOIN $tblQuiz cq ON cq.iid = te.exe_exo_id |
||
8628 | $innerJoinUser |
||
8629 | WHERE |
||
8630 | te.c_id = %s AND |
||
8631 | cq.iid IN (%s) |
||
8632 | $condition |
||
8633 | ORDER BY cq.iid"; |
||
8634 | $sql = sprintf($sql, $courseId, $ids); |
||
8635 | } |
||
8636 | $result = Database::query($sql); |
||
8637 | $rows = []; |
||
8638 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
8639 | $rows[] = $row; |
||
8640 | } |
||
8641 | |||
8642 | return $rows; |
||
8643 | } |
||
8644 | |||
8645 | /** |
||
8646 | * @param $exeId |
||
8647 | * @param $exercise_stat_info |
||
8648 | * @param $remindList |
||
8649 | * @param $currentQuestion |
||
8650 | * |
||
8651 | * @return int|null |
||
8652 | */ |
||
8653 | public static function getNextQuestionId( |
||
8654 | $exeId, |
||
8655 | $exercise_stat_info, |
||
8656 | $remindList, |
||
8657 | $currentQuestion |
||
8658 | ) { |
||
8659 | $result = Event::get_exercise_results_by_attempt($exeId, 'incomplete'); |
||
8660 | |||
8661 | if (isset($result[$exeId])) { |
||
8662 | $result = $result[$exeId]; |
||
8663 | } else { |
||
8664 | return null; |
||
8665 | } |
||
8666 | |||
8667 | $data_tracking = $exercise_stat_info['data_tracking']; |
||
8668 | $data_tracking = explode(',', $data_tracking); |
||
8669 | |||
8670 | // if this is the final question do nothing. |
||
8671 | if ($currentQuestion == count($data_tracking)) { |
||
8672 | return null; |
||
8673 | } |
||
8674 | |||
8675 | $currentQuestion--; |
||
8676 | |||
8677 | if (!empty($result['question_list'])) { |
||
8678 | $answeredQuestions = []; |
||
8679 | foreach ($result['question_list'] as $question) { |
||
8680 | if (!empty($question['answer'])) { |
||
8681 | $answeredQuestions[] = $question['question_id']; |
||
8682 | } |
||
8683 | } |
||
8684 | |||
8685 | // Checking answered questions |
||
8686 | $counterAnsweredQuestions = 0; |
||
8687 | foreach ($data_tracking as $questionId) { |
||
8688 | if (!in_array($questionId, $answeredQuestions)) { |
||
8689 | if ($currentQuestion != $counterAnsweredQuestions) { |
||
8690 | break; |
||
8691 | } |
||
8692 | } |
||
8693 | $counterAnsweredQuestions++; |
||
8694 | } |
||
8695 | |||
8696 | $counterRemindListQuestions = 0; |
||
8697 | // Checking questions saved in the reminder list |
||
8698 | if (!empty($remindList)) { |
||
8699 | foreach ($data_tracking as $questionId) { |
||
8700 | if (in_array($questionId, $remindList)) { |
||
8701 | // Skip the current question |
||
8702 | if ($currentQuestion != $counterRemindListQuestions) { |
||
8703 | break; |
||
8704 | } |
||
8705 | } |
||
8706 | $counterRemindListQuestions++; |
||
8707 | } |
||
8708 | |||
8709 | if ($counterRemindListQuestions < $currentQuestion) { |
||
8710 | return null; |
||
8711 | } |
||
8712 | |||
8713 | if (!empty($counterRemindListQuestions)) { |
||
8714 | if ($counterRemindListQuestions > $counterAnsweredQuestions) { |
||
8715 | return $counterAnsweredQuestions; |
||
8716 | } else { |
||
8717 | return $counterRemindListQuestions; |
||
8718 | } |
||
8719 | } |
||
8720 | } |
||
8721 | |||
8722 | return $counterAnsweredQuestions; |
||
8723 | } |
||
8724 | } |
||
8725 | |||
8726 | /** |
||
8727 | * Gets the position of a questionId in the question list. |
||
8728 | * |
||
8729 | * @param $questionId |
||
8730 | * |
||
8731 | * @return int |
||
8732 | */ |
||
8733 | public function getPositionInCompressedQuestionList($questionId) |
||
8734 | { |
||
8735 | $questionList = $this->getQuestionListWithMediasCompressed(); |
||
8736 | $mediaQuestions = $this->getMediaList(); |
||
8737 | $position = 1; |
||
8738 | foreach ($questionList as $id) { |
||
8739 | if (isset($mediaQuestions[$id]) && in_array($questionId, $mediaQuestions[$id])) { |
||
8740 | $mediaQuestionList = $mediaQuestions[$id]; |
||
8741 | if (in_array($questionId, $mediaQuestionList)) { |
||
8742 | return $position; |
||
8743 | } else { |
||
8744 | $position++; |
||
8745 | } |
||
8746 | } else { |
||
8747 | if ($id == $questionId) { |
||
8748 | return $position; |
||
8749 | } else { |
||
8750 | $position++; |
||
8751 | } |
||
8752 | } |
||
8753 | } |
||
8754 | |||
8755 | return 1; |
||
8756 | } |
||
8757 | |||
8758 | /** |
||
8759 | * Get the correct answers in all attempts. |
||
8760 | * |
||
8761 | * @param int $learnPathId |
||
8762 | * @param int $learnPathItemId |
||
8763 | * @param bool $onlyCorrect |
||
8764 | * |
||
8765 | * @return array |
||
8766 | */ |
||
8767 | public function getAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0, $onlyCorrect = true) |
||
8768 | { |
||
8769 | $attempts = Event::getExerciseResultsByUser( |
||
8770 | api_get_user_id(), |
||
8771 | $this->iid, |
||
8772 | api_get_course_int_id(), |
||
8773 | api_get_session_id(), |
||
8774 | $learnPathId, |
||
8775 | $learnPathItemId, |
||
8776 | 'DESC' |
||
8777 | ); |
||
8778 | |||
8779 | $list = []; |
||
8780 | foreach ($attempts as $attempt) { |
||
8781 | foreach ($attempt['question_list'] as $answers) { |
||
8782 | foreach ($answers as $answer) { |
||
8783 | $objAnswer = new Answer($answer['question_id']); |
||
8784 | if ($onlyCorrect) { |
||
8785 | switch ($objAnswer->getQuestionType()) { |
||
8786 | case FILL_IN_BLANKS: |
||
8787 | case FILL_IN_BLANKS_COMBINATION: |
||
8788 | $isCorrect = FillBlanks::isCorrect($answer['answer']); |
||
8789 | break; |
||
8790 | case MATCHING: |
||
8791 | case MATCHING_COMBINATION: |
||
8792 | case DRAGGABLE: |
||
8793 | case MATCHING_DRAGGABLE_COMBINATION: |
||
8794 | case MATCHING_DRAGGABLE: |
||
8795 | $isCorrect = Matching::isCorrect( |
||
8796 | $answer['position'], |
||
8797 | $answer['answer'], |
||
8798 | $answer['question_id'] |
||
8799 | ); |
||
8800 | break; |
||
8801 | case ORAL_EXPRESSION: |
||
8802 | $isCorrect = false; |
||
8803 | break; |
||
8804 | default: |
||
8805 | $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']); |
||
8806 | } |
||
8807 | if ($isCorrect) { |
||
8808 | $list[$answer['question_id']][] = $answer; |
||
8809 | } |
||
8810 | } else { |
||
8811 | $list[$answer['question_id']][] = $answer; |
||
8812 | } |
||
8813 | } |
||
8814 | } |
||
8815 | |||
8816 | if (false === $onlyCorrect) { |
||
8817 | // Only take latest attempt |
||
8818 | break; |
||
8819 | } |
||
8820 | } |
||
8821 | |||
8822 | return $list; |
||
8823 | } |
||
8824 | |||
8825 | /** |
||
8826 | * Get the correct answers in all attempts. |
||
8827 | * |
||
8828 | * @param int $learnPathId |
||
8829 | * @param int $learnPathItemId |
||
8830 | * |
||
8831 | * @return array |
||
8832 | */ |
||
8833 | public function getCorrectAnswersInAllAttempts($learnPathId = 0, $learnPathItemId = 0) |
||
8834 | { |
||
8835 | return $this->getAnswersInAllAttempts($learnPathId, $learnPathItemId); |
||
8836 | } |
||
8837 | |||
8838 | /** |
||
8839 | * @return bool |
||
8840 | */ |
||
8841 | public function showPreviousButton() |
||
8842 | { |
||
8843 | $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting'); |
||
8844 | if (false === $allow) { |
||
8845 | return true; |
||
8846 | } |
||
8847 | |||
8848 | return $this->showPreviousButton; |
||
8849 | } |
||
8850 | |||
8851 | public function getPreventBackwards() |
||
8852 | { |
||
8853 | $allow = api_get_configuration_value('quiz_prevent_backwards_move'); |
||
8854 | if (false === $allow) { |
||
8855 | return 0; |
||
8856 | } |
||
8857 | |||
8858 | return (int) $this->preventBackwards; |
||
8859 | } |
||
8860 | |||
8861 | /** |
||
8862 | * @return int |
||
8863 | */ |
||
8864 | public function getExerciseCategoryId() |
||
8865 | { |
||
8866 | if (empty($this->exerciseCategoryId)) { |
||
8867 | return null; |
||
8868 | } |
||
8869 | |||
8870 | return (int) $this->exerciseCategoryId; |
||
8871 | } |
||
8872 | |||
8873 | /** |
||
8874 | * @param int $value |
||
8875 | */ |
||
8876 | public function setExerciseCategoryId($value) |
||
8877 | { |
||
8878 | if (!empty($value)) { |
||
8879 | $this->exerciseCategoryId = (int) $value; |
||
8880 | } |
||
8881 | } |
||
8882 | |||
8883 | /** |
||
8884 | * Set the value to 1 to hide the question number. |
||
8885 | * |
||
8886 | * @param int $value |
||
8887 | */ |
||
8888 | public function setHideQuestionNumber($value = 0) |
||
8889 | { |
||
8890 | $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number'); |
||
8891 | if ($showHideConfiguration) { |
||
8892 | $this->hideQuestionNumber = (int) $value; |
||
8893 | } |
||
8894 | } |
||
8895 | |||
8896 | /** |
||
8897 | * Gets the value to hide or show the question number. If it does not exist, it is set to 0. |
||
8898 | * |
||
8899 | * @return int 1 if the question number must be hidden |
||
8900 | */ |
||
8901 | public function getHideQuestionNumber() |
||
8902 | { |
||
8903 | $showHideConfiguration = api_get_configuration_value('quiz_hide_question_number'); |
||
8904 | if ($showHideConfiguration) { |
||
8905 | return (int) $this->hideQuestionNumber; |
||
8906 | } |
||
8907 | |||
8908 | return 0; |
||
8909 | } |
||
8910 | |||
8911 | /** |
||
8912 | * Set the value to 1 to hide the attempts table on start page. |
||
8913 | * |
||
8914 | * @param int $value |
||
8915 | */ |
||
8916 | public function setHideAttemptsTableOnStartPage($value = 0) |
||
8917 | { |
||
8918 | $showHideAttemptsTableOnStartPage = api_get_configuration_value('quiz_hide_attempts_table_on_start_page'); |
||
8919 | if ($showHideAttemptsTableOnStartPage) { |
||
8920 | $this->hideAttemptsTableOnStartPage = (int) $value; |
||
8921 | } |
||
8922 | } |
||
8923 | |||
8924 | /** |
||
8925 | * Gets the value to hide or show the attempts table on start page. If it does not exist, it is set to 0. |
||
8926 | * |
||
8927 | * @return int 1 if the attempts table must be hidden |
||
8928 | */ |
||
8929 | public function getHideAttemptsTableOnStartPage() |
||
8930 | { |
||
8931 | $showHideAttemptsTableOnStartPage = api_get_configuration_value('quiz_hide_attempts_table_on_start_page'); |
||
8932 | if ($showHideAttemptsTableOnStartPage) { |
||
8933 | return (int) $this->hideAttemptsTableOnStartPage; |
||
8934 | } |
||
8935 | |||
8936 | return 0; |
||
8937 | } |
||
8938 | |||
8939 | /** |
||
8940 | * @param array $values |
||
8941 | */ |
||
8942 | public function setPageResultConfiguration($values) |
||
8943 | { |
||
8944 | $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); |
||
8945 | if ($pageConfig) { |
||
8946 | $params = [ |
||
8947 | 'hide_expected_answer' => $values['hide_expected_answer'] ?? '', |
||
8948 | 'hide_question_score' => $values['hide_question_score'] ?? '', |
||
8949 | 'hide_total_score' => $values['hide_total_score'] ?? '', |
||
8950 | 'hide_category_table' => $values['hide_category_table'] ?? '', |
||
8951 | 'hide_correct_answered_questions' => $values['hide_correct_answered_questions'] ?? '', |
||
8952 | 'hide_comment' => $values['hide_comment'] ?? '', |
||
8953 | ]; |
||
8954 | $type = Type::getType('array'); |
||
8955 | $platform = Database::getManager()->getConnection()->getDatabasePlatform(); |
||
8956 | $this->pageResultConfiguration = $type->convertToDatabaseValue($params, $platform); |
||
8957 | } |
||
8958 | } |
||
8959 | |||
8960 | /** |
||
8961 | * @param array $defaults |
||
8962 | */ |
||
8963 | public function setPageResultConfigurationDefaults(&$defaults) |
||
8964 | { |
||
8965 | $configuration = $this->getPageResultConfiguration(); |
||
8966 | if (!empty($configuration) && !empty($defaults)) { |
||
8967 | $defaults = array_merge($defaults, $configuration); |
||
8968 | } |
||
8969 | } |
||
8970 | |||
8971 | /** |
||
8972 | * Sets the value to show or hide the question number in the default settings of the forms. |
||
8973 | * |
||
8974 | * @param array $defaults |
||
8975 | */ |
||
8976 | public function setHideQuestionNumberDefaults(&$defaults) |
||
8977 | { |
||
8978 | $configuration = $this->getHideQuestionNumberConfiguration(); |
||
8979 | if (!empty($configuration) && !empty($defaults)) { |
||
8980 | $defaults = array_merge($defaults, $configuration); |
||
8981 | } |
||
8982 | } |
||
8983 | |||
8984 | /** |
||
8985 | * Sets the value to show or hide the attempts table on start page in the default settings of the forms. |
||
8986 | * |
||
8987 | * @param array $defaults |
||
8988 | */ |
||
8989 | public function setHideAttemptsTableOnStartPageDefaults(&$defaults) |
||
8990 | { |
||
8991 | $configuration = $this->getHideAttemptsTableOnStartPageConfiguration(); |
||
8992 | if (!empty($configuration) && !empty($defaults)) { |
||
8993 | $defaults = array_merge($defaults, $configuration); |
||
8994 | } |
||
8995 | } |
||
8996 | |||
8997 | /** |
||
8998 | * @return array |
||
8999 | */ |
||
9000 | public function getPageResultConfiguration() |
||
9001 | { |
||
9002 | $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); |
||
9003 | if ($pageConfig) { |
||
9004 | $type = Type::getType('array'); |
||
9005 | $platform = Database::getManager()->getConnection()->getDatabasePlatform(); |
||
9006 | |||
9007 | return $type->convertToPHPValue($this->pageResultConfiguration, $platform); |
||
9008 | } |
||
9009 | |||
9010 | return []; |
||
9011 | } |
||
9012 | |||
9013 | /** |
||
9014 | * Get the value to show or hide the question number in the default settings of the forms. |
||
9015 | * |
||
9016 | * @return array |
||
9017 | */ |
||
9018 | public function getHideQuestionNumberConfiguration() |
||
9019 | { |
||
9020 | $pageConfig = api_get_configuration_value('quiz_hide_question_number'); |
||
9021 | if ($pageConfig) { |
||
9022 | return ['hide_question_number' => $this->hideQuestionNumber]; |
||
9023 | } |
||
9024 | |||
9025 | return []; |
||
9026 | } |
||
9027 | |||
9028 | /** |
||
9029 | * Get the value to show or hide the attempts table on start page in the default settings of the forms. |
||
9030 | * |
||
9031 | * @return array |
||
9032 | */ |
||
9033 | public function getHideAttemptsTableOnStartPageConfiguration() |
||
9034 | { |
||
9035 | $pageConfig = api_get_configuration_value('quiz_hide_attempts_table_on_start_page'); |
||
9036 | if ($pageConfig) { |
||
9037 | return ['hide_attempts_table' => $this->hideAttemptsTableOnStartPage]; |
||
9038 | } |
||
9039 | |||
9040 | return []; |
||
9041 | } |
||
9042 | |||
9043 | /** |
||
9044 | * @param string $attribute |
||
9045 | * |
||
9046 | * @return mixed|null |
||
9047 | */ |
||
9048 | public function getPageConfigurationAttribute($attribute) |
||
9049 | { |
||
9050 | $result = $this->getPageResultConfiguration(); |
||
9051 | |||
9052 | if (!empty($result)) { |
||
9053 | return isset($result[$attribute]) ? $result[$attribute] : null; |
||
9054 | } |
||
9055 | |||
9056 | return null; |
||
9057 | } |
||
9058 | |||
9059 | /** |
||
9060 | * @param bool $showPreviousButton |
||
9061 | * |
||
9062 | * @return Exercise |
||
9063 | */ |
||
9064 | public function setShowPreviousButton($showPreviousButton) |
||
9065 | { |
||
9066 | $this->showPreviousButton = $showPreviousButton; |
||
9067 | |||
9068 | return $this; |
||
9069 | } |
||
9070 | |||
9071 | /** |
||
9072 | * @param array $notifications |
||
9073 | */ |
||
9074 | public function setNotifications($notifications) |
||
9075 | { |
||
9076 | $this->notifications = $notifications; |
||
9077 | } |
||
9078 | |||
9079 | /** |
||
9080 | * @return array |
||
9081 | */ |
||
9082 | public function getNotifications() |
||
9083 | { |
||
9084 | return $this->notifications; |
||
9085 | } |
||
9086 | |||
9087 | /** |
||
9088 | * @return bool |
||
9089 | */ |
||
9090 | public function showExpectedChoice() |
||
9091 | { |
||
9092 | return api_get_configuration_value('show_exercise_expected_choice'); |
||
9093 | } |
||
9094 | |||
9095 | /** |
||
9096 | * @return bool |
||
9097 | */ |
||
9098 | public function showExpectedChoiceColumn() |
||
9099 | { |
||
9100 | if (true === $this->forceShowExpectedChoiceColumn) { |
||
9101 | return true; |
||
9102 | } |
||
9103 | |||
9104 | if ($this->hideExpectedAnswer) { |
||
9105 | return false; |
||
9106 | } |
||
9107 | |||
9108 | if (!in_array($this->results_disabled, [ |
||
9109 | RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, |
||
9110 | ]) |
||
9111 | ) { |
||
9112 | $hide = (int) $this->getPageConfigurationAttribute('hide_expected_answer'); |
||
9113 | if (1 === $hide) { |
||
9114 | return false; |
||
9115 | } |
||
9116 | |||
9117 | return true; |
||
9118 | } |
||
9119 | |||
9120 | return false; |
||
9121 | } |
||
9122 | |||
9123 | /** |
||
9124 | * @param string $class |
||
9125 | * @param string $scoreLabel |
||
9126 | * @param string $result |
||
9127 | * @param array |
||
9128 | * |
||
9129 | * @return string |
||
9130 | */ |
||
9131 | public function getQuestionRibbon($class, $scoreLabel, $result, $array) |
||
9132 | { |
||
9133 | $hide = (int) $this->getPageConfigurationAttribute('hide_question_score'); |
||
9134 | if (1 === $hide) { |
||
9135 | return ''; |
||
9136 | } |
||
9137 | |||
9138 | if ($this->showExpectedChoice()) { |
||
9139 | $html = null; |
||
9140 | $hideLabel = api_get_configuration_value('exercise_hide_label'); |
||
9141 | $label = '<div class="rib rib-'.$class.'"> |
||
9142 | <h3>'.$scoreLabel.'</h3> |
||
9143 | </div>'; |
||
9144 | if (!empty($result)) { |
||
9145 | $label .= '<h4>'.get_lang('Score').': '.$result.'</h4>'; |
||
9146 | } |
||
9147 | if (true === $hideLabel) { |
||
9148 | $answerUsed = (int) $array['used']; |
||
9149 | $answerMissing = (int) $array['missing'] - $answerUsed; |
||
9150 | for ($i = 1; $i <= $answerUsed; $i++) { |
||
9151 | $html .= '<span class="score-img">'. |
||
9152 | Display::return_icon('attempt-check.png', null, null, ICON_SIZE_SMALL). |
||
9153 | '</span>'; |
||
9154 | } |
||
9155 | for ($i = 1; $i <= $answerMissing; $i++) { |
||
9156 | $html .= '<span class="score-img">'. |
||
9157 | Display::return_icon('attempt-nocheck.png', null, null, ICON_SIZE_SMALL). |
||
9158 | '</span>'; |
||
9159 | } |
||
9160 | $label = '<div class="score-title">'.get_lang('CorrectAnswers').': '.$result.'</div>'; |
||
9161 | $label .= '<div class="score-limits">'; |
||
9162 | $label .= $html; |
||
9163 | $label .= '</div>'; |
||
9164 | } |
||
9165 | |||
9166 | return '<div class="ribbon"> |
||
9167 | '.$label.' |
||
9168 | </div>' |
||
9169 | ; |
||
9170 | } else { |
||
9171 | $html = '<div class="ribbon"> |
||
9172 | <div class="rib rib-'.$class.'"> |
||
9173 | <h3>'.$scoreLabel.'</h3> |
||
9174 | </div>'; |
||
9175 | if (!empty($result)) { |
||
9176 | $html .= '<h4>'.get_lang('Score').': '.$result.'</h4>'; |
||
9177 | } |
||
9178 | $html .= '</div>'; |
||
9179 | |||
9180 | return $html; |
||
9181 | } |
||
9182 | } |
||
9183 | |||
9184 | /** |
||
9185 | * @return int |
||
9186 | */ |
||
9187 | public function getAutoLaunch() |
||
9188 | { |
||
9189 | return $this->autolaunch; |
||
9190 | } |
||
9191 | |||
9192 | /** |
||
9193 | * Clean auto launch settings for all exercise in course/course-session. |
||
9194 | */ |
||
9195 | public function enableAutoLaunch() |
||
9196 | { |
||
9197 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
9198 | $sql = "UPDATE $table SET autolaunch = 1 |
||
9199 | WHERE iid = ".$this->iid; |
||
9200 | Database::query($sql); |
||
9201 | } |
||
9202 | |||
9203 | /** |
||
9204 | * Clean auto launch settings for all exercise in course/course-session. |
||
9205 | */ |
||
9206 | public function cleanCourseLaunchSettings() |
||
9207 | { |
||
9208 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
9209 | $sql = "UPDATE $table SET autolaunch = 0 |
||
9210 | WHERE c_id = ".$this->course_id." AND session_id = ".$this->sessionId; |
||
9211 | Database::query($sql); |
||
9212 | } |
||
9213 | |||
9214 | /** |
||
9215 | * Get the title without HTML tags. |
||
9216 | * |
||
9217 | * @return string |
||
9218 | */ |
||
9219 | public function getUnformattedTitle() |
||
9220 | { |
||
9221 | return strip_tags(api_html_entity_decode($this->title)); |
||
9222 | } |
||
9223 | |||
9224 | /** |
||
9225 | * Get the question IDs from quiz_rel_question for the current quiz, |
||
9226 | * using the parameters as the arguments to the SQL's LIMIT clause. |
||
9227 | * Because the exercise_id is known, it also comes with a filter on |
||
9228 | * the session, so sessions are not specified here. |
||
9229 | * |
||
9230 | * @param int $start At which question do we want to start the list |
||
9231 | * @param int $length Up to how many results we want |
||
9232 | * |
||
9233 | * @return array A list of question IDs |
||
9234 | */ |
||
9235 | public function getQuestionForTeacher($start = 0, $length = 10) |
||
9236 | { |
||
9237 | $start = (int) $start; |
||
9238 | if ($start < 0) { |
||
9239 | $start = 0; |
||
9240 | } |
||
9241 | |||
9242 | $length = (int) $length; |
||
9243 | |||
9244 | $quizRelQuestion = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
9245 | $question = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
9246 | $sql = "SELECT DISTINCT e.question_id |
||
9247 | FROM $quizRelQuestion e |
||
9248 | INNER JOIN $question q |
||
9249 | ON e.question_id = q.iid |
||
9250 | WHERE |
||
9251 | e.c_id = {$this->course_id} AND |
||
9252 | e.exercice_id = {$this->iid} |
||
9253 | ORDER BY question_order |
||
9254 | LIMIT $start, $length |
||
9255 | "; |
||
9256 | $result = Database::query($sql); |
||
9257 | $questionList = []; |
||
9258 | while ($object = Database::fetch_object($result)) { |
||
9259 | $questionList[] = $object->question_id; |
||
9260 | } |
||
9261 | |||
9262 | return $questionList; |
||
9263 | } |
||
9264 | |||
9265 | /** |
||
9266 | * @param int $exerciseId |
||
9267 | * @param array $courseInfo |
||
9268 | * @param int $sessionId |
||
9269 | * |
||
9270 | * @return bool |
||
9271 | */ |
||
9272 | public function generateStats($exerciseId, $courseInfo, $sessionId) |
||
9273 | { |
||
9274 | $allowStats = api_get_configuration_value('allow_gradebook_stats'); |
||
9275 | if (!$allowStats) { |
||
9276 | return false; |
||
9277 | } |
||
9278 | |||
9279 | if (empty($courseInfo)) { |
||
9280 | return false; |
||
9281 | } |
||
9282 | |||
9283 | $courseId = $courseInfo['real_id']; |
||
9284 | |||
9285 | $sessionId = (int) $sessionId; |
||
9286 | $exerciseId = (int) $exerciseId; |
||
9287 | |||
9288 | $result = $this->read($exerciseId); |
||
9289 | |||
9290 | if (empty($result)) { |
||
9291 | api_not_allowed(true); |
||
9292 | } |
||
9293 | |||
9294 | $statusToFilter = empty($sessionId) ? STUDENT : 0; |
||
9295 | |||
9296 | $studentList = CourseManager::get_user_list_from_course_code( |
||
9297 | $courseInfo['code'], |
||
9298 | $sessionId, |
||
9299 | null, |
||
9300 | null, |
||
9301 | $statusToFilter |
||
9302 | ); |
||
9303 | |||
9304 | if (empty($studentList)) { |
||
9305 | Display::addFlash(Display::return_message(get_lang('NoUsersInCourse'))); |
||
9306 | header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq()); |
||
9307 | exit; |
||
9308 | } |
||
9309 | |||
9310 | $tblStats = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
9311 | |||
9312 | $studentIdList = []; |
||
9313 | if (!empty($studentList)) { |
||
9314 | $studentIdList = array_column($studentList, 'user_id'); |
||
9315 | } |
||
9316 | |||
9317 | if (false == $this->exercise_was_added_in_lp) { |
||
9318 | $sql = "SELECT * FROM $tblStats |
||
9319 | WHERE |
||
9320 | exe_exo_id = $exerciseId AND |
||
9321 | orig_lp_id = 0 AND |
||
9322 | orig_lp_item_id = 0 AND |
||
9323 | status <> 'incomplete' AND |
||
9324 | session_id = $sessionId AND |
||
9325 | c_id = $courseId |
||
9326 | "; |
||
9327 | } else { |
||
9328 | $lpId = null; |
||
9329 | if (!empty($this->lpList)) { |
||
9330 | // Taking only the first LP |
||
9331 | $lpId = $this->getLpBySession($sessionId); |
||
9332 | $lpId = $lpId['lp_id']; |
||
9333 | } |
||
9334 | |||
9335 | $sql = "SELECT * |
||
9336 | FROM $tblStats |
||
9337 | WHERE |
||
9338 | exe_exo_id = $exerciseId AND |
||
9339 | orig_lp_id = $lpId AND |
||
9340 | status <> 'incomplete' AND |
||
9341 | session_id = $sessionId AND |
||
9342 | c_id = $courseId "; |
||
9343 | } |
||
9344 | |||
9345 | $sql .= ' ORDER BY exe_id DESC'; |
||
9346 | |||
9347 | $studentCount = 0; |
||
9348 | $sum = 0; |
||
9349 | $bestResult = 0; |
||
9350 | $sumResult = 0; |
||
9351 | $result = Database::query($sql); |
||
9352 | $students = []; |
||
9353 | while ($data = Database::fetch_array($result, 'ASSOC')) { |
||
9354 | // Only take into account users in the current student list. |
||
9355 | if (!empty($studentIdList)) { |
||
9356 | if (!in_array($data['exe_user_id'], $studentIdList)) { |
||
9357 | continue; |
||
9358 | } |
||
9359 | } |
||
9360 | |||
9361 | if (!isset($students[$data['exe_user_id']])) { |
||
9362 | if ($data['exe_weighting'] != 0) { |
||
9363 | $students[$data['exe_user_id']] = $data['exe_result']; |
||
9364 | if ($data['exe_result'] > $bestResult) { |
||
9365 | $bestResult = $data['exe_result']; |
||
9366 | } |
||
9367 | $sumResult += $data['exe_result']; |
||
9368 | } |
||
9369 | } |
||
9370 | } |
||
9371 | |||
9372 | $count = count($studentList); |
||
9373 | $average = $sumResult / $count; |
||
9374 | $em = Database::getManager(); |
||
9375 | |||
9376 | $links = AbstractLink::getGradebookLinksFromItem( |
||
9377 | $this->iid, |
||
9378 | LINK_EXERCISE, |
||
9379 | $courseInfo['code'], |
||
9380 | $sessionId |
||
9381 | ); |
||
9382 | |||
9383 | if (empty($links)) { |
||
9384 | $links = AbstractLink::getGradebookLinksFromItem( |
||
9385 | $this->iid, |
||
9386 | LINK_EXERCISE, |
||
9387 | $courseInfo['code'], |
||
9388 | $sessionId |
||
9389 | ); |
||
9390 | } |
||
9391 | |||
9392 | if (!empty($links)) { |
||
9393 | $repo = $em->getRepository('ChamiloCoreBundle:GradebookLink'); |
||
9394 | |||
9395 | foreach ($links as $link) { |
||
9396 | $linkId = $link['id']; |
||
9397 | /** @var GradebookLink $exerciseLink */ |
||
9398 | $exerciseLink = $repo->find($linkId); |
||
9399 | if ($exerciseLink) { |
||
9400 | $exerciseLink |
||
9401 | ->setUserScoreList($students) |
||
9402 | ->setBestScore($bestResult) |
||
9403 | ->setAverageScore($average) |
||
9404 | ->setScoreWeight($this->get_max_score()); |
||
9405 | $em->persist($exerciseLink); |
||
9406 | $em->flush(); |
||
9407 | } |
||
9408 | } |
||
9409 | } |
||
9410 | } |
||
9411 | |||
9412 | /** |
||
9413 | * Return an HTML table of exercises for on-screen printing, including |
||
9414 | * action icons. If no exercise is present and the user can edit the |
||
9415 | * course, show a "create test" button. |
||
9416 | * |
||
9417 | * @param int $categoryId |
||
9418 | * @param string $keyword |
||
9419 | * @param int $userId |
||
9420 | * @param int $courseId |
||
9421 | * @param int $sessionId |
||
9422 | * @param bool $returnData |
||
9423 | * @param int $minCategoriesInExercise |
||
9424 | * @param int $filterByResultDisabled |
||
9425 | * @param int $filterByAttempt |
||
9426 | * |
||
9427 | * @return string|SortableTableFromArrayConfig |
||
9428 | */ |
||
9429 | public static function exerciseGrid( |
||
9430 | $categoryId, |
||
9431 | $keyword = '', |
||
9432 | $userId = 0, |
||
9433 | $courseId = 0, |
||
9434 | $sessionId = 0, |
||
9435 | $returnData = false, |
||
9436 | $minCategoriesInExercise = 0, |
||
9437 | $filterByResultDisabled = 0, |
||
9438 | $filterByAttempt = 0, |
||
9439 | $myActions = null, |
||
9440 | $returnTable = false, |
||
9441 | $isAllowedToEdit = null |
||
9442 | ) { |
||
9443 | //$allowDelete = Exercise::allowAction('delete'); |
||
9444 | $allowClean = self::allowAction('clean_results'); |
||
9445 | |||
9446 | $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT); |
||
9447 | $TBL_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY); |
||
9448 | $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
9449 | $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); |
||
9450 | $TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
9451 | |||
9452 | $categoryId = (int) $categoryId; |
||
9453 | $keyword = Database::escape_string($keyword); |
||
9454 | $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : null; |
||
9455 | $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : null; |
||
9456 | |||
9457 | $autoLaunchAvailable = false; |
||
9458 | if (api_get_course_setting('enable_exercise_auto_launch') == 1 && |
||
9459 | api_get_configuration_value('allow_exercise_auto_launch') |
||
9460 | ) { |
||
9461 | $autoLaunchAvailable = true; |
||
9462 | } |
||
9463 | |||
9464 | if (!isset($isAllowedToEdit)) { |
||
9465 | $isAllowedToEdit = api_is_allowed_to_edit(null, true); |
||
9466 | } |
||
9467 | $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : api_get_course_info(); |
||
9468 | $sessionId = $sessionId ? (int) $sessionId : api_get_session_id(); |
||
9469 | $courseId = $courseInfo['real_id']; |
||
9470 | $tableRows = []; |
||
9471 | $uploadPath = DIR_HOTPOTATOES; //defined in main_api |
||
9472 | $exercisePath = api_get_self(); |
||
9473 | $origin = api_get_origin(); |
||
9474 | $userInfo = $userId ? api_get_user_info($userId) : api_get_user_info(); |
||
9475 | $charset = 'utf-8'; |
||
9476 | $token = Security::get_token(); |
||
9477 | $userId = $userId ? (int) $userId : api_get_user_id(); |
||
9478 | $isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh($userId, $courseInfo); |
||
9479 | $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; |
||
9480 | $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access'); |
||
9481 | |||
9482 | // Condition for the session |
||
9483 | $condition_session = api_get_session_condition($sessionId, true, true, 'e.session_id'); |
||
9484 | $content = ''; |
||
9485 | $column = 0; |
||
9486 | if ($isAllowedToEdit) { |
||
9487 | $column = 1; |
||
9488 | } |
||
9489 | |||
9490 | $table = new SortableTableFromArrayConfig( |
||
9491 | [], |
||
9492 | $column, |
||
9493 | self::PAGINATION_ITEMS_PER_PAGE, |
||
9494 | 'exercises_cat_'.$categoryId |
||
9495 | ); |
||
9496 | |||
9497 | $limit = $table->per_page; |
||
9498 | $page = $table->page_nr; |
||
9499 | $from = $limit * ($page - 1); |
||
9500 | |||
9501 | $categoryCondition = ''; |
||
9502 | if (api_get_configuration_value('allow_exercise_categories')) { |
||
9503 | if (!empty($categoryId)) { |
||
9504 | $categoryCondition = " AND exercise_category_id = $categoryId "; |
||
9505 | } else { |
||
9506 | $categoryCondition = ' AND exercise_category_id IS NULL '; |
||
9507 | } |
||
9508 | } |
||
9509 | |||
9510 | $keywordCondition = ''; |
||
9511 | if (!empty($keyword)) { |
||
9512 | $keywordCondition = " AND title LIKE '%$keyword%' "; |
||
9513 | } |
||
9514 | |||
9515 | $filterByResultDisabledCondition = ''; |
||
9516 | $filterByResultDisabled = (int) $filterByResultDisabled; |
||
9517 | if (!empty($filterByResultDisabled)) { |
||
9518 | $filterByResultDisabledCondition = ' AND e.results_disabled = '.$filterByResultDisabled; |
||
9519 | } |
||
9520 | $filterByAttemptCondition = ''; |
||
9521 | $filterByAttempt = (int) $filterByAttempt; |
||
9522 | if (!empty($filterByAttempt)) { |
||
9523 | $filterByAttemptCondition = ' AND e.max_attempt = '.$filterByAttempt; |
||
9524 | } |
||
9525 | |||
9526 | // Only for administrators |
||
9527 | if ($isAllowedToEdit) { |
||
9528 | $total_sql = "SELECT count(iid) as count |
||
9529 | FROM $TBL_EXERCISES e |
||
9530 | WHERE |
||
9531 | c_id = $courseId AND |
||
9532 | active <> -1 |
||
9533 | $condition_session |
||
9534 | $categoryCondition |
||
9535 | $keywordCondition |
||
9536 | $filterByResultDisabledCondition |
||
9537 | $filterByAttemptCondition |
||
9538 | "; |
||
9539 | $sql = "SELECT * FROM $TBL_EXERCISES e |
||
9540 | WHERE |
||
9541 | c_id = $courseId AND |
||
9542 | active <> -1 |
||
9543 | $condition_session |
||
9544 | $categoryCondition |
||
9545 | $keywordCondition |
||
9546 | $filterByResultDisabledCondition |
||
9547 | $filterByAttemptCondition |
||
9548 | ORDER BY title |
||
9549 | LIMIT $from , $limit"; |
||
9550 | } else { |
||
9551 | // Only for students |
||
9552 | if (empty($sessionId)) { |
||
9553 | $condition_session = ' AND ( session_id = 0 OR session_id IS NULL) '; |
||
9554 | $total_sql = "SELECT count(DISTINCT(e.iid)) as count |
||
9555 | FROM $TBL_EXERCISES e |
||
9556 | WHERE |
||
9557 | e.c_id = $courseId AND |
||
9558 | e.active = 1 |
||
9559 | $condition_session |
||
9560 | $categoryCondition |
||
9561 | $keywordCondition |
||
9562 | "; |
||
9563 | |||
9564 | $sql = "SELECT DISTINCT e.* FROM $TBL_EXERCISES e |
||
9565 | WHERE |
||
9566 | e.c_id = $courseId AND |
||
9567 | e.active = 1 |
||
9568 | $condition_session |
||
9569 | $categoryCondition |
||
9570 | $keywordCondition |
||
9571 | ORDER BY title |
||
9572 | LIMIT $from , $limit"; |
||
9573 | } else { |
||
9574 | $invisibleSql = "SELECT e.iid |
||
9575 | FROM $TBL_EXERCISES e |
||
9576 | INNER JOIN $TBL_ITEM_PROPERTY ip |
||
9577 | ON (e.iid = ip.ref AND e.c_id = ip.c_id) |
||
9578 | WHERE |
||
9579 | ip.tool = '".TOOL_QUIZ."' AND |
||
9580 | e.c_id = $courseId AND |
||
9581 | e.active = 1 AND |
||
9582 | ip.visibility = 0 AND |
||
9583 | ip.session_id = $sessionId |
||
9584 | $categoryCondition |
||
9585 | $keywordCondition |
||
9586 | "; |
||
9587 | |||
9588 | $result = Database::query($invisibleSql); |
||
9589 | $result = Database::store_result($result); |
||
9590 | $hiddenFromSessionCondition = ' 1=1 '; |
||
9591 | if (!empty($result)) { |
||
9592 | $hiddenFromSession = implode("','", array_column($result, 'iid')); |
||
9593 | $hiddenFromSessionCondition = " e.iid not in ('$hiddenFromSession')"; |
||
9594 | } |
||
9595 | |||
9596 | $condition_session = " AND ( |
||
9597 | (e.session_id = $sessionId OR e.session_id = 0 OR e.session_id IS NULL) AND |
||
9598 | $hiddenFromSessionCondition |
||
9599 | ) |
||
9600 | "; |
||
9601 | |||
9602 | // Only for students |
||
9603 | $total_sql = "SELECT count(DISTINCT(e.iid)) as count |
||
9604 | FROM $TBL_EXERCISES e |
||
9605 | WHERE |
||
9606 | e.c_id = $courseId AND |
||
9607 | e.active = 1 |
||
9608 | $condition_session |
||
9609 | $categoryCondition |
||
9610 | $keywordCondition |
||
9611 | "; |
||
9612 | $sql = "SELECT DISTINCT e.* FROM $TBL_EXERCISES e |
||
9613 | WHERE |
||
9614 | e.c_id = $courseId AND |
||
9615 | e.active = 1 |
||
9616 | $condition_session |
||
9617 | $categoryCondition |
||
9618 | $keywordCondition |
||
9619 | ORDER BY title |
||
9620 | LIMIT $from , $limit"; |
||
9621 | } |
||
9622 | } |
||
9623 | |||
9624 | $result = Database::query($sql); |
||
9625 | $result_total = Database::query($total_sql); |
||
9626 | |||
9627 | $total_exercises = 0; |
||
9628 | if (Database::num_rows($result_total)) { |
||
9629 | $result_total = Database::fetch_array($result_total); |
||
9630 | $total_exercises = $result_total['count']; |
||
9631 | } |
||
9632 | |||
9633 | //get HotPotatoes files (active and inactive) |
||
9634 | if ($isAllowedToEdit) { |
||
9635 | $sql = "SELECT * FROM $TBL_DOCUMENT |
||
9636 | WHERE |
||
9637 | c_id = $courseId AND |
||
9638 | path LIKE '".Database::escape_string($uploadPath.'/%/%')."'"; |
||
9639 | $res = Database::query($sql); |
||
9640 | $hp_count = Database::num_rows($res); |
||
9641 | } else { |
||
9642 | $sql = "SELECT * FROM $TBL_DOCUMENT d |
||
9643 | INNER JOIN $TBL_ITEM_PROPERTY ip |
||
9644 | ON (d.iid = ip.ref) |
||
9645 | WHERE |
||
9646 | ip.tool = '".TOOL_DOCUMENT."' AND |
||
9647 | d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' AND |
||
9648 | ip.visibility = 1 AND |
||
9649 | d.c_id = $courseId AND |
||
9650 | ip.c_id = $courseId"; |
||
9651 | $res = Database::query($sql); |
||
9652 | $hp_count = Database::num_rows($res); |
||
9653 | } |
||
9654 | |||
9655 | $total = $total_exercises + $hp_count; |
||
9656 | $exerciseList = []; |
||
9657 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
9658 | $exerciseList[] = $row; |
||
9659 | } |
||
9660 | |||
9661 | if (!empty($exerciseList) && |
||
9662 | api_get_setting('exercise_invisible_in_session') === 'true' |
||
9663 | ) { |
||
9664 | if (!empty($sessionId)) { |
||
9665 | $changeDefaultVisibility = true; |
||
9666 | if (api_get_setting('configure_exercise_visibility_in_course') === 'true') { |
||
9667 | $changeDefaultVisibility = false; |
||
9668 | if (api_get_course_setting('exercise_invisible_in_session') == 1) { |
||
9669 | $changeDefaultVisibility = true; |
||
9670 | } |
||
9671 | } |
||
9672 | |||
9673 | if ($changeDefaultVisibility) { |
||
9674 | // Check exercise |
||
9675 | foreach ($exerciseList as $exercise) { |
||
9676 | if ($exercise['session_id'] == 0) { |
||
9677 | $visibilityInfo = api_get_item_property_info( |
||
9678 | $courseId, |
||
9679 | TOOL_QUIZ, |
||
9680 | $exercise['iid'], |
||
9681 | $sessionId |
||
9682 | ); |
||
9683 | |||
9684 | if (empty($visibilityInfo)) { |
||
9685 | // Create a record for this |
||
9686 | api_item_property_update( |
||
9687 | $courseInfo, |
||
9688 | TOOL_QUIZ, |
||
9689 | $exercise['iid'], |
||
9690 | 'invisible', |
||
9691 | api_get_user_id(), |
||
9692 | 0, |
||
9693 | null, |
||
9694 | '', |
||
9695 | '', |
||
9696 | $sessionId |
||
9697 | ); |
||
9698 | } |
||
9699 | } |
||
9700 | } |
||
9701 | } |
||
9702 | } |
||
9703 | } |
||
9704 | |||
9705 | $webPath = api_get_path(WEB_CODE_PATH); |
||
9706 | if (!empty($exerciseList)) { |
||
9707 | if ($origin !== 'learnpath') { |
||
9708 | $visibilitySetting = api_get_configuration_value('show_hidden_exercise_added_to_lp'); |
||
9709 | //avoid sending empty parameters |
||
9710 | $mylpid = empty($learnpath_id) ? '' : '&learnpath_id='.$learnpath_id; |
||
9711 | $mylpitemid = empty($learnpath_item_id) ? '' : '&learnpath_item_id='.$learnpath_item_id; |
||
9712 | foreach ($exerciseList as $row) { |
||
9713 | $currentRow = []; |
||
9714 | $my_exercise_id = $row['iid']; |
||
9715 | $attempt_text = ''; |
||
9716 | $actions = ''; |
||
9717 | $exercise = new Exercise($returnData ? $courseId : 0); |
||
9718 | $exercise->read($my_exercise_id, false); |
||
9719 | |||
9720 | if (empty($exercise->iid)) { |
||
9721 | continue; |
||
9722 | } |
||
9723 | |||
9724 | $locked = $exercise->is_gradebook_locked; |
||
9725 | // Validation when belongs to a session |
||
9726 | $session_img = api_get_session_image($row['session_id'], $userInfo['status']); |
||
9727 | |||
9728 | $time_limits = false; |
||
9729 | if (!empty($row['start_time']) || !empty($row['end_time'])) { |
||
9730 | $time_limits = true; |
||
9731 | } |
||
9732 | |||
9733 | $is_actived_time = false; |
||
9734 | if ($time_limits) { |
||
9735 | // check if start time |
||
9736 | $start_time = false; |
||
9737 | if (!empty($row['start_time'])) { |
||
9738 | $start_time = api_strtotime($row['start_time'], 'UTC'); |
||
9739 | } |
||
9740 | $end_time = false; |
||
9741 | if (!empty($row['end_time'])) { |
||
9742 | $end_time = api_strtotime($row['end_time'], 'UTC'); |
||
9743 | } |
||
9744 | $now = time(); |
||
9745 | |||
9746 | //If both "clocks" are enable |
||
9747 | if ($start_time && $end_time) { |
||
9748 | if ($now > $start_time && $end_time > $now) { |
||
9749 | $is_actived_time = true; |
||
9750 | } |
||
9751 | } else { |
||
9752 | //we check the start and end |
||
9753 | if ($start_time) { |
||
9754 | if ($now > $start_time) { |
||
9755 | $is_actived_time = true; |
||
9756 | } |
||
9757 | } |
||
9758 | if ($end_time) { |
||
9759 | if ($end_time > $now) { |
||
9760 | $is_actived_time = true; |
||
9761 | } |
||
9762 | } |
||
9763 | } |
||
9764 | } |
||
9765 | |||
9766 | // Blocking empty start times see BT#2800 |
||
9767 | global $_custom; |
||
9768 | if (isset($_custom['exercises_hidden_when_no_start_date']) && |
||
9769 | $_custom['exercises_hidden_when_no_start_date'] |
||
9770 | ) { |
||
9771 | if (empty($row['start_time'])) { |
||
9772 | $time_limits = true; |
||
9773 | $is_actived_time = false; |
||
9774 | } |
||
9775 | } |
||
9776 | |||
9777 | $cut_title = $exercise->getCutTitle(); |
||
9778 | $alt_title = ''; |
||
9779 | if ($cut_title != $row['title']) { |
||
9780 | $alt_title = ' title = "'.$exercise->getUnformattedTitle().'" '; |
||
9781 | } |
||
9782 | |||
9783 | // Teacher only |
||
9784 | if ($isAllowedToEdit) { |
||
9785 | $lp_blocked = null; |
||
9786 | if ($exercise->exercise_was_added_in_lp == true) { |
||
9787 | $lp_blocked = Display::div( |
||
9788 | get_lang('AddedToLPCannotBeAccessed'), |
||
9789 | ['class' => 'lp_content_type_label'] |
||
9790 | ); |
||
9791 | } |
||
9792 | |||
9793 | // Get visibility in base course |
||
9794 | $visibility = api_get_item_visibility( |
||
9795 | $courseInfo, |
||
9796 | TOOL_QUIZ, |
||
9797 | $my_exercise_id, |
||
9798 | 0 |
||
9799 | ); |
||
9800 | |||
9801 | if (!empty($sessionId)) { |
||
9802 | // If we are in a session, the test is invisible |
||
9803 | // in the base course, it is included in a LP |
||
9804 | // *and* the setting to show it is *not* |
||
9805 | // specifically set to true, then hide it. |
||
9806 | if ($visibility == 0) { |
||
9807 | if (!$visibilitySetting) { |
||
9808 | if ($exercise->exercise_was_added_in_lp == true) { |
||
9809 | continue; |
||
9810 | } |
||
9811 | } |
||
9812 | } |
||
9813 | |||
9814 | $visibility = api_get_item_visibility( |
||
9815 | $courseInfo, |
||
9816 | TOOL_QUIZ, |
||
9817 | $my_exercise_id, |
||
9818 | $sessionId |
||
9819 | ); |
||
9820 | } |
||
9821 | |||
9822 | if ($row['active'] == 0 || $visibility == 0) { |
||
9823 | $title = Display::tag('font', $cut_title, ['style' => 'color:grey']); |
||
9824 | } else { |
||
9825 | $title = $cut_title; |
||
9826 | } |
||
9827 | |||
9828 | $overviewUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php'; |
||
9829 | $url = Security::remove_XSS( |
||
9830 | '<a |
||
9831 | '.$alt_title.' |
||
9832 | id="tooltip_'.$row['iid'].'" |
||
9833 | href="'.$overviewUrl.'?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['iid'].'" |
||
9834 | > |
||
9835 | '.Display::return_icon('quiz.png', $row['title']).' |
||
9836 | '.$title.' |
||
9837 | </a>'); |
||
9838 | |||
9839 | if (ExerciseLib::isQuizEmbeddable($row)) { |
||
9840 | $embeddableIcon = Display::return_icon('om_integration.png', get_lang('ThisQuizCanBeEmbeddable')); |
||
9841 | $url .= Display::div($embeddableIcon, ['class' => 'pull-right']); |
||
9842 | } |
||
9843 | |||
9844 | $currentRow['title'] = $url.' '.$session_img.$lp_blocked; |
||
9845 | |||
9846 | if ($returnData) { |
||
9847 | $currentRow['title'] = $exercise->getUnformattedTitle(); |
||
9848 | } |
||
9849 | |||
9850 | // Count number exercise - teacher |
||
9851 | $sql = "SELECT count(*) count FROM $TBL_EXERCISE_QUESTION |
||
9852 | WHERE c_id = $courseId AND exercice_id = $my_exercise_id"; |
||
9853 | $sqlresult = Database::query($sql); |
||
9854 | $rowi = (int) Database::result($sqlresult, 0, 0); |
||
9855 | |||
9856 | if ($sessionId == $row['session_id']) { |
||
9857 | // Questions list |
||
9858 | $actions = Display::url( |
||
9859 | Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL), |
||
9860 | 'admin.php?'.api_get_cidreq().'&exerciseId='.$row['iid'] |
||
9861 | ); |
||
9862 | |||
9863 | // Test settings |
||
9864 | $settings = Display::url( |
||
9865 | Display::return_icon('settings.png', get_lang('Configure'), '', ICON_SIZE_SMALL), |
||
9866 | 'exercise_admin.php?'.api_get_cidreq().'&exerciseId='.$row['iid'] |
||
9867 | ); |
||
9868 | |||
9869 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
9870 | $settings = ''; |
||
9871 | } |
||
9872 | $actions .= $settings; |
||
9873 | |||
9874 | // Exercise results |
||
9875 | $resultsLink = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['iid'].'">'. |
||
9876 | Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>'; |
||
9877 | |||
9878 | if ($limitTeacherAccess) { |
||
9879 | if (api_is_platform_admin()) { |
||
9880 | $actions .= $resultsLink; |
||
9881 | } |
||
9882 | } else { |
||
9883 | // Exercise results |
||
9884 | $actions .= $resultsLink; |
||
9885 | } |
||
9886 | |||
9887 | // Auto launch |
||
9888 | if ($autoLaunchAvailable) { |
||
9889 | $autoLaunch = $exercise->getAutoLaunch(); |
||
9890 | if (empty($autoLaunch)) { |
||
9891 | $actions .= Display::url( |
||
9892 | Display::return_icon( |
||
9893 | 'launch_na.png', |
||
9894 | get_lang('Enable'), |
||
9895 | '', |
||
9896 | ICON_SIZE_SMALL |
||
9897 | ), |
||
9898 | 'exercise.php?'.api_get_cidreq().'&choice=enable_launch&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
9899 | ); |
||
9900 | } else { |
||
9901 | $actions .= Display::url( |
||
9902 | Display::return_icon( |
||
9903 | 'launch.png', |
||
9904 | get_lang('Disable'), |
||
9905 | '', |
||
9906 | ICON_SIZE_SMALL |
||
9907 | ), |
||
9908 | 'exercise.php?'.api_get_cidreq().'&choice=disable_launch&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
9909 | ); |
||
9910 | } |
||
9911 | } |
||
9912 | |||
9913 | // Export |
||
9914 | $actions .= Display::url( |
||
9915 | Display::return_icon('cd.png', get_lang('CopyExercise')), |
||
9916 | '', |
||
9917 | [ |
||
9918 | 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", |
||
9919 | 'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['iid'], |
||
9920 | ] |
||
9921 | ); |
||
9922 | |||
9923 | // Link to embed the quiz |
||
9924 | $urlEmbed = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&origin=iframe&exerciseId='.$row['iid']; |
||
9925 | $actions .= Display::url( |
||
9926 | Display::return_icon('new_link.png', get_lang('Embed')), |
||
9927 | '', |
||
9928 | [ |
||
9929 | 'class' => 'ajax', |
||
9930 | 'data-title' => get_lang('EmbedExerciseLink'), |
||
9931 | 'title' => get_lang('EmbedExerciseLink'), |
||
9932 | 'data-content' => get_lang('CopyUrlToIncludeInIframe').'<br>'.$urlEmbed.'<br><br>'.get_lang('CopyIframeCodeToIncludeExercise').'<br><textarea rows="5" cols="70"><iframe width="840" height="472" src="'.$urlEmbed.'" title="Chamilo exercise"></iframe></textarea>', |
||
9933 | 'href' => 'javascript:void(0);', |
||
9934 | ] |
||
9935 | ); |
||
9936 | |||
9937 | // Clean exercise |
||
9938 | $clean = ''; |
||
9939 | if (true === $allowClean) { |
||
9940 | if (false == $locked) { |
||
9941 | $clean = Display::url( |
||
9942 | Display::return_icon( |
||
9943 | 'clean.png', |
||
9944 | get_lang('CleanStudentResults'), |
||
9945 | '', |
||
9946 | ICON_SIZE_SMALL |
||
9947 | ), |
||
9948 | '', |
||
9949 | [ |
||
9950 | 'onclick' => "javascript:if(!confirm('".addslashes( |
||
9951 | api_htmlentities( |
||
9952 | get_lang('AreYouSureToDeleteResults'), |
||
9953 | ENT_QUOTES, |
||
9954 | $charset |
||
9955 | ) |
||
9956 | )." ".addslashes($row['title'])."?"."')) return false;", |
||
9957 | 'href' => 'exercise.php?'.api_get_cidreq( |
||
9958 | ).'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['iid'], |
||
9959 | ] |
||
9960 | ); |
||
9961 | } else { |
||
9962 | $clean = Display::return_icon( |
||
9963 | 'clean_na.png', |
||
9964 | get_lang('ResourceLockedByGradebook'), |
||
9965 | '', |
||
9966 | ICON_SIZE_SMALL |
||
9967 | ); |
||
9968 | } |
||
9969 | } |
||
9970 | |||
9971 | $actions .= $clean; |
||
9972 | |||
9973 | // Visible / invisible |
||
9974 | // Check if this exercise was added in a LP |
||
9975 | if ($exercise->exercise_was_added_in_lp == true) { |
||
9976 | $visibility = Display::return_icon( |
||
9977 | 'invisible.png', |
||
9978 | get_lang('AddedToLPCannotBeAccessed'), |
||
9979 | '', |
||
9980 | ICON_SIZE_SMALL |
||
9981 | ); |
||
9982 | } else { |
||
9983 | if ($row['active'] == 0 || $visibility == 0) { |
||
9984 | $visibility = Display::url( |
||
9985 | Display::return_icon( |
||
9986 | 'invisible.png', |
||
9987 | get_lang('Activate'), |
||
9988 | '', |
||
9989 | ICON_SIZE_SMALL |
||
9990 | ), |
||
9991 | 'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
9992 | ); |
||
9993 | } else { |
||
9994 | // else if not active |
||
9995 | $visibility = Display::url( |
||
9996 | Display::return_icon( |
||
9997 | 'visible.png', |
||
9998 | get_lang('Deactivate'), |
||
9999 | '', |
||
10000 | ICON_SIZE_SMALL |
||
10001 | ), |
||
10002 | 'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
10003 | ); |
||
10004 | } |
||
10005 | } |
||
10006 | |||
10007 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
10008 | $visibility = ''; |
||
10009 | } |
||
10010 | |||
10011 | $actions .= $visibility; |
||
10012 | |||
10013 | // Export qti ... |
||
10014 | $export = Display::url( |
||
10015 | Display::return_icon( |
||
10016 | 'export_qti2.png', |
||
10017 | 'IMS/QTI', |
||
10018 | '', |
||
10019 | ICON_SIZE_SMALL |
||
10020 | ), |
||
10021 | 'exercise.php?action=exportqti2&exerciseId='.$row['iid'].'&'.api_get_cidreq() |
||
10022 | ); |
||
10023 | |||
10024 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
10025 | $export = ''; |
||
10026 | } |
||
10027 | |||
10028 | $actions .= $export; |
||
10029 | } else { |
||
10030 | // not session |
||
10031 | $actions = Display::return_icon( |
||
10032 | 'edit_na.png', |
||
10033 | get_lang('ExerciseEditionNotAvailableInSession') |
||
10034 | ); |
||
10035 | |||
10036 | // Check if this exercise was added in a LP |
||
10037 | if ($exercise->exercise_was_added_in_lp == true) { |
||
10038 | $visibility = Display::return_icon( |
||
10039 | 'invisible.png', |
||
10040 | get_lang('AddedToLPCannotBeAccessed'), |
||
10041 | '', |
||
10042 | ICON_SIZE_SMALL |
||
10043 | ); |
||
10044 | } else { |
||
10045 | if ($row['active'] == 0 || $visibility == 0) { |
||
10046 | $visibility = Display::url( |
||
10047 | Display::return_icon( |
||
10048 | 'invisible.png', |
||
10049 | get_lang('Activate'), |
||
10050 | '', |
||
10051 | ICON_SIZE_SMALL |
||
10052 | ), |
||
10053 | 'exercise.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
10054 | ); |
||
10055 | } else { |
||
10056 | // else if not active |
||
10057 | $visibility = Display::url( |
||
10058 | Display::return_icon( |
||
10059 | 'visible.png', |
||
10060 | get_lang('Deactivate'), |
||
10061 | '', |
||
10062 | ICON_SIZE_SMALL |
||
10063 | ), |
||
10064 | 'exercise.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&exerciseId='.$row['iid'] |
||
10065 | ); |
||
10066 | } |
||
10067 | } |
||
10068 | |||
10069 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
10070 | $visibility = ''; |
||
10071 | } |
||
10072 | |||
10073 | $actions .= $visibility; |
||
10074 | $actions .= '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['iid'].'">'. |
||
10075 | Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>'; |
||
10076 | $actions .= Display::url( |
||
10077 | Display::return_icon('cd.gif', get_lang('CopyExercise')), |
||
10078 | '', |
||
10079 | [ |
||
10080 | 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", |
||
10081 | 'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['iid'], |
||
10082 | ] |
||
10083 | ); |
||
10084 | } |
||
10085 | |||
10086 | // Delete |
||
10087 | $delete = ''; |
||
10088 | if ($sessionId == $row['session_id']) { |
||
10089 | if ($locked == false) { |
||
10090 | $delete = Display::url( |
||
10091 | Display::return_icon( |
||
10092 | 'delete.png', |
||
10093 | get_lang('Delete'), |
||
10094 | '', |
||
10095 | ICON_SIZE_SMALL |
||
10096 | ), |
||
10097 | '', |
||
10098 | [ |
||
10099 | 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset))." ".addslashes($exercise->getUnformattedTitle())."?"."')) return false;", |
||
10100 | 'href' => 'exercise.php?'.api_get_cidreq().'&choice=delete&sec_token='.$token.'&exerciseId='.$row['iid'], |
||
10101 | ] |
||
10102 | ); |
||
10103 | } else { |
||
10104 | $delete = Display::return_icon( |
||
10105 | 'delete_na.png', |
||
10106 | get_lang('ResourceLockedByGradebook'), |
||
10107 | '', |
||
10108 | ICON_SIZE_SMALL |
||
10109 | ); |
||
10110 | } |
||
10111 | } |
||
10112 | |||
10113 | if ($limitTeacherAccess && !api_is_platform_admin()) { |
||
10114 | $delete = ''; |
||
10115 | } |
||
10116 | |||
10117 | if (!empty($minCategoriesInExercise)) { |
||
10118 | $cats = TestCategory::getListOfCategoriesForTest($exercise); |
||
10119 | if (!(count($cats) >= $minCategoriesInExercise)) { |
||
10120 | continue; |
||
10121 | } |
||
10122 | } |
||
10123 | |||
10124 | $actions .= $delete; |
||
10125 | $usersToRemind = self::getUsersInExercise( |
||
10126 | $row['iid'], |
||
10127 | $row['c_id'], |
||
10128 | $row['session_id'], |
||
10129 | true |
||
10130 | ); |
||
10131 | if ($usersToRemind > 0) { |
||
10132 | $actions .= Display::url( |
||
10133 | Display::return_icon('announce.png', get_lang('EmailNotifySubscription')), |
||
10134 | '', |
||
10135 | [ |
||
10136 | 'href' => '#!', |
||
10137 | 'onclick' => 'showUserToSendNotificacion(this)', |
||
10138 | 'data-link' => 'exercise.php?'.api_get_cidreq() |
||
10139 | .'&choice=send_reminder&sec_token='.$token.'&exerciseId='.$row['iid'], |
||
10140 | ] |
||
10141 | ); |
||
10142 | } |
||
10143 | |||
10144 | // Number of questions |
||
10145 | $random_label = null; |
||
10146 | if ($row['random'] > 0 || $row['random'] == -1) { |
||
10147 | // if random == -1 means use random questions with all questions |
||
10148 | $random_number_of_question = $row['random']; |
||
10149 | if ($random_number_of_question == -1) { |
||
10150 | $random_number_of_question = $rowi; |
||
10151 | } |
||
10152 | if ($row['random_by_category'] > 0) { |
||
10153 | $nbQuestionsTotal = TestCategory::getNumberOfQuestionRandomByCategory( |
||
10154 | $my_exercise_id, |
||
10155 | $random_number_of_question |
||
10156 | ); |
||
10157 | $number_of_questions = $nbQuestionsTotal.' '; |
||
10158 | $number_of_questions .= ($nbQuestionsTotal > 1) ? get_lang('QuestionsLowerCase') : get_lang('QuestionLowerCase'); |
||
10159 | $number_of_questions .= ' - '; |
||
10160 | $number_of_questions .= min( |
||
10161 | TestCategory::getNumberMaxQuestionByCat($my_exercise_id), $random_number_of_question |
||
10162 | ).' '.get_lang('QuestionByCategory'); |
||
10163 | } else { |
||
10164 | $random_label = ' ('.get_lang('Random').') '; |
||
10165 | $number_of_questions = $random_number_of_question.' '.$random_label.' / '.$rowi; |
||
10166 | // Bug if we set a random value bigger than the real number of questions |
||
10167 | if ($random_number_of_question > $rowi) { |
||
10168 | $number_of_questions = $rowi.' '.$random_label; |
||
10169 | } |
||
10170 | } |
||
10171 | } else { |
||
10172 | $number_of_questions = $rowi; |
||
10173 | } |
||
10174 | |||
10175 | $currentRow['count_questions'] = $number_of_questions; |
||
10176 | } else { |
||
10177 | // Student only. |
||
10178 | $visibility = api_get_item_visibility( |
||
10179 | $courseInfo, |
||
10180 | TOOL_QUIZ, |
||
10181 | $my_exercise_id, |
||
10182 | $sessionId |
||
10183 | ); |
||
10184 | |||
10185 | if ($visibility == 0) { |
||
10186 | continue; |
||
10187 | } |
||
10188 | |||
10189 | $url = '<a '.$alt_title.' href="overview.php?'.api_get_cidreq().$mylpid.$mylpitemid.'&exerciseId='.$row['iid'].'">'. |
||
10190 | $cut_title.'</a>'; |
||
10191 | |||
10192 | // Link of the exercise. |
||
10193 | $currentRow['title'] = $url.' '.$session_img; |
||
10194 | |||
10195 | if ($returnData) { |
||
10196 | $currentRow['title'] = $exercise->getUnformattedTitle(); |
||
10197 | } |
||
10198 | |||
10199 | // This query might be improved later on by ordering by the new "tms" field rather than by exe_id |
||
10200 | // Don't remove this marker: note-query-exe-results |
||
10201 | $sql = "SELECT * FROM $TBL_TRACK_EXERCISES |
||
10202 | WHERE |
||
10203 | exe_exo_id = ".$row['iid']." AND |
||
10204 | exe_user_id = $userId AND |
||
10205 | c_id = ".api_get_course_int_id()." AND |
||
10206 | status <> 'incomplete' AND |
||
10207 | orig_lp_id = 0 AND |
||
10208 | orig_lp_item_id = 0 AND |
||
10209 | session_id = '".api_get_session_id()."' |
||
10210 | ORDER BY exe_id DESC"; |
||
10211 | |||
10212 | $qryres = Database::query($sql); |
||
10213 | $num = Database::num_rows($qryres); |
||
10214 | |||
10215 | // Hide the results. |
||
10216 | $my_result_disabled = $row['results_disabled']; |
||
10217 | |||
10218 | $attempt_text = '-'; |
||
10219 | // Time limits are on |
||
10220 | if ($time_limits) { |
||
10221 | // Exam is ready to be taken |
||
10222 | if ($is_actived_time) { |
||
10223 | // Show results |
||
10224 | if ( |
||
10225 | in_array( |
||
10226 | $my_result_disabled, |
||
10227 | [ |
||
10228 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS, |
||
10229 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, |
||
10230 | RESULT_DISABLE_SHOW_SCORE_ONLY, |
||
10231 | RESULT_DISABLE_RANKING, |
||
10232 | ] |
||
10233 | ) |
||
10234 | ) { |
||
10235 | // More than one attempt |
||
10236 | if ($num > 0) { |
||
10237 | $row_track = Database::fetch_array($qryres); |
||
10238 | $attempt_text = get_lang('LatestAttempt').' : '; |
||
10239 | $attempt_text .= ExerciseLib::show_score( |
||
10240 | $row_track['exe_result'], |
||
10241 | $row_track['exe_weighting'] |
||
10242 | ); |
||
10243 | } else { |
||
10244 | //No attempts |
||
10245 | $attempt_text = get_lang('NotAttempted'); |
||
10246 | } |
||
10247 | } else { |
||
10248 | $attempt_text = '-'; |
||
10249 | } |
||
10250 | } else { |
||
10251 | // Quiz not ready due to time limits |
||
10252 | //@todo use the is_visible function |
||
10253 | if (!empty($row['start_time']) && !empty($row['end_time'])) { |
||
10254 | $today = time(); |
||
10255 | $start_time = api_strtotime($row['start_time'], 'UTC'); |
||
10256 | $end_time = api_strtotime($row['end_time'], 'UTC'); |
||
10257 | if ($today < $start_time) { |
||
10258 | $attempt_text = sprintf( |
||
10259 | get_lang('ExerciseWillBeActivatedFromXToY'), |
||
10260 | api_convert_and_format_date($row['start_time']), |
||
10261 | api_convert_and_format_date($row['end_time']) |
||
10262 | ); |
||
10263 | } else { |
||
10264 | if ($today > $end_time) { |
||
10265 | $attempt_text = sprintf( |
||
10266 | get_lang('ExerciseWasActivatedFromXToY'), |
||
10267 | api_convert_and_format_date($row['start_time']), |
||
10268 | api_convert_and_format_date($row['end_time']) |
||
10269 | ); |
||
10270 | } |
||
10271 | } |
||
10272 | } else { |
||
10273 | //$attempt_text = get_lang('ExamNotAvailableAtThisTime'); |
||
10274 | if (!empty($row['start_time'])) { |
||
10275 | $attempt_text = sprintf( |
||
10276 | get_lang('ExerciseAvailableFromX'), |
||
10277 | api_convert_and_format_date($row['start_time']) |
||
10278 | ); |
||
10279 | } |
||
10280 | if (!empty($row['end_time'])) { |
||
10281 | $attempt_text = sprintf( |
||
10282 | get_lang('ExerciseAvailableUntilX'), |
||
10283 | api_convert_and_format_date($row['end_time']) |
||
10284 | ); |
||
10285 | } |
||
10286 | } |
||
10287 | } |
||
10288 | } else { |
||
10289 | // Normal behaviour. |
||
10290 | // Show results. |
||
10291 | if ( |
||
10292 | in_array( |
||
10293 | $my_result_disabled, |
||
10294 | [ |
||
10295 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS, |
||
10296 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, |
||
10297 | RESULT_DISABLE_SHOW_SCORE_ONLY, |
||
10298 | RESULT_DISABLE_RANKING, |
||
10299 | RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK, |
||
10300 | ] |
||
10301 | ) |
||
10302 | ) { |
||
10303 | if ($num > 0) { |
||
10304 | $row_track = Database::fetch_array($qryres); |
||
10305 | $attempt_text = get_lang('LatestAttempt').' : '; |
||
10306 | $attempt_text .= ExerciseLib::show_score( |
||
10307 | $row_track['exe_result'], |
||
10308 | $row_track['exe_weighting'] |
||
10309 | ); |
||
10310 | } else { |
||
10311 | $attempt_text = get_lang('NotAttempted'); |
||
10312 | } |
||
10313 | } |
||
10314 | } |
||
10315 | |||
10316 | if ($returnData) { |
||
10317 | $attempt_text = $num; |
||
10318 | } |
||
10319 | } |
||
10320 | |||
10321 | $currentRow['attempt'] = $attempt_text; |
||
10322 | |||
10323 | if ($isAllowedToEdit) { |
||
10324 | $additionalActions = ExerciseLib::getAdditionalTeacherActions($row['iid']); |
||
10325 | |||
10326 | if (!empty($additionalActions)) { |
||
10327 | $actions .= $additionalActions.PHP_EOL; |
||
10328 | } |
||
10329 | |||
10330 | // Replace with custom actions. |
||
10331 | if (!empty($myActions) && is_callable($myActions)) { |
||
10332 | $actions = $myActions($row); |
||
10333 | } |
||
10334 | |||
10335 | $rowTitle = $currentRow['title']; |
||
10336 | $currentRow = [ |
||
10337 | $row['iid'], |
||
10338 | $rowTitle, |
||
10339 | $currentRow['count_questions'], |
||
10340 | $actions, |
||
10341 | ]; |
||
10342 | |||
10343 | if ($returnData) { |
||
10344 | $currentRow['id'] = $exercise->iid; |
||
10345 | $currentRow['url'] = $webPath.'exercise/overview.php?' |
||
10346 | .api_get_cidreq_params($courseInfo['code'], $sessionId).'&' |
||
10347 | ."$mylpid$mylpitemid&exerciseId={$row['iid']}"; |
||
10348 | $currentRow['name'] = $rowTitle; |
||
10349 | } |
||
10350 | } else { |
||
10351 | $rowTitle = $currentRow['title']; |
||
10352 | $currentRow = [ |
||
10353 | $rowTitle, |
||
10354 | $currentRow['attempt'], |
||
10355 | ]; |
||
10356 | |||
10357 | if ($isDrhOfCourse) { |
||
10358 | $currentRow[] = '<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$row['iid'].'">'. |
||
10359 | Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>'; |
||
10360 | } |
||
10361 | |||
10362 | if ($returnData) { |
||
10363 | $currentRow['id'] = $exercise->iid; |
||
10364 | $currentRow['url'] = $webPath.'exercise/overview.php?' |
||
10365 | .api_get_cidreq_params($courseInfo['code'], $sessionId).'&' |
||
10366 | ."$mylpid$mylpitemid&exerciseId={$row['iid']}"; |
||
10367 | $currentRow['name'] = $rowTitle; |
||
10368 | } |
||
10369 | } |
||
10370 | |||
10371 | $tableRows[] = $currentRow; |
||
10372 | } |
||
10373 | } |
||
10374 | } |
||
10375 | |||
10376 | // end exercise list |
||
10377 | // Hotpotatoes results |
||
10378 | if ($isAllowedToEdit) { |
||
10379 | $sql = "SELECT d.iid, d.path as path, d.comment as comment |
||
10380 | FROM $TBL_DOCUMENT d |
||
10381 | WHERE |
||
10382 | d.c_id = $courseId AND |
||
10383 | (d.path LIKE '%htm%') AND |
||
10384 | d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' |
||
10385 | LIMIT $from , $limit"; // only .htm or .html files listed |
||
10386 | } else { |
||
10387 | $sql = "SELECT d.iid, d.path as path, d.comment as comment |
||
10388 | FROM $TBL_DOCUMENT d |
||
10389 | WHERE |
||
10390 | d.c_id = $courseId AND |
||
10391 | (d.path LIKE '%htm%') AND |
||
10392 | d.path LIKE '".Database::escape_string($uploadPath.'/%/%')."' |
||
10393 | LIMIT $from , $limit"; |
||
10394 | } |
||
10395 | |||
10396 | $result = Database::query($sql); |
||
10397 | $attributes = []; |
||
10398 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
10399 | $attributes[$row['iid']] = $row; |
||
10400 | } |
||
10401 | |||
10402 | $nbrActiveTests = 0; |
||
10403 | if (!empty($attributes)) { |
||
10404 | foreach ($attributes as $item) { |
||
10405 | $id = $item['iid']; |
||
10406 | $path = $item['path']; |
||
10407 | |||
10408 | $title = GetQuizName($path, $documentPath); |
||
10409 | if ($title == '') { |
||
10410 | $title = basename($path); |
||
10411 | } |
||
10412 | |||
10413 | // prof only |
||
10414 | if ($isAllowedToEdit) { |
||
10415 | $visibility = api_get_item_visibility( |
||
10416 | ['real_id' => $courseId], |
||
10417 | TOOL_DOCUMENT, |
||
10418 | $id, |
||
10419 | 0 |
||
10420 | ); |
||
10421 | |||
10422 | if (!empty($sessionId)) { |
||
10423 | if (0 == $visibility) { |
||
10424 | continue; |
||
10425 | } |
||
10426 | |||
10427 | $visibility = api_get_item_visibility( |
||
10428 | ['real_id' => $courseId], |
||
10429 | TOOL_DOCUMENT, |
||
10430 | $id, |
||
10431 | $sessionId |
||
10432 | ); |
||
10433 | } |
||
10434 | |||
10435 | $title = |
||
10436 | implode(PHP_EOL, [ |
||
10437 | Display::return_icon('hotpotatoes_s.png', 'HotPotatoes'), |
||
10438 | Display::url( |
||
10439 | $title, |
||
10440 | 'showinframes.php?'.api_get_cidreq().'&'.http_build_query([ |
||
10441 | 'file' => $path, |
||
10442 | 'uid' => $userId, |
||
10443 | ]), |
||
10444 | ['class' => $visibility == 0 ? 'text-muted' : null] |
||
10445 | ), |
||
10446 | ]); |
||
10447 | |||
10448 | $actions = Display::url( |
||
10449 | Display::return_icon( |
||
10450 | 'edit.png', |
||
10451 | get_lang('Edit'), |
||
10452 | '', |
||
10453 | ICON_SIZE_SMALL |
||
10454 | ), |
||
10455 | 'adminhp.php?'.api_get_cidreq().'&hotpotatoesName='.$path |
||
10456 | ); |
||
10457 | |||
10458 | $actions .= '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'. |
||
10459 | Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL). |
||
10460 | '</a>'; |
||
10461 | |||
10462 | // if active |
||
10463 | if ($visibility != 0) { |
||
10464 | $nbrActiveTests = $nbrActiveTests + 1; |
||
10465 | $actions .= ' <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=disable&file='.$path.'">'. |
||
10466 | Display::return_icon('visible.png', get_lang('Deactivate'), '', ICON_SIZE_SMALL).'</a>'; |
||
10467 | } else { // else if not active |
||
10468 | $actions .= ' <a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=enable&file='.$path.'">'. |
||
10469 | Display::return_icon('invisible.png', get_lang('Activate'), '', ICON_SIZE_SMALL).'</a>'; |
||
10470 | } |
||
10471 | $actions .= '<a href="'.$exercisePath.'?'.api_get_cidreq().'&hpchoice=delete&file='.$path.'" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('AreYouSureToDeleteJS'), ENT_QUOTES, $charset)).'\')) return false;">'. |
||
10472 | Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL).'</a>'; |
||
10473 | |||
10474 | $currentRow = [ |
||
10475 | '', |
||
10476 | $title, |
||
10477 | '', |
||
10478 | $actions, |
||
10479 | ]; |
||
10480 | } else { |
||
10481 | $visibility = api_get_item_visibility( |
||
10482 | ['real_id' => $courseId], |
||
10483 | TOOL_DOCUMENT, |
||
10484 | $id, |
||
10485 | $sessionId |
||
10486 | ); |
||
10487 | |||
10488 | if (0 == $visibility) { |
||
10489 | continue; |
||
10490 | } |
||
10491 | |||
10492 | // Student only |
||
10493 | $attempt = ExerciseLib::getLatestHotPotatoResult( |
||
10494 | $path, |
||
10495 | $userId, |
||
10496 | api_get_course_int_id(), |
||
10497 | api_get_session_id() |
||
10498 | ); |
||
10499 | |||
10500 | $nbrActiveTests = $nbrActiveTests + 1; |
||
10501 | $title = Display::url( |
||
10502 | $title, |
||
10503 | 'showinframes.php?'.api_get_cidreq().'&'.http_build_query( |
||
10504 | [ |
||
10505 | 'file' => $path, |
||
10506 | 'cid' => api_get_course_id(), |
||
10507 | 'uid' => $userId, |
||
10508 | ] |
||
10509 | ) |
||
10510 | ); |
||
10511 | |||
10512 | if (!empty($attempt)) { |
||
10513 | $actions = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'&filter_by_user='.$userId.'">'.Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>'; |
||
10514 | $attemptText = get_lang('LatestAttempt').' : '; |
||
10515 | $attemptText .= ExerciseLib::show_score( |
||
10516 | $attempt['exe_result'], |
||
10517 | $attempt['exe_weighting'] |
||
10518 | ).' '; |
||
10519 | $attemptText .= $actions; |
||
10520 | } else { |
||
10521 | // No attempts. |
||
10522 | $attemptText = get_lang('NotAttempted').' '; |
||
10523 | } |
||
10524 | |||
10525 | $currentRow = [ |
||
10526 | $title, |
||
10527 | $attemptText, |
||
10528 | ]; |
||
10529 | |||
10530 | if ($isDrhOfCourse) { |
||
10531 | $currentRow[] = '<a href="hotpotatoes_exercise_report.php?'.api_get_cidreq().'&path='.$path.'">'. |
||
10532 | Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>'; |
||
10533 | } |
||
10534 | } |
||
10535 | |||
10536 | $tableRows[] = $currentRow; |
||
10537 | } |
||
10538 | } |
||
10539 | |||
10540 | if ($returnData) { |
||
10541 | return $tableRows; |
||
10542 | } |
||
10543 | |||
10544 | if (empty($tableRows) && empty($categoryId)) { |
||
10545 | if ($isAllowedToEdit && $origin !== 'learnpath') { |
||
10546 | $content .= '<div id="no-data-view">'; |
||
10547 | $content .= '<h3>'.get_lang('Quiz').'</h3>'; |
||
10548 | $content .= Display::return_icon('quiz.png', '', [], 64); |
||
10549 | $content .= '<div class="controls">'; |
||
10550 | $content .= Display::url( |
||
10551 | '<em class="fa fa-plus"></em> '.get_lang('NewEx'), |
||
10552 | 'exercise_admin.php?'.api_get_cidreq(), |
||
10553 | ['class' => 'btn btn-primary'] |
||
10554 | ); |
||
10555 | $content .= '</div>'; |
||
10556 | $content .= '</div>'; |
||
10557 | } |
||
10558 | } else { |
||
10559 | if (empty($tableRows)) { |
||
10560 | return ''; |
||
10561 | } |
||
10562 | |||
10563 | if (true === api_get_configuration_value('allow_exercise_categories') && empty($categoryId)) { |
||
10564 | echo Display::page_subheader(get_lang('NoCategory')); |
||
10565 | } |
||
10566 | |||
10567 | $table->setTableData($tableRows); |
||
10568 | $table->setTotalNumberOfItems($total); |
||
10569 | $table->set_additional_parameters([ |
||
10570 | 'cidReq' => api_get_course_id(), |
||
10571 | 'id_session' => api_get_session_id(), |
||
10572 | 'category_id' => $categoryId, |
||
10573 | ]); |
||
10574 | |||
10575 | if ($isAllowedToEdit) { |
||
10576 | $formActions = []; |
||
10577 | $formActions['visible'] = get_lang('Activate'); |
||
10578 | $formActions['invisible'] = get_lang('Deactivate'); |
||
10579 | $formActions['delete'] = get_lang('Delete'); |
||
10580 | $table->set_form_actions($formActions); |
||
10581 | } |
||
10582 | |||
10583 | $i = 0; |
||
10584 | if ($isAllowedToEdit) { |
||
10585 | $table->set_header($i++, '', false, 'width="18px"'); |
||
10586 | } |
||
10587 | $table->set_header($i++, get_lang('ExerciseName'), false); |
||
10588 | |||
10589 | if ($isAllowedToEdit) { |
||
10590 | $table->set_header($i++, get_lang('QuantityQuestions'), false); |
||
10591 | $table->set_header($i++, get_lang('Actions'), false); |
||
10592 | } else { |
||
10593 | $table->set_header($i++, get_lang('Status'), false); |
||
10594 | if ($isDrhOfCourse) { |
||
10595 | $table->set_header($i++, get_lang('Actions'), false); |
||
10596 | } |
||
10597 | } |
||
10598 | |||
10599 | if ($returnTable) { |
||
10600 | return $table; |
||
10601 | } |
||
10602 | |||
10603 | $content .= $table->return_table(); |
||
10604 | } |
||
10605 | |||
10606 | return $content; |
||
10607 | } |
||
10608 | |||
10609 | /** |
||
10610 | * @return int value in minutes |
||
10611 | */ |
||
10612 | public function getResultAccess() |
||
10613 | { |
||
10614 | $extraFieldValue = new ExtraFieldValue('exercise'); |
||
10615 | $value = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
10616 | $this->iid, |
||
10617 | 'results_available_for_x_minutes' |
||
10618 | ); |
||
10619 | |||
10620 | if (!empty($value) && isset($value['value'])) { |
||
10621 | return (int) $value['value']; |
||
10622 | } |
||
10623 | |||
10624 | return 0; |
||
10625 | } |
||
10626 | |||
10627 | /** |
||
10628 | * @param array $exerciseResultInfo |
||
10629 | * |
||
10630 | * @return bool |
||
10631 | */ |
||
10632 | public function getResultAccessTimeDiff($exerciseResultInfo) |
||
10633 | { |
||
10634 | $value = $this->getResultAccess(); |
||
10635 | if (!empty($value)) { |
||
10636 | $endDate = new DateTime($exerciseResultInfo['exe_date'], new DateTimeZone('UTC')); |
||
10637 | $endDate->add(new DateInterval('PT'.$value.'M')); |
||
10638 | $now = time(); |
||
10639 | if ($endDate->getTimestamp() > $now) { |
||
10640 | return (int) $endDate->getTimestamp() - $now; |
||
10641 | } |
||
10642 | } |
||
10643 | |||
10644 | return 0; |
||
10645 | } |
||
10646 | |||
10647 | /** |
||
10648 | * @param array $exerciseResultInfo |
||
10649 | * |
||
10650 | * @return bool |
||
10651 | */ |
||
10652 | public function hasResultsAccess($exerciseResultInfo) |
||
10653 | { |
||
10654 | $diff = $this->getResultAccessTimeDiff($exerciseResultInfo); |
||
10655 | if (0 === $diff) { |
||
10656 | return false; |
||
10657 | } |
||
10658 | |||
10659 | return true; |
||
10660 | } |
||
10661 | |||
10662 | /** |
||
10663 | * @return int |
||
10664 | */ |
||
10665 | public function getResultsAccess() |
||
10666 | { |
||
10667 | $extraFieldValue = new ExtraFieldValue('exercise'); |
||
10668 | $value = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
10669 | $this->iid, |
||
10670 | 'results_available_for_x_minutes' |
||
10671 | ); |
||
10672 | if (!empty($value)) { |
||
10673 | return (int) $value; |
||
10674 | } |
||
10675 | |||
10676 | return 0; |
||
10677 | } |
||
10678 | |||
10679 | /** |
||
10680 | * Get results of a delineation type question. |
||
10681 | * Params described here are only non-typed params. |
||
10682 | * |
||
10683 | * @param int $questionId |
||
10684 | * @param bool $show_results |
||
10685 | * @param array $question_result |
||
10686 | */ |
||
10687 | public function getDelineationResult(Question $objQuestionTmp, $questionId, $show_results, $question_result) |
||
10688 | { |
||
10689 | $id = $objQuestionTmp->iid; |
||
10690 | $questionId = (int) $questionId; |
||
10691 | |||
10692 | $final_overlap = $question_result['extra']['final_overlap']; |
||
10693 | $final_missing = $question_result['extra']['final_missing']; |
||
10694 | $final_excess = $question_result['extra']['final_excess']; |
||
10695 | |||
10696 | $overlap_color = $question_result['extra']['overlap_color']; |
||
10697 | $missing_color = $question_result['extra']['missing_color']; |
||
10698 | $excess_color = $question_result['extra']['excess_color']; |
||
10699 | |||
10700 | $threadhold1 = $question_result['extra']['threadhold1']; |
||
10701 | $threadhold2 = $question_result['extra']['threadhold2']; |
||
10702 | $threadhold3 = $question_result['extra']['threadhold3']; |
||
10703 | |||
10704 | if ($show_results) { |
||
10705 | if ($overlap_color) { |
||
10706 | $overlap_color = 'green'; |
||
10707 | } else { |
||
10708 | $overlap_color = 'red'; |
||
10709 | } |
||
10710 | |||
10711 | if ($missing_color) { |
||
10712 | $missing_color = 'green'; |
||
10713 | } else { |
||
10714 | $missing_color = 'red'; |
||
10715 | } |
||
10716 | if ($excess_color) { |
||
10717 | $excess_color = 'green'; |
||
10718 | } else { |
||
10719 | $excess_color = 'red'; |
||
10720 | } |
||
10721 | |||
10722 | if (!is_numeric($final_overlap)) { |
||
10723 | $final_overlap = 0; |
||
10724 | } |
||
10725 | |||
10726 | if (!is_numeric($final_missing)) { |
||
10727 | $final_missing = 0; |
||
10728 | } |
||
10729 | if (!is_numeric($final_excess)) { |
||
10730 | $final_excess = 0; |
||
10731 | } |
||
10732 | |||
10733 | if ($final_excess > 100) { |
||
10734 | $final_excess = 100; |
||
10735 | } |
||
10736 | |||
10737 | $table_resume = ' |
||
10738 | <table class="table table-hover table-striped data_table"> |
||
10739 | <tr class="row_odd" > |
||
10740 | <td> </td> |
||
10741 | <td><b>'.get_lang('Requirements').'</b></td> |
||
10742 | <td><b>'.get_lang('YourAnswer').'</b></td> |
||
10743 | </tr> |
||
10744 | <tr class="row_even"> |
||
10745 | <td><b>'.get_lang('Overlap').'</b></td> |
||
10746 | <td>'.get_lang('Min').' '.$threadhold1.'</td> |
||
10747 | <td> |
||
10748 | <div style="color:'.$overlap_color.'"> |
||
10749 | '.(($final_overlap < 0) ? 0 : intval($final_overlap)).' |
||
10750 | </div> |
||
10751 | </td> |
||
10752 | </tr> |
||
10753 | <tr> |
||
10754 | <td><b>'.get_lang('Excess').'</b></td> |
||
10755 | <td>'.get_lang('Max').' '.$threadhold2.'</td> |
||
10756 | <td> |
||
10757 | <div style="color:'.$excess_color.'"> |
||
10758 | '.(($final_excess < 0) ? 0 : intval($final_excess)).' |
||
10759 | </div> |
||
10760 | </td> |
||
10761 | </tr> |
||
10762 | <tr class="row_even"> |
||
10763 | <td><b>'.get_lang('Missing').'</b></td> |
||
10764 | <td>'.get_lang('Max').' '.$threadhold3.'</td> |
||
10765 | <td> |
||
10766 | <div style="color:'.$missing_color.'"> |
||
10767 | '.(($final_missing < 0) ? 0 : intval($final_missing)).' |
||
10768 | </div> |
||
10769 | </td> |
||
10770 | </tr> |
||
10771 | </table> |
||
10772 | '; |
||
10773 | |||
10774 | $answerType = $objQuestionTmp->selectType(); |
||
10775 | if ($answerType != HOT_SPOT_DELINEATION) { |
||
10776 | $item_list = explode('@@', $destination); |
||
10777 | $try = $item_list[0]; |
||
10778 | $lp = $item_list[1]; |
||
10779 | $destinationid = $item_list[2]; |
||
10780 | $url = $item_list[3]; |
||
10781 | $table_resume = ''; |
||
10782 | } else { |
||
10783 | if ($next == 0) { |
||
10784 | $try = $try_hotspot; |
||
10785 | $lp = $lp_hotspot; |
||
10786 | $destinationid = $select_question_hotspot; |
||
10787 | $url = $url_hotspot; |
||
10788 | } else { |
||
10789 | //show if no error |
||
10790 | $comment = $answerComment = $objAnswerTmp->selectComment($nbrAnswers); |
||
10791 | $answerDestination = $objAnswerTmp->selectDestination($nbrAnswers); |
||
10792 | } |
||
10793 | } |
||
10794 | |||
10795 | echo '<h1><div style="color:#333;">'.get_lang('Feedback').'</div></h1>'; |
||
10796 | if ($answerType == HOT_SPOT_DELINEATION) { |
||
10797 | if ($organs_at_risk_hit > 0) { |
||
10798 | $message = '<br />'.get_lang('ResultIs').' <b>'.$result_comment.'</b><br />'; |
||
10799 | $message .= '<p style="color:#DC0A0A;"><b>'.get_lang('OARHit').'</b></p>'; |
||
10800 | } else { |
||
10801 | $message = '<p>'.get_lang('YourDelineation').'</p>'; |
||
10802 | $message .= $table_resume; |
||
10803 | $message .= '<br />'.get_lang('ResultIs').' <b>'.$result_comment.'</b><br />'; |
||
10804 | } |
||
10805 | $message .= '<p>'.$comment.'</p>'; |
||
10806 | echo $message; |
||
10807 | } else { |
||
10808 | echo '<p>'.$comment.'</p>'; |
||
10809 | } |
||
10810 | |||
10811 | // Showing the score |
||
10812 | /*$queryfree = "SELECT marks FROM $TBL_TRACK_ATTEMPT |
||
10813 | WHERE exe_id = $id AND question_id = $questionId"; |
||
10814 | $resfree = Database::query($queryfree); |
||
10815 | $questionScore = Database::result($resfree, 0, 'marks'); |
||
10816 | $totalScore += $questionScore;*/ |
||
10817 | $relPath = api_get_path(REL_CODE_PATH); |
||
10818 | echo '</table></td></tr>'; |
||
10819 | echo " |
||
10820 | <tr> |
||
10821 | <td colspan=\"2\"> |
||
10822 | <div id=\"hotspot-solution\"></div> |
||
10823 | <script> |
||
10824 | $(function() { |
||
10825 | new HotspotQuestion({ |
||
10826 | questionId: $questionId, |
||
10827 | exerciseId: {$this->iid}, |
||
10828 | exeId: $id, |
||
10829 | selector: '#hotspot-solution', |
||
10830 | for: 'solution', |
||
10831 | relPath: '$relPath' |
||
10832 | }); |
||
10833 | }); |
||
10834 | </script> |
||
10835 | </td> |
||
10836 | </tr> |
||
10837 | </table> |
||
10838 | "; |
||
10839 | } |
||
10840 | } |
||
10841 | |||
10842 | /** |
||
10843 | * Clean exercise session variables. |
||
10844 | */ |
||
10845 | public static function cleanSessionVariables() |
||
10846 | { |
||
10847 | Session::erase('objExercise'); |
||
10848 | Session::erase('exe_id'); |
||
10849 | Session::erase('calculatedAnswerId'); |
||
10850 | Session::erase('duration_time_previous'); |
||
10851 | Session::erase('duration_time'); |
||
10852 | Session::erase('objQuestion'); |
||
10853 | Session::erase('objAnswer'); |
||
10854 | Session::erase('questionList'); |
||
10855 | Session::erase('categoryList'); |
||
10856 | Session::erase('exerciseResult'); |
||
10857 | Session::erase('firstTime'); |
||
10858 | Session::erase('time_per_question'); |
||
10859 | Session::erase('question_start'); |
||
10860 | Session::erase('exerciseResultCoordinates'); |
||
10861 | Session::erase('hotspot_coord'); |
||
10862 | Session::erase('hotspot_dest'); |
||
10863 | Session::erase('hotspot_delineation_result'); |
||
10864 | } |
||
10865 | |||
10866 | /** |
||
10867 | * Get the first LP found matching the session ID. |
||
10868 | * |
||
10869 | * @param int $sessionId |
||
10870 | * |
||
10871 | * @return array |
||
10872 | */ |
||
10873 | public function getLpBySession($sessionId) |
||
10874 | { |
||
10875 | if (!empty($this->lpList)) { |
||
10876 | $sessionId = (int) $sessionId; |
||
10877 | |||
10878 | foreach ($this->lpList as $lp) { |
||
10879 | if ((int) $lp['session_id'] == $sessionId) { |
||
10880 | return $lp; |
||
10881 | } |
||
10882 | } |
||
10883 | |||
10884 | return current($this->lpList); |
||
10885 | } |
||
10886 | |||
10887 | return [ |
||
10888 | 'lp_id' => 0, |
||
10889 | 'max_score' => 0, |
||
10890 | 'session_id' => 0, |
||
10891 | ]; |
||
10892 | } |
||
10893 | |||
10894 | public static function saveExerciseInLp($safe_item_id, $safe_exe_id) |
||
10895 | { |
||
10896 | $lp = Session::read('oLP'); |
||
10897 | |||
10898 | $safe_exe_id = (int) $safe_exe_id; |
||
10899 | $safe_item_id = (int) $safe_item_id; |
||
10900 | |||
10901 | if (empty($lp) || empty($safe_exe_id) || empty($safe_item_id)) { |
||
10902 | return false; |
||
10903 | } |
||
10904 | |||
10905 | $viewId = $lp->get_view_id(); |
||
10906 | $course_id = api_get_course_int_id(); |
||
10907 | $userId = (int) api_get_user_id(); |
||
10908 | $viewId = (int) $viewId; |
||
10909 | |||
10910 | $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
10911 | $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW); |
||
10912 | $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM); |
||
10913 | |||
10914 | $sql = "SELECT start_date, exe_date, exe_result, exe_weighting, exe_exo_id, exe_duration |
||
10915 | FROM $TBL_TRACK_EXERCICES |
||
10916 | WHERE exe_id = $safe_exe_id AND exe_user_id = $userId"; |
||
10917 | $res = Database::query($sql); |
||
10918 | $row_dates = Database::fetch_array($res); |
||
10919 | |||
10920 | if (empty($row_dates)) { |
||
10921 | return false; |
||
10922 | } |
||
10923 | |||
10924 | $duration = (int) $row_dates['exe_duration']; |
||
10925 | $score = (float) $row_dates['exe_result']; |
||
10926 | $max_score = (float) $row_dates['exe_weighting']; |
||
10927 | |||
10928 | $sql = "UPDATE $TBL_LP_ITEM SET |
||
10929 | max_score = '$max_score' |
||
10930 | WHERE iid = $safe_item_id"; |
||
10931 | Database::query($sql); |
||
10932 | |||
10933 | $sql = "SELECT iid FROM $TBL_LP_ITEM_VIEW |
||
10934 | WHERE |
||
10935 | c_id = $course_id AND |
||
10936 | lp_item_id = $safe_item_id AND |
||
10937 | lp_view_id = $viewId |
||
10938 | ORDER BY id DESC |
||
10939 | LIMIT 1"; |
||
10940 | $res_last_attempt = Database::query($sql); |
||
10941 | |||
10942 | if (Database::num_rows($res_last_attempt) && !api_is_invitee()) { |
||
10943 | $row_last_attempt = Database::fetch_row($res_last_attempt); |
||
10944 | $lp_item_view_id = $row_last_attempt[0]; |
||
10945 | |||
10946 | $exercise = new Exercise($course_id); |
||
10947 | $exercise->read($row_dates['exe_exo_id']); |
||
10948 | $status = 'completed'; |
||
10949 | |||
10950 | if (!empty($exercise->pass_percentage)) { |
||
10951 | $status = 'failed'; |
||
10952 | $success = ExerciseLib::isSuccessExerciseResult( |
||
10953 | $score, |
||
10954 | $max_score, |
||
10955 | $exercise->pass_percentage |
||
10956 | ); |
||
10957 | if ($success) { |
||
10958 | $status = 'passed'; |
||
10959 | } |
||
10960 | } |
||
10961 | |||
10962 | $sql = "UPDATE $TBL_LP_ITEM_VIEW SET |
||
10963 | status = '$status', |
||
10964 | score = $score, |
||
10965 | total_time = $duration |
||
10966 | WHERE iid = $lp_item_view_id"; |
||
10967 | Database::query($sql); |
||
10968 | |||
10969 | $sql = "UPDATE $TBL_TRACK_EXERCICES SET |
||
10970 | orig_lp_item_view_id = $lp_item_view_id |
||
10971 | WHERE exe_id = ".$safe_exe_id; |
||
10972 | Database::query($sql); |
||
10973 | } |
||
10974 | } |
||
10975 | |||
10976 | /** |
||
10977 | * Get the user answers saved in exercise. |
||
10978 | * |
||
10979 | * @param int $attemptId |
||
10980 | * |
||
10981 | * @return array |
||
10982 | */ |
||
10983 | public function getUserAnswersSavedInExercise($attemptId) |
||
10984 | { |
||
10985 | $exerciseResult = []; |
||
10986 | $attemptList = Event::getAllExerciseEventByExeId($attemptId); |
||
10987 | |||
10988 | foreach ($attemptList as $questionId => $options) { |
||
10989 | foreach ($options as $option) { |
||
10990 | $question = Question::read($option['question_id']); |
||
10991 | if ($question) { |
||
10992 | switch ($question->type) { |
||
10993 | case FILL_IN_BLANKS: |
||
10994 | case FILL_IN_BLANKS_COMBINATION: |
||
10995 | $option['answer'] = $this->fill_in_blank_answer_to_string($option['answer']); |
||
10996 | if ($option['answer'] === "0") { |
||
10997 | $option['answer'] = "there is 0 as answer so we do not want to consider it empty"; |
||
10998 | } |
||
10999 | break; |
||
11000 | } |
||
11001 | } |
||
11002 | |||
11003 | if (!empty($option['answer'])) { |
||
11004 | $exerciseResult[] = $questionId; |
||
11005 | |||
11006 | break; |
||
11007 | } |
||
11008 | } |
||
11009 | } |
||
11010 | |||
11011 | return $exerciseResult; |
||
11012 | } |
||
11013 | |||
11014 | /** |
||
11015 | * Get the number of user answers saved in exercise. |
||
11016 | * |
||
11017 | * @param int $attemptId |
||
11018 | * |
||
11019 | * @return int |
||
11020 | */ |
||
11021 | public function countUserAnswersSavedInExercise($attemptId) |
||
11022 | { |
||
11023 | $answers = $this->getUserAnswersSavedInExercise($attemptId); |
||
11024 | |||
11025 | return count($answers); |
||
11026 | } |
||
11027 | |||
11028 | public static function allowAction($action) |
||
11029 | { |
||
11030 | if (api_is_platform_admin()) { |
||
11031 | return true; |
||
11032 | } |
||
11033 | |||
11034 | $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access'); |
||
11035 | $disableClean = api_get_configuration_value('disable_clean_exercise_results_for_teachers'); |
||
11036 | |||
11037 | switch ($action) { |
||
11038 | case 'delete': |
||
11039 | if (api_is_allowed_to_edit(null, true)) { |
||
11040 | if ($limitTeacherAccess) { |
||
11041 | return false; |
||
11042 | } |
||
11043 | |||
11044 | return true; |
||
11045 | } |
||
11046 | break; |
||
11047 | case 'clean_results': |
||
11048 | if (api_is_allowed_to_edit(null, true)) { |
||
11049 | if ($limitTeacherAccess) { |
||
11050 | return false; |
||
11051 | } |
||
11052 | |||
11053 | if ($disableClean) { |
||
11054 | return false; |
||
11055 | } |
||
11056 | |||
11057 | return true; |
||
11058 | } |
||
11059 | |||
11060 | break; |
||
11061 | } |
||
11062 | |||
11063 | return false; |
||
11064 | } |
||
11065 | |||
11066 | public static function getLpListFromExercise($exerciseId, $courseId) |
||
11067 | { |
||
11068 | $tableLpItem = Database::get_course_table(TABLE_LP_ITEM); |
||
11069 | $tblLp = Database::get_course_table(TABLE_LP_MAIN); |
||
11070 | |||
11071 | $exerciseId = (int) $exerciseId; |
||
11072 | $courseId = (int) $courseId; |
||
11073 | |||
11074 | $sql = "SELECT |
||
11075 | lp.name, |
||
11076 | lpi.lp_id, |
||
11077 | lpi.max_score, |
||
11078 | lp.session_id |
||
11079 | FROM $tableLpItem lpi |
||
11080 | INNER JOIN $tblLp lp |
||
11081 | ON (lpi.lp_id = lp.iid AND lpi.c_id = lp.c_id) |
||
11082 | WHERE |
||
11083 | lpi.c_id = $courseId AND |
||
11084 | lpi.item_type = '".TOOL_QUIZ."' AND |
||
11085 | lpi.path = '$exerciseId'"; |
||
11086 | $result = Database::query($sql); |
||
11087 | $lpList = []; |
||
11088 | if (Database::num_rows($result) > 0) { |
||
11089 | $lpList = Database::store_result($result, 'ASSOC'); |
||
11090 | } |
||
11091 | |||
11092 | return $lpList; |
||
11093 | } |
||
11094 | |||
11095 | public function getReminderTable($questionList, $exercise_stat_info, $disableCheckBoxes = false) |
||
11096 | { |
||
11097 | $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0; |
||
11098 | $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0; |
||
11099 | $learnpath_item_view_id = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0; |
||
11100 | $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0; |
||
11101 | |||
11102 | if (empty($exercise_stat_info)) { |
||
11103 | return ''; |
||
11104 | } |
||
11105 | |||
11106 | $remindList = $exercise_stat_info['questions_to_check']; |
||
11107 | $remindList = explode(',', $remindList); |
||
11108 | $exeId = $exercise_stat_info['exe_id']; |
||
11109 | $exerciseId = $exercise_stat_info['exe_exo_id']; |
||
11110 | $exercise_result = $this->getUserAnswersSavedInExercise($exeId); |
||
11111 | |||
11112 | $content = Display::label(get_lang('QuestionWithNoAnswer'), 'danger'); |
||
11113 | $content .= '<div class="clear"></div><br />'; |
||
11114 | $table = ''; |
||
11115 | $counter = 0; |
||
11116 | // Loop over all question to show results for each of them, one by one |
||
11117 | foreach ($questionList as $questionId) { |
||
11118 | $objQuestionTmp = Question::read($questionId); |
||
11119 | $check_id = 'remind_list['.$questionId.']'; |
||
11120 | $attributes = [ |
||
11121 | 'id' => $check_id, |
||
11122 | 'onclick' => "save_remind_item(this, '$questionId');", |
||
11123 | 'data-question-id' => $questionId, |
||
11124 | ]; |
||
11125 | if (in_array($questionId, $remindList)) { |
||
11126 | $attributes['checked'] = 1; |
||
11127 | } |
||
11128 | |||
11129 | $checkbox = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes); |
||
11130 | $checkbox = '<div class="pretty p-svg p-curve"> |
||
11131 | '.$checkbox.' |
||
11132 | <div class="state p-primary "> |
||
11133 | <svg class="svg svg-icon" viewBox="0 0 20 20"> |
||
11134 | <path d="M7.629,14.566c0.125,0.125,0.291,0.188,0.456,0.188c0.164,0,0.329-0.062,0.456-0.188l8.219-8.221c0.252-0.252,0.252-0.659,0-0.911c-0.252-0.252-0.659-0.252-0.911,0l-7.764,7.763L4.152,9.267c-0.252-0.251-0.66-0.251-0.911,0c-0.252,0.252-0.252,0.66,0,0.911L7.629,14.566z" style="stroke: white;fill:white;"></path> |
||
11135 | </svg> |
||
11136 | <label> </label> |
||
11137 | </div> |
||
11138 | </div>'; |
||
11139 | $counter++; |
||
11140 | $questionTitle = $counter.'. '.strip_tags($objQuestionTmp->selectTitle()); |
||
11141 | // Check if the question doesn't have an answer. |
||
11142 | if (!in_array($questionId, $exercise_result)) { |
||
11143 | $questionTitle = Display::label($questionTitle, 'danger'); |
||
11144 | } |
||
11145 | $label_attributes = []; |
||
11146 | $label_attributes['for'] = $check_id; |
||
11147 | if (false === $disableCheckBoxes) { |
||
11148 | $questionTitle = Display::tag('label', $checkbox.$questionTitle, $label_attributes); |
||
11149 | } |
||
11150 | $table .= Display::div($questionTitle, ['class' => 'exercise_reminder_item ']); |
||
11151 | } |
||
11152 | |||
11153 | $content .= Display::div('', ['id' => 'message']). |
||
11154 | Display::div($table, ['class' => 'question-check-test']); |
||
11155 | |||
11156 | $content .= '<script> |
||
11157 | var lp_data = $.param({ |
||
11158 | "learnpath_id": '.$learnpath_id.', |
||
11159 | "learnpath_item_id" : '.$learnpath_item_id.', |
||
11160 | "learnpath_item_view_id": '.$learnpath_item_view_id.' |
||
11161 | }); |
||
11162 | |||
11163 | function final_submit() { |
||
11164 | // Normal inputs. |
||
11165 | window.location = "'.api_get_path(WEB_CODE_PATH).'exercise/exercise_result.php?'.api_get_cidreq().'&exe_id='.$exeId.'&" + lp_data; |
||
11166 | } |
||
11167 | |||
11168 | function selectAll() { |
||
11169 | $("input[type=checkbox]").each(function () { |
||
11170 | $(this).prop("checked", 1); |
||
11171 | var question_id = $(this).data("question-id"); |
||
11172 | var action = "add"; |
||
11173 | $.ajax({ |
||
11174 | url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder", |
||
11175 | data: "question_id="+question_id+"&exe_id='.$exeId.'&action="+action, |
||
11176 | success: function(returnValue) { |
||
11177 | } |
||
11178 | }); |
||
11179 | }); |
||
11180 | } |
||
11181 | |||
11182 | function changeOptionStatus(status) |
||
11183 | { |
||
11184 | $("input[type=checkbox]").each(function () { |
||
11185 | $(this).prop("checked", status); |
||
11186 | }); |
||
11187 | var action = ""; |
||
11188 | var option = "remove_all"; |
||
11189 | if (status == 1) { |
||
11190 | option = "add_all"; |
||
11191 | } |
||
11192 | $.ajax({ |
||
11193 | url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder", |
||
11194 | data: "option="+option+"&exe_id='.$exeId.'&action="+action, |
||
11195 | success: function(returnValue) { |
||
11196 | } |
||
11197 | }); |
||
11198 | } |
||
11199 | |||
11200 | function reviewQuestions() { |
||
11201 | var isChecked = 1; |
||
11202 | $("input[type=checkbox]").each(function () { |
||
11203 | if ($(this).prop("checked")) { |
||
11204 | isChecked = 2; |
||
11205 | return false; |
||
11206 | } |
||
11207 | }); |
||
11208 | |||
11209 | if (isChecked == 1) { |
||
11210 | $("#message").addClass("warning-message"); |
||
11211 | $("#message").html("'.addslashes(get_lang('SelectAQuestionToReview')).'"); |
||
11212 | } else { |
||
11213 | window.location = "exercise_submit.php?'.api_get_cidreq().'&category_id='.$categoryId.'&exerciseId='.$exerciseId.'&reminder=2&" + lp_data; |
||
11214 | } |
||
11215 | } |
||
11216 | |||
11217 | function save_remind_item(obj, question_id) { |
||
11218 | var action = ""; |
||
11219 | if ($(obj).prop("checked")) { |
||
11220 | action = "add"; |
||
11221 | } else { |
||
11222 | action = "delete"; |
||
11223 | } |
||
11224 | $.ajax({ |
||
11225 | url: "'.api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=add_question_to_reminder", |
||
11226 | data: "question_id="+question_id+"&exe_id='.$exeId.'&action="+action, |
||
11227 | success: function(returnValue) { |
||
11228 | } |
||
11229 | }); |
||
11230 | } |
||
11231 | </script>'; |
||
11232 | |||
11233 | return $content; |
||
11234 | } |
||
11235 | |||
11236 | public function getRadarsFromUsers($userList, $exercises, $dataSetLabels, $courseId, $sessionId) |
||
11237 | { |
||
11238 | $dataSet = []; |
||
11239 | $labels = []; |
||
11240 | $labelsWithId = []; |
||
11241 | $cutLabelAtChar = 30; |
||
11242 | /** @var Exercise $exercise */ |
||
11243 | foreach ($exercises as $exercise) { |
||
11244 | if (empty($labels)) { |
||
11245 | $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iid); |
||
11246 | if (!empty($categoryNameList)) { |
||
11247 | $labelsWithId = array_column($categoryNameList, 'title', 'iid'); |
||
11248 | asort($labelsWithId); |
||
11249 | $labels = array_values($labelsWithId); |
||
11250 | foreach ($labels as $labelId => $label) { |
||
11251 | // Cut if label is too long to maintain chart visibility |
||
11252 | $labels[$labelId] = cut($label, $cutLabelAtChar); |
||
11253 | } |
||
11254 | } |
||
11255 | } |
||
11256 | |||
11257 | foreach ($userList as $userId) { |
||
11258 | $results = Event::getExerciseResultsByUser( |
||
11259 | $userId, |
||
11260 | $exercise->iid, |
||
11261 | $courseId, |
||
11262 | $sessionId |
||
11263 | ); |
||
11264 | |||
11265 | if ($results) { |
||
11266 | $firstAttempt = end($results); |
||
11267 | $exeId = $firstAttempt['exe_id']; |
||
11268 | |||
11269 | ob_start(); |
||
11270 | $stats = ExerciseLib::displayQuestionListByAttempt( |
||
11271 | $exercise, |
||
11272 | $exeId, |
||
11273 | false |
||
11274 | ); |
||
11275 | ob_end_clean(); |
||
11276 | |||
11277 | $categoryList = $stats['category_list']; |
||
11278 | $tempResult = []; |
||
11279 | foreach ($labelsWithId as $category_id => $title) { |
||
11280 | if (isset($categoryList[$category_id])) { |
||
11281 | $categoryItem = $categoryList[$category_id]; |
||
11282 | if ($categoryItem['total'] > 0) { |
||
11283 | $tempResult[] = round($categoryItem['score'] / $categoryItem['total'] * 10); |
||
11284 | } else { |
||
11285 | $tempResult[] = 0; |
||
11286 | } |
||
11287 | } else { |
||
11288 | $tempResult[] = 0; |
||
11289 | } |
||
11290 | } |
||
11291 | $dataSet[] = $tempResult; |
||
11292 | } |
||
11293 | } |
||
11294 | } |
||
11295 | |||
11296 | return $this->getRadar($labels, $dataSet, $dataSetLabels); |
||
11297 | } |
||
11298 | |||
11299 | public function getAverageRadarsFromUsers($userList, $exercises, $dataSetLabels, $courseId, $sessionId) |
||
11300 | { |
||
11301 | $dataSet = []; |
||
11302 | $labels = []; |
||
11303 | $labelsWithId = []; |
||
11304 | $cutLabelAtChar = 30; |
||
11305 | $tempResult = []; |
||
11306 | /** @var Exercise $exercise */ |
||
11307 | foreach ($exercises as $exercise) { |
||
11308 | $exerciseId = $exercise->iid; |
||
11309 | if (empty($labels)) { |
||
11310 | $categoryNameList = TestCategory::getListOfCategoriesNameForTest($exercise->iid); |
||
11311 | if (!empty($categoryNameList)) { |
||
11312 | $labelsWithId = array_column($categoryNameList, 'title', 'iid'); |
||
11313 | asort($labelsWithId); |
||
11314 | $labels = array_values($labelsWithId); |
||
11315 | foreach ($labels as $labelId => $label) { |
||
11316 | // Cut if label is too long to maintain chart visibility |
||
11317 | $labels[$labelId] = cut($label, $cutLabelAtChar); |
||
11318 | } |
||
11319 | } |
||
11320 | } |
||
11321 | |||
11322 | foreach ($userList as $userId) { |
||
11323 | $results = Event::getExerciseResultsByUser( |
||
11324 | $userId, |
||
11325 | $exerciseId, |
||
11326 | $courseId, |
||
11327 | $sessionId |
||
11328 | ); |
||
11329 | |||
11330 | if ($results) { |
||
11331 | $firstAttempt = end($results); |
||
11332 | $exeId = $firstAttempt['exe_id']; |
||
11333 | |||
11334 | ob_start(); |
||
11335 | $stats = ExerciseLib::displayQuestionListByAttempt( |
||
11336 | $exercise, |
||
11337 | $exeId, |
||
11338 | false |
||
11339 | ); |
||
11340 | ob_end_clean(); |
||
11341 | |||
11342 | $categoryList = $stats['category_list']; |
||
11343 | foreach ($labelsWithId as $category_id => $title) { |
||
11344 | if (isset($categoryList[$category_id])) { |
||
11345 | $categoryItem = $categoryList[$category_id]; |
||
11346 | if (!isset($tempResult[$exerciseId][$category_id])) { |
||
11347 | $tempResult[$exerciseId][$category_id] = 0; |
||
11348 | } |
||
11349 | $tempResult[$exerciseId][$category_id] += |
||
11350 | $categoryItem['score'] / $categoryItem['total'] * 10; |
||
11351 | } |
||
11352 | } |
||
11353 | } |
||
11354 | } |
||
11355 | } |
||
11356 | |||
11357 | foreach ($exercises as $exercise) { |
||
11358 | $exerciseId = $exercise->iid; |
||
11359 | $totalResults = ExerciseLib::getNumberStudentsFinishExercise($exerciseId, $courseId, $sessionId); |
||
11360 | $data = []; |
||
11361 | foreach ($labelsWithId as $category_id => $title) { |
||
11362 | if (isset($tempResult[$exerciseId]) && isset($tempResult[$exerciseId][$category_id])) { |
||
11363 | $data[] = round($tempResult[$exerciseId][$category_id] / $totalResults); |
||
11364 | } else { |
||
11365 | $data[] = 0; |
||
11366 | } |
||
11367 | } |
||
11368 | $dataSet[] = $data; |
||
11369 | } |
||
11370 | |||
11371 | return $this->getRadar($labels, $dataSet, $dataSetLabels); |
||
11372 | } |
||
11373 | |||
11374 | public function getRadar($labels, $dataSet, $dataSetLabels = []) |
||
11375 | { |
||
11376 | if (empty($labels) || empty($dataSet)) { |
||
11377 | return ''; |
||
11378 | } |
||
11379 | |||
11380 | $displayLegend = 0; |
||
11381 | if (!empty($dataSetLabels)) { |
||
11382 | $displayLegend = 1; |
||
11383 | } |
||
11384 | |||
11385 | $labels = json_encode($labels); |
||
11386 | |||
11387 | $colorList = ChamiloApi::getColorPalette(true, true); |
||
11388 | |||
11389 | $dataSetToJson = []; |
||
11390 | $counter = 0; |
||
11391 | foreach ($dataSet as $index => $resultsArray) { |
||
11392 | $color = isset($colorList[$counter]) ? $colorList[$counter] : 'rgb('.rand(0, 255).', '.rand(0, 255).', '.rand(0, 255).', 1.0)'; |
||
11393 | |||
11394 | $label = isset($dataSetLabels[$index]) ? $dataSetLabels[$index] : ''; |
||
11395 | $background = str_replace('1.0', '0.2', $color); |
||
11396 | $dataSetToJson[] = [ |
||
11397 | 'fill' => false, |
||
11398 | 'label' => $label, |
||
11399 | 'backgroundColor' => $background, |
||
11400 | 'borderColor' => $color, |
||
11401 | 'pointBackgroundColor' => $color, |
||
11402 | 'pointBorderColor' => '#fff', |
||
11403 | 'pointHoverBackgroundColor' => '#fff', |
||
11404 | 'pointHoverBorderColor' => $color, |
||
11405 | 'pointRadius' => 6, |
||
11406 | 'pointBorderWidth' => 3, |
||
11407 | 'pointHoverRadius' => 10, |
||
11408 | 'data' => $resultsArray, |
||
11409 | ]; |
||
11410 | $counter++; |
||
11411 | } |
||
11412 | $resultsToJson = json_encode($dataSetToJson); |
||
11413 | |||
11414 | return " |
||
11415 | <canvas id='categoryRadar' height='200'></canvas> |
||
11416 | <script> |
||
11417 | var data = { |
||
11418 | labels: $labels, |
||
11419 | datasets: $resultsToJson |
||
11420 | } |
||
11421 | var options = { |
||
11422 | responsive: true, |
||
11423 | scale: { |
||
11424 | angleLines: { |
||
11425 | display: false |
||
11426 | }, |
||
11427 | ticks: { |
||
11428 | beginAtZero: true, |
||
11429 | min: 0, |
||
11430 | max: 10, |
||
11431 | stepSize: 1, |
||
11432 | }, |
||
11433 | pointLabels: { |
||
11434 | fontSize: 14, |
||
11435 | //fontStyle: 'bold' |
||
11436 | }, |
||
11437 | }, |
||
11438 | elements: { |
||
11439 | line: { |
||
11440 | tension: 0, |
||
11441 | borderWidth: 3 |
||
11442 | } |
||
11443 | }, |
||
11444 | legend: { |
||
11445 | //position: 'bottom' |
||
11446 | display: $displayLegend |
||
11447 | }, |
||
11448 | animation: { |
||
11449 | animateScale: true, |
||
11450 | animateRotate: true |
||
11451 | }, |
||
11452 | }; |
||
11453 | var ctx = document.getElementById('categoryRadar').getContext('2d'); |
||
11454 | var myRadarChart = new Chart(ctx, { |
||
11455 | type: 'radar', |
||
11456 | data: data, |
||
11457 | options: options |
||
11458 | }); |
||
11459 | </script> |
||
11460 | "; |
||
11461 | } |
||
11462 | |||
11463 | /** |
||
11464 | * Returns a list of users based on the id of an exercise, the course and the session. |
||
11465 | * If the count is true, it returns only the number of users. |
||
11466 | * |
||
11467 | * @param int $exerciseId |
||
11468 | * @param int $courseId |
||
11469 | * @param int $sessionId |
||
11470 | * @param false $count |
||
11471 | */ |
||
11472 | public static function getUsersInExercise( |
||
11473 | $exerciseId = 0, |
||
11474 | $courseId = 0, |
||
11475 | $sessionId = 0, |
||
11476 | $count = false, |
||
11477 | $toUsers = [], |
||
11478 | $withSelectAll = true |
||
11479 | ) { |
||
11480 | $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId; |
||
11481 | $courseId = empty($courseId) ? api_get_course_id() : (int) $courseId; |
||
11482 | $exerciseId = (int) $exerciseId; |
||
11483 | if ((0 == $sessionId && 0 == $courseId) || 0 == $exerciseId) { |
||
11484 | return []; |
||
11485 | } |
||
11486 | $tblCourse = Database::get_main_table(TABLE_MAIN_COURSE); |
||
11487 | $tblCourseRelUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
11488 | $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
11489 | |||
11490 | $data = []; |
||
11491 | |||
11492 | $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST); |
||
11493 | $countSelect = " COUNT(*) AS total "; |
||
11494 | $sqlToUsers = ''; |
||
11495 | if (0 == $sessionId) { |
||
11496 | // Courses |
||
11497 | if (false === $count) { |
||
11498 | $countSelect = " cq.title AS quiz_title, |
||
11499 | cq.iid AS quiz_id, |
||
11500 | cru.c_id AS course_id, |
||
11501 | cru.user_id AS user_id, |
||
11502 | c.title AS title, |
||
11503 | c.code AS 'code', |
||
11504 | cq.active AS active, |
||
11505 | cq.session_id AS session_id "; |
||
11506 | } |
||
11507 | |||
11508 | $sql = "SELECT $countSelect |
||
11509 | FROM $tblCourseRelUser AS cru |
||
11510 | INNER JOIN $tblCourse AS c ON ( cru.c_id = c.id ) |
||
11511 | INNER JOIN $tblQuiz AS cq ON ( cq.c_id = c.id ) |
||
11512 | WHERE cru.is_tutor IS NULL |
||
11513 | AND ( cq.session_id = 0 OR cq.session_id IS NULL) |
||
11514 | AND cq.active != -1 |
||
11515 | AND cq.c_id = $courseId |
||
11516 | AND cq.iid = $exerciseId "; |
||
11517 | if (!empty($toUsers)) { |
||
11518 | $sqlToUsers = ' AND cru.user_id IN ('.implode(',', $toUsers).') '; |
||
11519 | } |
||
11520 | } else { |
||
11521 | //Sessions |
||
11522 | if (false === $count) { |
||
11523 | $countSelect = " cq.title AS quiz_title, |
||
11524 | cq.iid AS quiz_id, |
||
11525 | sru.user_id AS user_id, |
||
11526 | cq.c_id AS course_id, |
||
11527 | sru.session_id AS session_id, |
||
11528 | c.title AS title, |
||
11529 | c.code AS 'code', |
||
11530 | cq.active AS active "; |
||
11531 | } |
||
11532 | if (!empty($toUsers)) { |
||
11533 | $sqlToUsers = ' AND sru.user_id IN ('.implode(',', $toUsers).') '; |
||
11534 | } |
||
11535 | $sql = " SELECT $countSelect |
||
11536 | FROM $tblSessionRelUser AS sru |
||
11537 | INNER JOIN $tblQuiz AS cq ON ( sru.session_id = sru.session_id ) |
||
11538 | INNER JOIN $tblCourse AS c ON ( c.id = cq.c_id ) |
||
11539 | WHERE cq.active != 1 |
||
11540 | AND cq.c_id = $courseId |
||
11541 | AND sru.session_id = $sessionId |
||
11542 | AND cq.iid = $exerciseId "; |
||
11543 | } |
||
11544 | $sql .= " $sqlToUsers ORDER BY cq.c_id "; |
||
11545 | |||
11546 | $result = Database::query($sql); |
||
11547 | $data = Database::store_result($result); |
||
11548 | Database::free_result($result); |
||
11549 | if (true === $count) { |
||
11550 | return (isset($data[0]) && isset($data[0]['total'])) ? $data[0]['total'] : 0; |
||
11551 | } |
||
11552 | $usersArray = []; |
||
11553 | |||
11554 | $return = []; |
||
11555 | if ($withSelectAll) { |
||
11556 | $return[] = [ |
||
11557 | 'user_id' => 'X', |
||
11558 | 'value' => 'X', |
||
11559 | 'user_name' => get_lang('AllStudents'), |
||
11560 | ]; |
||
11561 | } |
||
11562 | |||
11563 | foreach ($data as $index => $item) { |
||
11564 | if (isset($item['user_id'])) { |
||
11565 | $userId = (int) $item['user_id']; |
||
11566 | if (!isset($usersArray[$userId])) { |
||
11567 | $usersArray[$userId] = api_get_user_info($userId); |
||
11568 | } |
||
11569 | $usersArray[$userId]['user_id'] = $userId; |
||
11570 | $userData = $usersArray[$userId]; |
||
11571 | $data[$index]['user_name'] = $userData['complete_name']; |
||
11572 | $return[] = $data[$index]; |
||
11573 | } |
||
11574 | } |
||
11575 | |||
11576 | return $return; |
||
11577 | } |
||
11578 | |||
11579 | /** |
||
11580 | * Search the users who are in an exercise to send them an exercise reminder email and to the human resources |
||
11581 | * managers, it is necessary the exercise id, the course and the session if it exists. |
||
11582 | * |
||
11583 | * @param int $exerciseId |
||
11584 | * @param int $courseId |
||
11585 | * @param int $sessionId |
||
11586 | */ |
||
11587 | public static function notifyUsersOfTheExercise( |
||
11588 | $exerciseId = 0, |
||
11589 | $courseId = 0, |
||
11590 | $sessionId = 0, |
||
11591 | $toUsers = [] |
||
11592 | ) { |
||
11593 | $users = self::getUsersInExercise( |
||
11594 | $exerciseId, |
||
11595 | $courseId, |
||
11596 | $sessionId, |
||
11597 | false, |
||
11598 | $toUsers |
||
11599 | ); |
||
11600 | $totalUsers = count($users); |
||
11601 | $usersArray = []; |
||
11602 | $courseTitle = ''; |
||
11603 | $quizTitle = ''; |
||
11604 | |||
11605 | for ($i = 0; $i < $totalUsers; $i++) { |
||
11606 | $user = $users[$i]; |
||
11607 | $userId = (int) $user['user_id']; |
||
11608 | if (0 != $userId) { |
||
11609 | $quizTitle = $user['quiz_title']; |
||
11610 | $courseTitle = $user['title']; |
||
11611 | if (!isset($usersArray[$userId])) { |
||
11612 | $usersArray[$userId] = api_get_user_info($userId); |
||
11613 | } |
||
11614 | } |
||
11615 | } |
||
11616 | |||
11617 | $objExerciseTmp = new Exercise(); |
||
11618 | $objExerciseTmp->read($exerciseId); |
||
11619 | $isAddedInLp = !empty($objExerciseTmp->lpList); |
||
11620 | $end = $objExerciseTmp->end_time; |
||
11621 | $start = $objExerciseTmp->start_time; |
||
11622 | $minutes = $objExerciseTmp->expired_time; |
||
11623 | $formatDate = DATE_TIME_FORMAT_LONG; |
||
11624 | $tblCourseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
11625 | $tblSession = Database::get_main_table(TABLE_MAIN_SESSION); |
||
11626 | $tblSessionUser = Database::get_main_table(TABLE_MAIN_SESSION_USER); |
||
11627 | $tblSessionUserRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
11628 | $teachersName = []; |
||
11629 | $teachersPrint = []; |
||
11630 | if (0 == $sessionId) { |
||
11631 | $sql = "SELECT course_user.user_id AS user_id |
||
11632 | FROM $tblCourseUser AS course_user |
||
11633 | WHERE course_user.status = 1 AND course_user.c_id ='".$courseId."'"; |
||
11634 | $result = Database::query($sql); |
||
11635 | $data = Database::store_result($result); |
||
11636 | Database::free_result($result); |
||
11637 | foreach ($data as $teacher) { |
||
11638 | $teacherId = (int) $teacher['user_id']; |
||
11639 | if (!isset($teachersName[$teacherId])) { |
||
11640 | $teachersName[$teacherId] = api_get_user_info($teacherId); |
||
11641 | } |
||
11642 | $teacherData = $teachersName[$teacherId]; |
||
11643 | $teachersPrint[] = $teacherData['complete_name']; |
||
11644 | } |
||
11645 | } else { |
||
11646 | // general tutor |
||
11647 | $sql = "SELECT sesion.id_coach AS user_id |
||
11648 | FROM $tblSession AS sesion |
||
11649 | WHERE sesion.id = $sessionId"; |
||
11650 | $result = Database::query($sql); |
||
11651 | $data = Database::store_result($result); |
||
11652 | Database::free_result($result); |
||
11653 | foreach ($data as $teacher) { |
||
11654 | $teacherId = (int) $teacher['user_id']; |
||
11655 | if (!isset($teachersName[$teacherId])) { |
||
11656 | $teachersName[$teacherId] = api_get_user_info($teacherId); |
||
11657 | } |
||
11658 | $teacherData = $teachersName[$teacherId]; |
||
11659 | $teachersPrint[] = $teacherData['complete_name']; |
||
11660 | } |
||
11661 | // Teacher into sessions course |
||
11662 | $sql = "SELECT session_rel_course_rel_user.user_id |
||
11663 | FROM $tblSessionUserRelCourse AS session_rel_course_rel_user |
||
11664 | WHERE session_rel_course_rel_user.session_id = $sessionId AND |
||
11665 | session_rel_course_rel_user.c_id = $courseId AND |
||
11666 | session_rel_course_rel_user.status = 2"; |
||
11667 | $result = Database::query($sql); |
||
11668 | $data = Database::store_result($result); |
||
11669 | Database::free_result($result); |
||
11670 | foreach ($data as $teacher) { |
||
11671 | $teacherId = (int) $teacher['user_id']; |
||
11672 | if (!isset($teachersName[$teacherId])) { |
||
11673 | $teachersName[$teacherId] = api_get_user_info($teacherId); |
||
11674 | } |
||
11675 | $teacherData = $teachersName[$teacherId]; |
||
11676 | $teachersPrint[] = $teacherData['complete_name']; |
||
11677 | } |
||
11678 | } |
||
11679 | |||
11680 | $teacherName = implode('<br>', $teachersPrint); |
||
11681 | |||
11682 | if ($isAddedInLp) { |
||
11683 | $lpInfo = current($objExerciseTmp->lpList); |
||
11684 | $url = api_get_path(WEB_CODE_PATH)."lp/lp_controller.php?".api_get_cidreq().'&' |
||
11685 | .http_build_query(['action' => 'view', 'lp_id' => $lpInfo['lp_id']]); |
||
11686 | } else { |
||
11687 | $url = api_get_path(WEB_CODE_PATH)."exercise/overview.php?".api_get_cidreq()."&exerciseId=$exerciseId"; |
||
11688 | } |
||
11689 | |||
11690 | foreach ($usersArray as $userId => $userData) { |
||
11691 | $studentName = $userData['complete_name']; |
||
11692 | $title = sprintf(get_lang('QuizRemindSubject'), $teacherName); |
||
11693 | $content = sprintf( |
||
11694 | get_lang('QuizFirstRemindBody'), |
||
11695 | $studentName, |
||
11696 | $quizTitle, |
||
11697 | $courseTitle, |
||
11698 | $courseTitle, |
||
11699 | $quizTitle |
||
11700 | ); |
||
11701 | if (!empty($minutes)) { |
||
11702 | $content .= sprintf(get_lang('QuizRemindDuration'), $minutes); |
||
11703 | } |
||
11704 | if (!empty($start)) { |
||
11705 | // api_get_utc_datetime |
||
11706 | $start = api_format_date(($start), $formatDate); |
||
11707 | |||
11708 | $content .= sprintf(get_lang('QuizRemindStartDate'), $start); |
||
11709 | } |
||
11710 | if (!empty($end)) { |
||
11711 | $end = api_format_date(($end), $formatDate); |
||
11712 | $content .= sprintf(get_lang('QuizRemindEndDate'), $end); |
||
11713 | } |
||
11714 | $content .= sprintf( |
||
11715 | get_lang('QuizLastRemindBody'), |
||
11716 | $url, |
||
11717 | $url, |
||
11718 | $teacherName |
||
11719 | ); |
||
11720 | $drhList = UserManager::getDrhListFromUser($userId); |
||
11721 | if (!empty($drhList)) { |
||
11722 | foreach ($drhList as $drhUser) { |
||
11723 | $drhUserData = api_get_user_info($drhUser['id']); |
||
11724 | $drhName = $drhUserData['complete_name']; |
||
11725 | $contentDHR = sprintf( |
||
11726 | get_lang('QuizDhrRemindBody'), |
||
11727 | $drhName, |
||
11728 | $studentName, |
||
11729 | $quizTitle, |
||
11730 | $courseTitle, |
||
11731 | $studentName, |
||
11732 | $courseTitle, |
||
11733 | $quizTitle |
||
11734 | ); |
||
11735 | if (!empty($minutes)) { |
||
11736 | $contentDHR .= sprintf(get_lang('QuizRemindDuration'), $minutes); |
||
11737 | } |
||
11738 | if (!empty($start)) { |
||
11739 | // api_get_utc_datetime |
||
11740 | $start = api_format_date(($start), $formatDate); |
||
11741 | |||
11742 | $contentDHR .= sprintf(get_lang('QuizRemindStartDate'), $start); |
||
11743 | } |
||
11744 | if (!empty($end)) { |
||
11745 | $end = api_format_date(($end), $formatDate); |
||
11746 | $contentDHR .= sprintf(get_lang('QuizRemindEndDate'), $end); |
||
11747 | } |
||
11748 | MessageManager::send_message( |
||
11749 | $drhUser['id'], |
||
11750 | $title, |
||
11751 | $contentDHR |
||
11752 | ); |
||
11753 | } |
||
11754 | } |
||
11755 | MessageManager::send_message( |
||
11756 | $userData['id'], |
||
11757 | $title, |
||
11758 | $content |
||
11759 | ); |
||
11760 | } |
||
11761 | |||
11762 | return $usersArray; |
||
11763 | } |
||
11764 | |||
11765 | /** |
||
11766 | * Returns true if the exercise is locked by percentage. an exercise attempt must be passed. |
||
11767 | */ |
||
11768 | public function isBlockedByPercentage(array $attempt = []): bool |
||
11769 | { |
||
11770 | if (empty($attempt)) { |
||
11771 | return false; |
||
11772 | } |
||
11773 | $extraFieldValue = new ExtraFieldValue('exercise'); |
||
11774 | $blockExercise = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
11775 | $this->iid, |
||
11776 | 'blocking_percentage' |
||
11777 | ); |
||
11778 | |||
11779 | if (empty($blockExercise['value'])) { |
||
11780 | return false; |
||
11781 | } |
||
11782 | |||
11783 | $blockPercentage = (int) $blockExercise['value']; |
||
11784 | |||
11785 | if (0 === $blockPercentage) { |
||
11786 | return false; |
||
11787 | } |
||
11788 | |||
11789 | $resultPercentage = 0; |
||
11790 | |||
11791 | if (isset($attempt['exe_result']) && isset($attempt['exe_weighting'])) { |
||
11792 | $weight = (int) $attempt['exe_weighting']; |
||
11793 | $weight = (0 == $weight) ? 1 : $weight; |
||
11794 | $resultPercentage = float_format( |
||
11795 | ($attempt['exe_result'] / $weight) * 100, |
||
11796 | 1 |
||
11797 | ); |
||
11798 | } |
||
11799 | if ($resultPercentage <= $blockPercentage) { |
||
11800 | return true; |
||
11801 | } |
||
11802 | |||
11803 | return false; |
||
11804 | } |
||
11805 | |||
11806 | /** |
||
11807 | * Gets the question list ordered by the question_order setting (drag and drop). |
||
11808 | * |
||
11809 | * @param bool $adminView Optional. |
||
11810 | * |
||
11811 | * @return array |
||
11812 | */ |
||
11813 | public function getQuestionOrderedList($adminView = false) |
||
11814 | { |
||
11815 | $TBL_QUIZ_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
11816 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
11817 | |||
11818 | // Getting question_order to verify that the question |
||
11819 | // list is correct and all question_order's were set |
||
11820 | $sql = "SELECT DISTINCT count(e.question_order) as count |
||
11821 | FROM $TBL_QUIZ_QUESTION e |
||
11822 | INNER JOIN $TBL_QUESTIONS q |
||
11823 | ON e.question_id = q.iid |
||
11824 | WHERE |
||
11825 | e.c_id = {$this->course_id} AND |
||
11826 | e.exercice_id = ".$this->iid; |
||
11827 | |||
11828 | $result = Database::query($sql); |
||
11829 | $row = Database::fetch_array($result); |
||
11830 | $count_question_orders = $row['count']; |
||
11831 | |||
11832 | // Getting question list from the order (question list drag n drop interface). |
||
11833 | $sql = "SELECT DISTINCT e.question_id, e.question_order |
||
11834 | FROM $TBL_QUIZ_QUESTION e |
||
11835 | INNER JOIN $TBL_QUESTIONS q |
||
11836 | ON e.question_id = q.iid |
||
11837 | WHERE |
||
11838 | e.c_id = {$this->course_id} AND |
||
11839 | e.exercice_id = '".$this->iid."' |
||
11840 | ORDER BY question_order"; |
||
11841 | $result = Database::query($sql); |
||
11842 | |||
11843 | // Fills the array with the question ID for this exercise |
||
11844 | // the key of the array is the question position |
||
11845 | $temp_question_list = []; |
||
11846 | $counter = 1; |
||
11847 | $questionList = []; |
||
11848 | while ($new_object = Database::fetch_object($result)) { |
||
11849 | if (!$adminView) { |
||
11850 | // Correct order. |
||
11851 | $questionList[$new_object->question_order] = $new_object->question_id; |
||
11852 | } else { |
||
11853 | $questionList[$counter] = $new_object->question_id; |
||
11854 | } |
||
11855 | |||
11856 | // Just in case we save the order in other array |
||
11857 | $temp_question_list[$counter] = $new_object->question_id; |
||
11858 | $counter++; |
||
11859 | } |
||
11860 | |||
11861 | if (!empty($temp_question_list)) { |
||
11862 | /* If both array don't match it means that question_order was not correctly set |
||
11863 | for all questions using the default mysql order */ |
||
11864 | if (count($temp_question_list) != $count_question_orders || count($temp_question_list) !== count($questionList)) { |
||
11865 | $questionList = $temp_question_list; |
||
11866 | } |
||
11867 | } |
||
11868 | |||
11869 | return $questionList; |
||
11870 | } |
||
11871 | |||
11872 | /** |
||
11873 | * Returns a literal for the given numerical feedback type (usually |
||
11874 | * coming from the DB or a constant). The literal is also the string |
||
11875 | * used to get the translation, not the translation itself as it is |
||
11876 | * more vulnerable to changes. |
||
11877 | */ |
||
11878 | public static function getFeedbackTypeLiteral(int $feedbackType): string |
||
11879 | { |
||
11880 | $feedbackType = (int) $feedbackType; |
||
11881 | $result = ''; |
||
11882 | static $arrayFeedbackTypes = [ |
||
11883 | EXERCISE_FEEDBACK_TYPE_END => 'ExerciseAtTheEndOfTheTest', |
||
11884 | EXERCISE_FEEDBACK_TYPE_DIRECT => 'DirectFeedback', |
||
11885 | EXERCISE_FEEDBACK_TYPE_EXAM => 'NoFeedback', |
||
11886 | EXERCISE_FEEDBACK_TYPE_POPUP => 'ExerciseDirectPopUp', |
||
11887 | ]; |
||
11888 | |||
11889 | if (array_key_exists($feedbackType, $arrayFeedbackTypes)) { |
||
11890 | $result = $arrayFeedbackTypes[$feedbackType]; |
||
11891 | } |
||
11892 | |||
11893 | return $result; |
||
11894 | } |
||
11895 | |||
11896 | /** |
||
11897 | * Return the text to display, based on the score and the max score. |
||
11898 | * |
||
11899 | * @param int|float $score |
||
11900 | * @param int|float $maxScore |
||
11901 | */ |
||
11902 | public function getFinishText($score, $maxScore): string |
||
11903 | { |
||
11904 | if (true !== api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
11905 | return $this->getTextWhenFinished(); |
||
11906 | } |
||
11907 | |||
11908 | $passPercentage = $this->selectPassPercentage(); |
||
11909 | |||
11910 | if (!empty($passPercentage)) { |
||
11911 | $percentage = float_format( |
||
11912 | ($score / (0 != $maxScore ? $maxScore : 1)) * 100, |
||
11913 | 1 |
||
11914 | ); |
||
11915 | |||
11916 | if ($percentage < $passPercentage) { |
||
11917 | return $this->getTextWhenFinishedFailure(); |
||
11918 | } |
||
11919 | } |
||
11920 | |||
11921 | return $this->getTextWhenFinished(); |
||
11922 | } |
||
11923 | |||
11924 | /** |
||
11925 | * Get number of questions in exercise by user attempt. |
||
11926 | * |
||
11927 | * @return int |
||
11928 | */ |
||
11929 | private function countQuestionsInExercise() |
||
11930 | { |
||
11931 | $lpId = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0; |
||
11932 | $lpItemId = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0; |
||
11933 | $lpItemViewId = isset($_REQUEST['learnpath_item_view_id']) ? (int) $_REQUEST['learnpath_item_view_id'] : 0; |
||
11934 | |||
11935 | $trackInfo = $this->get_stat_track_exercise_info($lpId, $lpItemId, $lpItemViewId); |
||
11936 | |||
11937 | if (!empty($trackInfo)) { |
||
11938 | $questionIds = explode(',', $trackInfo['data_tracking']); |
||
11939 | |||
11940 | return count($questionIds); |
||
11941 | } |
||
11942 | |||
11943 | return $this->getQuestionCount(); |
||
11944 | } |
||
11945 | |||
11946 | /** |
||
11947 | * Select N values from the questions per category array. |
||
11948 | * |
||
11949 | * @param array $categoriesAddedInExercise |
||
11950 | * @param array $question_list |
||
11951 | * @param array $questions_by_category |
||
11952 | * @param bool $flatResult |
||
11953 | * @param bool $randomizeQuestions |
||
11954 | * @param array $questionsByCategoryMandatory |
||
11955 | * |
||
11956 | * @return array |
||
11957 | */ |
||
11958 | private function pickQuestionsPerCategory( |
||
11959 | $categoriesAddedInExercise, |
||
11960 | $question_list, |
||
11961 | &$questions_by_category, |
||
11962 | $flatResult = true, |
||
11963 | $randomizeQuestions = false, |
||
11964 | $questionsByCategoryMandatory = [] |
||
11965 | ) { |
||
11966 | $addAll = true; |
||
11967 | $categoryCountArray = []; |
||
11968 | |||
11969 | // Getting how many questions will be selected per category. |
||
11970 | if (!empty($categoriesAddedInExercise)) { |
||
11971 | $addAll = false; |
||
11972 | // Parsing question according the category rel exercise settings |
||
11973 | foreach ($categoriesAddedInExercise as $category_info) { |
||
11974 | $category_id = $category_info['category_id']; |
||
11975 | if (isset($questions_by_category[$category_id])) { |
||
11976 | // How many question will be picked from this category. |
||
11977 | $count = $category_info['count_questions']; |
||
11978 | // -1 means all questions |
||
11979 | $categoryCountArray[$category_id] = $count; |
||
11980 | if (-1 == $count) { |
||
11981 | $categoryCountArray[$category_id] = 999; |
||
11982 | } |
||
11983 | } |
||
11984 | } |
||
11985 | } |
||
11986 | |||
11987 | if (!empty($questions_by_category)) { |
||
11988 | $temp_question_list = []; |
||
11989 | foreach ($questions_by_category as $category_id => &$categoryQuestionList) { |
||
11990 | if (isset($categoryCountArray) && !empty($categoryCountArray)) { |
||
11991 | $numberOfQuestions = 0; |
||
11992 | if (isset($categoryCountArray[$category_id])) { |
||
11993 | $numberOfQuestions = $categoryCountArray[$category_id]; |
||
11994 | } |
||
11995 | } |
||
11996 | |||
11997 | if ($addAll) { |
||
11998 | $numberOfQuestions = 999; |
||
11999 | } |
||
12000 | if (!empty($numberOfQuestions)) { |
||
12001 | $mandatoryQuestions = []; |
||
12002 | if (isset($questionsByCategoryMandatory[$category_id])) { |
||
12003 | $mandatoryQuestions = $questionsByCategoryMandatory[$category_id]; |
||
12004 | } |
||
12005 | |||
12006 | $elements = TestCategory::getNElementsFromArray( |
||
12007 | $categoryQuestionList, |
||
12008 | $numberOfQuestions, |
||
12009 | $randomizeQuestions, |
||
12010 | $mandatoryQuestions |
||
12011 | ); |
||
12012 | if (!empty($elements)) { |
||
12013 | $temp_question_list[$category_id] = $elements; |
||
12014 | $categoryQuestionList = $elements; |
||
12015 | } |
||
12016 | } |
||
12017 | } |
||
12018 | |||
12019 | if (!empty($temp_question_list)) { |
||
12020 | if ($flatResult) { |
||
12021 | $temp_question_list = array_flatten($temp_question_list); |
||
12022 | } |
||
12023 | $question_list = $temp_question_list; |
||
12024 | } |
||
12025 | } |
||
12026 | |||
12027 | return $question_list; |
||
12028 | } |
||
12029 | |||
12030 | /** |
||
12031 | * Changes the exercise id. |
||
12032 | * |
||
12033 | * @param int $id - exercise id |
||
12034 | */ |
||
12035 | private function updateId($id) |
||
12036 | { |
||
12037 | $this->iid = $id; |
||
12038 | } |
||
12039 | |||
12040 | /** |
||
12041 | * Sends a notification when a user ends an examn. |
||
12042 | * |
||
12043 | * @param array $question_list_answers |
||
12044 | * @param string $origin |
||
12045 | * @param array $user_info |
||
12046 | * @param string $url_email |
||
12047 | * @param array $teachers |
||
12048 | */ |
||
12049 | private function sendNotificationForOpenQuestions( |
||
12050 | $question_list_answers, |
||
12051 | $origin, |
||
12052 | $user_info, |
||
12053 | $url_email, |
||
12054 | $teachers |
||
12055 | ) { |
||
12056 | // Email configuration settings |
||
12057 | $courseCode = api_get_course_id(); |
||
12058 | $courseInfo = api_get_course_info($courseCode); |
||
12059 | $sessionId = api_get_session_id(); |
||
12060 | $sessionData = ''; |
||
12061 | if (!empty($sessionId)) { |
||
12062 | $sessionInfo = api_get_session_info($sessionId); |
||
12063 | if (!empty($sessionInfo)) { |
||
12064 | $sessionData = '<tr>' |
||
12065 | .'<td><em>'.get_lang('SessionName').'</em></td>' |
||
12066 | .'<td> <b>'.$sessionInfo['name'].'</b></td>' |
||
12067 | .'</tr>'; |
||
12068 | } |
||
12069 | } |
||
12070 | |||
12071 | $msg = get_lang('OpenQuestionsAttempted').'<br /><br />' |
||
12072 | .get_lang('AttemptDetails').' : <br /><br />' |
||
12073 | .'<table>' |
||
12074 | .'<tr>' |
||
12075 | .'<td><em>'.get_lang('CourseName').'</em></td>' |
||
12076 | .'<td> <b>#course#</b></td>' |
||
12077 | .'</tr>' |
||
12078 | .$sessionData |
||
12079 | .'<tr>' |
||
12080 | .'<td>'.get_lang('TestAttempted').'</td>' |
||
12081 | .'<td> #exercise#</td>' |
||
12082 | .'</tr>' |
||
12083 | .'<tr>' |
||
12084 | .'<td>'.get_lang('StudentName').'</td>' |
||
12085 | .'<td> #firstName# #lastName#</td>' |
||
12086 | .'</tr>' |
||
12087 | .'<tr>' |
||
12088 | .'<td>'.get_lang('StudentEmail').'</td>' |
||
12089 | .'<td> #mail#</td>' |
||
12090 | .'</tr>' |
||
12091 | .'</table>'; |
||
12092 | |||
12093 | $open_question_list = null; |
||
12094 | foreach ($question_list_answers as $item) { |
||
12095 | $question = $item['question']; |
||
12096 | $answer = $item['answer']; |
||
12097 | $answer_type = $item['answer_type']; |
||
12098 | if (!empty($question) && !empty($answer) && $answer_type == FREE_ANSWER) { |
||
12099 | $open_question_list .= |
||
12100 | '<tr>' |
||
12101 | .'<td width="220" valign="top" bgcolor="#E5EDF8"> '.get_lang('Question').'</td>' |
||
12102 | .'<td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td>' |
||
12103 | .'</tr>' |
||
12104 | .'<tr>' |
||
12105 | .'<td width="220" valign="top" bgcolor="#E5EDF8"> '.get_lang('Answer').'</td>' |
||
12106 | .'<td valign="top" bgcolor="#F3F3F3">'.$answer.'</td>' |
||
12107 | .'</tr>'; |
||
12108 | } |
||
12109 | } |
||
12110 | |||
12111 | if (!empty($open_question_list)) { |
||
12112 | $msg .= '<p><br />'.get_lang('OpenQuestionsAttemptedAre').' :</p>'. |
||
12113 | '<table width="730" height="136" border="0" cellpadding="3" cellspacing="3">'; |
||
12114 | $msg .= $open_question_list; |
||
12115 | $msg .= '</table><br />'; |
||
12116 | |||
12117 | $msg = str_replace('#exercise#', $this->exercise, $msg); |
||
12118 | $msg = str_replace('#firstName#', $user_info['firstname'], $msg); |
||
12119 | $msg = str_replace('#lastName#', $user_info['lastname'], $msg); |
||
12120 | $msg = str_replace('#mail#', $user_info['email'], $msg); |
||
12121 | $msg = str_replace( |
||
12122 | '#course#', |
||
12123 | Display::url($courseInfo['title'], $courseInfo['course_public_url'].'?id_session='.$sessionId), |
||
12124 | $msg |
||
12125 | ); |
||
12126 | |||
12127 | if ($origin != 'learnpath') { |
||
12128 | $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>'; |
||
12129 | } |
||
12130 | $msg = str_replace('#url#', $url_email, $msg); |
||
12131 | $subject = get_lang('OpenQuestionsAttempted'); |
||
12132 | |||
12133 | if (!empty($teachers)) { |
||
12134 | foreach ($teachers as $user_id => $teacher_data) { |
||
12135 | MessageManager::send_message_simple( |
||
12136 | $user_id, |
||
12137 | $subject, |
||
12138 | $msg |
||
12139 | ); |
||
12140 | } |
||
12141 | } |
||
12142 | } |
||
12143 | } |
||
12144 | |||
12145 | /** |
||
12146 | * Send notification for oral questions. |
||
12147 | * |
||
12148 | * @param array $question_list_answers |
||
12149 | * @param string $origin |
||
12150 | * @param int $exe_id |
||
12151 | * @param array $user_info |
||
12152 | * @param string $url_email |
||
12153 | * @param array $teachers |
||
12154 | */ |
||
12155 | private function sendNotificationForOralQuestions( |
||
12156 | $question_list_answers, |
||
12157 | $origin, |
||
12158 | $exe_id, |
||
12159 | $user_info, |
||
12160 | $url_email, |
||
12161 | $teachers |
||
12162 | ) { |
||
12163 | // Email configuration settings |
||
12164 | $courseCode = api_get_course_id(); |
||
12165 | $courseInfo = api_get_course_info($courseCode); |
||
12166 | $oral_question_list = null; |
||
12167 | foreach ($question_list_answers as $item) { |
||
12168 | $question = $item['question']; |
||
12169 | $file = $item['generated_oral_file']; |
||
12170 | $answer = $item['answer']; |
||
12171 | if (0 == $answer) { |
||
12172 | $answer = ''; |
||
12173 | } |
||
12174 | $answer_type = $item['answer_type']; |
||
12175 | if (!empty($question) && (!empty($answer) || !empty($file)) && $answer_type == ORAL_EXPRESSION) { |
||
12176 | if (!empty($file)) { |
||
12177 | $file = Display::url($file, $file); |
||
12178 | } |
||
12179 | $oral_question_list .= '<br /> |
||
12180 | <table width="730" height="136" border="0" cellpadding="3" cellspacing="3"> |
||
12181 | <tr> |
||
12182 | <td width="220" valign="top" bgcolor="#E5EDF8"> '.get_lang('Question').'</td> |
||
12183 | <td width="473" valign="top" bgcolor="#F3F3F3">'.$question.'</td> |
||
12184 | </tr> |
||
12185 | <tr> |
||
12186 | <td width="220" valign="top" bgcolor="#E5EDF8"> '.get_lang('Answer').'</td> |
||
12187 | <td valign="top" bgcolor="#F3F3F3">'.$answer.$file.'</td> |
||
12188 | </tr></table>'; |
||
12189 | } |
||
12190 | } |
||
12191 | |||
12192 | if (!empty($oral_question_list)) { |
||
12193 | $msg = get_lang('OralQuestionsAttempted').'<br /><br /> |
||
12194 | '.get_lang('AttemptDetails').' : <br /><br /> |
||
12195 | <table> |
||
12196 | <tr> |
||
12197 | <td><em>'.get_lang('CourseName').'</em></td> |
||
12198 | <td> <b>#course#</b></td> |
||
12199 | </tr> |
||
12200 | <tr> |
||
12201 | <td>'.get_lang('TestAttempted').'</td> |
||
12202 | <td> #exercise#</td> |
||
12203 | </tr> |
||
12204 | <tr> |
||
12205 | <td>'.get_lang('StudentName').'</td> |
||
12206 | <td> #firstName# #lastName#</td> |
||
12207 | </tr> |
||
12208 | <tr> |
||
12209 | <td>'.get_lang('StudentEmail').'</td> |
||
12210 | <td> #mail#</td> |
||
12211 | </tr> |
||
12212 | </table>'; |
||
12213 | $msg .= '<br />'.sprintf(get_lang('OralQuestionsAttemptedAreX'), $oral_question_list).'<br />'; |
||
12214 | $msg1 = str_replace("#exercise#", $this->exercise, $msg); |
||
12215 | $msg = str_replace("#firstName#", $user_info['firstname'], $msg1); |
||
12216 | $msg1 = str_replace("#lastName#", $user_info['lastname'], $msg); |
||
12217 | $msg = str_replace("#mail#", $user_info['email'], $msg1); |
||
12218 | $msg = str_replace("#course#", $courseInfo['name'], $msg1); |
||
12219 | |||
12220 | if (!in_array($origin, ['learnpath', 'embeddable', 'iframe'])) { |
||
12221 | $msg .= '<br /><a href="#url#">'.get_lang('ClickToCommentAndGiveFeedback').'</a>'; |
||
12222 | } |
||
12223 | $msg1 = str_replace("#url#", $url_email, $msg); |
||
12224 | $mail_content = $msg1; |
||
12225 | $subject = get_lang('OralQuestionsAttempted'); |
||
12226 | |||
12227 | if (!empty($teachers)) { |
||
12228 | foreach ($teachers as $user_id => $teacher_data) { |
||
12229 | MessageManager::send_message_simple( |
||
12230 | $user_id, |
||
12231 | $subject, |
||
12232 | $mail_content |
||
12233 | ); |
||
12234 | } |
||
12235 | } |
||
12236 | } |
||
12237 | } |
||
12238 | |||
12239 | /** |
||
12240 | * Returns an array with the media list. |
||
12241 | * |
||
12242 | * @param array question list |
||
12243 | * |
||
12244 | * @example there's 1 question with iid 5 that belongs to the media question with iid = 100 |
||
12245 | * <code> |
||
12246 | * array (size=2) |
||
12247 | * 999 => |
||
12248 | * array (size=3) |
||
12249 | * 0 => int 7 |
||
12250 | * 1 => int 6 |
||
12251 | * 2 => int 3254 |
||
12252 | * 100 => |
||
12253 | * array (size=1) |
||
12254 | * 0 => int 5 |
||
12255 | * </code> |
||
12256 | */ |
||
12257 | private function setMediaList($questionList) |
||
12258 | { |
||
12259 | $mediaList = []; |
||
12260 | /* |
||
12261 | * Media feature is not activated in 1.11.x |
||
12262 | if (!empty($questionList)) { |
||
12263 | foreach ($questionList as $questionId) { |
||
12264 | $objQuestionTmp = Question::read($questionId, $this->course_id); |
||
12265 | // If a media question exists |
||
12266 | if (isset($objQuestionTmp->parent_id) && $objQuestionTmp->parent_id != 0) { |
||
12267 | $mediaList[$objQuestionTmp->parent_id][] = $objQuestionTmp->iid; |
||
12268 | } else { |
||
12269 | // Always the last item |
||
12270 | $mediaList[999][] = $objQuestionTmp->iid; |
||
12271 | } |
||
12272 | } |
||
12273 | }*/ |
||
12274 | |||
12275 | $this->mediaList = $mediaList; |
||
12276 | } |
||
12277 | |||
12278 | /** |
||
12279 | * Returns the part of the form for the disabled results option. |
||
12280 | * |
||
12281 | * @return HTML_QuickForm_group |
||
12282 | */ |
||
12283 | private function setResultDisabledGroup(FormValidator $form) |
||
12284 | { |
||
12285 | $resultDisabledGroup = []; |
||
12286 | |||
12287 | $resultDisabledGroup[] = $form->createElement( |
||
12288 | 'radio', |
||
12289 | 'results_disabled', |
||
12290 | null, |
||
12291 | get_lang('ShowScoreAndRightAnswer'), |
||
12292 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS, |
||
12293 | ['id' => 'result_disabled_0'] |
||
12294 | ); |
||
12295 | |||
12296 | $warning = sprintf( |
||
12297 | get_lang('TheSettingXWillChangeToX'), |
||
12298 | get_lang('FeedbackType'), |
||
12299 | get_lang('NoFeedback') |
||
12300 | ); |
||
12301 | |||
12302 | $resultDisabledGroup[] = $form->createElement( |
||
12303 | 'radio', |
||
12304 | 'results_disabled', |
||
12305 | null, |
||
12306 | get_lang('DoNotShowScoreNorRightAnswer'), |
||
12307 | RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS, |
||
12308 | [ |
||
12309 | 'id' => 'result_disabled_1', |
||
12310 | //'onclick' => 'check_results_disabled()' |
||
12311 | 'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ', |
||
12312 | ] |
||
12313 | ); |
||
12314 | |||
12315 | $resultDisabledGroup[] = $form->createElement( |
||
12316 | 'radio', |
||
12317 | 'results_disabled', |
||
12318 | null, |
||
12319 | get_lang('OnlyShowScore'), |
||
12320 | RESULT_DISABLE_SHOW_SCORE_ONLY, |
||
12321 | [ |
||
12322 | 'id' => 'result_disabled_2', |
||
12323 | //'onclick' => 'check_results_disabled()' |
||
12324 | 'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ', |
||
12325 | ] |
||
12326 | ); |
||
12327 | |||
12328 | if (in_array($this->getFeedbackType(), [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP])) { |
||
12329 | return $form->addGroup( |
||
12330 | $resultDisabledGroup, |
||
12331 | null, |
||
12332 | get_lang('ShowResultsToStudents') |
||
12333 | ); |
||
12334 | } |
||
12335 | |||
12336 | $resultDisabledGroup[] = $form->createElement( |
||
12337 | 'radio', |
||
12338 | 'results_disabled', |
||
12339 | null, |
||
12340 | get_lang('ShowScoreEveryAttemptShowAnswersLastAttempt'), |
||
12341 | RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT, |
||
12342 | ['id' => 'result_disabled_4'] |
||
12343 | ); |
||
12344 | |||
12345 | $resultDisabledGroup[] = $form->createElement( |
||
12346 | 'radio', |
||
12347 | 'results_disabled', |
||
12348 | null, |
||
12349 | get_lang('DontShowScoreOnlyWhenUserFinishesAllAttemptsButShowFeedbackEachAttempt'), |
||
12350 | RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK, |
||
12351 | [ |
||
12352 | 'id' => 'result_disabled_5', |
||
12353 | //'onclick' => 'check_results_disabled()' |
||
12354 | 'onclick' => 'javascript:if(confirm('."'".addslashes($warning)."'".')) { check_results_disabled(); } else { return false;} ', |
||
12355 | ] |
||
12356 | ); |
||
12357 | |||
12358 | $resultDisabledGroup[] = $form->createElement( |
||
12359 | 'radio', |
||
12360 | 'results_disabled', |
||
12361 | null, |
||
12362 | get_lang('ExerciseRankingMode'), |
||
12363 | RESULT_DISABLE_RANKING, |
||
12364 | ['id' => 'result_disabled_6'] |
||
12365 | ); |
||
12366 | |||
12367 | $resultDisabledGroup[] = $form->createElement( |
||
12368 | 'radio', |
||
12369 | 'results_disabled', |
||
12370 | null, |
||
12371 | get_lang('ExerciseShowOnlyGlobalScoreAndCorrectAnswers'), |
||
12372 | RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, |
||
12373 | ['id' => 'result_disabled_7'] |
||
12374 | ); |
||
12375 | |||
12376 | $resultDisabledGroup[] = $form->createElement( |
||
12377 | 'radio', |
||
12378 | 'results_disabled', |
||
12379 | null, |
||
12380 | get_lang('ExerciseAutoEvaluationAndRankingMode'), |
||
12381 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, |
||
12382 | ['id' => 'result_disabled_8'] |
||
12383 | ); |
||
12384 | |||
12385 | $resultDisabledGroup[] = $form->createElement( |
||
12386 | 'radio', |
||
12387 | 'results_disabled', |
||
12388 | null, |
||
12389 | get_lang('ExerciseCategoriesRadarMode'), |
||
12390 | RESULT_DISABLE_RADAR, |
||
12391 | ['id' => 'result_disabled_9'] |
||
12392 | ); |
||
12393 | |||
12394 | $resultDisabledGroup[] = $form->createElement( |
||
12395 | 'radio', |
||
12396 | 'results_disabled', |
||
12397 | null, |
||
12398 | get_lang('ShowScoreEveryAttemptShowAnswersLastAttemptNoFeedback'), |
||
12399 | RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK, |
||
12400 | ['id' => 'result_disabled_10'] |
||
12401 | ); |
||
12402 | |||
12403 | return $form->addGroup( |
||
12404 | $resultDisabledGroup, |
||
12405 | null, |
||
12406 | get_lang('ShowResultsToStudents') |
||
12407 | ); |
||
12408 | } |
||
12409 | } |
||
12410 |
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: