1 | <?php |
||
2 | |||
3 | /* For licensing terms, see /license.txt */ |
||
4 | |||
5 | use Chamilo\CoreBundle\Component\Utils\ChamiloApi; |
||
6 | use Chamilo\CoreBundle\Entity\Session as SessionEntity; |
||
7 | use Chamilo\CoreBundle\Entity\TrackEExercises; |
||
8 | use Chamilo\CourseBundle\Entity\CQuizQuestion; |
||
9 | use ChamiloSession as Session; |
||
10 | |||
11 | /** |
||
12 | * Class ExerciseLib |
||
13 | * shows a question and its answers. |
||
14 | * |
||
15 | * @author Olivier Brouckaert <[email protected]> 2003-2004 |
||
16 | * @author Hubert Borderiou 2011-10-21 |
||
17 | * @author ivantcholakov2009-07-20 |
||
18 | * @author Julio Montoya |
||
19 | */ |
||
20 | class ExerciseLib |
||
21 | { |
||
22 | /** |
||
23 | * Shows a question. |
||
24 | * |
||
25 | * @param Exercise $exercise |
||
26 | * @param int $questionId $questionId question id |
||
27 | * @param bool $only_questions if true only show the questions, no exercise title |
||
28 | * @param bool $origin i.e = learnpath |
||
29 | * @param string $current_item current item from the list of questions |
||
30 | * @param bool $show_title |
||
31 | * @param bool $freeze |
||
32 | * @param array $user_choice |
||
33 | * @param bool $show_comment |
||
34 | * @param bool $show_answers |
||
35 | * |
||
36 | * @throws \Exception |
||
37 | * |
||
38 | * @return bool|int |
||
39 | */ |
||
40 | public static function showQuestion( |
||
41 | $exercise, |
||
42 | $questionId, |
||
43 | $only_questions = false, |
||
44 | $origin = false, |
||
45 | $current_item = '', |
||
46 | $show_title = true, |
||
47 | $freeze = false, |
||
48 | $user_choice = [], |
||
49 | $show_comment = false, |
||
50 | $show_answers = false, |
||
51 | $show_icon = false |
||
52 | ) { |
||
53 | $course_id = $exercise->course_id; |
||
54 | $exerciseId = $exercise->iid; |
||
55 | |||
56 | if (empty($course_id)) { |
||
57 | return ''; |
||
58 | } |
||
59 | $course = $exercise->course; |
||
60 | |||
61 | // Change false to true in the following line to enable answer hinting |
||
62 | $debug_mark_answer = $show_answers; |
||
63 | // Reads question information |
||
64 | if (!$objQuestionTmp = Question::read($questionId, $course)) { |
||
65 | // Question not found |
||
66 | return false; |
||
67 | } |
||
68 | |||
69 | $questionRequireAuth = WhispeakAuthPlugin::questionRequireAuthentify($questionId); |
||
70 | |||
71 | if ($exercise->getFeedbackType() != EXERCISE_FEEDBACK_TYPE_END) { |
||
72 | $show_comment = false; |
||
73 | } |
||
74 | |||
75 | $answerType = $objQuestionTmp->selectType(); |
||
76 | $pictureName = $objQuestionTmp->getPictureFilename(); |
||
77 | $s = ''; |
||
78 | if ($answerType != HOT_SPOT && |
||
79 | $answerType != HOT_SPOT_COMBINATION && |
||
80 | $answerType != HOT_SPOT_DELINEATION && |
||
81 | $answerType != ANNOTATION |
||
82 | ) { |
||
83 | // Question is not a hotspot |
||
84 | if (!$only_questions) { |
||
85 | $questionDescription = $objQuestionTmp->selectDescription(); |
||
86 | if ($show_title) { |
||
87 | if ($exercise->display_category_name) { |
||
88 | TestCategory::displayCategoryAndTitle($objQuestionTmp->iid); |
||
89 | } |
||
90 | $titleToDisplay = Security::remove_XSS($objQuestionTmp->getTitleToDisplay($current_item, $exerciseId)); |
||
91 | if ($answerType == READING_COMPREHENSION) { |
||
92 | // In READING_COMPREHENSION, the title of the question |
||
93 | // contains the question itself, which can only be |
||
94 | // shown at the end of the given time, so hide for now |
||
95 | $titleToDisplay = Display::div( |
||
96 | $current_item.'. '.get_lang('ReadingComprehension'), |
||
97 | ['class' => 'question_title'] |
||
98 | ); |
||
99 | } |
||
100 | echo $titleToDisplay; |
||
101 | } |
||
102 | |||
103 | if ($questionRequireAuth) { |
||
104 | WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise); |
||
105 | |||
106 | return false; |
||
107 | } |
||
108 | |||
109 | if (!empty($questionDescription) && $answerType != READING_COMPREHENSION) { |
||
110 | echo Display::div( |
||
111 | $questionDescription, |
||
112 | ['class' => 'question_description'] |
||
113 | ); |
||
114 | } |
||
115 | } |
||
116 | |||
117 | if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION, UPLOAD_ANSWER, ANSWER_IN_OFFICE_DOC]) && $freeze) { |
||
118 | return ''; |
||
119 | } |
||
120 | |||
121 | echo '<div class="question_options">'; |
||
122 | // construction of the Answer object (also gets all answers details) |
||
123 | $objAnswerTmp = new Answer($questionId, $course_id, $exercise); |
||
124 | $nbrAnswers = $objAnswerTmp->selectNbrAnswers(); |
||
125 | $quizQuestionOptions = Question::readQuestionOption($questionId, $course_id); |
||
126 | $selectableOptions = []; |
||
127 | |||
128 | for ($i = 1; $i <= $objAnswerTmp->nbrAnswers; $i++) { |
||
129 | $selectableOptions[$objAnswerTmp->iid[$i]] = $objAnswerTmp->answer[$i]; |
||
130 | } |
||
131 | |||
132 | // For "matching" type here, we need something a little bit special |
||
133 | // because the match between the suggestions and the answers cannot be |
||
134 | // done easily (suggestions and answers are in the same table), so we |
||
135 | // have to go through answers first (elems with "correct" value to 0). |
||
136 | $select_items = []; |
||
137 | //This will contain the number of answers on the left side. We call them |
||
138 | // suggestions here, for the sake of comprehensions, while the ones |
||
139 | // on the right side are called answers |
||
140 | $num_suggestions = 0; |
||
141 | switch ($answerType) { |
||
142 | case MATCHING: |
||
143 | case MATCHING_COMBINATION: |
||
144 | case DRAGGABLE: |
||
145 | case MATCHING_DRAGGABLE_COMBINATION: |
||
146 | case MATCHING_DRAGGABLE: |
||
147 | if ($answerType == DRAGGABLE) { |
||
148 | $isVertical = $objQuestionTmp->extra == 'v'; |
||
149 | $s .= ' |
||
150 | <div class="row"> |
||
151 | <div class="col-md-12"> |
||
152 | <p class="small">'.get_lang('DraggableQuestionIntro').'</p> |
||
153 | <ul class="exercise-draggable-answer list-unstyled ' |
||
154 | .($isVertical ? '' : 'list-inline').'" id="question-'.$questionId.'" data-question="' |
||
155 | .$questionId.'"> |
||
156 | '; |
||
157 | } else { |
||
158 | $s .= '<div id="drag'.$questionId.'_question" class="drag_question"> |
||
159 | <table class="table table-hover table-striped data_table">'; |
||
160 | } |
||
161 | |||
162 | // Iterate through answers. |
||
163 | $x = 1; |
||
164 | // Mark letters for each answer. |
||
165 | $letter = 'A'; |
||
166 | $answer_matching = []; |
||
167 | $cpt1 = []; |
||
168 | for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { |
||
169 | $answerCorrect = $objAnswerTmp->isCorrect($answerId); |
||
170 | $numAnswer = $objAnswerTmp->selectId($answerId); |
||
171 | if ($answerCorrect == 0) { |
||
172 | // options (A, B, C, ...) that will be put into the list-box |
||
173 | // have the "correct" field set to 0 because they are answer |
||
174 | $cpt1[$x] = $letter; |
||
175 | $answer_matching[$x] = $objAnswerTmp->selectAnswerById($numAnswer); |
||
176 | $x++; |
||
177 | $letter++; |
||
178 | } |
||
179 | } |
||
180 | |||
181 | $i = 1; |
||
182 | $select_items[0]['id'] = 0; |
||
183 | $select_items[0]['letter'] = '--'; |
||
184 | $select_items[0]['answer'] = ''; |
||
185 | foreach ($answer_matching as $id => $value) { |
||
186 | $select_items[$i]['id'] = $value['iid']; |
||
187 | $select_items[$i]['letter'] = $cpt1[$id]; |
||
188 | $select_items[$i]['answer'] = $value['answer']; |
||
189 | $i++; |
||
190 | } |
||
191 | |||
192 | $user_choice_array_position = []; |
||
193 | if (!empty($user_choice)) { |
||
194 | foreach ($user_choice as $item) { |
||
195 | $user_choice_array_position[$item['position']] = $item['answer']; |
||
196 | } |
||
197 | } |
||
198 | $num_suggestions = ($nbrAnswers - $x) + 1; |
||
199 | break; |
||
200 | case FREE_ANSWER: |
||
201 | $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null; |
||
202 | $form = new FormValidator('free_choice_'.$questionId); |
||
203 | $config = [ |
||
204 | 'ToolbarSet' => 'TestFreeAnswer', |
||
205 | 'id' => 'choice['.$questionId.']', |
||
206 | ]; |
||
207 | $form->addHtmlEditor( |
||
208 | 'choice['.$questionId.']', |
||
209 | null, |
||
210 | false, |
||
211 | false, |
||
212 | $config |
||
213 | ); |
||
214 | $form->setDefaults(["choice[".$questionId."]" => $fck_content]); |
||
215 | $s .= $form->returnForm(); |
||
216 | break; |
||
217 | case UPLOAD_ANSWER: |
||
218 | global $exe_id; |
||
219 | $answer = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null; |
||
220 | $path = '/upload_answer/'.$exe_id.'/'.$questionId.'/'; |
||
221 | $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=upload_answer&curdirpath='.$path; |
||
222 | $multipleForm = new FormValidator( |
||
223 | 'drag_drop', |
||
224 | 'post', |
||
225 | '#', |
||
226 | ['enctype' => 'multipart/form-data'] |
||
227 | ); |
||
228 | $iconDelete = Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL); |
||
229 | $multipleForm->addMultipleUpload($url); |
||
230 | $s .= '<script> |
||
231 | function setRemoveLink(dataContext) { |
||
232 | var removeLink = $("<a>", { |
||
233 | html: " '.addslashes($iconDelete).'", |
||
234 | href: "#", |
||
235 | click: function(e) { |
||
236 | e.preventDefault(); |
||
237 | dataContext.parent().remove(); |
||
238 | } |
||
239 | }); |
||
240 | dataContext.append(removeLink); |
||
241 | } |
||
242 | $(function() { |
||
243 | $("#input_file_upload").bind("fileuploaddone", function (e, data) { |
||
244 | $.each(data.result.files, function (index, file) { |
||
245 | if (file.name) { |
||
246 | var input = $("<input>", { |
||
247 | type: "hidden", |
||
248 | name: "uploadChoice['.$questionId.'][]", |
||
249 | value: file.name |
||
250 | }); |
||
251 | $(data.context.children()[index]).parent().append(input); |
||
252 | // set the remove link |
||
253 | setRemoveLink($(data.context.children()[index]).parent()); |
||
254 | } |
||
255 | }); |
||
256 | }); |
||
257 | }); |
||
258 | </script>'; |
||
259 | // Set default values |
||
260 | if (!empty($answer)) { |
||
261 | $userWebpath = UserManager::getUserPathById(api_get_user_id(), 'web').'my_files'.'/upload_answer/'.$exe_id.'/'.$questionId.'/'; |
||
262 | $userSyspath = UserManager::getUserPathById(api_get_user_id(), 'system').'my_files'.'/upload_answer/'.$exe_id.'/'.$questionId.'/'; |
||
263 | $filesNames = explode('|', $answer); |
||
264 | $icon = Display::return_icon('file_txt.gif'); |
||
265 | $default = ''; |
||
266 | foreach ($filesNames as $fileName) { |
||
267 | $fileName = Security::remove_XSS($fileName); |
||
268 | if (file_exists($userSyspath.$fileName)) { |
||
269 | $default .= '<a target="_blank" class="panel-image" href="'.$userWebpath.$fileName.'"><div class="row"><div class="col-sm-4">'.$icon.'</div><div class="col-sm-5 file_name">'.$fileName.'</div><input type="hidden" name="uploadChoice['.$questionId.'][]" value="'.$fileName.'"><div class="col-sm-3"></div></div></a>'; |
||
270 | } |
||
271 | } |
||
272 | $s .= '<script> |
||
273 | $(function() { |
||
274 | if ($("#files").length > 0) { |
||
275 | $("#files").html("'.addslashes($default).'"); |
||
276 | var links = $("#files").children(); |
||
277 | links.each(function(index) { |
||
278 | var dataContext = $(links[index]).find(".row"); |
||
279 | setRemoveLink(dataContext); |
||
280 | }); |
||
281 | } |
||
282 | }); |
||
283 | </script>'; |
||
284 | } |
||
285 | $s .= $multipleForm->returnForm(); |
||
286 | break; |
||
287 | case ANSWER_IN_OFFICE_DOC: |
||
288 | if ('true' === OnlyofficePlugin::create()->get('enable_onlyoffice_plugin')) { |
||
289 | global $exe_id; |
||
290 | if (!empty($objQuestionTmp->extra)) { |
||
291 | $fileUrl = api_get_course_path()."/exercises/onlyoffice/{$exerciseId}/{$questionId}/".$objQuestionTmp->extra; |
||
292 | $documentUrl = OnlyofficeTools::getPathToView($fileUrl, false, $exe_id, $questionId); |
||
293 | echo '<div class="office-doc-container">'; |
||
294 | echo "<iframe src='{$documentUrl}' width='100%' height='600' style='border:none;'></iframe>"; |
||
295 | echo '</div>'; |
||
296 | } else { |
||
297 | echo '<p>'.get_lang('NoOfficeDocProvided').'</p>'; |
||
298 | } |
||
299 | } else { |
||
300 | echo '<p>'.get_lang('OnlyOfficePluginRequired').'</p>'; |
||
301 | } |
||
302 | break; |
||
303 | case ORAL_EXPRESSION: |
||
304 | // Add nanog |
||
305 | if (api_get_setting('enable_record_audio') === 'true') { |
||
306 | //@todo pass this as a parameter |
||
307 | global $exercise_stat_info; |
||
308 | if (!empty($exercise_stat_info)) { |
||
309 | $objQuestionTmp->initFile( |
||
310 | api_get_session_id(), |
||
311 | api_get_user_id(), |
||
312 | $exercise_stat_info['exe_exo_id'], |
||
313 | $exercise_stat_info['exe_id'] |
||
314 | ); |
||
315 | } else { |
||
316 | $objQuestionTmp->initFile( |
||
317 | api_get_session_id(), |
||
318 | api_get_user_id(), |
||
319 | $exerciseId, |
||
320 | 'temp_exe' |
||
321 | ); |
||
322 | } |
||
323 | |||
324 | echo $objQuestionTmp->returnRecorder(); |
||
325 | } |
||
326 | $fileUrl = $objQuestionTmp->getFileUrl(); |
||
327 | if (isset($fileUrl)) { |
||
328 | $s .= ' |
||
329 | <div class="col-sm-4 col-sm-offset-4"> |
||
330 | <div class="form-group text-center"> |
||
331 | <audio src="'.$fileUrl.'" controls id="record-preview-'.$questionId.'-previous"></audio> |
||
332 | </div> |
||
333 | </div> |
||
334 | '; |
||
335 | } |
||
336 | $s .= '<script> |
||
337 | // The buttons are blocked waiting for the audio file to be uploaded |
||
338 | $(document).ajaxStart(function() { |
||
339 | $("button").attr("disabled", true); |
||
340 | $("button").attr("title", "'.get_lang('WaitingForTheAudioFileToBeUploaded').'"); |
||
341 | }); |
||
342 | $(document).ajaxComplete(function() { |
||
343 | $("button").attr("disabled", false); |
||
344 | $("button").removeAttr("title"); |
||
345 | }); |
||
346 | </script>'; |
||
347 | $form = new FormValidator('free_choice_'.$questionId); |
||
348 | $config = ['ToolbarSet' => 'TestFreeAnswer']; |
||
349 | |||
350 | $form->addHtml('<div id="'.'hide_description_'.$questionId.'_options" style="display: none;">'); |
||
351 | $form->addHtmlEditor( |
||
352 | "choice[$questionId]", |
||
353 | null, |
||
354 | false, |
||
355 | false, |
||
356 | $config |
||
357 | ); |
||
358 | $form->addHtml('</div>'); |
||
359 | $s .= $form->returnForm(); |
||
360 | break; |
||
361 | case MULTIPLE_ANSWER_DROPDOWN: |
||
362 | case MULTIPLE_ANSWER_DROPDOWN_COMBINATION: |
||
363 | if ($debug_mark_answer) { |
||
364 | $s .= '<p><strong>' |
||
365 | .( |
||
366 | MULTIPLE_ANSWER_DROPDOWN == $answerType |
||
367 | ? '<span class="pull-right">'.get_lang('Weighting').'</span>' |
||
368 | : '' |
||
369 | ) |
||
370 | .get_lang('CorrectAnswer').'</strong></p>'; |
||
371 | } |
||
372 | break; |
||
373 | } |
||
374 | |||
375 | // Now navigate through the possible answers, using the max number of |
||
376 | // answers for the question as a limiter |
||
377 | $lines_count = 1; // a counter for matching-type answers |
||
378 | if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE || |
||
379 | $answerType == MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE |
||
380 | ) { |
||
381 | $header = Display::tag('th', get_lang('Options')); |
||
382 | foreach ($objQuestionTmp->options as $item) { |
||
383 | if ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) { |
||
384 | if (in_array($item, $objQuestionTmp->options)) { |
||
385 | $header .= Display::tag('th', get_lang($item)); |
||
386 | } else { |
||
387 | $header .= Display::tag('th', $item); |
||
388 | } |
||
389 | } else { |
||
390 | $header .= Display::tag('th', $item); |
||
391 | } |
||
392 | } |
||
393 | if ($show_comment) { |
||
394 | $header .= Display::tag('th', get_lang('Feedback')); |
||
395 | } |
||
396 | $s .= '<table class="table table-hover table-striped">'; |
||
397 | $s .= Display::tag( |
||
398 | 'tr', |
||
399 | $header, |
||
400 | ['style' => 'text-align:left;'] |
||
401 | ); |
||
402 | } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
403 | $header = Display::tag('th', get_lang('Options'), ['width' => '50%']); |
||
404 | echo " |
||
405 | <script> |
||
406 | function RadioValidator(question_id, answer_id) |
||
407 | { |
||
408 | var ShowAlert = ''; |
||
409 | var typeRadioB = ''; |
||
410 | var AllFormElements = window.document.getElementById('exercise_form').elements; |
||
411 | |||
412 | for (i = 0; i < AllFormElements.length; i++) { |
||
413 | if (AllFormElements[i].type == 'radio') { |
||
414 | var ThisRadio = AllFormElements[i].name; |
||
415 | var ThisChecked = 'No'; |
||
416 | var AllRadioOptions = document.getElementsByName(ThisRadio); |
||
417 | |||
418 | for (x = 0; x < AllRadioOptions.length; x++) { |
||
419 | if (AllRadioOptions[x].checked && ThisChecked == 'No') { |
||
420 | ThisChecked = 'Yes'; |
||
421 | break; |
||
422 | } |
||
423 | } |
||
424 | |||
425 | var AlreadySearched = ShowAlert.indexOf(ThisRadio); |
||
426 | if (ThisChecked == 'No' && AlreadySearched == -1) { |
||
427 | ShowAlert = ShowAlert + ThisRadio; |
||
428 | } |
||
429 | } |
||
430 | } |
||
431 | if (ShowAlert != '') { |
||
432 | |||
433 | } else { |
||
434 | $('.question-validate-btn').removeAttr('disabled'); |
||
435 | } |
||
436 | } |
||
437 | |||
438 | function handleRadioRow(event, question_id, answer_id) { |
||
439 | var t = event.target; |
||
440 | if (t && t.tagName == 'INPUT') |
||
441 | return; |
||
442 | while (t && t.tagName != 'TD') { |
||
443 | t = t.parentElement; |
||
444 | } |
||
445 | var r = t.getElementsByTagName('INPUT')[0]; |
||
446 | r.click(); |
||
447 | RadioValidator(question_id, answer_id); |
||
448 | } |
||
449 | |||
450 | $(function() { |
||
451 | var ShowAlert = ''; |
||
452 | var typeRadioB = ''; |
||
453 | var question_id = $('input[name=question_id]').val(); |
||
454 | var AllFormElements = window.document.getElementById('exercise_form').elements; |
||
455 | |||
456 | for (i = 0; i < AllFormElements.length; i++) { |
||
457 | if (AllFormElements[i].type == 'radio') { |
||
458 | var ThisRadio = AllFormElements[i].name; |
||
459 | var ThisChecked = 'No'; |
||
460 | var AllRadioOptions = document.getElementsByName(ThisRadio); |
||
461 | |||
462 | for (x = 0; x < AllRadioOptions.length; x++) { |
||
463 | if (AllRadioOptions[x].checked && ThisChecked == 'No') { |
||
464 | ThisChecked = \"Yes\"; |
||
465 | break; |
||
466 | } |
||
467 | } |
||
468 | |||
469 | var AlreadySearched = ShowAlert.indexOf(ThisRadio); |
||
470 | if (ThisChecked == 'No' && AlreadySearched == -1) { |
||
471 | ShowAlert = ShowAlert + ThisRadio; |
||
472 | } |
||
473 | } |
||
474 | } |
||
475 | |||
476 | if (ShowAlert != '') { |
||
477 | $('.question-validate-btn').attr('disabled', 'disabled'); |
||
478 | } else { |
||
479 | $('.question-validate-btn').removeAttr('disabled'); |
||
480 | } |
||
481 | }); |
||
482 | </script>"; |
||
483 | |||
484 | foreach ($objQuestionTmp->optionsTitle as $item) { |
||
485 | if (in_array($item, $objQuestionTmp->optionsTitle)) { |
||
486 | $properties = []; |
||
487 | if ($item === 'Answers') { |
||
488 | $properties['colspan'] = 2; |
||
489 | $properties['style'] = 'background-color: #F56B2A; color: #ffffff;'; |
||
490 | } elseif ($item == 'DegreeOfCertaintyThatMyAnswerIsCorrect') { |
||
491 | $properties['colspan'] = 6; |
||
492 | $properties['style'] = 'background-color: #330066; color: #ffffff;'; |
||
493 | } |
||
494 | $header .= Display::tag('th', get_lang($item), $properties); |
||
495 | } else { |
||
496 | $header .= Display::tag('th', $item); |
||
497 | } |
||
498 | } |
||
499 | |||
500 | if ($show_comment) { |
||
501 | $header .= Display::tag('th', get_lang('Feedback')); |
||
502 | } |
||
503 | |||
504 | $s .= '<table class="table table-hover table-striped data_table">'; |
||
505 | $s .= Display::tag('tr', $header, ['style' => 'text-align:left;']); |
||
506 | |||
507 | // ajout de la 2eme ligne d'entête pour true/falss et les pourcentages de certitude |
||
508 | $header1 = Display::tag('th', ' '); |
||
509 | $cpt1 = 0; |
||
510 | foreach ($objQuestionTmp->options as $item) { |
||
511 | $colorBorder1 = $cpt1 == (count($objQuestionTmp->options) - 1) |
||
512 | ? '' : 'border-right: solid #FFFFFF 1px;'; |
||
513 | if ($item === 'True' || $item === 'False') { |
||
514 | $header1 .= Display::tag( |
||
515 | 'th', |
||
516 | get_lang($item), |
||
517 | ['style' => 'background-color: #F7C9B4; color: black;'.$colorBorder1] |
||
518 | ); |
||
519 | } else { |
||
520 | $header1 .= Display::tag( |
||
521 | 'th', |
||
522 | $item, |
||
523 | ['style' => 'background-color: #e6e6ff; color: black;padding:5px; '.$colorBorder1] |
||
524 | ); |
||
525 | } |
||
526 | $cpt1++; |
||
527 | } |
||
528 | if ($show_comment) { |
||
529 | $header1 .= Display::tag('th', ' '); |
||
530 | } |
||
531 | |||
532 | $s .= Display::tag('tr', $header1); |
||
533 | |||
534 | // add explanation |
||
535 | $header2 = Display::tag('th', ' '); |
||
536 | $descriptionList = [ |
||
537 | get_lang('DegreeOfCertaintyIDeclareMyIgnorance'), |
||
538 | get_lang('DegreeOfCertaintyIAmVeryUnsure'), |
||
539 | get_lang('DegreeOfCertaintyIAmUnsure'), |
||
540 | get_lang('DegreeOfCertaintyIAmPrettySure'), |
||
541 | get_lang('DegreeOfCertaintyIAmSure'), |
||
542 | get_lang('DegreeOfCertaintyIAmVerySure'), |
||
543 | ]; |
||
544 | $counter2 = 0; |
||
545 | foreach ($objQuestionTmp->options as $item) { |
||
546 | if ($item === 'True' || $item === 'False') { |
||
547 | $header2 .= Display::tag('td', |
||
548 | ' ', |
||
549 | ['style' => 'background-color: #F7E1D7; color: black;border-right: solid #FFFFFF 1px;']); |
||
550 | } else { |
||
551 | $color_border2 = ($counter2 == (count($objQuestionTmp->options) - 1)) ? |
||
552 | '' : 'border-right: solid #FFFFFF 1px;font-size:11px;'; |
||
553 | $header2 .= Display::tag( |
||
554 | 'td', |
||
555 | nl2br($descriptionList[$counter2]), |
||
556 | ['style' => 'background-color: #EFEFFC; color: black; width: 110px; text-align:center; |
||
557 | vertical-align: top; padding:5px; '.$color_border2]); |
||
558 | $counter2++; |
||
559 | } |
||
560 | } |
||
561 | if ($show_comment) { |
||
562 | $header2 .= Display::tag('th', ' '); |
||
563 | } |
||
564 | $s .= Display::tag('tr', $header2); |
||
565 | } |
||
566 | |||
567 | if ($show_comment) { |
||
568 | if (in_array( |
||
569 | $answerType, |
||
570 | [ |
||
571 | MULTIPLE_ANSWER, |
||
572 | MULTIPLE_ANSWER_COMBINATION, |
||
573 | UNIQUE_ANSWER, |
||
574 | UNIQUE_ANSWER_IMAGE, |
||
575 | UNIQUE_ANSWER_NO_OPTION, |
||
576 | GLOBAL_MULTIPLE_ANSWER, |
||
577 | ] |
||
578 | )) { |
||
579 | $header = Display::tag('th', get_lang('Options')); |
||
580 | if ($exercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END) { |
||
581 | $header .= Display::tag('th', get_lang('Feedback')); |
||
582 | } |
||
583 | $s .= '<table class="table table-hover table-striped">'; |
||
584 | $s .= Display::tag( |
||
585 | 'tr', |
||
586 | $header, |
||
587 | ['style' => 'text-align:left;'] |
||
588 | ); |
||
589 | } |
||
590 | } |
||
591 | |||
592 | $matching_correct_answer = 0; |
||
593 | $userChoiceList = []; |
||
594 | if (!empty($user_choice)) { |
||
595 | foreach ($user_choice as $item) { |
||
596 | $userChoiceList[] = $item['answer']; |
||
597 | } |
||
598 | } |
||
599 | |||
600 | $hidingClass = ''; |
||
601 | if ($answerType == READING_COMPREHENSION) { |
||
602 | $objQuestionTmp->setExerciseType($exercise->selectType()); |
||
603 | $objQuestionTmp->processText($objQuestionTmp->selectDescription()); |
||
604 | $hidingClass = 'hide-reading-answers'; |
||
605 | $s .= Display::div( |
||
606 | $objQuestionTmp->selectTitle(), |
||
607 | ['class' => 'question_title '.$hidingClass] |
||
608 | ); |
||
609 | } |
||
610 | |||
611 | $userStatus = STUDENT; |
||
612 | // Allows to do a remove_XSS in question of exercise with user status COURSEMANAGER |
||
613 | // see BT#18242 |
||
614 | if (api_get_configuration_value('question_exercise_html_strict_filtering')) { |
||
615 | $userStatus = COURSEMANAGERLOWSECURITY; |
||
616 | } |
||
617 | |||
618 | for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { |
||
619 | $answer = $objAnswerTmp->selectAnswer($answerId); |
||
620 | $answerCorrect = $objAnswerTmp->isCorrect($answerId); |
||
621 | $numAnswer = $objAnswerTmp->selectId($answerId); |
||
622 | $comment = Security::remove_XSS($objAnswerTmp->selectComment($answerId)); |
||
623 | $attributes = []; |
||
624 | |||
625 | switch ($answerType) { |
||
626 | case UNIQUE_ANSWER: |
||
627 | case UNIQUE_ANSWER_NO_OPTION: |
||
628 | case UNIQUE_ANSWER_IMAGE: |
||
629 | case READING_COMPREHENSION: |
||
630 | $input_id = 'choice-'.$questionId.'-'.$answerId; |
||
631 | if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) { |
||
632 | $attributes = [ |
||
633 | 'id' => $input_id, |
||
634 | 'checked' => 1, |
||
635 | 'selected' => 1, |
||
636 | ]; |
||
637 | } else { |
||
638 | $attributes = ['id' => $input_id]; |
||
639 | } |
||
640 | |||
641 | if ($debug_mark_answer) { |
||
642 | if ($answerCorrect) { |
||
643 | $attributes['checked'] = 1; |
||
644 | $attributes['selected'] = 1; |
||
645 | } |
||
646 | } |
||
647 | |||
648 | if ($show_comment) { |
||
649 | $s .= '<tr><td>'; |
||
650 | } |
||
651 | |||
652 | if ($answerType == UNIQUE_ANSWER_IMAGE) { |
||
653 | if ($show_comment) { |
||
654 | if (empty($comment)) { |
||
655 | $s .= '<div id="answer'.$questionId.$numAnswer.'" |
||
656 | class="exercise-unique-answer-image" style="text-align: center">'; |
||
657 | } else { |
||
658 | $s .= '<div id="answer'.$questionId.$numAnswer.'" |
||
659 | class="exercise-unique-answer-image col-xs-6 col-sm-12" |
||
660 | style="text-align: center">'; |
||
661 | } |
||
662 | } else { |
||
663 | $s .= '<div id="answer'.$questionId.$numAnswer.'" |
||
664 | class="exercise-unique-answer-image col-xs-6 col-md-3" |
||
665 | style="text-align: center">'; |
||
666 | } |
||
667 | } |
||
668 | |||
669 | if ($answerType != UNIQUE_ANSWER_IMAGE) { |
||
670 | $answer = Security::remove_XSS($answer, $userStatus); |
||
671 | } |
||
672 | $s .= Display::input( |
||
673 | 'hidden', |
||
674 | 'choice2['.$questionId.']', |
||
675 | '0' |
||
676 | ); |
||
677 | |||
678 | $answer_input = null; |
||
679 | $attributes['class'] = 'checkradios'; |
||
680 | if ($answerType == UNIQUE_ANSWER_IMAGE) { |
||
681 | $attributes['class'] = ''; |
||
682 | $attributes['style'] = 'display: none;'; |
||
683 | $answer = '<div class="thumbnail">'.$answer.'</div>'; |
||
684 | } |
||
685 | |||
686 | $answer_input .= '<label class="radio '.$hidingClass.'">'; |
||
687 | $answer_input .= Display::input( |
||
688 | 'radio', |
||
689 | 'choice['.$questionId.']', |
||
690 | $numAnswer, |
||
691 | $attributes |
||
692 | ); |
||
693 | $answer_input .= $answer; |
||
694 | $answer_input .= '</label>'; |
||
695 | |||
696 | if ($answerType == UNIQUE_ANSWER_IMAGE) { |
||
697 | $answer_input .= "</div>"; |
||
698 | } |
||
699 | |||
700 | if ($show_comment) { |
||
701 | $s .= $answer_input; |
||
702 | $s .= '</td>'; |
||
703 | $s .= '<td>'; |
||
704 | $s .= $comment; |
||
705 | $s .= '</td>'; |
||
706 | $s .= '</tr>'; |
||
707 | } else { |
||
708 | $s .= $answer_input; |
||
709 | } |
||
710 | break; |
||
711 | case MULTIPLE_ANSWER: |
||
712 | case MULTIPLE_ANSWER_TRUE_FALSE: |
||
713 | case GLOBAL_MULTIPLE_ANSWER: |
||
714 | case MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY: |
||
715 | $input_id = 'choice-'.$questionId.'-'.$answerId; |
||
716 | $answer = Security::remove_XSS($answer, $userStatus); |
||
717 | |||
718 | if (in_array($numAnswer, $userChoiceList)) { |
||
719 | $attributes = [ |
||
720 | 'id' => $input_id, |
||
721 | 'checked' => 1, |
||
722 | 'selected' => 1, |
||
723 | ]; |
||
724 | } else { |
||
725 | $attributes = ['id' => $input_id]; |
||
726 | } |
||
727 | |||
728 | if ($debug_mark_answer) { |
||
729 | if ($answerCorrect) { |
||
730 | $attributes['checked'] = 1; |
||
731 | $attributes['selected'] = 1; |
||
732 | } |
||
733 | } |
||
734 | |||
735 | if ($answerType == MULTIPLE_ANSWER || $answerType == GLOBAL_MULTIPLE_ANSWER) { |
||
736 | $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />'; |
||
737 | $attributes['class'] = 'checkradios'; |
||
738 | $answer_input = '<label class="checkbox">'; |
||
739 | $answer_input .= Display::input( |
||
740 | 'checkbox', |
||
741 | 'choice['.$questionId.']['.$numAnswer.']', |
||
742 | $numAnswer, |
||
743 | $attributes |
||
744 | ); |
||
745 | $answer_input .= $answer; |
||
746 | $answer_input .= '</label>'; |
||
747 | |||
748 | if ($show_comment) { |
||
749 | $s .= '<tr><td>'; |
||
750 | $s .= $answer_input; |
||
751 | $s .= '</td>'; |
||
752 | $s .= '<td>'; |
||
753 | $s .= $comment; |
||
754 | $s .= '</td>'; |
||
755 | $s .= '</tr>'; |
||
756 | } else { |
||
757 | $s .= $answer_input; |
||
758 | } |
||
759 | } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE) { |
||
760 | $myChoice = []; |
||
761 | if (!empty($userChoiceList)) { |
||
762 | foreach ($userChoiceList as $item) { |
||
763 | $item = explode(':', $item); |
||
764 | if (!empty($item)) { |
||
765 | $myChoice[$item[0]] = isset($item[1]) ? $item[1] : ''; |
||
766 | } |
||
767 | } |
||
768 | } |
||
769 | |||
770 | $s .= '<tr>'; |
||
771 | $s .= Display::tag('td', $answer); |
||
772 | |||
773 | if (!empty($quizQuestionOptions)) { |
||
774 | foreach ($quizQuestionOptions as $id => $item) { |
||
775 | if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) { |
||
776 | $attributes = [ |
||
777 | 'checked' => 1, |
||
778 | 'selected' => 1, |
||
779 | ]; |
||
780 | } else { |
||
781 | $attributes = []; |
||
782 | } |
||
783 | |||
784 | if ($debug_mark_answer) { |
||
785 | if ($id == $answerCorrect) { |
||
786 | $attributes['checked'] = 1; |
||
787 | $attributes['selected'] = 1; |
||
788 | } |
||
789 | } |
||
790 | $s .= Display::tag( |
||
791 | 'td', |
||
792 | Display::input( |
||
793 | 'radio', |
||
794 | 'choice['.$questionId.']['.$numAnswer.']', |
||
795 | $id, |
||
796 | $attributes |
||
797 | ), |
||
798 | ['style' => ''] |
||
799 | ); |
||
800 | } |
||
801 | } |
||
802 | |||
803 | if ($show_comment) { |
||
804 | $s .= '<td>'; |
||
805 | $s .= $comment; |
||
806 | $s .= '</td>'; |
||
807 | } |
||
808 | $s .= '</tr>'; |
||
809 | } elseif ($answerType == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
810 | $myChoice = []; |
||
811 | if (!empty($userChoiceList)) { |
||
812 | foreach ($userChoiceList as $item) { |
||
813 | $item = explode(':', $item); |
||
814 | $myChoice[$item[0]] = $item[1]; |
||
815 | } |
||
816 | } |
||
817 | $myChoiceDegreeCertainty = []; |
||
818 | if (!empty($userChoiceList)) { |
||
819 | foreach ($userChoiceList as $item) { |
||
820 | $item = explode(':', $item); |
||
821 | $myChoiceDegreeCertainty[$item[0]] = $item[2]; |
||
822 | } |
||
823 | } |
||
824 | $s .= '<tr>'; |
||
825 | $s .= Display::tag('td', $answer); |
||
826 | |||
827 | if (!empty($quizQuestionOptions)) { |
||
828 | foreach ($quizQuestionOptions as $id => $item) { |
||
829 | if (isset($myChoice[$numAnswer]) && $id == $myChoice[$numAnswer]) { |
||
830 | $attributes = ['checked' => 1, 'selected' => 1]; |
||
831 | } else { |
||
832 | $attributes = []; |
||
833 | } |
||
834 | $attributes['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')'; |
||
835 | |||
836 | // radio button selection |
||
837 | if (isset($myChoiceDegreeCertainty[$numAnswer]) && |
||
838 | $id == $myChoiceDegreeCertainty[$numAnswer] |
||
839 | ) { |
||
840 | $attributes1 = ['checked' => 1, 'selected' => 1]; |
||
841 | } else { |
||
842 | $attributes1 = []; |
||
843 | } |
||
844 | |||
845 | $attributes1['onChange'] = 'RadioValidator('.$questionId.', '.$numAnswer.')'; |
||
846 | |||
847 | if ($debug_mark_answer) { |
||
848 | if ($id == $answerCorrect) { |
||
849 | $attributes['checked'] = 1; |
||
850 | $attributes['selected'] = 1; |
||
851 | } |
||
852 | } |
||
853 | |||
854 | if ($item['name'] == 'True' || $item['name'] == 'False') { |
||
855 | $s .= Display::tag('td', |
||
856 | Display::input('radio', |
||
857 | 'choice['.$questionId.']['.$numAnswer.']', |
||
858 | $id, |
||
859 | $attributes |
||
860 | ), |
||
861 | ['style' => 'text-align:center; background-color:#F7E1D7;', |
||
862 | 'onclick' => 'handleRadioRow(event, '. |
||
863 | $questionId.', '. |
||
864 | $numAnswer.')', |
||
865 | ] |
||
866 | ); |
||
867 | } else { |
||
868 | $s .= Display::tag('td', |
||
869 | Display::input('radio', |
||
870 | 'choiceDegreeCertainty['.$questionId.']['.$numAnswer.']', |
||
871 | $id, |
||
872 | $attributes1 |
||
873 | ), |
||
874 | ['style' => 'text-align:center; background-color:#EFEFFC;', |
||
875 | 'onclick' => 'handleRadioRow(event, '. |
||
876 | $questionId.', '. |
||
877 | $numAnswer.')', |
||
878 | ] |
||
879 | ); |
||
880 | } |
||
881 | } |
||
882 | } |
||
883 | |||
884 | if ($show_comment) { |
||
885 | $s .= '<td>'; |
||
886 | $s .= $comment; |
||
887 | $s .= '</td>'; |
||
888 | } |
||
889 | $s .= '</tr>'; |
||
890 | } |
||
891 | break; |
||
892 | case MULTIPLE_ANSWER_COMBINATION: |
||
893 | // multiple answers |
||
894 | $input_id = 'choice-'.$questionId.'-'.$answerId; |
||
895 | |||
896 | if (in_array($numAnswer, $userChoiceList)) { |
||
897 | $attributes = [ |
||
898 | 'id' => $input_id, |
||
899 | 'checked' => 1, |
||
900 | 'selected' => 1, |
||
901 | ]; |
||
902 | } else { |
||
903 | $attributes = ['id' => $input_id]; |
||
904 | } |
||
905 | |||
906 | if ($debug_mark_answer) { |
||
907 | if ($answerCorrect) { |
||
908 | $attributes['checked'] = 1; |
||
909 | $attributes['selected'] = 1; |
||
910 | } |
||
911 | } |
||
912 | $answer = Security::remove_XSS($answer, $userStatus); |
||
913 | $answer_input = '<input type="hidden" name="choice2['.$questionId.']" value="0" />'; |
||
914 | $answer_input .= '<label class="checkbox">'; |
||
915 | $answer_input .= Display::input( |
||
916 | 'checkbox', |
||
917 | 'choice['.$questionId.']['.$numAnswer.']', |
||
918 | 1, |
||
919 | $attributes |
||
920 | ); |
||
921 | $answer_input .= $answer; |
||
922 | $answer_input .= '</label>'; |
||
923 | |||
924 | if ($show_comment) { |
||
925 | $s .= '<tr>'; |
||
926 | $s .= '<td>'; |
||
927 | $s .= $answer_input; |
||
928 | $s .= '</td>'; |
||
929 | $s .= '<td>'; |
||
930 | $s .= $comment; |
||
931 | $s .= '</td>'; |
||
932 | $s .= '</tr>'; |
||
933 | } else { |
||
934 | $s .= $answer_input; |
||
935 | } |
||
936 | break; |
||
937 | case MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE: |
||
938 | $s .= '<input type="hidden" name="choice2['.$questionId.']" value="0" />'; |
||
939 | $myChoice = []; |
||
940 | if (!empty($userChoiceList)) { |
||
941 | foreach ($userChoiceList as $item) { |
||
942 | $item = explode(':', $item); |
||
943 | if (isset($item[1]) && isset($item[0])) { |
||
944 | $myChoice[$item[0]] = $item[1]; |
||
945 | } |
||
946 | } |
||
947 | } |
||
948 | |||
949 | $answer = Security::remove_XSS($answer, $userStatus); |
||
950 | $s .= '<tr>'; |
||
951 | $s .= Display::tag('td', $answer); |
||
952 | foreach ($objQuestionTmp->options as $key => $item) { |
||
953 | if (isset($myChoice[$numAnswer]) && $key == $myChoice[$numAnswer]) { |
||
954 | $attributes = [ |
||
955 | 'checked' => 1, |
||
956 | 'selected' => 1, |
||
957 | ]; |
||
958 | } else { |
||
959 | $attributes = []; |
||
960 | } |
||
961 | |||
962 | if ($debug_mark_answer) { |
||
963 | if ($key == $answerCorrect) { |
||
964 | $attributes['checked'] = 1; |
||
965 | $attributes['selected'] = 1; |
||
966 | } |
||
967 | } |
||
968 | $s .= Display::tag( |
||
969 | 'td', |
||
970 | Display::input( |
||
971 | 'radio', |
||
972 | 'choice['.$questionId.']['.$numAnswer.']', |
||
973 | $key, |
||
974 | $attributes |
||
975 | ) |
||
976 | ); |
||
977 | } |
||
978 | |||
979 | if ($show_comment) { |
||
980 | $s .= '<td>'; |
||
981 | $s .= $comment; |
||
982 | $s .= '</td>'; |
||
983 | } |
||
984 | $s .= '</tr>'; |
||
985 | break; |
||
986 | case FILL_IN_BLANKS: |
||
987 | case FILL_IN_BLANKS_COMBINATION: |
||
988 | // display the question, with field empty, for student to fill it, |
||
989 | // or filled to display the answer in the Question preview of the exercise/admin.php page |
||
990 | $displayForStudent = true; |
||
991 | $listAnswerInfo = FillBlanks::getAnswerInfo($answer); |
||
992 | // Correct answers |
||
993 | $correctAnswerList = $listAnswerInfo['words']; |
||
994 | // Student's answer |
||
995 | $studentAnswerList = []; |
||
996 | if (isset($user_choice[0]['answer'])) { |
||
997 | $arrayStudentAnswer = FillBlanks::getAnswerInfo( |
||
998 | $user_choice[0]['answer'], |
||
999 | true |
||
1000 | ); |
||
1001 | $studentAnswerList = $arrayStudentAnswer['student_answer']; |
||
1002 | } |
||
1003 | |||
1004 | // If the question must be shown with the answer (in page exercise/admin.php) |
||
1005 | // for teacher preview set the student-answer to the correct answer |
||
1006 | if ($debug_mark_answer) { |
||
1007 | $studentAnswerList = $correctAnswerList; |
||
1008 | $displayForStudent = false; |
||
1009 | } |
||
1010 | |||
1011 | if (!empty($correctAnswerList) && !empty($studentAnswerList)) { |
||
1012 | $answer = ''; |
||
1013 | for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) { |
||
1014 | // display the common word |
||
1015 | $answer .= $listAnswerInfo['common_words'][$i]; |
||
1016 | // display the blank word |
||
1017 | $correctItem = $listAnswerInfo['words'][$i]; |
||
1018 | if (isset($studentAnswerList[$i])) { |
||
1019 | // If student already started this test and answered this question, |
||
1020 | // fill the blank with his previous answers |
||
1021 | // may be "" if student viewed the question, but did not fill the blanks |
||
1022 | $correctItem = $studentAnswerList[$i]; |
||
1023 | } |
||
1024 | $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px'; |
||
1025 | $answer .= FillBlanks::getFillTheBlankHtml( |
||
1026 | $current_item, |
||
1027 | $questionId, |
||
1028 | $correctItem, |
||
1029 | $attributes, |
||
1030 | $answer, |
||
1031 | $listAnswerInfo, |
||
1032 | $displayForStudent, |
||
1033 | $i |
||
1034 | ); |
||
1035 | } |
||
1036 | // display the last common word |
||
1037 | $answer .= $listAnswerInfo['common_words'][$i]; |
||
1038 | } else { |
||
1039 | // display empty [input] with the right width for student to fill it |
||
1040 | $answer = ''; |
||
1041 | for ($i = 0; $i < count($listAnswerInfo['common_words']) - 1; $i++) { |
||
1042 | // display the common words |
||
1043 | $answer .= $listAnswerInfo['common_words'][$i]; |
||
1044 | // display the blank word |
||
1045 | $attributes['style'] = 'width:'.$listAnswerInfo['input_size'][$i].'px'; |
||
1046 | $answer .= FillBlanks::getFillTheBlankHtml( |
||
1047 | $current_item, |
||
1048 | $questionId, |
||
1049 | '', |
||
1050 | $attributes, |
||
1051 | $answer, |
||
1052 | $listAnswerInfo, |
||
1053 | $displayForStudent, |
||
1054 | $i |
||
1055 | ); |
||
1056 | } |
||
1057 | // display the last common word |
||
1058 | $answer .= $listAnswerInfo['common_words'][$i]; |
||
1059 | } |
||
1060 | $s .= $answer; |
||
1061 | break; |
||
1062 | case CALCULATED_ANSWER: |
||
1063 | /* |
||
1064 | * In the CALCULATED_ANSWER test |
||
1065 | * you mustn't have [ and ] in the textarea |
||
1066 | * you mustn't have @@ in the textarea |
||
1067 | * the text to find mustn't be empty or contains only spaces |
||
1068 | * the text to find mustn't contains HTML tags |
||
1069 | * the text to find mustn't contains char " |
||
1070 | */ |
||
1071 | if (null !== $origin) { |
||
1072 | global $exe_id; |
||
1073 | $exe_id = (int) $exe_id; |
||
1074 | $trackAttempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
1075 | $sql = "SELECT answer FROM $trackAttempts |
||
1076 | WHERE exe_id = $exe_id AND question_id = $questionId"; |
||
1077 | $rsLastAttempt = Database::query($sql); |
||
1078 | $rowLastAttempt = Database::fetch_array($rsLastAttempt); |
||
1079 | |||
1080 | $answer = null; |
||
1081 | if (isset($rowLastAttempt['answer'])) { |
||
1082 | $answer = $rowLastAttempt['answer']; |
||
1083 | $answerParts = explode(':::', $answer); |
||
1084 | if (isset($answerParts[1])) { |
||
1085 | $answer = $answerParts[0]; |
||
1086 | $calculatedAnswerList[$questionId] = $answerParts[1]; |
||
1087 | Session::write('calculatedAnswerId', $calculatedAnswerList); |
||
1088 | } |
||
1089 | } else { |
||
1090 | $calculatedAnswerList = Session::read('calculatedAnswerId'); |
||
1091 | if (!isset($calculatedAnswerList[$questionId])) { |
||
1092 | $calculatedAnswerList[$questionId] = mt_rand(1, $nbrAnswers); |
||
1093 | Session::write('calculatedAnswerId', $calculatedAnswerList); |
||
1094 | } |
||
1095 | $answer = $objAnswerTmp->selectAnswer($calculatedAnswerList[$questionId]); |
||
1096 | } |
||
1097 | } |
||
1098 | |||
1099 | [$answer] = explode('@@', $answer); |
||
1100 | // $correctAnswerList array of array with correct answers 0=> [0=>[\p] 1=>[plop]] |
||
1101 | api_preg_match_all( |
||
1102 | '/\[[^]]+\]/', |
||
1103 | $answer, |
||
1104 | $correctAnswerList |
||
1105 | ); |
||
1106 | |||
1107 | // get student answer to display it if student go back |
||
1108 | // to previous calculated answer question in a test |
||
1109 | if (isset($user_choice[0]['answer'])) { |
||
1110 | api_preg_match_all( |
||
1111 | '/\[[^]]+\]/', |
||
1112 | $answer, |
||
1113 | $studentAnswerList |
||
1114 | ); |
||
1115 | $studentAnswerListToClean = $studentAnswerList[0]; |
||
1116 | $studentAnswerList = []; |
||
1117 | |||
1118 | $maxStudents = count($studentAnswerListToClean); |
||
1119 | for ($i = 0; $i < $maxStudents; $i++) { |
||
1120 | $answerCorrected = $studentAnswerListToClean[$i]; |
||
1121 | $answerCorrected = api_preg_replace( |
||
1122 | '| / <font color="green"><b>.*$|', |
||
1123 | '', |
||
1124 | $answerCorrected |
||
1125 | ); |
||
1126 | $answerCorrected = api_preg_replace( |
||
1127 | '/^\[/', |
||
1128 | '', |
||
1129 | $answerCorrected |
||
1130 | ); |
||
1131 | $answerCorrected = api_preg_replace( |
||
1132 | '|^<font color="red"><s>|', |
||
1133 | '', |
||
1134 | $answerCorrected |
||
1135 | ); |
||
1136 | $answerCorrected = api_preg_replace( |
||
1137 | '|</s></font>$|', |
||
1138 | '', |
||
1139 | $answerCorrected |
||
1140 | ); |
||
1141 | $answerCorrected = '['.$answerCorrected.']'; |
||
1142 | $studentAnswerList[] = $answerCorrected; |
||
1143 | } |
||
1144 | } |
||
1145 | |||
1146 | // If display preview of answer in test view for exemple, |
||
1147 | // set the student answer to the correct answers |
||
1148 | if ($debug_mark_answer) { |
||
1149 | // contain the rights answers surronded with brackets |
||
1150 | $studentAnswerList = $correctAnswerList[0]; |
||
1151 | } |
||
1152 | |||
1153 | /* |
||
1154 | Split the response by bracket |
||
1155 | tabComments is an array with text surrounding the text to find |
||
1156 | we add a space before and after the answerQuestion to be sure to |
||
1157 | have a block of text before and after [xxx] patterns |
||
1158 | so we have n text to find ([xxx]) and n+1 block of texts before, |
||
1159 | between and after the text to find |
||
1160 | */ |
||
1161 | $tabComments = api_preg_split( |
||
1162 | '/\[[^]]+\]/', |
||
1163 | ' '.$answer.' ' |
||
1164 | ); |
||
1165 | if (!empty($correctAnswerList) && !empty($studentAnswerList)) { |
||
1166 | $answer = ''; |
||
1167 | $i = 0; |
||
1168 | foreach ($studentAnswerList as $studentItem) { |
||
1169 | // Remove surrounding brackets |
||
1170 | $studentResponse = api_substr( |
||
1171 | $studentItem, |
||
1172 | 1, |
||
1173 | api_strlen($studentItem) - 2 |
||
1174 | ); |
||
1175 | $size = strlen($studentItem); |
||
1176 | $attributes['class'] = self::detectInputAppropriateClass($size); |
||
1177 | $answer .= $tabComments[$i]. |
||
1178 | Display::input( |
||
1179 | 'text', |
||
1180 | "choice[$questionId][]", |
||
1181 | $studentResponse, |
||
1182 | $attributes |
||
1183 | ); |
||
1184 | $i++; |
||
1185 | } |
||
1186 | $answer .= $tabComments[$i]; |
||
1187 | } else { |
||
1188 | // display exercise with empty input fields |
||
1189 | // every [xxx] are replaced with an empty input field |
||
1190 | foreach ($correctAnswerList[0] as $item) { |
||
1191 | $size = strlen($item); |
||
1192 | $attributes['class'] = self::detectInputAppropriateClass($size); |
||
1193 | if (EXERCISE_FEEDBACK_TYPE_POPUP == $exercise->getFeedbackType()) { |
||
1194 | $attributes['id'] = "question_$questionId"; |
||
1195 | $attributes['class'] .= ' checkCalculatedQuestionOnEnter '; |
||
1196 | } |
||
1197 | |||
1198 | $answer = str_replace( |
||
1199 | $item, |
||
1200 | Display::input( |
||
1201 | 'text', |
||
1202 | "choice[$questionId][]", |
||
1203 | '', |
||
1204 | $attributes |
||
1205 | ), |
||
1206 | $answer |
||
1207 | ); |
||
1208 | } |
||
1209 | } |
||
1210 | if (null !== $origin) { |
||
1211 | $s = $answer; |
||
1212 | break; |
||
1213 | } else { |
||
1214 | $s .= $answer; |
||
1215 | } |
||
1216 | break; |
||
1217 | case MATCHING: |
||
1218 | case MATCHING_COMBINATION: |
||
1219 | // matching type, showing suggestions and answers |
||
1220 | // TODO: replace $answerId by $numAnswer |
||
1221 | if ($answerCorrect != 0) { |
||
1222 | // only show elements to be answered (not the contents of |
||
1223 | // the select boxes, who are correct = 0) |
||
1224 | $s .= '<tr><td width="45%" valign="top">'; |
||
1225 | $parsed_answer = $answer; |
||
1226 | // Left part questions |
||
1227 | $s .= '<p class="indent">'.$lines_count.'. '.$parsed_answer.'</p></td>'; |
||
1228 | // Middle part (matches selects) |
||
1229 | // Id of select is # question + # of option |
||
1230 | $s .= '<td width="10%" valign="top" align="center"> |
||
1231 | <div class="select-matching"> |
||
1232 | <select |
||
1233 | id="choice_id_'.$current_item.'_'.$lines_count.'" |
||
1234 | name="choice['.$questionId.']['.$numAnswer.']">'; |
||
1235 | |||
1236 | // fills the list-box |
||
1237 | foreach ($select_items as $key => $val) { |
||
1238 | // set $debug_mark_answer to true at function start to |
||
1239 | // show the correct answer with a suffix '-x' |
||
1240 | $selected = ''; |
||
1241 | if ($debug_mark_answer) { |
||
1242 | if ($val['id'] == $answerCorrect) { |
||
1243 | $selected = 'selected="selected"'; |
||
1244 | } |
||
1245 | } |
||
1246 | //$user_choice_array_position |
||
1247 | if (isset($user_choice_array_position[$numAnswer]) && |
||
1248 | $val['id'] == $user_choice_array_position[$numAnswer] |
||
1249 | ) { |
||
1250 | $selected = 'selected="selected"'; |
||
1251 | } |
||
1252 | $s .= '<option value="'.$val['id'].'" '.$selected.'>'.$val['letter'].'</option>'; |
||
1253 | } |
||
1254 | |||
1255 | $s .= '</select></div></td><td width="5%" class="separate"> </td>'; |
||
1256 | $s .= '<td width="40%" valign="top" >'; |
||
1257 | if (isset($select_items[$lines_count])) { |
||
1258 | $s .= '<div class="text-right"> |
||
1259 | <p class="indent">'. |
||
1260 | $select_items[$lines_count]['letter'].'. '. |
||
1261 | $select_items[$lines_count]['answer'].' |
||
1262 | </p> |
||
1263 | </div>'; |
||
1264 | } else { |
||
1265 | $s .= ' '; |
||
1266 | } |
||
1267 | $s .= '</td>'; |
||
1268 | $s .= '</tr>'; |
||
1269 | $lines_count++; |
||
1270 | // If the left side of the "matching" has been completely |
||
1271 | // shown but the right side still has values to show... |
||
1272 | if (($lines_count - 1) == $num_suggestions) { |
||
1273 | // if it remains answers to shown at the right side |
||
1274 | while (isset($select_items[$lines_count])) { |
||
1275 | $s .= '<tr> |
||
1276 | <td colspan="2"></td> |
||
1277 | <td valign="top">'; |
||
1278 | $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '. |
||
1279 | $select_items[$lines_count]['answer']; |
||
1280 | $s .= "</td> |
||
1281 | </tr>"; |
||
1282 | $lines_count++; |
||
1283 | } |
||
1284 | } |
||
1285 | $matching_correct_answer++; |
||
1286 | } |
||
1287 | break; |
||
1288 | case DRAGGABLE: |
||
1289 | if ($answerCorrect) { |
||
1290 | $windowId = $questionId.'_'.$lines_count; |
||
1291 | $s .= '<li class="touch-items" id="'.$windowId.'">'; |
||
1292 | $s .= Display::div( |
||
1293 | $answer, |
||
1294 | [ |
||
1295 | 'id' => "window_$windowId", |
||
1296 | 'class' => "window{$questionId}_question_draggable exercise-draggable-answer-option", |
||
1297 | ] |
||
1298 | ); |
||
1299 | |||
1300 | $draggableSelectOptions = []; |
||
1301 | $selectedValue = 0; |
||
1302 | $selectedIndex = 0; |
||
1303 | if ($user_choice) { |
||
1304 | foreach ($user_choice as $userChoiceKey => $chosen) { |
||
1305 | $userChoiceKey++; |
||
1306 | if ($lines_count != $userChoiceKey) { |
||
1307 | continue; |
||
1308 | } |
||
1309 | /*if ($answerCorrect != $chosen['answer']) { |
||
1310 | continue; |
||
1311 | }*/ |
||
1312 | $selectedValue = $chosen['answer']; |
||
1313 | } |
||
1314 | } |
||
1315 | foreach ($select_items as $key => $select_item) { |
||
1316 | $draggableSelectOptions[$select_item['id']] = $select_item['letter']; |
||
1317 | } |
||
1318 | |||
1319 | foreach ($draggableSelectOptions as $value => $text) { |
||
1320 | if ($value == $selectedValue) { |
||
1321 | break; |
||
1322 | } |
||
1323 | $selectedIndex++; |
||
1324 | } |
||
1325 | |||
1326 | $s .= Display::select( |
||
1327 | "choice[$questionId][$numAnswer]", |
||
1328 | $draggableSelectOptions, |
||
1329 | $selectedValue, |
||
1330 | [ |
||
1331 | 'id' => "window_{$windowId}_select", |
||
1332 | 'class' => 'select_option hidden', |
||
1333 | ], |
||
1334 | false |
||
1335 | ); |
||
1336 | |||
1337 | if ($selectedValue && $selectedIndex) { |
||
1338 | $s .= " |
||
1339 | <script> |
||
1340 | $(function() { |
||
1341 | DraggableAnswer.deleteItem( |
||
1342 | $('#{$questionId}_$lines_count'), |
||
1343 | $('#drop_{$questionId}_{$selectedIndex}') |
||
1344 | ); |
||
1345 | }); |
||
1346 | </script> |
||
1347 | "; |
||
1348 | } |
||
1349 | |||
1350 | if (isset($select_items[$lines_count])) { |
||
1351 | $s .= Display::div( |
||
1352 | Display::tag( |
||
1353 | 'b', |
||
1354 | $select_items[$lines_count]['letter'] |
||
1355 | ).$select_items[$lines_count]['answer'], |
||
1356 | [ |
||
1357 | 'id' => "window_{$windowId}_answer", |
||
1358 | 'class' => 'hidden', |
||
1359 | ] |
||
1360 | ); |
||
1361 | } else { |
||
1362 | $s .= ' '; |
||
1363 | } |
||
1364 | |||
1365 | $lines_count++; |
||
1366 | if (($lines_count - 1) == $num_suggestions) { |
||
1367 | while (isset($select_items[$lines_count])) { |
||
1368 | $s .= Display::tag('b', $select_items[$lines_count]['letter']); |
||
1369 | $s .= $select_items[$lines_count]['answer']; |
||
1370 | $lines_count++; |
||
1371 | } |
||
1372 | } |
||
1373 | |||
1374 | $matching_correct_answer++; |
||
1375 | $s .= '</li>'; |
||
1376 | } |
||
1377 | break; |
||
1378 | case MATCHING_DRAGGABLE_COMBINATION: |
||
1379 | case MATCHING_DRAGGABLE: |
||
1380 | if ($answerId == 1) { |
||
1381 | echo $objAnswerTmp->getJs(); |
||
1382 | } |
||
1383 | if ($answerCorrect != 0) { |
||
1384 | $windowId = "{$questionId}_{$lines_count}"; |
||
1385 | $s .= <<<HTML |
||
1386 | <tr> |
||
1387 | <td width="45%"> |
||
1388 | <div id="window_{$windowId}" |
||
1389 | class="window window_left_question window{$questionId}_question"> |
||
1390 | <strong>$lines_count.</strong> |
||
1391 | $answer |
||
1392 | </div> |
||
1393 | </td> |
||
1394 | <td width="10%"> |
||
1395 | HTML; |
||
1396 | |||
1397 | $draggableSelectOptions = []; |
||
1398 | $selectedValue = 0; |
||
1399 | $selectedIndex = 0; |
||
1400 | |||
1401 | if ($user_choice) { |
||
1402 | foreach ($user_choice as $chosen) { |
||
1403 | if ($numAnswer == $chosen['position']) { |
||
1404 | $selectedValue = $chosen['answer']; |
||
1405 | break; |
||
1406 | } |
||
1407 | } |
||
1408 | } |
||
1409 | |||
1410 | foreach ($select_items as $key => $selectItem) { |
||
1411 | $draggableSelectOptions[$selectItem['id']] = $selectItem['letter']; |
||
1412 | } |
||
1413 | |||
1414 | foreach ($draggableSelectOptions as $value => $text) { |
||
1415 | if ($value == $selectedValue) { |
||
1416 | break; |
||
1417 | } |
||
1418 | $selectedIndex++; |
||
1419 | } |
||
1420 | |||
1421 | $s .= Display::select( |
||
1422 | "choice[$questionId][$numAnswer]", |
||
1423 | $draggableSelectOptions, |
||
1424 | $selectedValue, |
||
1425 | [ |
||
1426 | 'id' => "window_{$windowId}_select", |
||
1427 | 'class' => 'hidden', |
||
1428 | ], |
||
1429 | false |
||
1430 | ); |
||
1431 | |||
1432 | if (!empty($answerCorrect) && !empty($selectedValue)) { |
||
1433 | // Show connect if is not freeze (question preview) |
||
1434 | if (!$freeze) { |
||
1435 | $s .= " |
||
1436 | <script> |
||
1437 | $(function() { |
||
1438 | $(window).on('load', function() { |
||
1439 | jsPlumb.connect({ |
||
1440 | source: 'window_$windowId', |
||
1441 | target: 'window_{$questionId}_{$selectedIndex}_answer', |
||
1442 | endpoint: ['Blank', {radius: 15}], |
||
1443 | anchors: ['RightMiddle', 'LeftMiddle'], |
||
1444 | paintStyle: {strokeStyle: '#8A8888', lineWidth: 8}, |
||
1445 | connector: [ |
||
1446 | MatchingDraggable.connectorType, |
||
1447 | {curvines: MatchingDraggable.curviness} |
||
1448 | ] |
||
1449 | }); |
||
1450 | }); |
||
1451 | }); |
||
1452 | </script> |
||
1453 | "; |
||
1454 | } |
||
1455 | } |
||
1456 | |||
1457 | $s .= '</td><td width="45%">'; |
||
1458 | if (isset($select_items[$lines_count])) { |
||
1459 | $s .= <<<HTML |
||
1460 | <div id="window_{$windowId}_answer" class="window window_right_question"> |
||
1461 | <strong>{$select_items[$lines_count]['letter']}.</strong> |
||
1462 | {$select_items[$lines_count]['answer']} |
||
1463 | </div> |
||
1464 | HTML; |
||
1465 | } else { |
||
1466 | $s .= ' '; |
||
1467 | } |
||
1468 | |||
1469 | $s .= '</td></tr>'; |
||
1470 | $lines_count++; |
||
1471 | if (($lines_count - 1) == $num_suggestions) { |
||
1472 | while (isset($select_items[$lines_count])) { |
||
1473 | $s .= <<<HTML |
||
1474 | <tr> |
||
1475 | <td colspan="2"></td> |
||
1476 | <td> |
||
1477 | <strong>{$select_items[$lines_count]['letter']}</strong> |
||
1478 | {$select_items[$lines_count]['answer']} |
||
1479 | </td> |
||
1480 | </tr> |
||
1481 | HTML; |
||
1482 | $lines_count++; |
||
1483 | } |
||
1484 | } |
||
1485 | $matching_correct_answer++; |
||
1486 | } |
||
1487 | break; |
||
1488 | case MULTIPLE_ANSWER_DROPDOWN: |
||
1489 | case MULTIPLE_ANSWER_DROPDOWN_COMBINATION: |
||
1490 | if ($debug_mark_answer && $answerCorrect) { |
||
1491 | $s .= '<p>' |
||
1492 | .( |
||
1493 | MULTIPLE_ANSWER_DROPDOWN == $answerType |
||
1494 | ? '<span class="pull-right">'.$objAnswerTmp->weighting[$answerId].'</span>' |
||
1495 | : '' |
||
1496 | ) |
||
1497 | .Display::returnFontAwesomeIcon('check-square-o', '', true); |
||
1498 | $s .= Security::remove_XSS($objAnswerTmp->answer[$answerId]).'</p>'; |
||
1499 | } |
||
1500 | break; |
||
1501 | } |
||
1502 | } |
||
1503 | |||
1504 | if (in_array($answerType, [MULTIPLE_ANSWER_DROPDOWN, MULTIPLE_ANSWER_DROPDOWN_COMBINATION]) |
||
1505 | && !$debug_mark_answer |
||
1506 | ) { |
||
1507 | $userChoiceList = array_unique($userChoiceList); |
||
1508 | $input_id = "choice-$questionId"; |
||
1509 | |||
1510 | $s .= Display::input('hidden', "choice2[$questionId]", '0') |
||
1511 | .'<p>' |
||
1512 | .Display::select( |
||
1513 | "choice[$questionId][]", |
||
1514 | $selectableOptions, |
||
1515 | $userChoiceList, |
||
1516 | [ |
||
1517 | 'id' => $input_id, |
||
1518 | 'multiple' => 'multiple', |
||
1519 | ], |
||
1520 | false |
||
1521 | ) |
||
1522 | .'</p>' |
||
1523 | .'<script>$(function () { |
||
1524 | $(\'#'.$input_id.'\').select2({ |
||
1525 | selectOnClose: false, |
||
1526 | placeholder: {id: -2, text: "'.get_lang('None').'"} |
||
1527 | }); |
||
1528 | });</script>' |
||
1529 | .'<style> |
||
1530 | .select2-container--default .select2-selection--multiple .select2-selection__rendered li { |
||
1531 | display:block; |
||
1532 | width: 100%; |
||
1533 | white-space: break-spaces; |
||
1534 | }</style>' |
||
1535 | ; |
||
1536 | } |
||
1537 | |||
1538 | if ($show_comment) { |
||
1539 | if (in_array( |
||
1540 | $answerType, |
||
1541 | [ |
||
1542 | MULTIPLE_ANSWER, |
||
1543 | MULTIPLE_ANSWER_COMBINATION, |
||
1544 | UNIQUE_ANSWER, |
||
1545 | UNIQUE_ANSWER_IMAGE, |
||
1546 | UNIQUE_ANSWER_NO_OPTION, |
||
1547 | GLOBAL_MULTIPLE_ANSWER, |
||
1548 | ] |
||
1549 | )) { |
||
1550 | $s .= '</table>'; |
||
1551 | } |
||
1552 | } elseif (in_array( |
||
1553 | $answerType, |
||
1554 | [ |
||
1555 | MATCHING, |
||
1556 | MATCHING_COMBINATION, |
||
1557 | MATCHING_DRAGGABLE, |
||
1558 | MATCHING_DRAGGABLE_COMBINATION, |
||
1559 | UNIQUE_ANSWER_NO_OPTION, |
||
1560 | MULTIPLE_ANSWER_TRUE_FALSE, |
||
1561 | MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE, |
||
1562 | MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY, |
||
1563 | ] |
||
1564 | )) { |
||
1565 | $s .= '</table>'; |
||
1566 | } |
||
1567 | |||
1568 | if ($answerType == DRAGGABLE) { |
||
1569 | $isVertical = $objQuestionTmp->extra == 'v'; |
||
1570 | $s .= " |
||
1571 | </ul> |
||
1572 | </div><!-- .col-md-12 --> |
||
1573 | </div><!-- .row --> |
||
1574 | "; |
||
1575 | $counterAnswer = 1; |
||
1576 | $s .= $isVertical ? '' : '<div class="row">'; |
||
1577 | for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { |
||
1578 | $answerCorrect = $objAnswerTmp->isCorrect($answerId); |
||
1579 | $windowId = $questionId.'_'.$counterAnswer; |
||
1580 | if ($answerCorrect) { |
||
1581 | $s .= $isVertical ? '<div class="row">' : ''; |
||
1582 | $s .= ' |
||
1583 | <div class="'.($isVertical ? 'col-md-12' : 'col-xs-12 col-sm-4 col-md-3 col-lg-2').'"> |
||
1584 | <div class="droppable-item"> |
||
1585 | <span class="number">'.$counterAnswer.'.</span> |
||
1586 | <div id="drop_'.$windowId.'" class="droppable"> |
||
1587 | </div> |
||
1588 | </div> |
||
1589 | </div> |
||
1590 | '; |
||
1591 | $s .= $isVertical ? '</div>' : ''; |
||
1592 | $counterAnswer++; |
||
1593 | } |
||
1594 | } |
||
1595 | |||
1596 | $s .= $isVertical ? '' : '</div>'; // row |
||
1597 | // $s .= '</div>'; |
||
1598 | } |
||
1599 | |||
1600 | if (in_array($answerType, [MATCHING, MATCHING_COMBINATION, MATCHING_DRAGGABLE, MATCHING_DRAGGABLE_COMBINATION])) { |
||
1601 | $s .= '</div>'; //drag_question |
||
1602 | } |
||
1603 | |||
1604 | $s .= '</div>'; //question_options row |
||
1605 | |||
1606 | // destruction of the Answer object |
||
1607 | unset($objAnswerTmp); |
||
1608 | // destruction of the Question object |
||
1609 | unset($objQuestionTmp); |
||
1610 | if ('export' == $origin) { |
||
1611 | return $s; |
||
1612 | } |
||
1613 | echo $s; |
||
1614 | } elseif (in_array($answerType, [HOT_SPOT, HOT_SPOT_DELINEATION, HOT_SPOT_COMBINATION])) { |
||
1615 | global $exe_id; |
||
1616 | // Question is a HOT_SPOT |
||
1617 | // Checking document/images visibility |
||
1618 | if (api_is_platform_admin() || api_is_course_admin()) { |
||
1619 | $doc_id = $objQuestionTmp->getPictureId(); |
||
1620 | if (is_numeric($doc_id)) { |
||
1621 | $images_folder_visibility = api_get_item_visibility( |
||
1622 | $course, |
||
1623 | 'document', |
||
1624 | $doc_id, |
||
1625 | api_get_session_id() |
||
1626 | ); |
||
1627 | if (!$images_folder_visibility) { |
||
1628 | // Show only to the course/platform admin if the image is set to visibility = false |
||
1629 | echo Display::return_message( |
||
1630 | get_lang('ChangeTheVisibilityOfTheCurrentImage'), |
||
1631 | 'warning' |
||
1632 | ); |
||
1633 | } |
||
1634 | } |
||
1635 | } |
||
1636 | $questionDescription = $objQuestionTmp->selectDescription(); |
||
1637 | |||
1638 | // Get the answers, make a list |
||
1639 | $objAnswerTmp = new Answer($questionId, $course_id); |
||
1640 | $nbrAnswers = $objAnswerTmp->selectNbrAnswers(); |
||
1641 | |||
1642 | // get answers of hotpost |
||
1643 | $answers_hotspot = []; |
||
1644 | for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { |
||
1645 | $answers = $objAnswerTmp->selectAnswerByAutoId( |
||
1646 | $objAnswerTmp->selectAutoId($answerId) |
||
1647 | ); |
||
1648 | $answers_hotspot[$answers['iid']] = $objAnswerTmp->selectAnswer( |
||
1649 | $answerId |
||
1650 | ); |
||
1651 | } |
||
1652 | |||
1653 | $answerList = ''; |
||
1654 | $hotspotColor = 0; |
||
1655 | if ($answerType != HOT_SPOT_DELINEATION) { |
||
1656 | $answerList = ' |
||
1657 | <div class="well well-sm"> |
||
1658 | <h5 class="page-header">'.get_lang('HotspotZones').'</h5> |
||
1659 | <ol> |
||
1660 | '; |
||
1661 | |||
1662 | if (!empty($answers_hotspot)) { |
||
1663 | Session::write("hotspot_ordered$questionId", array_keys($answers_hotspot)); |
||
1664 | foreach ($answers_hotspot as $value) { |
||
1665 | $answerList .= '<li>'; |
||
1666 | if ($freeze) { |
||
1667 | $answerList .= '<span class="hotspot-color-'.$hotspotColor |
||
1668 | .' fa fa-square" aria-hidden="true"></span>'.PHP_EOL; |
||
1669 | } |
||
1670 | $answerList .= $value; |
||
1671 | $answerList .= '</li>'; |
||
1672 | $hotspotColor++; |
||
1673 | } |
||
1674 | } |
||
1675 | |||
1676 | $answerList .= ' |
||
1677 | </ol> |
||
1678 | </div> |
||
1679 | '; |
||
1680 | } |
||
1681 | |||
1682 | if ($freeze) { |
||
1683 | $relPath = api_get_path(WEB_CODE_PATH); |
||
1684 | echo " |
||
1685 | <div class=\"row\"> |
||
1686 | <div class=\"col-sm-9\"> |
||
1687 | <div id=\"hotspot-preview-$questionId\"></div> |
||
1688 | </div> |
||
1689 | <div class=\"col-sm-3\"> |
||
1690 | $answerList |
||
1691 | </div> |
||
1692 | </div> |
||
1693 | <script> |
||
1694 | new ".(in_array($answerType, [HOT_SPOT, HOT_SPOT_COMBINATION]) ? "HotspotQuestion" : "DelineationQuestion")."({ |
||
1695 | questionId: $questionId, |
||
1696 | exerciseId: {$exercise->iid}, |
||
1697 | exeId: 0, |
||
1698 | selector: '#hotspot-preview-$questionId', |
||
1699 | 'for': 'preview', |
||
1700 | relPath: '$relPath' |
||
1701 | }); |
||
1702 | </script> |
||
1703 | "; |
||
1704 | |||
1705 | return; |
||
1706 | } |
||
1707 | |||
1708 | if (!$only_questions) { |
||
1709 | if ($show_title) { |
||
1710 | if ($exercise->display_category_name) { |
||
1711 | TestCategory::displayCategoryAndTitle($objQuestionTmp->iid); |
||
1712 | } |
||
1713 | echo $objQuestionTmp->getTitleToDisplay($current_item, $exerciseId); |
||
1714 | } |
||
1715 | |||
1716 | if ($questionRequireAuth) { |
||
1717 | WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise); |
||
1718 | |||
1719 | return false; |
||
1720 | } |
||
1721 | |||
1722 | //@todo I need to the get the feedback type |
||
1723 | echo <<<HOTSPOT |
||
1724 | <input type="hidden" name="hidden_hotspot_id" value="$questionId" /> |
||
1725 | <div class="exercise_questions"> |
||
1726 | $questionDescription |
||
1727 | <div class="row"> |
||
1728 | HOTSPOT; |
||
1729 | } |
||
1730 | |||
1731 | $relPath = api_get_path(WEB_CODE_PATH); |
||
1732 | $s .= "<div class=\"col-sm-8 col-md-9\"> |
||
1733 | <div class=\"hotspot-image\"></div> |
||
1734 | <script> |
||
1735 | $(function() { |
||
1736 | new ".($answerType == HOT_SPOT_DELINEATION ? 'DelineationQuestion' : 'HotspotQuestion')."({ |
||
1737 | questionId: $questionId, |
||
1738 | exerciseId: {$exercise->iid}, |
||
1739 | exeId: 0, |
||
1740 | selector: '#question_div_' + $questionId + ' .hotspot-image', |
||
1741 | 'for': 'user', |
||
1742 | relPath: '$relPath' |
||
1743 | }); |
||
1744 | }); |
||
1745 | </script> |
||
1746 | </div> |
||
1747 | <div class=\"col-sm-4 col-md-3\"> |
||
1748 | $answerList |
||
1749 | </div> |
||
1750 | "; |
||
1751 | |||
1752 | echo <<<HOTSPOT |
||
1753 | $s |
||
1754 | </div> |
||
1755 | </div> |
||
1756 | HOTSPOT; |
||
1757 | } elseif ($answerType == ANNOTATION) { |
||
1758 | global $exe_id; |
||
1759 | $relPath = api_get_path(WEB_CODE_PATH); |
||
1760 | if (api_is_platform_admin() || api_is_course_admin()) { |
||
1761 | $docId = DocumentManager::get_document_id($course, '/images/'.$pictureName); |
||
1762 | if ($docId) { |
||
1763 | $images_folder_visibility = api_get_item_visibility( |
||
1764 | $course, |
||
1765 | 'document', |
||
1766 | $docId, |
||
1767 | api_get_session_id() |
||
1768 | ); |
||
1769 | |||
1770 | if (!$images_folder_visibility) { |
||
1771 | echo Display::return_message(get_lang('ChangeTheVisibilityOfTheCurrentImage'), 'warning'); |
||
1772 | } |
||
1773 | } |
||
1774 | |||
1775 | if ($freeze) { |
||
1776 | echo Display::img( |
||
1777 | api_get_path(WEB_COURSE_PATH).$course['path'].'/document/images/'.$pictureName, |
||
1778 | $objQuestionTmp->selectTitle(), |
||
1779 | ['width' => '600px'] |
||
1780 | ); |
||
1781 | |||
1782 | return 0; |
||
1783 | } |
||
1784 | } |
||
1785 | |||
1786 | if (!$only_questions) { |
||
1787 | if ($show_title) { |
||
1788 | if ($exercise->display_category_name) { |
||
1789 | TestCategory::displayCategoryAndTitle($objQuestionTmp->iid); |
||
1790 | } |
||
1791 | echo $objQuestionTmp->getTitleToDisplay($current_item, $exerciseId); |
||
1792 | } |
||
1793 | |||
1794 | if ($questionRequireAuth) { |
||
1795 | WhispeakAuthPlugin::quizQuestionAuthentify($questionId, $exercise); |
||
1796 | |||
1797 | return false; |
||
1798 | } |
||
1799 | |||
1800 | echo ' |
||
1801 | <input type="hidden" name="hidden_hotspot_id" value="'.$questionId.'" /> |
||
1802 | <div class="exercise_questions"> |
||
1803 | '.$objQuestionTmp->selectDescription().' |
||
1804 | <div class="row"> |
||
1805 | <div class="col-sm-8 col-md-9"> |
||
1806 | <div id="annotation-canvas-'.$questionId.'" class="annotation-canvas center-block"> |
||
1807 | </div> |
||
1808 | </div> |
||
1809 | <div class="col-sm-4 col-md-3" id="annotation-toolbar-'.$questionId.'"> |
||
1810 | <div class="btn-toolbar" style="margin-top: 0;"> |
||
1811 | <div class="btn-group" data-toggle="buttons"> |
||
1812 | <label class="btn btn-default active" |
||
1813 | aria-label="'.get_lang('AddAnnotationPath').'"> |
||
1814 | <input |
||
1815 | type="radio" value="0" |
||
1816 | name="'.$questionId.'-options" autocomplete="off" checked> |
||
1817 | <span class="fa fa-pencil" aria-hidden="true"></span> |
||
1818 | </label> |
||
1819 | <label class="btn btn-default" |
||
1820 | aria-label="'.get_lang('AddAnnotationText').'"> |
||
1821 | <input |
||
1822 | type="radio" value="1" |
||
1823 | name="'.$questionId.'-options" autocomplete="off"> |
||
1824 | <span class="fa fa-font fa-fw" aria-hidden="true"></span> |
||
1825 | </label> |
||
1826 | </div> |
||
1827 | <div class="btn-group"> |
||
1828 | <button type="button" class="btn btn-default btn-small" |
||
1829 | title="'.get_lang('ClearAnswers').'" |
||
1830 | id="btn-reset-'.$questionId.'"> |
||
1831 | <span class="fa fa-times-rectangle fa-fw" aria-hidden="true"></span> |
||
1832 | </button> |
||
1833 | </div> |
||
1834 | <div class="btn-group"> |
||
1835 | <button type="button" class="btn btn-default" |
||
1836 | title="'.get_lang('Undo').'" |
||
1837 | id="btn-undo-'.$questionId.'"> |
||
1838 | <span class="fa fa-undo fa-fw" aria-hidden="true"></span> |
||
1839 | </button> |
||
1840 | <button type="button" class="btn btn-default" |
||
1841 | title="'.get_lang('Redo').'" |
||
1842 | id="btn-redo-'.$questionId.'"> |
||
1843 | <span class="fa fa-repeat fa-fw" aria-hidden="true"></span> |
||
1844 | </button> |
||
1845 | </div> |
||
1846 | </div> |
||
1847 | </div> |
||
1848 | </div> |
||
1849 | <script> |
||
1850 | AnnotationQuestion({ |
||
1851 | questionId: '.$questionId.', |
||
1852 | exerciseId: '.$exe_id.', |
||
1853 | relPath: \''.$relPath.'\', |
||
1854 | courseId: '.$course_id.', |
||
1855 | }); |
||
1856 | </script> |
||
1857 | </div> |
||
1858 | '; |
||
1859 | } |
||
1860 | $objAnswerTmp = new Answer($questionId); |
||
1861 | $nbrAnswers = $objAnswerTmp->selectNbrAnswers(); |
||
1862 | unset($objAnswerTmp, $objQuestionTmp); |
||
1863 | } |
||
1864 | |||
1865 | return $nbrAnswers; |
||
1866 | } |
||
1867 | |||
1868 | /** |
||
1869 | * Get an HTML string with the list of exercises where the given question |
||
1870 | * is being used. |
||
1871 | * |
||
1872 | * @param int $questionId The iid of the question being observed |
||
1873 | * @param int $excludeTestId If defined, exclude this (current) test from the list of results |
||
1874 | * |
||
1875 | * @return string An HTML string containing a div and a table |
||
1876 | */ |
||
1877 | public static function showTestsWhereQuestionIsUsed(int $questionId, int $excludeTestId = 0) |
||
1878 | { |
||
1879 | $questionId = (int) $questionId; |
||
1880 | $sql = "SELECT qz.title quiz_title, |
||
1881 | c.title course_title, |
||
1882 | s.name session_name, |
||
1883 | qz.iid as quiz_id, |
||
1884 | qz.c_id, |
||
1885 | qz.session_id |
||
1886 | FROM c_quiz qz, |
||
1887 | c_quiz_rel_question qq, |
||
1888 | course c, |
||
1889 | session s |
||
1890 | WHERE qz.c_id = c.id AND |
||
1891 | (qz.session_id = s.id OR qz.session_id = 0) AND |
||
1892 | qq.exercice_id = qz.iid AND "; |
||
1893 | if (!empty($excludeTestId)) { |
||
1894 | $excludeTestId = (int) $excludeTestId; |
||
1895 | $sql .= " qz.iid != $excludeTestId AND "; |
||
1896 | } |
||
1897 | $sql .= " qq.question_id = $questionId |
||
1898 | GROUP BY qq.iid"; |
||
1899 | |||
1900 | $result = []; |
||
1901 | $html = ""; |
||
1902 | |||
1903 | $sqlResult = Database::query($sql); |
||
1904 | |||
1905 | if (Database::num_rows($sqlResult) != 0) { |
||
1906 | while ($row = Database::fetch_array($sqlResult, 'ASSOC')) { |
||
1907 | $tmp = []; |
||
1908 | $tmp[0] = $row['course_title']; |
||
1909 | $tmp[1] = $row['session_name']; |
||
1910 | $tmp[2] = $row['quiz_title']; |
||
1911 | $courseDetails = api_get_course_info_by_id($row['c_id']); |
||
1912 | $courseCode = $courseDetails['code']; |
||
1913 | // Send do other test with r=1 to reset current test session variables |
||
1914 | $urlToQuiz = api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq_params($courseCode, $row['session_id']).'&exerciseId='.$row['quiz_id'].'&r=1'; |
||
1915 | $tmp[3] = '<a href="'.$urlToQuiz.'">'.Display::return_icon('quiz.png', get_lang('Edit')).'</a>'; |
||
1916 | if ((int) $row['session_id'] == 0) { |
||
1917 | $tmp[1] = '-'; |
||
1918 | } |
||
1919 | |||
1920 | $result[] = $tmp; |
||
1921 | } |
||
1922 | |||
1923 | $headers = [ |
||
1924 | get_lang('Course'), |
||
1925 | get_lang('Session'), |
||
1926 | get_lang('Quiz'), |
||
1927 | get_lang('LinkToTestEdition'), |
||
1928 | ]; |
||
1929 | |||
1930 | $title = Display::div( |
||
1931 | get_lang('QuestionAlsoUsedInTheFollowingTests'), |
||
1932 | [ |
||
1933 | 'class' => 'section-title', |
||
1934 | 'style' => 'margin-top: 25px; border-bottom: none', |
||
1935 | ] |
||
1936 | ); |
||
1937 | |||
1938 | $html = $title.Display::table($headers, $result); |
||
1939 | } |
||
1940 | |||
1941 | echo $html; |
||
1942 | } |
||
1943 | |||
1944 | /** |
||
1945 | * Get the table as array of results of exercises attempts with questions score. |
||
1946 | * |
||
1947 | * @return array |
||
1948 | */ |
||
1949 | public static function getTrackExerciseAttemptsTable(Exercise $objExercise) |
||
1950 | { |
||
1951 | $tblQuiz = Database::get_course_table(TABLE_QUIZ_TEST); |
||
1952 | $tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
1953 | |||
1954 | $exerciseId = $objExercise->iid; |
||
1955 | $courseId = $objExercise->course_id; |
||
1956 | $sessionId = (int) $objExercise->sessionId; |
||
1957 | $questionList = $objExercise->getQuestionForTeacher(0, $objExercise->getQuestionCount()); |
||
1958 | |||
1959 | $headers = [ |
||
1960 | get_lang('UserName'), |
||
1961 | get_lang('Email'), |
||
1962 | ]; |
||
1963 | |||
1964 | $extraField = new ExtraField('user'); |
||
1965 | $extraFieldValue = new ExtraFieldValue('user'); |
||
1966 | $extraFieldQuestion = new ExtraFieldValue('question'); |
||
1967 | |||
1968 | $extraFields = $extraField->get_all(['filter = ?' => 1]); |
||
1969 | $userExtraFields = []; |
||
1970 | if (!empty($extraFields)) { |
||
1971 | foreach ($extraFields as $extra) { |
||
1972 | $headers[] = $extra['display_text']; |
||
1973 | $userExtraFields[] = $extra['variable']; |
||
1974 | } |
||
1975 | } |
||
1976 | |||
1977 | $headersXls = $headers; |
||
1978 | if (!empty($questionList)) { |
||
1979 | foreach ($questionList as $questionId) { |
||
1980 | $questionObj = Question::read($questionId); |
||
1981 | $questionName = cut($questionObj->question, 200); |
||
1982 | $headers[] = '<a href="#" title="'.$questionName.'">'.$questionId.'</a>'; |
||
1983 | $headersXls[] = $questionName; |
||
1984 | } |
||
1985 | } |
||
1986 | |||
1987 | $sql = "SELECT |
||
1988 | te.exe_id, |
||
1989 | te.exe_user_id |
||
1990 | FROM |
||
1991 | $tblTrackExercises te |
||
1992 | INNER JOIN |
||
1993 | $tblQuiz q ON (q.iid = te.exe_exo_id AND q.c_id = te.c_id) |
||
1994 | WHERE |
||
1995 | te.c_id = $courseId AND |
||
1996 | te.session_id = $sessionId AND |
||
1997 | te.status = '' AND |
||
1998 | te.exe_exo_id = $exerciseId |
||
1999 | "; |
||
2000 | $rs = Database::query($sql); |
||
2001 | $data = []; |
||
2002 | if (Database::num_rows($rs) > 0) { |
||
2003 | $x = 0; |
||
2004 | while ($row = Database::fetch_array($rs)) { |
||
2005 | $userInfo = api_get_user_info($row['exe_user_id']); |
||
2006 | $data[$x]['username'] = $userInfo['username']; |
||
2007 | $data[$x]['email'] = $userInfo['email']; |
||
2008 | if (!empty($userExtraFields)) { |
||
2009 | foreach ($userExtraFields as $variable) { |
||
2010 | $extra = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
2011 | $row['exe_user_id'], |
||
2012 | $variable |
||
2013 | ); |
||
2014 | $data[$x][$variable] = $extra['value'] ?? ''; |
||
2015 | } |
||
2016 | } |
||
2017 | |||
2018 | // the questions |
||
2019 | if (!empty($questionList)) { |
||
2020 | foreach ($questionList as $questionId) { |
||
2021 | $questionObj = Question::read($questionId); |
||
2022 | $questionName = cut($questionObj->question, 200); |
||
2023 | $questionResult = $objExercise->manage_answer( |
||
2024 | $row['exe_id'], |
||
2025 | $questionId, |
||
2026 | '', |
||
2027 | 'exercise_show', |
||
2028 | [], |
||
2029 | false, |
||
2030 | true, |
||
2031 | false, |
||
2032 | $objExercise->selectPropagateNeg() |
||
2033 | ); |
||
2034 | |||
2035 | $displayValue = $questionResult['score']; |
||
2036 | $differentiation = $extraFieldQuestion->get_values_by_handler_and_field_variable($questionId, 'differentiation'); |
||
2037 | if (!empty($differentiation['value'])) { |
||
2038 | $answerType = $questionObj->selectType(); |
||
2039 | $objAnswerTmp = new Answer($questionId, api_get_course_int_id()); |
||
2040 | $userChoice = []; |
||
2041 | if (!empty($questionResult['correct_answer_id']) && HOT_SPOT_DELINEATION != $answerType) { |
||
2042 | foreach ($questionResult['correct_answer_id'] as $answerId) { |
||
2043 | $answer = $objAnswerTmp->getAnswerByAutoId($answerId); |
||
2044 | if (!empty($answer)) { |
||
2045 | $userChoice[] = $answer['answer']; |
||
2046 | } else { |
||
2047 | $answer = $objAnswerTmp->selectAnswer($answerId); |
||
2048 | $userChoice[] = $answer; |
||
2049 | } |
||
2050 | } |
||
2051 | } |
||
2052 | if (!empty($userChoice)) { |
||
2053 | $displayValue = implode('|', $userChoice); |
||
2054 | } |
||
2055 | } |
||
2056 | $questionModalUrl = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&a=show_question_attempt&exercise='.$exerciseId.'&question='.$questionId.'&exe_id='.$row['exe_id']; |
||
2057 | $data[$x][$questionId] = '<a href="'.$questionModalUrl.'" class="ajax" data-title="'.$questionName.'" title="'.get_lang('ClickToViewDetails').'">'.$displayValue.'</a>'; |
||
2058 | } |
||
2059 | } |
||
2060 | $x++; |
||
2061 | } |
||
2062 | } |
||
2063 | |||
2064 | $table['headers'] = $headers; |
||
2065 | $table['headers_xls'] = $headersXls; |
||
2066 | $table['rows'] = $data; |
||
2067 | |||
2068 | return $table; |
||
2069 | } |
||
2070 | |||
2071 | /** |
||
2072 | * @param int $exeId |
||
2073 | * |
||
2074 | * @return array |
||
2075 | */ |
||
2076 | public static function get_exercise_track_exercise_info($exeId) |
||
2077 | { |
||
2078 | $quizTable = Database::get_course_table(TABLE_QUIZ_TEST); |
||
2079 | $trackExerciseTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
2080 | $courseTable = Database::get_main_table(TABLE_MAIN_COURSE); |
||
2081 | $exeId = (int) $exeId; |
||
2082 | $result = []; |
||
2083 | if (!empty($exeId)) { |
||
2084 | $sql = " SELECT q.*, tee.* |
||
2085 | FROM $quizTable as q |
||
2086 | INNER JOIN $trackExerciseTable as tee |
||
2087 | ON q.iid = tee.exe_exo_id |
||
2088 | INNER JOIN $courseTable c |
||
2089 | ON c.id = tee.c_id |
||
2090 | WHERE tee.exe_id = $exeId |
||
2091 | AND q.c_id = c.id"; |
||
2092 | |||
2093 | $sqlResult = Database::query($sql); |
||
2094 | if (Database::num_rows($sqlResult)) { |
||
2095 | $result = Database::fetch_array($sqlResult, 'ASSOC'); |
||
2096 | $result['duration_formatted'] = ''; |
||
2097 | if (!empty($result['exe_duration'])) { |
||
2098 | $time = api_format_time($result['exe_duration'], 'js'); |
||
2099 | $result['duration_formatted'] = $time; |
||
2100 | } |
||
2101 | } |
||
2102 | } |
||
2103 | |||
2104 | return $result; |
||
2105 | } |
||
2106 | |||
2107 | /** |
||
2108 | * Validates the time control key. |
||
2109 | * |
||
2110 | * @param int $exercise_id |
||
2111 | * @param int $lp_id |
||
2112 | * @param int $lp_item_id |
||
2113 | * |
||
2114 | * @return bool |
||
2115 | */ |
||
2116 | public static function exercise_time_control_is_valid( |
||
2117 | $exercise_id, |
||
2118 | $lp_id = 0, |
||
2119 | $lp_item_id = 0 |
||
2120 | ) { |
||
2121 | $course_id = api_get_course_int_id(); |
||
2122 | $exercise_id = (int) $exercise_id; |
||
2123 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
2124 | $sql = "SELECT expired_time FROM $table |
||
2125 | WHERE iid = $exercise_id"; |
||
2126 | $result = Database::query($sql); |
||
2127 | $row = Database::fetch_array($result, 'ASSOC'); |
||
2128 | if (!empty($row['expired_time'])) { |
||
2129 | $current_expired_time_key = self::get_time_control_key( |
||
2130 | $exercise_id, |
||
2131 | $lp_id, |
||
2132 | $lp_item_id |
||
2133 | ); |
||
2134 | if (isset($_SESSION['expired_time'][$current_expired_time_key])) { |
||
2135 | $current_time = time(); |
||
2136 | $expired_time = api_strtotime( |
||
2137 | $_SESSION['expired_time'][$current_expired_time_key], |
||
2138 | 'UTC' |
||
2139 | ); |
||
2140 | $total_time_allowed = $expired_time + 30; |
||
2141 | if ($total_time_allowed < $current_time) { |
||
2142 | return false; |
||
2143 | } |
||
2144 | |||
2145 | return true; |
||
2146 | } |
||
2147 | |||
2148 | return false; |
||
2149 | } |
||
2150 | |||
2151 | return true; |
||
2152 | } |
||
2153 | |||
2154 | /** |
||
2155 | * Deletes the time control token. |
||
2156 | * |
||
2157 | * @param int $exercise_id |
||
2158 | * @param int $lp_id |
||
2159 | * @param int $lp_item_id |
||
2160 | */ |
||
2161 | public static function exercise_time_control_delete( |
||
2162 | $exercise_id, |
||
2163 | $lp_id = 0, |
||
2164 | $lp_item_id = 0 |
||
2165 | ) { |
||
2166 | $current_expired_time_key = self::get_time_control_key( |
||
2167 | $exercise_id, |
||
2168 | $lp_id, |
||
2169 | $lp_item_id |
||
2170 | ); |
||
2171 | unset($_SESSION['expired_time'][$current_expired_time_key]); |
||
2172 | } |
||
2173 | |||
2174 | /** |
||
2175 | * Generates the time control key. |
||
2176 | * |
||
2177 | * @param int $exercise_id |
||
2178 | * @param int $lp_id |
||
2179 | * @param int $lp_item_id |
||
2180 | * |
||
2181 | * @return string |
||
2182 | */ |
||
2183 | public static function get_time_control_key( |
||
2184 | $exercise_id, |
||
2185 | $lp_id = 0, |
||
2186 | $lp_item_id = 0 |
||
2187 | ) { |
||
2188 | $exercise_id = (int) $exercise_id; |
||
2189 | $lp_id = (int) $lp_id; |
||
2190 | $lp_item_id = (int) $lp_item_id; |
||
2191 | |||
2192 | return |
||
2193 | api_get_course_int_id().'_'. |
||
2194 | api_get_session_id().'_'. |
||
2195 | $exercise_id.'_'. |
||
2196 | api_get_user_id().'_'. |
||
2197 | $lp_id.'_'. |
||
2198 | $lp_item_id; |
||
2199 | } |
||
2200 | |||
2201 | /** |
||
2202 | * Get session time control. |
||
2203 | * |
||
2204 | * @param int $exercise_id |
||
2205 | * @param int $lp_id |
||
2206 | * @param int $lp_item_id |
||
2207 | * |
||
2208 | * @return int |
||
2209 | */ |
||
2210 | public static function get_session_time_control_key( |
||
2211 | $exercise_id, |
||
2212 | $lp_id = 0, |
||
2213 | $lp_item_id = 0 |
||
2214 | ) { |
||
2215 | $return_value = 0; |
||
2216 | $time_control_key = self::get_time_control_key( |
||
2217 | $exercise_id, |
||
2218 | $lp_id, |
||
2219 | $lp_item_id |
||
2220 | ); |
||
2221 | if (isset($_SESSION['expired_time']) && isset($_SESSION['expired_time'][$time_control_key])) { |
||
2222 | $return_value = $_SESSION['expired_time'][$time_control_key]; |
||
2223 | } |
||
2224 | |||
2225 | return $return_value; |
||
2226 | } |
||
2227 | |||
2228 | /** |
||
2229 | * Gets count of exam results. |
||
2230 | * |
||
2231 | * @param int $exerciseId |
||
2232 | * @param array $conditions |
||
2233 | * @param string $courseCode |
||
2234 | * @param bool $showSession |
||
2235 | * @param bool $searchAllTeacherCourses |
||
2236 | * @param int $status |
||
2237 | * |
||
2238 | * @return array |
||
2239 | */ |
||
2240 | public static function get_count_exam_results( |
||
2241 | $exerciseId, |
||
2242 | $conditions, |
||
2243 | $courseCode = '', |
||
2244 | $showSession = false, |
||
2245 | $searchAllTeacherCourses = false, |
||
2246 | $status = 0, |
||
2247 | $showAttemptsInSessions = false, |
||
2248 | $questionType = 0, |
||
2249 | $originPending = false |
||
2250 | ) { |
||
2251 | return self::get_exam_results_data( |
||
2252 | null, |
||
2253 | null, |
||
2254 | null, |
||
2255 | null, |
||
2256 | $exerciseId, |
||
2257 | $conditions, |
||
2258 | true, |
||
2259 | $courseCode, |
||
2260 | $showSession, |
||
2261 | false, |
||
2262 | [], |
||
2263 | false, |
||
2264 | false, |
||
2265 | false, |
||
2266 | $searchAllTeacherCourses, |
||
2267 | $status, |
||
2268 | $showAttemptsInSessions, |
||
2269 | $questionType, |
||
2270 | $originPending |
||
2271 | ); |
||
2272 | } |
||
2273 | |||
2274 | /** |
||
2275 | * @param string $path |
||
2276 | * |
||
2277 | * @return int |
||
2278 | */ |
||
2279 | public static function get_count_exam_hotpotatoes_results($path) |
||
2280 | { |
||
2281 | return self::get_exam_results_hotpotatoes_data( |
||
2282 | 0, |
||
2283 | 0, |
||
2284 | '', |
||
2285 | '', |
||
2286 | $path, |
||
2287 | true, |
||
2288 | '' |
||
2289 | ); |
||
2290 | } |
||
2291 | |||
2292 | /** |
||
2293 | * @param int $in_from |
||
2294 | * @param int $in_number_of_items |
||
2295 | * @param int $in_column |
||
2296 | * @param int $in_direction |
||
2297 | * @param string $in_hotpot_path |
||
2298 | * @param bool $in_get_count |
||
2299 | * @param null $where_condition |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
2300 | * |
||
2301 | * @return array|int |
||
2302 | */ |
||
2303 | public static function get_exam_results_hotpotatoes_data( |
||
2304 | $in_from, |
||
2305 | $in_number_of_items, |
||
2306 | $in_column, |
||
2307 | $in_direction, |
||
2308 | $in_hotpot_path, |
||
2309 | $in_get_count = false, |
||
2310 | $where_condition = null |
||
2311 | ) { |
||
2312 | $courseId = api_get_course_int_id(); |
||
2313 | // by default in_column = 1 If parameters given, it is the name of the column witch is the bdd field name |
||
2314 | if ($in_column == 1) { |
||
2315 | $in_column = 'firstname'; |
||
2316 | } |
||
2317 | $in_hotpot_path = Database::escape_string($in_hotpot_path); |
||
2318 | $in_direction = Database::escape_string($in_direction); |
||
2319 | $in_direction = !in_array(strtolower(trim($in_direction)), ['asc', 'desc']) ? 'asc' : $in_direction; |
||
2320 | $in_column = Database::escape_string($in_column); |
||
2321 | $in_number_of_items = (int) $in_number_of_items; |
||
2322 | $in_from = (int) $in_from; |
||
2323 | |||
2324 | $TBL_TRACK_HOTPOTATOES = Database::get_main_table( |
||
2325 | TABLE_STATISTIC_TRACK_E_HOTPOTATOES |
||
2326 | ); |
||
2327 | $TBL_USER = Database::get_main_table(TABLE_MAIN_USER); |
||
2328 | |||
2329 | $sql = "SELECT *, thp.id AS thp_id |
||
2330 | FROM $TBL_TRACK_HOTPOTATOES thp |
||
2331 | JOIN $TBL_USER u |
||
2332 | ON thp.exe_user_id = u.user_id |
||
2333 | WHERE |
||
2334 | thp.c_id = $courseId AND |
||
2335 | exe_name LIKE '$in_hotpot_path%'"; |
||
2336 | |||
2337 | // just count how many answers |
||
2338 | if ($in_get_count) { |
||
2339 | $res = Database::query($sql); |
||
2340 | |||
2341 | return Database::num_rows($res); |
||
2342 | } |
||
2343 | // get a number of sorted results |
||
2344 | $sql .= " $where_condition |
||
2345 | ORDER BY `$in_column` $in_direction |
||
2346 | LIMIT $in_from, $in_number_of_items"; |
||
2347 | |||
2348 | $res = Database::query($sql); |
||
2349 | $result = []; |
||
2350 | $apiIsAllowedToEdit = api_is_allowed_to_edit(); |
||
2351 | $urlBase = api_get_path(WEB_CODE_PATH). |
||
2352 | 'exercise/hotpotatoes_exercise_report.php?action=delete&'. |
||
2353 | api_get_cidreq().'&id='; |
||
2354 | while ($data = Database::fetch_array($res)) { |
||
2355 | $actions = null; |
||
2356 | |||
2357 | if ($apiIsAllowedToEdit) { |
||
2358 | $url = $urlBase.$data['thp_id'].'&path='.$data['exe_name']; |
||
2359 | $actions = Display::url( |
||
2360 | Display::return_icon('delete.png', get_lang('Delete')), |
||
2361 | $url |
||
2362 | ); |
||
2363 | } |
||
2364 | |||
2365 | $result[] = [ |
||
2366 | 'firstname' => $data['firstname'], |
||
2367 | 'lastname' => $data['lastname'], |
||
2368 | 'username' => $data['username'], |
||
2369 | 'group_name' => implode( |
||
2370 | '<br/>', |
||
2371 | GroupManager::get_user_group_name($data['user_id']) |
||
2372 | ), |
||
2373 | 'exe_date' => $data['exe_date'], |
||
2374 | 'score' => $data['exe_result'].' / '.$data['exe_weighting'], |
||
2375 | 'actions' => $actions, |
||
2376 | ]; |
||
2377 | } |
||
2378 | |||
2379 | return $result; |
||
2380 | } |
||
2381 | |||
2382 | /** |
||
2383 | * @param string $exercisePath |
||
2384 | * @param int $userId |
||
2385 | * @param int $courseId |
||
2386 | * @param int $sessionId |
||
2387 | * |
||
2388 | * @return array |
||
2389 | */ |
||
2390 | public static function getLatestHotPotatoResult( |
||
2391 | $exercisePath, |
||
2392 | $userId, |
||
2393 | $courseId, |
||
2394 | $sessionId |
||
2395 | ) { |
||
2396 | $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES); |
||
2397 | $exercisePath = Database::escape_string($exercisePath); |
||
2398 | $userId = (int) $userId; |
||
2399 | $courseId = (int) $courseId; |
||
2400 | |||
2401 | $sql = "SELECT * FROM $table |
||
2402 | WHERE |
||
2403 | c_id = $courseId AND |
||
2404 | exe_name LIKE '$exercisePath%' AND |
||
2405 | exe_user_id = $userId |
||
2406 | ORDER BY id |
||
2407 | LIMIT 1"; |
||
2408 | $result = Database::query($sql); |
||
2409 | $attempt = []; |
||
2410 | if (Database::num_rows($result)) { |
||
2411 | $attempt = Database::fetch_array($result, 'ASSOC'); |
||
2412 | } |
||
2413 | |||
2414 | return $attempt; |
||
2415 | } |
||
2416 | |||
2417 | /** |
||
2418 | * Export the pending attempts to excel. |
||
2419 | * |
||
2420 | * @params $values |
||
2421 | */ |
||
2422 | public static function exportPendingAttemptsToExcel($values) |
||
2423 | { |
||
2424 | $headers = [ |
||
2425 | get_lang('Course'), |
||
2426 | get_lang('Exercise'), |
||
2427 | get_lang('FirstName'), |
||
2428 | get_lang('LastName'), |
||
2429 | get_lang('LoginName'), |
||
2430 | get_lang('Duration').' ('.get_lang('MinMinute').')', |
||
2431 | get_lang('StartDate'), |
||
2432 | get_lang('EndDate'), |
||
2433 | get_lang('Score'), |
||
2434 | get_lang('IP'), |
||
2435 | get_lang('Status'), |
||
2436 | get_lang('Corrector'), |
||
2437 | get_lang('CorrectionDate'), |
||
2438 | ]; |
||
2439 | $tableXls[] = $headers; |
||
2440 | |||
2441 | $courseId = $values['course_id'] ?? 0; |
||
2442 | $exerciseId = $values['exercise_id'] ?? 0; |
||
2443 | $status = $values['status'] ?? 0; |
||
2444 | $whereCondition = ''; |
||
2445 | if (isset($_GET['filter_by_user']) && !empty($_GET['filter_by_user'])) { |
||
2446 | $filter_user = (int) $_GET['filter_by_user']; |
||
2447 | if (empty($whereCondition)) { |
||
2448 | $whereCondition .= " te.exe_user_id = '$filter_user'"; |
||
2449 | } else { |
||
2450 | $whereCondition .= " AND te.exe_user_id = '$filter_user'"; |
||
2451 | } |
||
2452 | } |
||
2453 | |||
2454 | if (isset($_GET['group_id_in_toolbar']) && !empty($_GET['group_id_in_toolbar'])) { |
||
2455 | $groupIdFromToolbar = (int) $_GET['group_id_in_toolbar']; |
||
2456 | if (!empty($groupIdFromToolbar)) { |
||
2457 | if (empty($whereCondition)) { |
||
2458 | $whereCondition .= " te.group_id = '$groupIdFromToolbar'"; |
||
2459 | } else { |
||
2460 | $whereCondition .= " AND group_id = '$groupIdFromToolbar'"; |
||
2461 | } |
||
2462 | } |
||
2463 | } |
||
2464 | |||
2465 | if (!empty($whereCondition)) { |
||
2466 | $whereCondition = " AND $whereCondition"; |
||
2467 | } |
||
2468 | |||
2469 | if (!empty($courseId)) { |
||
2470 | $whereCondition .= " AND te.c_id = $courseId"; |
||
2471 | } |
||
2472 | |||
2473 | $result = ExerciseLib::get_exam_results_data( |
||
2474 | 0, |
||
2475 | 10000000, |
||
2476 | 'c_id', |
||
2477 | 'asc', |
||
2478 | $exerciseId, |
||
2479 | $whereCondition, |
||
2480 | false, |
||
2481 | null, |
||
2482 | false, |
||
2483 | false, |
||
2484 | [], |
||
2485 | false, |
||
2486 | false, |
||
2487 | false, |
||
2488 | true, |
||
2489 | $status |
||
2490 | ); |
||
2491 | |||
2492 | if (!empty($result)) { |
||
2493 | foreach ($result as $attempt) { |
||
2494 | $data = [ |
||
2495 | $attempt['course'], |
||
2496 | $attempt['exercise'], |
||
2497 | $attempt['firstname'], |
||
2498 | $attempt['lastname'], |
||
2499 | $attempt['username'], |
||
2500 | $attempt['exe_duration'], |
||
2501 | $attempt['start_date'], |
||
2502 | $attempt['exe_date'], |
||
2503 | strip_tags($attempt['score']), |
||
2504 | $attempt['user_ip'], |
||
2505 | strip_tags($attempt['status']), |
||
2506 | $attempt['qualificator_fullname'], |
||
2507 | $attempt['date_of_qualification'], |
||
2508 | ]; |
||
2509 | $tableXls[] = $data; |
||
2510 | } |
||
2511 | } |
||
2512 | |||
2513 | $fileName = get_lang('PendingAttempts').'_'.api_get_local_time(); |
||
2514 | Export::arrayToXls($tableXls, $fileName); |
||
2515 | |||
2516 | return true; |
||
2517 | } |
||
2518 | |||
2519 | /** |
||
2520 | * Gets exercise results. |
||
2521 | * |
||
2522 | * @todo this function should be moved in a library + no global calls |
||
2523 | * |
||
2524 | * @param int $from |
||
2525 | * @param int $number_of_items |
||
2526 | * @param int $column |
||
2527 | * @param string $direction |
||
2528 | * @param int $exercise_id |
||
2529 | * @param null $extra_where_conditions |
||
0 ignored issues
–
show
|
|||
2530 | * @param bool $get_count |
||
2531 | * @param string $courseCode |
||
2532 | * @param bool $showSessionField |
||
2533 | * @param bool $showExerciseCategories |
||
2534 | * @param array $userExtraFieldsToAdd |
||
2535 | * @param bool $useCommaAsDecimalPoint |
||
2536 | * @param bool $roundValues |
||
2537 | * @param bool $getOnlyIds |
||
2538 | * |
||
2539 | * @return array |
||
2540 | */ |
||
2541 | public static function get_exam_results_data( |
||
2542 | $from, |
||
2543 | $number_of_items, |
||
2544 | $column, |
||
2545 | $direction, |
||
2546 | $exercise_id, |
||
2547 | $extra_where_conditions = null, |
||
2548 | $get_count = false, |
||
2549 | $courseCode = null, |
||
2550 | $showSessionField = false, |
||
2551 | $showExerciseCategories = false, |
||
2552 | $userExtraFieldsToAdd = [], |
||
2553 | $useCommaAsDecimalPoint = false, |
||
2554 | $roundValues = false, |
||
2555 | $getOnlyIds = false, |
||
2556 | $searchAllTeacherCourses = false, |
||
2557 | $status = 0, |
||
2558 | $showAttemptsInSessions = false, |
||
2559 | $questionType = 0, |
||
2560 | $originPending = false |
||
2561 | ) { |
||
2562 | //@todo replace all this globals |
||
2563 | global $filter; |
||
2564 | $courseCode = empty($courseCode) ? api_get_course_id() : $courseCode; |
||
2565 | $courseInfo = api_get_course_info($courseCode); |
||
2566 | $documentPath = ''; |
||
2567 | $sessionId = api_get_session_id(); |
||
2568 | $courseId = 0; |
||
2569 | if (!empty($courseInfo)) { |
||
2570 | $courseId = $courseInfo['real_id']; |
||
2571 | $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'; |
||
2572 | } |
||
2573 | |||
2574 | $is_allowedToEdit = |
||
2575 | api_is_allowed_to_edit(null, true) || |
||
2576 | api_is_allowed_to_edit(true) || |
||
2577 | api_is_drh() || |
||
2578 | api_is_student_boss() || |
||
2579 | api_is_session_admin(); |
||
2580 | |||
2581 | $courseCondition = "c_id = $courseId"; |
||
2582 | $statusCondition = ''; |
||
2583 | |||
2584 | $exercisesFilter = ''; |
||
2585 | $exercises_where = ''; |
||
2586 | |||
2587 | if ($questionType == 1) { |
||
2588 | $TBL_EXERCISES_REL_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||
2589 | $TBL_EXERCISES_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
2590 | |||
2591 | $sqlExercise = "SELECT exercice_id |
||
2592 | FROM $TBL_EXERCISES_REL_QUESTION terq |
||
2593 | LEFT JOIN $TBL_EXERCISES_QUESTION teq |
||
2594 | ON terq.question_id = teq.iid |
||
2595 | WHERE teq.type in (".FREE_ANSWER.", ".ORAL_EXPRESSION.", ".ANNOTATION.", ".UPLOAD_ANSWER.", ".ANSWER_IN_OFFICE_DOC.") |
||
2596 | "; |
||
2597 | |||
2598 | $resultExerciseIds = Database::query($sqlExercise); |
||
2599 | $exercises = Database::store_result($resultExerciseIds, 'ASSOC'); |
||
2600 | $exerciseIds = []; |
||
2601 | foreach ($exercises as $exercise) { |
||
2602 | $exerciseIds[] = $exercise['exercice_id']; |
||
2603 | } |
||
2604 | $exercises_where = " AND te.exe_exo_id IN(".implode(',', $exerciseIds).")"; |
||
2605 | $exercisesFilter = " AND exe_exo_id IN(".implode(',', $exerciseIds).")"; |
||
2606 | } |
||
2607 | |||
2608 | if (!empty($status)) { |
||
2609 | switch ($status) { |
||
2610 | case 2: |
||
2611 | // validated |
||
2612 | $statusCondition = ' AND revised = 1 '; |
||
2613 | break; |
||
2614 | case 3: |
||
2615 | // not validated |
||
2616 | $statusCondition = ' AND revised = 0 '; |
||
2617 | break; |
||
2618 | } |
||
2619 | } |
||
2620 | |||
2621 | if (false === $searchAllTeacherCourses && true === api_is_teacher()) { |
||
2622 | if (empty($courseInfo)) { |
||
2623 | return []; |
||
2624 | } |
||
2625 | } elseif (false === api_is_platform_admin(true, false)) { |
||
2626 | $courses = CourseManager::get_courses_list_by_user_id(api_get_user_id(), $showAttemptsInSessions, false, false); |
||
2627 | |||
2628 | if (empty($courses)) { |
||
2629 | return []; |
||
2630 | } |
||
2631 | |||
2632 | $courses = array_column($courses, 'real_id'); |
||
2633 | $is_allowedToEdit = true; |
||
2634 | $courseCondition = "c_id IN ('".implode("', '", $courses)."') "; |
||
2635 | } |
||
2636 | |||
2637 | $exercise_id = (int) $exercise_id; |
||
2638 | |||
2639 | $TBL_USER = Database::get_main_table(TABLE_MAIN_USER); |
||
2640 | $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST); |
||
2641 | $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER); |
||
2642 | $TBL_GROUP = Database::get_course_table(TABLE_GROUP); |
||
2643 | $TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
2644 | $TBL_TRACK_HOTPOTATOES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES); |
||
2645 | $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING); |
||
2646 | $TBL_ACCESS_URL_REL_SESSION = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION); |
||
2647 | $TBL_ACCESS_URL_REL_USER = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER); |
||
2648 | |||
2649 | $currentUrl = api_get_current_access_url_id(); |
||
2650 | $te_access_url_session_filter = " te.session_id in (select session_id from $TBL_ACCESS_URL_REL_SESSION where access_url_id = $currentUrl)"; |
||
2651 | $te_access_url_user_filter = " te.exe_user_id in (select user_id from $TBL_ACCESS_URL_REL_USER where access_url_id = $currentUrl)"; |
||
2652 | |||
2653 | $session_id_and = ''; |
||
2654 | $sessionCondition = ''; |
||
2655 | if (!$showSessionField) { |
||
2656 | $session_id_and = " AND te.session_id = $sessionId "; |
||
2657 | $sessionCondition = " AND ttte.session_id = $sessionId"; |
||
2658 | } |
||
2659 | |||
2660 | if ($searchAllTeacherCourses) { |
||
2661 | $session_id_and = " AND te.session_id = 0 "; |
||
2662 | $sessionCondition = " AND ttte.session_id = 0"; |
||
2663 | } |
||
2664 | |||
2665 | if ($showAttemptsInSessions) { |
||
2666 | $sessions = SessionManager::get_sessions_by_general_coach(api_get_user_id()); |
||
2667 | if (!empty($sessions)) { |
||
2668 | $sessionIds = []; |
||
2669 | foreach ($sessions as $session) { |
||
2670 | $sessionIds[] = $session['id']; |
||
2671 | } |
||
2672 | $session_id_and = " AND te.session_id IN(".implode(',', $sessionIds).") AND $te_access_url_session_filter"; |
||
2673 | $sessionCondition = " AND ttte.session_id IN(".implode(',', $sessionIds).")"; |
||
2674 | } elseif (empty($sessionId) && |
||
2675 | api_get_configuration_value('show_exercise_session_attempts_in_base_course') |
||
2676 | ) { |
||
2677 | $session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)"; |
||
2678 | $sessionCondition = ""; |
||
2679 | } else { |
||
2680 | return false; |
||
2681 | } |
||
2682 | } elseif (empty($sessionId) && |
||
2683 | api_get_configuration_value('show_exercise_session_attempts_in_base_course') |
||
2684 | ) { |
||
2685 | $session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)"; |
||
2686 | $sessionCondition = ""; |
||
2687 | } |
||
2688 | |||
2689 | if ((api_is_platform_admin() || true === api_is_session_admin()) && $originPending) { |
||
2690 | $session_id_and = " AND (te.session_id = 0 OR $te_access_url_session_filter)"; |
||
2691 | $sessionCondition = ""; |
||
2692 | if (false !== $searchAllTeacherCourses) { |
||
2693 | $courseCondition = "c_id is not null "; |
||
2694 | } |
||
2695 | } |
||
2696 | |||
2697 | $exercise_where = ''; |
||
2698 | $exerciseFilter = ''; |
||
2699 | if (!empty($exercise_id)) { |
||
2700 | $exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.' '; |
||
2701 | $exerciseFilter = " AND exe_exo_id = $exercise_id "; |
||
2702 | } |
||
2703 | |||
2704 | $hotpotatoe_where = ''; |
||
2705 | if (!empty($_GET['path'])) { |
||
2706 | $hotpotatoe_path = Database::escape_string($_GET['path']); |
||
2707 | $hotpotatoe_where .= ' AND exe_name = "'.$hotpotatoe_path.'" '; |
||
2708 | } |
||
2709 | |||
2710 | // sql for chamilo-type tests for teacher / tutor view |
||
2711 | $sql_inner_join_tbl_track_exercices = " |
||
2712 | ( |
||
2713 | SELECT DISTINCT ttte.*, if(tr.exe_id,1, 0) as revised, tr.author as corrector, MAX(tr.insert_date) as correction_date |
||
2714 | FROM $TBL_TRACK_EXERCICES ttte |
||
2715 | LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr |
||
2716 | ON (ttte.exe_id = tr.exe_id) |
||
2717 | WHERE |
||
2718 | $courseCondition |
||
2719 | $exerciseFilter |
||
2720 | $exercisesFilter |
||
2721 | $sessionCondition |
||
2722 | GROUP BY ttte.exe_id |
||
2723 | )"; |
||
2724 | |||
2725 | if ($is_allowedToEdit) { |
||
2726 | //@todo fix to work with COURSE_RELATION_TYPE_RRHH in both queries |
||
2727 | // Hack in order to filter groups |
||
2728 | $sql_inner_join_tbl_user = ''; |
||
2729 | if (strpos($extra_where_conditions, 'group_id')) { |
||
2730 | $sql_inner_join_tbl_user = " |
||
2731 | ( |
||
2732 | SELECT |
||
2733 | u.user_id, |
||
2734 | firstname, |
||
2735 | lastname, |
||
2736 | official_code, |
||
2737 | email, |
||
2738 | username, |
||
2739 | g.name as group_name, |
||
2740 | g.id as group_id |
||
2741 | FROM $TBL_USER u |
||
2742 | INNER JOIN $TBL_GROUP_REL_USER gru |
||
2743 | ON (gru.user_id = u.user_id AND gru.c_id= $courseId ) |
||
2744 | INNER JOIN $TBL_GROUP g |
||
2745 | ON (gru.group_id = g.id AND g.c_id= $courseId ) |
||
2746 | )"; |
||
2747 | } |
||
2748 | |||
2749 | if (strpos($extra_where_conditions, 'group_all')) { |
||
2750 | $extra_where_conditions = str_replace( |
||
2751 | "AND ( group_id = 'group_all' )", |
||
2752 | '', |
||
2753 | $extra_where_conditions |
||
2754 | ); |
||
2755 | $extra_where_conditions = str_replace( |
||
2756 | "AND group_id = 'group_all'", |
||
2757 | '', |
||
2758 | $extra_where_conditions |
||
2759 | ); |
||
2760 | $extra_where_conditions = str_replace( |
||
2761 | "group_id = 'group_all' AND", |
||
2762 | '', |
||
2763 | $extra_where_conditions |
||
2764 | ); |
||
2765 | |||
2766 | $sql_inner_join_tbl_user = " |
||
2767 | ( |
||
2768 | SELECT |
||
2769 | u.user_id, |
||
2770 | firstname, |
||
2771 | lastname, |
||
2772 | official_code, |
||
2773 | email, |
||
2774 | username, |
||
2775 | '' as group_name, |
||
2776 | '' as group_id |
||
2777 | FROM $TBL_USER u |
||
2778 | )"; |
||
2779 | $sql_inner_join_tbl_user = null; |
||
2780 | } |
||
2781 | |||
2782 | if (strpos($extra_where_conditions, 'group_none')) { |
||
2783 | $extra_where_conditions = str_replace( |
||
2784 | "AND ( group_id = 'group_none' )", |
||
2785 | "AND ( group_id is null )", |
||
2786 | $extra_where_conditions |
||
2787 | ); |
||
2788 | $extra_where_conditions = str_replace( |
||
2789 | "AND group_id = 'group_none'", |
||
2790 | "AND ( group_id is null )", |
||
2791 | $extra_where_conditions |
||
2792 | ); |
||
2793 | $sql_inner_join_tbl_user = " |
||
2794 | ( |
||
2795 | SELECT |
||
2796 | u.user_id, |
||
2797 | firstname, |
||
2798 | lastname, |
||
2799 | official_code, |
||
2800 | email, |
||
2801 | username, |
||
2802 | g.name as group_name, |
||
2803 | g.id as group_id |
||
2804 | FROM $TBL_USER u |
||
2805 | LEFT OUTER JOIN $TBL_GROUP_REL_USER gru |
||
2806 | ON ( gru.user_id = u.user_id AND gru.c_id = $courseId ) |
||
2807 | LEFT OUTER JOIN $TBL_GROUP g |
||
2808 | ON (gru.group_id = g.id AND g.c_id = $courseId ) |
||
2809 | )"; |
||
2810 | } |
||
2811 | |||
2812 | // All |
||
2813 | $is_empty_sql_inner_join_tbl_user = false; |
||
2814 | if (empty($sql_inner_join_tbl_user)) { |
||
2815 | $is_empty_sql_inner_join_tbl_user = true; |
||
2816 | $sql_inner_join_tbl_user = " |
||
2817 | ( |
||
2818 | SELECT u.user_id, firstname, lastname, email, username, ' ' as group_name, '' as group_id, official_code |
||
2819 | FROM $TBL_USER u |
||
2820 | WHERE u.status NOT IN(".api_get_users_status_ignored_in_reports('string').") |
||
2821 | )"; |
||
2822 | } |
||
2823 | |||
2824 | $sqlFromOption = ''; |
||
2825 | $sqlWhereOption = ''; |
||
2826 | if (false === $searchAllTeacherCourses) { |
||
2827 | $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru "; |
||
2828 | $sqlWhereOption = " AND gru.c_id = $courseId AND gru.user_id = user.user_id "; |
||
2829 | } |
||
2830 | |||
2831 | $first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname"; |
||
2832 | |||
2833 | if ($get_count) { |
||
2834 | $sql_select = 'SELECT count(te.exe_id) '; |
||
2835 | } else { |
||
2836 | $sql_select = "SELECT DISTINCT |
||
2837 | user_id, |
||
2838 | $first_and_last_name, |
||
2839 | official_code, |
||
2840 | ce.title, |
||
2841 | username, |
||
2842 | te.exe_result, |
||
2843 | te.exe_weighting, |
||
2844 | te.exe_date, |
||
2845 | te.exe_id, |
||
2846 | te.c_id, |
||
2847 | te.session_id, |
||
2848 | email as exemail, |
||
2849 | te.start_date, |
||
2850 | ce.expired_time, |
||
2851 | steps_counter, |
||
2852 | exe_user_id, |
||
2853 | te.exe_duration, |
||
2854 | te.status as completion_status, |
||
2855 | propagate_neg, |
||
2856 | revised, |
||
2857 | group_name, |
||
2858 | group_id, |
||
2859 | orig_lp_id, |
||
2860 | te.user_ip, |
||
2861 | corrector, |
||
2862 | correction_date"; |
||
2863 | } |
||
2864 | |||
2865 | $sql = " $sql_select |
||
2866 | FROM $TBL_EXERCICES AS ce |
||
2867 | INNER JOIN $sql_inner_join_tbl_track_exercices AS te |
||
2868 | ON (te.exe_exo_id = ce.iid) |
||
2869 | INNER JOIN $sql_inner_join_tbl_user AS user |
||
2870 | ON (user.user_id = exe_user_id) |
||
2871 | WHERE |
||
2872 | te.$courseCondition |
||
2873 | $session_id_and AND |
||
2874 | $te_access_url_user_filter AND |
||
2875 | ce.active <> -1 AND |
||
2876 | ce.$courseCondition |
||
2877 | $exercise_where |
||
2878 | $exercises_where |
||
2879 | $extra_where_conditions |
||
2880 | $statusCondition |
||
2881 | "; |
||
2882 | |||
2883 | // sql for hotpotatoes tests for teacher / tutor view |
||
2884 | if ($get_count) { |
||
2885 | $hpsql_select = ' SELECT count(username) '; |
||
2886 | } else { |
||
2887 | $hpsql_select = " SELECT |
||
2888 | $first_and_last_name , |
||
2889 | username, |
||
2890 | official_code, |
||
2891 | tth.exe_name, |
||
2892 | tth.exe_result , |
||
2893 | tth.exe_weighting, |
||
2894 | tth.exe_date"; |
||
2895 | } |
||
2896 | |||
2897 | $hpsql = " $hpsql_select |
||
2898 | FROM |
||
2899 | $TBL_TRACK_HOTPOTATOES tth, |
||
2900 | $TBL_USER user |
||
2901 | $sqlFromOption |
||
2902 | WHERE |
||
2903 | user.user_id=tth.exe_user_id AND |
||
2904 | tth.$courseCondition |
||
2905 | $hotpotatoe_where |
||
2906 | $sqlWhereOption AND |
||
2907 | user.status NOT IN (".api_get_users_status_ignored_in_reports('string').") |
||
2908 | ORDER BY tth.c_id ASC, tth.exe_date DESC "; |
||
2909 | } |
||
2910 | |||
2911 | if (empty($sql)) { |
||
2912 | return false; |
||
2913 | } |
||
2914 | |||
2915 | if ($get_count) { |
||
2916 | $resx = Database::query($sql); |
||
2917 | $rowx = Database::fetch_row($resx, 'ASSOC'); |
||
2918 | |||
2919 | return $rowx[0]; |
||
2920 | } |
||
2921 | |||
2922 | $teacher_id_list = []; |
||
2923 | if (!empty($courseCode)) { |
||
2924 | $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode); |
||
2925 | if (!empty($teacher_list)) { |
||
2926 | foreach ($teacher_list as $teacher) { |
||
2927 | $teacher_id_list[] = $teacher['user_id']; |
||
2928 | } |
||
2929 | } |
||
2930 | } |
||
2931 | |||
2932 | $scoreDisplay = new ScoreDisplay(); |
||
2933 | $decimalSeparator = '.'; |
||
2934 | $thousandSeparator = ','; |
||
2935 | |||
2936 | if ($useCommaAsDecimalPoint) { |
||
2937 | $decimalSeparator = ','; |
||
2938 | $thousandSeparator = ''; |
||
2939 | } |
||
2940 | |||
2941 | $hideIp = api_get_configuration_value('exercise_hide_ip'); |
||
2942 | $listInfo = []; |
||
2943 | // Simple exercises |
||
2944 | if (empty($hotpotatoe_where)) { |
||
2945 | $column = !empty($column) ? Database::escape_string($column) : null; |
||
2946 | $from = (int) $from; |
||
2947 | $number_of_items = (int) $number_of_items; |
||
2948 | $direction = !in_array(strtolower(trim($direction)), ['asc', 'desc']) ? 'asc' : $direction; |
||
2949 | |||
2950 | if (!empty($column)) { |
||
2951 | $sql .= " ORDER BY `$column` $direction "; |
||
2952 | } |
||
2953 | |||
2954 | if (!$getOnlyIds) { |
||
2955 | $sql .= " LIMIT $from, $number_of_items"; |
||
2956 | } |
||
2957 | |||
2958 | $results = []; |
||
2959 | $resx = Database::query($sql); |
||
2960 | while ($rowx = Database::fetch_array($resx, 'ASSOC')) { |
||
2961 | $results[] = $rowx; |
||
2962 | } |
||
2963 | |||
2964 | $clean_group_list = []; |
||
2965 | $lp_list = []; |
||
2966 | |||
2967 | if (!empty($courseInfo)) { |
||
2968 | $group_list = GroupManager::get_group_list(null, $courseInfo); |
||
2969 | if (!empty($group_list)) { |
||
2970 | foreach ($group_list as $group) { |
||
2971 | $clean_group_list[$group['id']] = $group['name']; |
||
2972 | } |
||
2973 | } |
||
2974 | |||
2975 | $lp_list_obj = new LearnpathList(api_get_user_id()); |
||
2976 | $lp_list = $lp_list_obj->get_flat_list(); |
||
2977 | $oldIds = array_column($lp_list, 'lp_old_id', 'iid'); |
||
2978 | } |
||
2979 | |||
2980 | if (is_array($results)) { |
||
2981 | $users_array_id = []; |
||
2982 | $from_gradebook = false; |
||
2983 | if (isset($_GET['gradebook']) && $_GET['gradebook'] === 'view') { |
||
2984 | $from_gradebook = true; |
||
2985 | } |
||
2986 | $sizeof = count($results); |
||
2987 | $locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE); |
||
2988 | $timeNow = strtotime(api_get_utc_datetime()); |
||
2989 | $courseItemList = []; |
||
2990 | // Looping results |
||
2991 | for ($i = 0; $i < $sizeof; $i++) { |
||
2992 | $attempt = $results[$i]; |
||
2993 | $revised = $attempt['revised']; |
||
2994 | $attemptSessionId = (int) $attempt['session_id']; |
||
2995 | if (false === $searchAllTeacherCourses) { |
||
2996 | $courseItemInfo = api_get_course_info(); |
||
2997 | $cidReq = api_get_cidreq(false).'&id_session='.$attemptSessionId; |
||
2998 | } else { |
||
2999 | if (isset($courseItemList[$attempt['c_id']])) { |
||
3000 | $courseItemInfo = $courseItemList[$attempt['c_id']]; |
||
3001 | } else { |
||
3002 | $courseItemInfo = api_get_course_info_by_id($attempt['c_id']); |
||
3003 | $courseItemList[$attempt['c_id']] = $courseItemInfo; |
||
3004 | } |
||
3005 | $cidReq = 'cidReq='.$courseItemInfo['code'].'&id_session='.$attemptSessionId; |
||
3006 | } |
||
3007 | |||
3008 | if ('incomplete' === $attempt['completion_status']) { |
||
3009 | // If the exercise was incomplete, we need to determine |
||
3010 | // if it is still into the time allowed, or if its |
||
3011 | // allowed time has expired and it can be closed |
||
3012 | // (it's "unclosed") |
||
3013 | $minutes = $attempt['expired_time']; |
||
3014 | if ($minutes == 0) { |
||
3015 | // There's no time limit, so obviously the attempt |
||
3016 | // can still be "ongoing", but the teacher should |
||
3017 | // be able to choose to close it, so mark it as |
||
3018 | // "unclosed" instead of "ongoing" |
||
3019 | $revised = 2; |
||
3020 | } else { |
||
3021 | $allowedSeconds = $minutes * 60; |
||
3022 | $timeAttemptStarted = strtotime($attempt['start_date']); |
||
3023 | $secondsSinceStart = $timeNow - $timeAttemptStarted; |
||
3024 | $revised = 3; // mark as "ongoing" |
||
3025 | if ($secondsSinceStart > $allowedSeconds) { |
||
3026 | $revised = 2; // mark as "unclosed" |
||
3027 | } |
||
3028 | } |
||
3029 | } |
||
3030 | |||
3031 | if (4 == $status && 2 != $revised) { |
||
3032 | // Filter by status "unclosed" |
||
3033 | continue; |
||
3034 | } |
||
3035 | |||
3036 | if (5 == $status && 3 != $revised) { |
||
3037 | // Filter by status "ongoing" |
||
3038 | continue; |
||
3039 | } |
||
3040 | |||
3041 | if (3 == $status && in_array($revised, [1, 2, 3])) { |
||
3042 | // Filter by status "not validated" |
||
3043 | continue; |
||
3044 | } |
||
3045 | |||
3046 | if ($from_gradebook && ($is_allowedToEdit)) { |
||
3047 | if (in_array( |
||
3048 | $attempt['username'].$attempt['firstname'].$attempt['lastname'], |
||
3049 | $users_array_id |
||
3050 | )) { |
||
3051 | continue; |
||
3052 | } |
||
3053 | $users_array_id[] = $attempt['username'].$attempt['firstname'].$attempt['lastname']; |
||
3054 | } |
||
3055 | |||
3056 | $lp_obj = isset($attempt['orig_lp_id']) && |
||
3057 | isset($lp_list[$attempt['orig_lp_id']]) ? $lp_list[$attempt['orig_lp_id']] : null; |
||
3058 | if (empty($lp_obj)) { |
||
3059 | // Try to get the old id (id instead of iid) |
||
3060 | $lpNewId = isset($attempt['orig_lp_id']) && |
||
3061 | isset($oldIds[$attempt['orig_lp_id']]) ? $oldIds[$attempt['orig_lp_id']] : null; |
||
3062 | if ($lpNewId) { |
||
3063 | $lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null; |
||
3064 | } |
||
3065 | } |
||
3066 | $lp_name = null; |
||
3067 | if ($lp_obj) { |
||
3068 | $url = api_get_path(WEB_CODE_PATH). |
||
3069 | 'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$attempt['orig_lp_id']; |
||
3070 | $lp_name = Display::url( |
||
3071 | $lp_obj['lp_name'], |
||
3072 | $url, |
||
3073 | ['target' => '_blank'] |
||
3074 | ); |
||
3075 | } |
||
3076 | |||
3077 | // Add all groups by user |
||
3078 | $group_name_list = ''; |
||
3079 | if ($is_empty_sql_inner_join_tbl_user) { |
||
3080 | $group_list = GroupManager::get_group_ids( |
||
3081 | api_get_course_int_id(), |
||
3082 | $attempt['user_id'] |
||
3083 | ); |
||
3084 | |||
3085 | foreach ($group_list as $id) { |
||
3086 | if (isset($clean_group_list[$id])) { |
||
3087 | $group_name_list .= $clean_group_list[$id].'<br/>'; |
||
3088 | } |
||
3089 | } |
||
3090 | $attempt['group_name'] = $group_name_list; |
||
3091 | } |
||
3092 | |||
3093 | $attempt['exe_duration'] = !empty($attempt['exe_duration']) ? round($attempt['exe_duration'] / 60) : 0; |
||
3094 | $id = $attempt['exe_id']; |
||
3095 | $dt = api_convert_and_format_date($attempt['exe_weighting']); |
||
3096 | |||
3097 | // we filter the results if we have the permission to |
||
3098 | $result_disabled = 0; |
||
3099 | if (isset($attempt['results_disabled'])) { |
||
3100 | $result_disabled = (int) $attempt['results_disabled']; |
||
3101 | } |
||
3102 | if ($result_disabled == 0) { |
||
3103 | $my_res = $attempt['exe_result']; |
||
3104 | $my_total = $attempt['exe_weighting']; |
||
3105 | $attempt['start_date'] = api_get_local_time($attempt['start_date']); |
||
3106 | $attempt['exe_date'] = api_get_local_time($attempt['exe_date']); |
||
3107 | |||
3108 | if (!$attempt['propagate_neg'] && $my_res < 0) { |
||
3109 | $my_res = 0; |
||
3110 | } |
||
3111 | |||
3112 | $score = self::show_score( |
||
3113 | $my_res, |
||
3114 | $my_total, |
||
3115 | true, |
||
3116 | true, |
||
3117 | false, |
||
3118 | false, |
||
3119 | $decimalSeparator, |
||
3120 | $thousandSeparator, |
||
3121 | $roundValues |
||
3122 | ); |
||
3123 | |||
3124 | $actions = '<div class="pull-right">'; |
||
3125 | if ($is_allowedToEdit) { |
||
3126 | if (isset($teacher_id_list)) { |
||
3127 | if (in_array( |
||
3128 | $attempt['exe_user_id'], |
||
3129 | $teacher_id_list |
||
3130 | )) { |
||
3131 | $actions .= Display::return_icon('teacher.png', get_lang('Teacher')); |
||
3132 | } |
||
3133 | } |
||
3134 | $revisedLabel = ''; |
||
3135 | switch ($revised) { |
||
3136 | case 0: |
||
3137 | $actions .= "<a href='exercise_show.php?".$cidReq."&action=qualify&id=$id'>". |
||
3138 | Display::return_icon( |
||
3139 | 'quiz.png', |
||
3140 | get_lang('Qualify') |
||
3141 | ); |
||
3142 | $actions .= '</a>'; |
||
3143 | $revisedLabel = Display::label( |
||
3144 | get_lang('NotValidated'), |
||
3145 | 'info' |
||
3146 | ); |
||
3147 | break; |
||
3148 | case 1: |
||
3149 | $actions .= "<a href='exercise_show.php?".$cidReq."&action=edit&id=$id'>". |
||
3150 | Display::return_icon( |
||
3151 | 'edit.png', |
||
3152 | get_lang('Edit'), |
||
3153 | [], |
||
3154 | ICON_SIZE_SMALL |
||
3155 | ); |
||
3156 | $actions .= '</a>'; |
||
3157 | $revisedLabel = Display::label( |
||
3158 | get_lang('Validated'), |
||
3159 | 'success' |
||
3160 | ); |
||
3161 | break; |
||
3162 | case 2: //finished but not marked as such |
||
3163 | $actions .= '<a href="exercise_report.php?' |
||
3164 | .$cidReq |
||
3165 | .'&exerciseId='.$exercise_id |
||
3166 | .'&a=close&id='.$id |
||
3167 | .'">'. |
||
3168 | Display::return_icon( |
||
3169 | 'lock.png', |
||
3170 | get_lang('MarkAttemptAsClosed'), |
||
3171 | [], |
||
3172 | ICON_SIZE_SMALL |
||
3173 | ); |
||
3174 | $actions .= '</a>'; |
||
3175 | $revisedLabel = Display::label( |
||
3176 | get_lang('Unclosed'), |
||
3177 | 'warning' |
||
3178 | ); |
||
3179 | break; |
||
3180 | case 3: //still ongoing |
||
3181 | $actions .= Display::return_icon( |
||
3182 | 'clock.png', |
||
3183 | get_lang('AttemptStillOngoingPleaseWait'), |
||
3184 | [], |
||
3185 | ICON_SIZE_SMALL |
||
3186 | ); |
||
3187 | $actions .= ''; |
||
3188 | $revisedLabel = Display::label( |
||
3189 | get_lang('Ongoing'), |
||
3190 | 'danger' |
||
3191 | ); |
||
3192 | break; |
||
3193 | } |
||
3194 | |||
3195 | if ($filter == 2) { |
||
3196 | $actions .= ' <a href="exercise_history.php?'.$cidReq.'&exe_id='.$id.'">'. |
||
3197 | Display::return_icon( |
||
3198 | 'history.png', |
||
3199 | get_lang('ViewHistoryChange') |
||
3200 | ).'</a>'; |
||
3201 | } |
||
3202 | |||
3203 | // Admin can always delete the attempt |
||
3204 | if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) { |
||
3205 | $ip = Tracking::get_ip_from_user_event( |
||
3206 | $attempt['exe_user_id'], |
||
3207 | api_get_utc_datetime(), |
||
3208 | false |
||
3209 | ); |
||
3210 | $actions .= '<a href="http://www.whatsmyip.org/ip-geo-location/?ip='.$ip.'" target="_blank">' |
||
3211 | .Display::return_icon('info.png', $ip) |
||
3212 | .'</a>'; |
||
3213 | |||
3214 | $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'. |
||
3215 | $cidReq.'&'. |
||
3216 | http_build_query([ |
||
3217 | 'id' => $id, |
||
3218 | 'exercise' => $exercise_id, |
||
3219 | 'user' => $attempt['exe_user_id'], |
||
3220 | ]); |
||
3221 | $actions .= Display::url( |
||
3222 | Display::return_icon('reload.png', get_lang('RecalculateResults')), |
||
3223 | $recalculateUrl, |
||
3224 | [ |
||
3225 | 'data-exercise' => $exercise_id, |
||
3226 | 'data-user' => $attempt['exe_user_id'], |
||
3227 | 'data-id' => $id, |
||
3228 | 'class' => 'exercise-recalculate', |
||
3229 | ] |
||
3230 | ); |
||
3231 | |||
3232 | $filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0; |
||
3233 | $delete_link = '<a |
||
3234 | href="exercise_report.php?'.$cidReq.'&filter_by_user='.$filterByUser.'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'" |
||
3235 | onclick="javascript:if(!confirm(\''.sprintf( |
||
3236 | addslashes(get_lang('DeleteAttempt')), |
||
3237 | $attempt['username'], |
||
3238 | $dt |
||
3239 | ).'\')) return false;">'; |
||
3240 | $delete_link .= Display::return_icon( |
||
3241 | 'delete.png', |
||
3242 | addslashes(get_lang('Delete')) |
||
3243 | ).'</a>'; |
||
3244 | |||
3245 | if (api_is_drh() && !api_is_platform_admin()) { |
||
3246 | $delete_link = null; |
||
3247 | } |
||
3248 | if (api_is_session_admin()) { |
||
3249 | $delete_link = ''; |
||
3250 | } |
||
3251 | if ($revised == 3) { |
||
3252 | $delete_link = null; |
||
3253 | } |
||
3254 | $actions .= $delete_link; |
||
3255 | } |
||
3256 | } else { |
||
3257 | $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.$cidReq.'&id='.$attempt['exe_id']; |
||
3258 | $attempt_link = Display::url( |
||
3259 | get_lang('Show'), |
||
3260 | $attempt_url, |
||
3261 | [ |
||
3262 | 'class' => 'ajax btn btn-default', |
||
3263 | 'data-title' => get_lang('Show'), |
||
3264 | ] |
||
3265 | ); |
||
3266 | $actions .= $attempt_link; |
||
3267 | } |
||
3268 | $actions .= '</div>'; |
||
3269 | |||
3270 | if (!empty($userExtraFieldsToAdd)) { |
||
3271 | foreach ($userExtraFieldsToAdd as $variable) { |
||
3272 | $extraFieldValue = new ExtraFieldValue('user'); |
||
3273 | $values = $extraFieldValue->get_values_by_handler_and_field_variable( |
||
3274 | $attempt['user_id'], |
||
3275 | $variable |
||
3276 | ); |
||
3277 | if (isset($values['value'])) { |
||
3278 | $attempt[$variable] = $values['value']; |
||
3279 | } |
||
3280 | } |
||
3281 | } |
||
3282 | |||
3283 | $exeId = $attempt['exe_id']; |
||
3284 | $attempt['id'] = $exeId; |
||
3285 | $category_list = []; |
||
3286 | if ($is_allowedToEdit) { |
||
3287 | $sessionName = ''; |
||
3288 | $sessionStartAccessDate = ''; |
||
3289 | if (!empty($attemptSessionId)) { |
||
3290 | $sessionInfo = api_get_session_info($attemptSessionId); |
||
3291 | if (!empty($sessionInfo)) { |
||
3292 | $sessionName = $sessionInfo['name']; |
||
3293 | $sessionStartAccessDate = api_get_local_time($sessionInfo['access_start_date']); |
||
3294 | } |
||
3295 | } |
||
3296 | |||
3297 | $courseId = $courseItemInfo['real_id']; |
||
3298 | |||
3299 | if ($searchAllTeacherCourses) { |
||
3300 | $attempt['course'] = $courseItemInfo['title']; |
||
3301 | $attempt['exercise'] = $attempt['title']; |
||
3302 | } |
||
3303 | |||
3304 | $objExercise = new Exercise($courseId); |
||
3305 | if ($showExerciseCategories) { |
||
3306 | // Getting attempt info |
||
3307 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||
3308 | if (!empty($exercise_stat_info['data_tracking'])) { |
||
3309 | $question_list = explode(',', $exercise_stat_info['data_tracking']); |
||
3310 | if (!empty($question_list)) { |
||
3311 | foreach ($question_list as $questionId) { |
||
3312 | $objQuestionTmp = Question::read($questionId, $objExercise->course); |
||
3313 | // We're inside *one* question. |
||
3314 | // Go through each possible answer for this question. |
||
3315 | $result = $objExercise->manage_answer( |
||
3316 | $exeId, |
||
3317 | $questionId, |
||
3318 | null, |
||
3319 | 'exercise_result', |
||
3320 | false, |
||
3321 | false, |
||
3322 | true, |
||
3323 | false, |
||
3324 | $objExercise->selectPropagateNeg(), |
||
3325 | null, |
||
3326 | true |
||
3327 | ); |
||
3328 | |||
3329 | $my_total_score = $result['score']; |
||
3330 | $my_total_weight = $result['weight']; |
||
3331 | |||
3332 | // Category report |
||
3333 | $category_was_added_for_this_test = false; |
||
3334 | if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) { |
||
3335 | if (!isset($category_list[$objQuestionTmp->category]['score'])) { |
||
3336 | $category_list[$objQuestionTmp->category]['score'] = 0; |
||
3337 | } |
||
3338 | if (!isset($category_list[$objQuestionTmp->category]['total'])) { |
||
3339 | $category_list[$objQuestionTmp->category]['total'] = 0; |
||
3340 | } |
||
3341 | $category_list[$objQuestionTmp->category]['score'] += $my_total_score; |
||
3342 | $category_list[$objQuestionTmp->category]['total'] += $my_total_weight; |
||
3343 | $category_was_added_for_this_test = true; |
||
3344 | } |
||
3345 | |||
3346 | if (isset($objQuestionTmp->category_list) && |
||
3347 | !empty($objQuestionTmp->category_list) |
||
3348 | ) { |
||
3349 | foreach ($objQuestionTmp->category_list as $category_id) { |
||
3350 | $category_list[$category_id]['score'] += $my_total_score; |
||
3351 | $category_list[$category_id]['total'] += $my_total_weight; |
||
3352 | $category_was_added_for_this_test = true; |
||
3353 | } |
||
3354 | } |
||
3355 | |||
3356 | // No category for this question! |
||
3357 | if ($category_was_added_for_this_test == false) { |
||
3358 | if (!isset($category_list['none']['score'])) { |
||
3359 | $category_list['none']['score'] = 0; |
||
3360 | } |
||
3361 | if (!isset($category_list['none']['total'])) { |
||
3362 | $category_list['none']['total'] = 0; |
||
3363 | } |
||
3364 | |||
3365 | $category_list['none']['score'] += $my_total_score; |
||
3366 | $category_list['none']['total'] += $my_total_weight; |
||
3367 | } |
||
3368 | } |
||
3369 | } |
||
3370 | } |
||
3371 | } |
||
3372 | |||
3373 | foreach ($category_list as $categoryId => $result) { |
||
3374 | $scoreToDisplay = self::show_score( |
||
3375 | $result['score'], |
||
3376 | $result['total'], |
||
3377 | true, |
||
3378 | true, |
||
3379 | false, |
||
3380 | false, |
||
3381 | $decimalSeparator, |
||
3382 | $thousandSeparator, |
||
3383 | $roundValues |
||
3384 | ); |
||
3385 | $attempt['category_'.$categoryId] = $scoreToDisplay; |
||
3386 | $attempt['category_'.$categoryId.'_score_percentage'] = self::show_score( |
||
3387 | $result['score'], |
||
3388 | $result['total'], |
||
3389 | true, |
||
3390 | true, |
||
3391 | true, |
||
3392 | true, |
||
3393 | $decimalSeparator, |
||
3394 | $thousandSeparator, |
||
3395 | $roundValues |
||
3396 | ); |
||
3397 | $attempt['category_'.$categoryId.'_only_score'] = $result['score']; |
||
3398 | $attempt['category_'.$categoryId.'_total'] = $result['total']; |
||
3399 | } |
||
3400 | $attempt['session'] = $sessionName; |
||
3401 | $attempt['session_access_start_date'] = $sessionStartAccessDate; |
||
3402 | $attempt['status'] = $revisedLabel; |
||
3403 | $attempt['score'] = $score; |
||
3404 | $attempt['qualificator_fullname'] = ''; |
||
3405 | $attempt['date_of_qualification'] = ''; |
||
3406 | if (!empty($attempt['corrector'])) { |
||
3407 | $qualificatorAuthor = api_get_user_info($attempt['corrector']); |
||
3408 | $attempt['qualificator_fullname'] = api_get_person_name($qualificatorAuthor['firstname'], $qualificatorAuthor['lastname']); |
||
3409 | } |
||
3410 | if (!empty($attempt['correction_date'])) { |
||
3411 | $attempt['date_of_qualification'] = api_convert_and_format_date($attempt['correction_date'], DATE_TIME_FORMAT_SHORT); |
||
3412 | } |
||
3413 | $attempt['score_percentage'] = self::show_score( |
||
3414 | $my_res, |
||
3415 | $my_total, |
||
3416 | true, |
||
3417 | true, |
||
3418 | true, |
||
3419 | true, |
||
3420 | $decimalSeparator, |
||
3421 | $thousandSeparator, |
||
3422 | $roundValues |
||
3423 | ); |
||
3424 | |||
3425 | if ($roundValues) { |
||
3426 | $whole = floor($my_res); // 1 |
||
3427 | $fraction = $my_res - $whole; // .25 |
||
3428 | if ($fraction >= 0.5) { |
||
3429 | $onlyScore = ceil($my_res); |
||
3430 | } else { |
||
3431 | $onlyScore = round($my_res); |
||
3432 | } |
||
3433 | } else { |
||
3434 | $onlyScore = $scoreDisplay->format_score( |
||
3435 | $my_res, |
||
3436 | false, |
||
3437 | $decimalSeparator, |
||
3438 | $thousandSeparator |
||
3439 | ); |
||
3440 | } |
||
3441 | |||
3442 | $attempt['only_score'] = $onlyScore; |
||
3443 | |||
3444 | if ($roundValues) { |
||
3445 | $whole = floor($my_total); // 1 |
||
3446 | $fraction = $my_total - $whole; // .25 |
||
3447 | if ($fraction >= 0.5) { |
||
3448 | $onlyTotal = ceil($my_total); |
||
3449 | } else { |
||
3450 | $onlyTotal = round($my_total); |
||
3451 | } |
||
3452 | } else { |
||
3453 | $onlyTotal = $scoreDisplay->format_score( |
||
3454 | $my_total, |
||
3455 | false, |
||
3456 | $decimalSeparator, |
||
3457 | $thousandSeparator |
||
3458 | ); |
||
3459 | } |
||
3460 | $attempt['total'] = $onlyTotal; |
||
3461 | $attempt['lp'] = $lp_name; |
||
3462 | $attempt['actions'] = $actions; |
||
3463 | if ($hideIp && isset($attempt['user_ip'])) { |
||
3464 | unset($attempt['user_ip']); |
||
3465 | } |
||
3466 | $listInfo[] = $attempt; |
||
3467 | } else { |
||
3468 | $attempt['status'] = $revisedLabel; |
||
3469 | $attempt['score'] = $score; |
||
3470 | $attempt['actions'] = $actions; |
||
3471 | if ($hideIp && isset($attempt['user_ip'])) { |
||
3472 | unset($attempt['user_ip']); |
||
3473 | } |
||
3474 | $listInfo[] = $attempt; |
||
3475 | } |
||
3476 | } |
||
3477 | } |
||
3478 | } |
||
3479 | } else { |
||
3480 | $hpresults = []; |
||
3481 | $res = Database::query($hpsql); |
||
3482 | if ($res !== false) { |
||
3483 | $i = 0; |
||
3484 | while ($resA = Database::fetch_array($res, 'NUM')) { |
||
3485 | for ($j = 0; $j < 6; $j++) { |
||
3486 | $hpresults[$i][$j] = $resA[$j]; |
||
3487 | } |
||
3488 | $i++; |
||
3489 | } |
||
3490 | } |
||
3491 | |||
3492 | // Print HotPotatoes test results. |
||
3493 | if (is_array($hpresults)) { |
||
3494 | for ($i = 0; $i < count($hpresults); $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
}
![]() |
|||
3495 | $hp_title = GetQuizName($hpresults[$i][3], $documentPath); |
||
3496 | if ($hp_title == '') { |
||
3497 | $hp_title = basename($hpresults[$i][3]); |
||
3498 | } |
||
3499 | |||
3500 | $hp_date = api_get_local_time( |
||
3501 | $hpresults[$i][6], |
||
3502 | null, |
||
3503 | date_default_timezone_get() |
||
3504 | ); |
||
3505 | $hp_result = round(($hpresults[$i][4] / ($hpresults[$i][5] != 0 ? $hpresults[$i][5] : 1)) * 100, 2); |
||
3506 | $hp_result .= '% ('.$hpresults[$i][4].' / '.$hpresults[$i][5].')'; |
||
3507 | |||
3508 | if ($is_allowedToEdit) { |
||
3509 | $listInfo[] = [ |
||
3510 | $hpresults[$i][0], |
||
3511 | $hpresults[$i][1], |
||
3512 | $hpresults[$i][2], |
||
3513 | '', |
||
3514 | $hp_title, |
||
3515 | '-', |
||
3516 | $hp_date, |
||
3517 | $hp_result, |
||
3518 | '-', |
||
3519 | ]; |
||
3520 | } else { |
||
3521 | $listInfo[] = [ |
||
3522 | $hp_title, |
||
3523 | '-', |
||
3524 | $hp_date, |
||
3525 | $hp_result, |
||
3526 | '-', |
||
3527 | ]; |
||
3528 | } |
||
3529 | } |
||
3530 | } |
||
3531 | } |
||
3532 | |||
3533 | return $listInfo; |
||
3534 | } |
||
3535 | |||
3536 | /** |
||
3537 | * @param $score |
||
3538 | * @param $weight |
||
3539 | * |
||
3540 | * @return array |
||
3541 | */ |
||
3542 | public static function convertScoreToPlatformSetting($score, $weight) |
||
3543 | { |
||
3544 | $maxNote = api_get_setting('exercise_max_score'); |
||
3545 | $minNote = api_get_setting('exercise_min_score'); |
||
3546 | |||
3547 | if ($maxNote != '' && $minNote != '') { |
||
3548 | if (!empty($weight) && (float) $weight !== (float) 0) { |
||
3549 | $score = $minNote + ($maxNote - $minNote) * $score / $weight; |
||
3550 | } else { |
||
3551 | $score = $minNote; |
||
3552 | } |
||
3553 | $weight = $maxNote; |
||
3554 | } |
||
3555 | |||
3556 | return ['score' => $score, 'weight' => $weight]; |
||
3557 | } |
||
3558 | |||
3559 | /** |
||
3560 | * Converts the score with the exercise_max_note and exercise_min_score |
||
3561 | * the platform settings + formats the results using the float_format function. |
||
3562 | * |
||
3563 | * @param float $score |
||
3564 | * @param float $weight |
||
3565 | * @param bool $show_percentage show percentage or not |
||
3566 | * @param bool $use_platform_settings use or not the platform settings |
||
3567 | * @param bool $show_only_percentage |
||
3568 | * @param bool $hidePercentageSign hide "%" sign |
||
3569 | * @param string $decimalSeparator |
||
3570 | * @param string $thousandSeparator |
||
3571 | * @param bool $roundValues This option rounds the float values into a int using ceil() |
||
3572 | * @param bool $removeEmptyDecimals |
||
3573 | * |
||
3574 | * @return string an html with the score modified |
||
3575 | */ |
||
3576 | public static function show_score( |
||
3577 | $score, |
||
3578 | $weight, |
||
3579 | $show_percentage = true, |
||
3580 | $use_platform_settings = true, |
||
3581 | $show_only_percentage = false, |
||
3582 | $hidePercentageSign = false, |
||
3583 | $decimalSeparator = '.', |
||
3584 | $thousandSeparator = ',', |
||
3585 | $roundValues = false, |
||
3586 | $removeEmptyDecimals = false |
||
3587 | ) { |
||
3588 | if (is_null($score) && is_null($weight)) { |
||
3589 | return '-'; |
||
3590 | } |
||
3591 | |||
3592 | $decimalSeparator = empty($decimalSeparator) ? '.' : $decimalSeparator; |
||
3593 | $thousandSeparator = empty($thousandSeparator) ? ',' : $thousandSeparator; |
||
3594 | |||
3595 | if ($use_platform_settings) { |
||
3596 | $result = self::convertScoreToPlatformSetting($score, $weight); |
||
3597 | $score = $result['score']; |
||
3598 | $weight = $result['weight']; |
||
3599 | } |
||
3600 | |||
3601 | $percentage = (100 * $score) / ($weight != 0 ? $weight : 1); |
||
3602 | |||
3603 | // Formats values |
||
3604 | $percentage = float_format($percentage, 1); |
||
3605 | $score = float_format($score, 1); |
||
3606 | $weight = float_format($weight, 1); |
||
3607 | |||
3608 | if ($roundValues) { |
||
3609 | $whole = floor($percentage); // 1 |
||
3610 | $fraction = $percentage - $whole; // .25 |
||
3611 | |||
3612 | // Formats values |
||
3613 | if ($fraction >= 0.5) { |
||
3614 | $percentage = ceil($percentage); |
||
3615 | } else { |
||
3616 | $percentage = round($percentage); |
||
3617 | } |
||
3618 | |||
3619 | $whole = floor($score); // 1 |
||
3620 | $fraction = $score - $whole; // .25 |
||
3621 | if ($fraction >= 0.5) { |
||
3622 | $score = ceil($score); |
||
3623 | } else { |
||
3624 | $score = round($score); |
||
3625 | } |
||
3626 | |||
3627 | $whole = floor($weight); // 1 |
||
3628 | $fraction = $weight - $whole; // .25 |
||
3629 | if ($fraction >= 0.5) { |
||
3630 | $weight = ceil($weight); |
||
3631 | } else { |
||
3632 | $weight = round($weight); |
||
3633 | } |
||
3634 | } else { |
||
3635 | // Formats values |
||
3636 | $percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator); |
||
3637 | $score = float_format($score, 1, $decimalSeparator, $thousandSeparator); |
||
3638 | $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator); |
||
3639 | } |
||
3640 | |||
3641 | if ($show_percentage) { |
||
3642 | $percentageSign = ' %'; |
||
3643 | if ($hidePercentageSign) { |
||
3644 | $percentageSign = ''; |
||
3645 | } |
||
3646 | $html = $percentage."$percentageSign ($score / $weight)"; |
||
3647 | if ($show_only_percentage) { |
||
3648 | $html = $percentage.$percentageSign; |
||
3649 | } |
||
3650 | } else { |
||
3651 | if ($removeEmptyDecimals) { |
||
3652 | if (ScoreDisplay::hasEmptyDecimals($weight)) { |
||
3653 | $weight = round($weight); |
||
3654 | } |
||
3655 | } |
||
3656 | $html = $score.' / '.$weight; |
||
3657 | } |
||
3658 | |||
3659 | // Over write score |
||
3660 | $scoreBasedInModel = self::convertScoreToModel($percentage); |
||
3661 | if (!empty($scoreBasedInModel)) { |
||
3662 | $html = $scoreBasedInModel; |
||
3663 | } |
||
3664 | |||
3665 | // Ignore other formats and use the configuration['exercise_score_format'] value |
||
3666 | // But also keep the round values settings. |
||
3667 | $format = api_get_configuration_value('exercise_score_format'); |
||
3668 | if (!empty($format)) { |
||
3669 | $html = ScoreDisplay::instance()->display_score([$score, $weight], $format); |
||
3670 | } |
||
3671 | |||
3672 | return Display::span($html, ['class' => 'score_exercise']); |
||
3673 | } |
||
3674 | |||
3675 | /** |
||
3676 | * @param array $model |
||
3677 | * @param float $percentage |
||
3678 | * |
||
3679 | * @return string |
||
3680 | */ |
||
3681 | public static function getModelStyle($model, $percentage) |
||
3682 | { |
||
3683 | return '<span class="'.$model['css_class'].'"> </span>'; |
||
3684 | } |
||
3685 | |||
3686 | /** |
||
3687 | * @param float $percentage value between 0 and 100 |
||
3688 | * |
||
3689 | * @return string |
||
3690 | */ |
||
3691 | public static function convertScoreToModel($percentage) |
||
3692 | { |
||
3693 | $model = self::getCourseScoreModel(); |
||
3694 | if (!empty($model)) { |
||
3695 | $scoreWithGrade = []; |
||
3696 | foreach ($model['score_list'] as $item) { |
||
3697 | if ($percentage >= $item['min'] && $percentage <= $item['max']) { |
||
3698 | $scoreWithGrade = $item; |
||
3699 | break; |
||
3700 | } |
||
3701 | } |
||
3702 | |||
3703 | if (!empty($scoreWithGrade)) { |
||
3704 | return self::getModelStyle($scoreWithGrade, $percentage); |
||
3705 | } |
||
3706 | } |
||
3707 | |||
3708 | return ''; |
||
3709 | } |
||
3710 | |||
3711 | /** |
||
3712 | * @return array |
||
3713 | */ |
||
3714 | public static function getCourseScoreModel() |
||
3715 | { |
||
3716 | $modelList = self::getScoreModels(); |
||
3717 | if (empty($modelList)) { |
||
3718 | return []; |
||
3719 | } |
||
3720 | |||
3721 | $courseInfo = api_get_course_info(); |
||
3722 | if (!empty($courseInfo)) { |
||
3723 | $scoreModelId = api_get_course_setting('score_model_id'); |
||
3724 | if (-1 != $scoreModelId) { |
||
3725 | $modelIdList = array_column($modelList['models'], 'id'); |
||
3726 | if (in_array($scoreModelId, $modelIdList)) { |
||
3727 | foreach ($modelList['models'] as $item) { |
||
3728 | if ($item['id'] == $scoreModelId) { |
||
3729 | return $item; |
||
3730 | } |
||
3731 | } |
||
3732 | } |
||
3733 | } |
||
3734 | } |
||
3735 | |||
3736 | return []; |
||
3737 | } |
||
3738 | |||
3739 | /** |
||
3740 | * @return array |
||
3741 | */ |
||
3742 | public static function getScoreModels() |
||
3743 | { |
||
3744 | return api_get_configuration_value('score_grade_model'); |
||
3745 | } |
||
3746 | |||
3747 | /** |
||
3748 | * @param float $score |
||
3749 | * @param float $weight |
||
3750 | * @param string $passPercentage |
||
3751 | * |
||
3752 | * @return bool |
||
3753 | */ |
||
3754 | public static function isSuccessExerciseResult($score, $weight, $passPercentage) |
||
3755 | { |
||
3756 | $percentage = float_format( |
||
3757 | ($score / (0 != $weight ? $weight : 1)) * 100, |
||
3758 | 1 |
||
3759 | ); |
||
3760 | if (isset($passPercentage) && !empty($passPercentage)) { |
||
3761 | if ($percentage >= $passPercentage) { |
||
3762 | return true; |
||
3763 | } |
||
3764 | } |
||
3765 | |||
3766 | return false; |
||
3767 | } |
||
3768 | |||
3769 | /** |
||
3770 | * @param string $name |
||
3771 | * @param $weight |
||
3772 | * @param $selected |
||
3773 | * |
||
3774 | * @return bool |
||
3775 | */ |
||
3776 | public static function addScoreModelInput( |
||
3777 | FormValidator $form, |
||
3778 | $name, |
||
3779 | $weight, |
||
3780 | $selected |
||
3781 | ) { |
||
3782 | $model = self::getCourseScoreModel(); |
||
3783 | if (empty($model)) { |
||
3784 | return false; |
||
3785 | } |
||
3786 | |||
3787 | /** @var HTML_QuickForm_select $element */ |
||
3788 | $element = $form->createElement( |
||
3789 | 'select', |
||
3790 | $name, |
||
3791 | get_lang('Qualification'), |
||
3792 | [], |
||
3793 | ['class' => 'exercise_mark_select'] |
||
3794 | ); |
||
3795 | |||
3796 | foreach ($model['score_list'] as $item) { |
||
3797 | $i = api_number_format($item['score_to_qualify'] / 100 * $weight, 2); |
||
3798 | $label = self::getModelStyle($item, $i); |
||
3799 | $attributes = [ |
||
3800 | 'class' => $item['css_class'], |
||
3801 | ]; |
||
3802 | if ($selected == $i) { |
||
3803 | $attributes['selected'] = 'selected'; |
||
3804 | } |
||
3805 | $element->addOption($label, $i, $attributes); |
||
3806 | } |
||
3807 | $form->addElement($element); |
||
3808 | } |
||
3809 | |||
3810 | /** |
||
3811 | * @return string |
||
3812 | */ |
||
3813 | public static function getJsCode() |
||
3814 | { |
||
3815 | // Filling the scores with the right colors. |
||
3816 | $models = self::getCourseScoreModel(); |
||
3817 | $cssListToString = ''; |
||
3818 | if (!empty($models)) { |
||
3819 | $cssList = array_column($models['score_list'], 'css_class'); |
||
3820 | $cssListToString = implode(' ', $cssList); |
||
3821 | } |
||
3822 | |||
3823 | if (empty($cssListToString)) { |
||
3824 | return ''; |
||
3825 | } |
||
3826 | $js = <<<EOT |
||
3827 | |||
3828 | function updateSelect(element) { |
||
3829 | var spanTag = element.parent().find('span.filter-option'); |
||
3830 | var value = element.val(); |
||
3831 | var selectId = element.attr('id'); |
||
3832 | var optionClass = $('#' + selectId + ' option[value="'+value+'"]').attr('class'); |
||
3833 | spanTag.removeClass('$cssListToString'); |
||
3834 | spanTag.addClass(optionClass); |
||
3835 | } |
||
3836 | |||
3837 | $(function() { |
||
3838 | // Loading values |
||
3839 | $('.exercise_mark_select').on('loaded.bs.select', function() { |
||
3840 | updateSelect($(this)); |
||
3841 | }); |
||
3842 | // On change |
||
3843 | $('.exercise_mark_select').on('changed.bs.select', function() { |
||
3844 | updateSelect($(this)); |
||
3845 | }); |
||
3846 | }); |
||
3847 | EOT; |
||
3848 | |||
3849 | return $js; |
||
3850 | } |
||
3851 | |||
3852 | /** |
||
3853 | * @param float $score |
||
3854 | * @param float $weight |
||
3855 | * @param string $pass_percentage |
||
3856 | * |
||
3857 | * @return string |
||
3858 | */ |
||
3859 | public static function showSuccessMessage($score, $weight, $pass_percentage) |
||
3860 | { |
||
3861 | $res = ''; |
||
3862 | if (self::isPassPercentageEnabled($pass_percentage)) { |
||
3863 | $isSuccess = self::isSuccessExerciseResult( |
||
3864 | $score, |
||
3865 | $weight, |
||
3866 | $pass_percentage |
||
3867 | ); |
||
3868 | |||
3869 | if ($isSuccess) { |
||
3870 | $html = get_lang('CongratulationsYouPassedTheTest'); |
||
3871 | $icon = Display::return_icon( |
||
3872 | 'completed.png', |
||
3873 | get_lang('Correct'), |
||
3874 | [], |
||
3875 | ICON_SIZE_MEDIUM |
||
3876 | ); |
||
3877 | } else { |
||
3878 | $html = get_lang('YouDidNotReachTheMinimumScore'); |
||
3879 | $icon = Display::return_icon( |
||
3880 | 'warning.png', |
||
3881 | get_lang('Wrong'), |
||
3882 | [], |
||
3883 | ICON_SIZE_MEDIUM |
||
3884 | ); |
||
3885 | } |
||
3886 | $html = Display::tag('h4', $html); |
||
3887 | $html .= Display::tag( |
||
3888 | 'h5', |
||
3889 | $icon, |
||
3890 | ['style' => 'width:40px; padding:2px 10px 0px 0px'] |
||
3891 | ); |
||
3892 | $res = $html; |
||
3893 | } |
||
3894 | |||
3895 | return $res; |
||
3896 | } |
||
3897 | |||
3898 | /** |
||
3899 | * Return true if pass_pourcentage activated (we use the pass pourcentage feature |
||
3900 | * return false if pass_percentage = 0 (we don't use the pass pourcentage feature. |
||
3901 | * |
||
3902 | * @param $value |
||
3903 | * |
||
3904 | * @return bool |
||
3905 | * In this version, pass_percentage and show_success_message are disabled if |
||
3906 | * pass_percentage is set to 0 |
||
3907 | */ |
||
3908 | public static function isPassPercentageEnabled($value) |
||
3909 | { |
||
3910 | return $value > 0; |
||
3911 | } |
||
3912 | |||
3913 | /** |
||
3914 | * Converts a numeric value in a percentage example 0.66666 to 66.67 %. |
||
3915 | * |
||
3916 | * @param $value |
||
3917 | * |
||
3918 | * @return float Converted number |
||
3919 | */ |
||
3920 | public static function convert_to_percentage($value) |
||
3921 | { |
||
3922 | $return = '-'; |
||
3923 | if ($value != '') { |
||
3924 | $return = float_format($value * 100, 1).' %'; |
||
3925 | } |
||
3926 | |||
3927 | return $return; |
||
3928 | } |
||
3929 | |||
3930 | /** |
||
3931 | * Getting all active exercises from a course from a session |
||
3932 | * (if a session_id is provided we will show all the exercises in the course + |
||
3933 | * all exercises in the session). |
||
3934 | * |
||
3935 | * @param array $course_info |
||
3936 | * @param int $session_id |
||
3937 | * @param bool $check_publication_dates |
||
3938 | * @param string $search Search exercise name |
||
3939 | * @param bool $search_all_sessions Search exercises in all sessions |
||
3940 | * @param int 0 = only inactive exercises |
||
0 ignored issues
–
show
|
|||
3941 | * 1 = only active exercises, |
||
3942 | * 2 = all exercises |
||
3943 | * 3 = active <> -1 |
||
3944 | * |
||
3945 | * @return array array with exercise data |
||
3946 | */ |
||
3947 | public static function get_all_exercises( |
||
3948 | $course_info = null, |
||
3949 | $session_id = 0, |
||
3950 | $check_publication_dates = false, |
||
3951 | $search = '', |
||
3952 | $search_all_sessions = false, |
||
3953 | $active = 2 |
||
3954 | ) { |
||
3955 | $course_id = api_get_course_int_id(); |
||
3956 | |||
3957 | if (!empty($course_info) && !empty($course_info['real_id'])) { |
||
3958 | $course_id = $course_info['real_id']; |
||
3959 | } |
||
3960 | |||
3961 | if ($session_id == -1) { |
||
3962 | $session_id = 0; |
||
3963 | } |
||
3964 | |||
3965 | $now = api_get_utc_datetime(); |
||
3966 | $timeConditions = ''; |
||
3967 | if ($check_publication_dates) { |
||
3968 | // Start and end are set |
||
3969 | $timeConditions = " AND ((start_time <> '' AND start_time < '$now' AND end_time <> '' AND end_time > '$now' ) OR "; |
||
3970 | // only start is set |
||
3971 | $timeConditions .= " (start_time <> '' AND start_time < '$now' AND end_time is NULL) OR "; |
||
3972 | // only end is set |
||
3973 | $timeConditions .= " (start_time IS NULL AND end_time <> '' AND end_time > '$now') OR "; |
||
3974 | // nothing is set |
||
3975 | $timeConditions .= ' (start_time IS NULL AND end_time IS NULL)) '; |
||
3976 | } |
||
3977 | |||
3978 | $needle_where = !empty($search) ? " AND title LIKE '?' " : ''; |
||
3979 | $needle = !empty($search) ? "%".$search."%" : ''; |
||
3980 | |||
3981 | // Show courses by active status |
||
3982 | $active_sql = ''; |
||
3983 | if ($active == 3) { |
||
3984 | $active_sql = ' active <> -1 AND'; |
||
3985 | } else { |
||
3986 | if ($active != 2) { |
||
3987 | $active_sql = sprintf(' active = %d AND', $active); |
||
3988 | } |
||
3989 | } |
||
3990 | |||
3991 | if ($search_all_sessions == true) { |
||
3992 | $conditions = [ |
||
3993 | 'where' => [ |
||
3994 | $active_sql.' c_id = ? '.$needle_where.$timeConditions => [ |
||
3995 | $course_id, |
||
3996 | $needle, |
||
3997 | ], |
||
3998 | ], |
||
3999 | 'order' => 'title', |
||
4000 | ]; |
||
4001 | } else { |
||
4002 | if (empty($session_id)) { |
||
4003 | $conditions = [ |
||
4004 | 'where' => [ |
||
4005 | $active_sql.' (session_id = 0 OR session_id IS NULL) AND c_id = ? '.$needle_where.$timeConditions => [ |
||
4006 | $course_id, |
||
4007 | $needle, |
||
4008 | ], |
||
4009 | ], |
||
4010 | 'order' => 'title', |
||
4011 | ]; |
||
4012 | } else { |
||
4013 | $conditions = [ |
||
4014 | 'where' => [ |
||
4015 | $active_sql.' (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ? '.$needle_where.$timeConditions => [ |
||
4016 | $session_id, |
||
4017 | $course_id, |
||
4018 | $needle, |
||
4019 | ], |
||
4020 | ], |
||
4021 | 'order' => 'title', |
||
4022 | ]; |
||
4023 | } |
||
4024 | } |
||
4025 | |||
4026 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
4027 | |||
4028 | return Database::select('*', $table, $conditions); |
||
4029 | } |
||
4030 | |||
4031 | /** |
||
4032 | * Getting all exercises (active only or all) |
||
4033 | * from a course from a session |
||
4034 | * (if a session_id is provided we will show all the exercises in the |
||
4035 | * course + all exercises in the session). |
||
4036 | * |
||
4037 | * @param array course data |
||
4038 | * @param int session id |
||
4039 | * @param int course c_id |
||
4040 | * @param bool $only_active_exercises |
||
4041 | * |
||
4042 | * @return array array with exercise data |
||
4043 | * modified by Hubert Borderiou |
||
4044 | */ |
||
4045 | public static function get_all_exercises_for_course_id( |
||
4046 | $course_info = null, |
||
4047 | $session_id = 0, |
||
4048 | $course_id = 0, |
||
4049 | $only_active_exercises = true |
||
4050 | ) { |
||
4051 | $table = Database::get_course_table(TABLE_QUIZ_TEST); |
||
4052 | |||
4053 | if ($only_active_exercises) { |
||
4054 | // Only active exercises. |
||
4055 | $sql_active_exercises = "active = 1 AND "; |
||
4056 | } else { |
||
4057 | // Not only active means visible and invisible NOT deleted (-2) |
||
4058 | $sql_active_exercises = "active IN (1, 0) AND "; |
||
4059 | } |
||
4060 | |||
4061 | if ($session_id == -1) { |
||
4062 | $session_id = 0; |
||
4063 | } |
||
4064 | |||
4065 | $params = [ |
||
4066 | $session_id, |
||
4067 | $course_id, |
||
4068 | ]; |
||
4069 | |||
4070 | if (empty($session_id)) { |
||
4071 | $conditions = [ |
||
4072 | 'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL) AND c_id = ?" => [$course_id]], |
||
4073 | 'order' => 'title', |
||
4074 | ]; |
||
4075 | } else { |
||
4076 | // All exercises |
||
4077 | $conditions = [ |
||
4078 | 'where' => ["$sql_active_exercises (session_id = 0 OR session_id IS NULL OR session_id = ? ) AND c_id = ?" => $params], |
||
4079 | 'order' => 'title', |
||
4080 | ]; |
||
4081 | } |
||
4082 | |||
4083 | return Database::select('*', $table, $conditions); |
||
4084 | } |
||
4085 | |||
4086 | /** |
||
4087 | * Gets the position of the score based in a given score (result/weight) |
||
4088 | * and the exe_id based in the user list |
||
4089 | * (NO Exercises in LPs ). |
||
4090 | * |
||
4091 | * @param float $my_score user score to be compared *attention* |
||
4092 | * $my_score = score/weight and not just the score |
||
4093 | * @param int $my_exe_id exe id of the exercise |
||
4094 | * (this is necessary because if 2 students have the same score the one |
||
4095 | * with the minor exe_id will have a best position, just to be fair and FIFO) |
||
4096 | * @param int $exercise_id |
||
4097 | * @param string $course_code |
||
4098 | * @param int $session_id |
||
4099 | * @param array $user_list |
||
4100 | * @param bool $return_string |
||
4101 | * |
||
4102 | * @return int the position of the user between his friends in a course |
||
4103 | * (or course within a session) |
||
4104 | */ |
||
4105 | public static function get_exercise_result_ranking( |
||
4106 | $my_score, |
||
4107 | $my_exe_id, |
||
4108 | $exercise_id, |
||
4109 | $course_code, |
||
4110 | $session_id = 0, |
||
4111 | $user_list = [], |
||
4112 | $return_string = true, |
||
4113 | $skipLpResults = true |
||
4114 | ) { |
||
4115 | //No score given we return |
||
4116 | if (is_null($my_score)) { |
||
4117 | return '-'; |
||
4118 | } |
||
4119 | if (empty($user_list)) { |
||
4120 | return '-'; |
||
4121 | } |
||
4122 | |||
4123 | $best_attempts = []; |
||
4124 | foreach ($user_list as $user_data) { |
||
4125 | $user_id = $user_data['user_id']; |
||
4126 | $best_attempts[$user_id] = self::get_best_attempt_by_user( |
||
4127 | $user_id, |
||
4128 | $exercise_id, |
||
4129 | $course_code, |
||
4130 | $session_id, |
||
4131 | $skipLpResults |
||
4132 | ); |
||
4133 | } |
||
4134 | |||
4135 | if (empty($best_attempts)) { |
||
4136 | return 1; |
||
4137 | } else { |
||
4138 | $position = 1; |
||
4139 | $my_ranking = []; |
||
4140 | foreach ($best_attempts as $user_id => $result) { |
||
4141 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4142 | $my_ranking[$user_id] = $result['exe_result'] / $result['exe_weighting']; |
||
4143 | } else { |
||
4144 | $my_ranking[$user_id] = 0; |
||
4145 | } |
||
4146 | } |
||
4147 | //if (!empty($my_ranking)) { |
||
4148 | asort($my_ranking); |
||
4149 | $position = count($my_ranking); |
||
4150 | if (!empty($my_ranking)) { |
||
4151 | foreach ($my_ranking as $user_id => $ranking) { |
||
4152 | if ($my_score >= $ranking) { |
||
4153 | if ($my_score == $ranking && isset($best_attempts[$user_id]['exe_id'])) { |
||
4154 | $exe_id = $best_attempts[$user_id]['exe_id']; |
||
4155 | if ($my_exe_id < $exe_id) { |
||
4156 | $position--; |
||
4157 | } |
||
4158 | } else { |
||
4159 | $position--; |
||
4160 | } |
||
4161 | } |
||
4162 | } |
||
4163 | } |
||
4164 | //} |
||
4165 | $return_value = [ |
||
4166 | 'position' => $position, |
||
4167 | 'count' => count($my_ranking), |
||
4168 | ]; |
||
4169 | |||
4170 | if ($return_string) { |
||
4171 | if (!empty($position) && !empty($my_ranking)) { |
||
4172 | $return_value = $position.'/'.count($my_ranking); |
||
4173 | } else { |
||
4174 | $return_value = '-'; |
||
4175 | } |
||
4176 | } |
||
4177 | |||
4178 | return $return_value; |
||
4179 | } |
||
4180 | } |
||
4181 | |||
4182 | /** |
||
4183 | * Gets the position of the score based in a given score (result/weight) and the exe_id based in all attempts |
||
4184 | * (NO Exercises in LPs ) old functionality by attempt. |
||
4185 | * |
||
4186 | * @param float user score to be compared attention => score/weight |
||
4187 | * @param int exe id of the exercise |
||
4188 | * (this is necessary because if 2 students have the same score the one |
||
4189 | * with the minor exe_id will have a best position, just to be fair and FIFO) |
||
4190 | * @param int exercise id |
||
4191 | * @param string course code |
||
4192 | * @param int session id |
||
4193 | * @param bool $return_string |
||
4194 | * |
||
4195 | * @return int the position of the user between his friends in a course (or course within a session) |
||
4196 | */ |
||
4197 | public static function get_exercise_result_ranking_by_attempt( |
||
4198 | $my_score, |
||
4199 | $my_exe_id, |
||
4200 | $exercise_id, |
||
4201 | $courseId, |
||
4202 | $session_id = 0, |
||
4203 | $return_string = true |
||
4204 | ) { |
||
4205 | if (empty($session_id)) { |
||
4206 | $session_id = 0; |
||
4207 | } |
||
4208 | if (is_null($my_score)) { |
||
4209 | return '-'; |
||
4210 | } |
||
4211 | $user_results = Event::get_all_exercise_results( |
||
4212 | $exercise_id, |
||
4213 | $courseId, |
||
4214 | $session_id, |
||
4215 | false |
||
4216 | ); |
||
4217 | $position_data = []; |
||
4218 | if (empty($user_results)) { |
||
4219 | return 1; |
||
4220 | } else { |
||
4221 | $position = 1; |
||
4222 | $my_ranking = []; |
||
4223 | foreach ($user_results as $result) { |
||
4224 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4225 | $my_ranking[$result['exe_id']] = $result['exe_result'] / $result['exe_weighting']; |
||
4226 | } else { |
||
4227 | $my_ranking[$result['exe_id']] = 0; |
||
4228 | } |
||
4229 | } |
||
4230 | asort($my_ranking); |
||
4231 | $position = count($my_ranking); |
||
4232 | if (!empty($my_ranking)) { |
||
4233 | foreach ($my_ranking as $exe_id => $ranking) { |
||
4234 | if ($my_score >= $ranking) { |
||
4235 | if ($my_score == $ranking) { |
||
4236 | if ($my_exe_id < $exe_id) { |
||
4237 | $position--; |
||
4238 | } |
||
4239 | } else { |
||
4240 | $position--; |
||
4241 | } |
||
4242 | } |
||
4243 | } |
||
4244 | } |
||
4245 | $return_value = [ |
||
4246 | 'position' => $position, |
||
4247 | 'count' => count($my_ranking), |
||
4248 | ]; |
||
4249 | |||
4250 | if ($return_string) { |
||
4251 | if (!empty($position) && !empty($my_ranking)) { |
||
4252 | return $position.'/'.count($my_ranking); |
||
4253 | } |
||
4254 | } |
||
4255 | |||
4256 | return $return_value; |
||
4257 | } |
||
4258 | } |
||
4259 | |||
4260 | /** |
||
4261 | * Get the best attempt in a exercise (NO Exercises in LPs ). |
||
4262 | * |
||
4263 | * @param int $exercise_id |
||
4264 | * @param int $courseId |
||
4265 | * @param int $session_id |
||
4266 | * |
||
4267 | * @return array |
||
4268 | */ |
||
4269 | public static function get_best_attempt_in_course($exercise_id, $courseId, $session_id, $skipLpResults = true) |
||
4270 | { |
||
4271 | $user_results = Event::get_all_exercise_results( |
||
4272 | $exercise_id, |
||
4273 | $courseId, |
||
4274 | $session_id, |
||
4275 | false, |
||
4276 | null, |
||
4277 | 0, |
||
4278 | $skipLpResults |
||
4279 | ); |
||
4280 | |||
4281 | $best_score_data = []; |
||
4282 | $best_score = 0; |
||
4283 | if (!empty($user_results)) { |
||
4284 | foreach ($user_results as $result) { |
||
4285 | if (!empty($result['exe_weighting']) && |
||
4286 | intval($result['exe_weighting']) != 0 |
||
4287 | ) { |
||
4288 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4289 | if ($score >= $best_score) { |
||
4290 | $best_score = $score; |
||
4291 | $best_score_data = $result; |
||
4292 | } |
||
4293 | } |
||
4294 | } |
||
4295 | } |
||
4296 | |||
4297 | return $best_score_data; |
||
4298 | } |
||
4299 | |||
4300 | /** |
||
4301 | * Get the best score in a exercise (NO Exercises in LPs ). |
||
4302 | * |
||
4303 | * @param int $user_id |
||
4304 | * @param int $exercise_id |
||
4305 | * @param int $courseId |
||
4306 | * @param int $session_id |
||
4307 | * |
||
4308 | * @return array |
||
4309 | */ |
||
4310 | public static function get_best_attempt_by_user( |
||
4311 | $user_id, |
||
4312 | $exercise_id, |
||
4313 | $courseId, |
||
4314 | $session_id, |
||
4315 | $skipLpResults = true |
||
4316 | ) { |
||
4317 | $user_results = Event::get_all_exercise_results( |
||
4318 | $exercise_id, |
||
4319 | $courseId, |
||
4320 | $session_id, |
||
4321 | false, |
||
4322 | $user_id, |
||
4323 | 0, |
||
4324 | $skipLpResults |
||
4325 | ); |
||
4326 | $best_score_data = []; |
||
4327 | $best_score = 0; |
||
4328 | if (!empty($user_results)) { |
||
4329 | foreach ($user_results as $result) { |
||
4330 | if (!empty($result['exe_weighting']) && (float) $result['exe_weighting'] != 0) { |
||
4331 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4332 | if ($score >= $best_score) { |
||
4333 | $best_score = $score; |
||
4334 | $best_score_data = $result; |
||
4335 | } |
||
4336 | } |
||
4337 | } |
||
4338 | } |
||
4339 | |||
4340 | return $best_score_data; |
||
4341 | } |
||
4342 | |||
4343 | /** |
||
4344 | * Get average score (NO Exercises in LPs ). |
||
4345 | * |
||
4346 | * @param int $exerciseId |
||
4347 | * @param int $courseId |
||
4348 | * @param int $sessionId |
||
4349 | * |
||
4350 | * @return float Average score |
||
4351 | */ |
||
4352 | public static function get_average_score($exerciseId, $courseId, $sessionId, $groupId = 0) |
||
4353 | { |
||
4354 | $user_results = Event::get_all_exercise_results( |
||
4355 | $exerciseId, |
||
4356 | $courseId, |
||
4357 | $sessionId, |
||
4358 | true, |
||
4359 | null, |
||
4360 | $groupId |
||
4361 | ); |
||
4362 | $avg_score = 0; |
||
4363 | if (!empty($user_results)) { |
||
4364 | foreach ($user_results as $result) { |
||
4365 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4366 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4367 | $avg_score += $score; |
||
4368 | } |
||
4369 | } |
||
4370 | $avg_score = float_format($avg_score / count($user_results), 1); |
||
4371 | } |
||
4372 | |||
4373 | return $avg_score; |
||
4374 | } |
||
4375 | |||
4376 | /** |
||
4377 | * Get average quiz score by course (Only exercises not added in a LP). |
||
4378 | * |
||
4379 | * @param int $courseId |
||
4380 | * @param int $sessionId |
||
4381 | * |
||
4382 | * @return float Average score |
||
4383 | */ |
||
4384 | public static function get_average_score_by_course($courseId, $sessionId) |
||
4385 | { |
||
4386 | $user_results = Event::get_all_exercise_results_by_course( |
||
4387 | $courseId, |
||
4388 | $sessionId, |
||
4389 | false |
||
4390 | ); |
||
4391 | $avg_score = 0; |
||
4392 | if (!empty($user_results)) { |
||
4393 | foreach ($user_results as $result) { |
||
4394 | if (!empty($result['exe_weighting']) && intval( |
||
4395 | $result['exe_weighting'] |
||
4396 | ) != 0 |
||
4397 | ) { |
||
4398 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4399 | $avg_score += $score; |
||
4400 | } |
||
4401 | } |
||
4402 | // We assume that all exe_weighting |
||
4403 | $avg_score = $avg_score / count($user_results); |
||
4404 | } |
||
4405 | |||
4406 | return $avg_score; |
||
4407 | } |
||
4408 | |||
4409 | /** |
||
4410 | * @param int $user_id |
||
4411 | * @param int $courseId |
||
4412 | * @param int $session_id |
||
4413 | * |
||
4414 | * @return float|int |
||
4415 | */ |
||
4416 | public static function get_average_score_by_course_by_user( |
||
4417 | $user_id, |
||
4418 | $courseId, |
||
4419 | $session_id |
||
4420 | ) { |
||
4421 | $user_results = Event::get_all_exercise_results_by_user( |
||
4422 | $user_id, |
||
4423 | $courseId, |
||
4424 | $session_id |
||
4425 | ); |
||
4426 | $avg_score = 0; |
||
4427 | if (!empty($user_results)) { |
||
4428 | foreach ($user_results as $result) { |
||
4429 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4430 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4431 | $avg_score += $score; |
||
4432 | } |
||
4433 | } |
||
4434 | // We assume that all exe_weighting |
||
4435 | $avg_score = ($avg_score / count($user_results)); |
||
4436 | } |
||
4437 | |||
4438 | return $avg_score; |
||
4439 | } |
||
4440 | |||
4441 | /** |
||
4442 | * Get average score by score (NO Exercises in LPs ). |
||
4443 | * |
||
4444 | * @param int $exercise_id |
||
4445 | * @param int $courseId |
||
4446 | * @param int $session_id |
||
4447 | * @param int $user_count |
||
4448 | * |
||
4449 | * @return float Best average score |
||
4450 | */ |
||
4451 | public static function get_best_average_score_by_exercise( |
||
4452 | $exercise_id, |
||
4453 | $courseId, |
||
4454 | $session_id, |
||
4455 | $user_count |
||
4456 | ) { |
||
4457 | $user_results = Event::get_best_exercise_results_by_user( |
||
4458 | $exercise_id, |
||
4459 | $courseId, |
||
4460 | $session_id |
||
4461 | ); |
||
4462 | $avg_score = 0; |
||
4463 | if (!empty($user_results)) { |
||
4464 | foreach ($user_results as $result) { |
||
4465 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4466 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4467 | $avg_score += $score; |
||
4468 | } |
||
4469 | } |
||
4470 | // We asumme that all exe_weighting |
||
4471 | if (!empty($user_count)) { |
||
4472 | $avg_score = float_format($avg_score / $user_count, 1) * 100; |
||
4473 | } else { |
||
4474 | $avg_score = 0; |
||
4475 | } |
||
4476 | } |
||
4477 | |||
4478 | return $avg_score; |
||
4479 | } |
||
4480 | |||
4481 | /** |
||
4482 | * Get average score by score (NO Exercises in LPs ). |
||
4483 | * |
||
4484 | * @param int $exercise_id |
||
4485 | * @param int $courseId |
||
4486 | * @param int $session_id |
||
4487 | * |
||
4488 | * @return float Best average score |
||
4489 | */ |
||
4490 | public static function getBestScoreByExercise( |
||
4491 | $exercise_id, |
||
4492 | $courseId, |
||
4493 | $session_id |
||
4494 | ) { |
||
4495 | $user_results = Event::get_best_exercise_results_by_user( |
||
4496 | $exercise_id, |
||
4497 | $courseId, |
||
4498 | $session_id |
||
4499 | ); |
||
4500 | $avg_score = 0; |
||
4501 | if (!empty($user_results)) { |
||
4502 | foreach ($user_results as $result) { |
||
4503 | if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) { |
||
4504 | $score = $result['exe_result'] / $result['exe_weighting']; |
||
4505 | $avg_score += $score; |
||
4506 | } |
||
4507 | } |
||
4508 | } |
||
4509 | |||
4510 | return $avg_score; |
||
4511 | } |
||
4512 | |||
4513 | /** |
||
4514 | * @param string $course_code |
||
4515 | * @param int $session_id |
||
4516 | * |
||
4517 | * @return array |
||
4518 | */ |
||
4519 | public static function get_exercises_to_be_taken($course_code, $session_id) |
||
4520 | { |
||
4521 | $course_info = api_get_course_info($course_code); |
||
4522 | $exercises = self::get_all_exercises($course_info, $session_id); |
||
4523 | $result = []; |
||
4524 | $now = time() + 15 * 24 * 60 * 60; |
||
4525 | foreach ($exercises as $exercise_item) { |
||
4526 | if (isset($exercise_item['end_time']) && |
||
4527 | !empty($exercise_item['end_time']) && |
||
4528 | api_strtotime($exercise_item['end_time'], 'UTC') < $now |
||
4529 | ) { |
||
4530 | $result[] = $exercise_item; |
||
4531 | } |
||
4532 | } |
||
4533 | |||
4534 | return $result; |
||
4535 | } |
||
4536 | |||
4537 | /** |
||
4538 | * Get student results (only in completed exercises) stats by question. |
||
4539 | * |
||
4540 | * @param int $question_id |
||
4541 | * @param int $exercise_id |
||
4542 | * @param int $courseId |
||
4543 | * @param int $session_id |
||
4544 | * @param bool $onlyStudent Filter only enrolled students |
||
4545 | * |
||
4546 | * @return array |
||
4547 | */ |
||
4548 | public static function get_student_stats_by_question( |
||
4549 | $question_id, |
||
4550 | $exercise_id, |
||
4551 | $courseId, |
||
4552 | $session_id, |
||
4553 | $onlyStudent = false |
||
4554 | ) { |
||
4555 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
4556 | $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
4557 | $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
4558 | |||
4559 | $question_id = (int) $question_id; |
||
4560 | $exercise_id = (int) $exercise_id; |
||
4561 | $session_id = (int) $session_id; |
||
4562 | $courseId = (int) $courseId; |
||
4563 | |||
4564 | $sql = "SELECT MAX(marks) as max, MIN(marks) as min, AVG(marks) as average |
||
4565 | FROM $track_exercises e "; |
||
4566 | if ($onlyStudent) { |
||
4567 | if (empty($session_id)) { |
||
4568 | $courseCondition = " |
||
4569 | INNER JOIN $courseUser c |
||
4570 | ON ( |
||
4571 | e.exe_user_id = c.user_id AND |
||
4572 | e.c_id = c.c_id AND |
||
4573 | c.status = ".STUDENT." |
||
4574 | AND relation_type <> 2 |
||
4575 | )"; |
||
4576 | } else { |
||
4577 | $sessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
4578 | $courseCondition = " |
||
4579 | INNER JOIN $sessionRelCourse sc |
||
4580 | ON ( |
||
4581 | e.exe_user_id = sc.user_id AND |
||
4582 | e.c_id = sc.c_id AND |
||
4583 | e.session_id = sc.session_id AND |
||
4584 | sc.status = 0 |
||
4585 | ) "; |
||
4586 | } |
||
4587 | $sql .= $courseCondition; |
||
4588 | } |
||
4589 | |||
4590 | $sql .= " |
||
4591 | INNER JOIN $track_attempt a |
||
4592 | ON ( |
||
4593 | a.exe_id = e.exe_id AND |
||
4594 | e.c_id = a.c_id AND |
||
4595 | e.session_id = a.session_id |
||
4596 | ) |
||
4597 | WHERE |
||
4598 | exe_exo_id = $exercise_id AND |
||
4599 | a.c_id = $courseId AND |
||
4600 | e.session_id = $session_id AND |
||
4601 | question_id = $question_id AND |
||
4602 | e.status = '' |
||
4603 | LIMIT 1"; |
||
4604 | $result = Database::query($sql); |
||
4605 | $return = []; |
||
4606 | if ($result) { |
||
0 ignored issues
–
show
|
|||
4607 | $return = Database::fetch_array($result, 'ASSOC'); |
||
4608 | } |
||
4609 | |||
4610 | return $return; |
||
4611 | } |
||
4612 | |||
4613 | /** |
||
4614 | * Get the correct answer count for a fill blanks question. |
||
4615 | * |
||
4616 | * @param int $question_id |
||
4617 | * @param int $exercise_id |
||
4618 | * |
||
4619 | * @return array |
||
4620 | */ |
||
4621 | public static function getNumberStudentsFillBlanksAnswerCount( |
||
4622 | $question_id, |
||
4623 | $exercise_id |
||
4624 | ) { |
||
4625 | $listStudentsId = []; |
||
4626 | $listAllStudentInfo = CourseManager::get_student_list_from_course_code( |
||
4627 | api_get_course_id(), |
||
4628 | true |
||
4629 | ); |
||
4630 | foreach ($listAllStudentInfo as $i => $listStudentInfo) { |
||
4631 | $listStudentsId[] = $listStudentInfo['user_id']; |
||
4632 | } |
||
4633 | |||
4634 | $listFillTheBlankResult = FillBlanks::getFillTheBlankResult( |
||
4635 | $exercise_id, |
||
4636 | $question_id, |
||
4637 | $listStudentsId, |
||
4638 | '1970-01-01', |
||
4639 | '3000-01-01' |
||
4640 | ); |
||
4641 | |||
4642 | $arrayCount = []; |
||
4643 | |||
4644 | foreach ($listFillTheBlankResult as $resultCount) { |
||
4645 | foreach ($resultCount as $index => $count) { |
||
4646 | //this is only for declare the array index per answer |
||
4647 | $arrayCount[$index] = 0; |
||
4648 | } |
||
4649 | } |
||
4650 | |||
4651 | foreach ($listFillTheBlankResult as $resultCount) { |
||
4652 | foreach ($resultCount as $index => $count) { |
||
4653 | $count = ($count === 0) ? 1 : 0; |
||
4654 | $arrayCount[$index] += $count; |
||
4655 | } |
||
4656 | } |
||
4657 | |||
4658 | return $arrayCount; |
||
4659 | } |
||
4660 | |||
4661 | /** |
||
4662 | * Get the number of questions with answers. |
||
4663 | * |
||
4664 | * @param int $question_id |
||
4665 | * @param int $exercise_id |
||
4666 | * @param string $course_code |
||
4667 | * @param int $session_id |
||
4668 | * @param string $questionType |
||
4669 | * |
||
4670 | * @return int |
||
4671 | */ |
||
4672 | public static function get_number_students_question_with_answer_count( |
||
4673 | $question_id, |
||
4674 | $exercise_id, |
||
4675 | $course_code, |
||
4676 | $session_id, |
||
4677 | $questionType = '' |
||
4678 | ) { |
||
4679 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
4680 | $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
4681 | $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
4682 | $courseTable = Database::get_main_table(TABLE_MAIN_COURSE); |
||
4683 | $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
4684 | |||
4685 | $question_id = intval($question_id); |
||
4686 | $exercise_id = intval($exercise_id); |
||
4687 | $courseId = api_get_course_int_id($course_code); |
||
4688 | $session_id = intval($session_id); |
||
4689 | |||
4690 | if (in_array($questionType, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||
4691 | $listStudentsId = []; |
||
4692 | $listAllStudentInfo = CourseManager::get_student_list_from_course_code( |
||
4693 | api_get_course_id(), |
||
4694 | true |
||
4695 | ); |
||
4696 | foreach ($listAllStudentInfo as $i => $listStudentInfo) { |
||
4697 | $listStudentsId[] = $listStudentInfo['user_id']; |
||
4698 | } |
||
4699 | |||
4700 | $listFillTheBlankResult = FillBlanks::getFillTheBlankResult( |
||
4701 | $exercise_id, |
||
4702 | $question_id, |
||
4703 | $listStudentsId, |
||
4704 | '1970-01-01', |
||
4705 | '3000-01-01' |
||
4706 | ); |
||
4707 | |||
4708 | return FillBlanks::getNbResultFillBlankAll($listFillTheBlankResult); |
||
4709 | } |
||
4710 | |||
4711 | if (empty($session_id)) { |
||
4712 | $courseCondition = " |
||
4713 | INNER JOIN $courseUser cu |
||
4714 | ON cu.c_id = c.id AND cu.user_id = exe_user_id"; |
||
4715 | $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT; |
||
4716 | } else { |
||
4717 | $courseCondition = " |
||
4718 | INNER JOIN $courseUserSession cu |
||
4719 | ON (cu.c_id = c.id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)"; |
||
4720 | $courseConditionWhere = " AND cu.status = 0 "; |
||
4721 | } |
||
4722 | |||
4723 | $sql = "SELECT DISTINCT exe_user_id |
||
4724 | FROM $track_exercises e |
||
4725 | INNER JOIN $track_attempt a |
||
4726 | ON ( |
||
4727 | a.exe_id = e.exe_id AND |
||
4728 | e.c_id = a.c_id AND |
||
4729 | e.session_id = a.session_id |
||
4730 | ) |
||
4731 | INNER JOIN $courseTable c |
||
4732 | ON (c.id = a.c_id) |
||
4733 | $courseCondition |
||
4734 | WHERE |
||
4735 | exe_exo_id = $exercise_id AND |
||
4736 | a.c_id = $courseId AND |
||
4737 | e.session_id = $session_id AND |
||
4738 | question_id = $question_id AND |
||
4739 | answer <> '0' AND |
||
4740 | e.status = '' |
||
4741 | $courseConditionWhere |
||
4742 | "; |
||
4743 | $result = Database::query($sql); |
||
4744 | $return = 0; |
||
4745 | if ($result) { |
||
0 ignored issues
–
show
|
|||
4746 | $return = Database::num_rows($result); |
||
4747 | } |
||
4748 | |||
4749 | return $return; |
||
4750 | } |
||
4751 | |||
4752 | /** |
||
4753 | * Get number of answers to hotspot questions. |
||
4754 | * |
||
4755 | * @param int $answer_id |
||
4756 | * @param int $question_id |
||
4757 | * @param int $exercise_id |
||
4758 | * @param string $course_code |
||
4759 | * @param int $session_id |
||
4760 | * |
||
4761 | * @return int |
||
4762 | */ |
||
4763 | public static function get_number_students_answer_hotspot_count( |
||
4764 | $answer_id, |
||
4765 | $question_id, |
||
4766 | $exercise_id, |
||
4767 | $course_code, |
||
4768 | $session_id |
||
4769 | ) { |
||
4770 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
4771 | $track_hotspot = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT); |
||
4772 | $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
4773 | $courseTable = Database::get_main_table(TABLE_MAIN_COURSE); |
||
4774 | $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
4775 | |||
4776 | $question_id = (int) $question_id; |
||
4777 | $answer_id = (int) $answer_id; |
||
4778 | $exercise_id = (int) $exercise_id; |
||
4779 | $course_code = Database::escape_string($course_code); |
||
4780 | $session_id = (int) $session_id; |
||
4781 | |||
4782 | if (empty($session_id)) { |
||
4783 | $courseCondition = " |
||
4784 | INNER JOIN $courseUser cu |
||
4785 | ON cu.c_id = c.id AND cu.user_id = exe_user_id"; |
||
4786 | $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT; |
||
4787 | } else { |
||
4788 | $courseCondition = " |
||
4789 | INNER JOIN $courseUserSession cu |
||
4790 | ON (cu.c_id = c.id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)"; |
||
4791 | $courseConditionWhere = ' AND cu.status = 0 '; |
||
4792 | } |
||
4793 | |||
4794 | $sql = "SELECT DISTINCT exe_user_id |
||
4795 | FROM $track_exercises e |
||
4796 | INNER JOIN $track_hotspot a |
||
4797 | ON (a.hotspot_exe_id = e.exe_id) |
||
4798 | INNER JOIN $courseTable c |
||
4799 | ON (hotspot_course_code = c.code) |
||
4800 | $courseCondition |
||
4801 | WHERE |
||
4802 | exe_exo_id = $exercise_id AND |
||
4803 | a.hotspot_course_code = '$course_code' AND |
||
4804 | e.session_id = $session_id AND |
||
4805 | hotspot_answer_id = $answer_id AND |
||
4806 | hotspot_question_id = $question_id AND |
||
4807 | hotspot_correct = 1 AND |
||
4808 | e.status = '' |
||
4809 | $courseConditionWhere |
||
4810 | "; |
||
4811 | |||
4812 | $result = Database::query($sql); |
||
4813 | $return = 0; |
||
4814 | if ($result) { |
||
0 ignored issues
–
show
|
|||
4815 | $return = Database::num_rows($result); |
||
4816 | } |
||
4817 | |||
4818 | return $return; |
||
4819 | } |
||
4820 | |||
4821 | /** |
||
4822 | * @param int $answer_id |
||
4823 | * @param int $question_id |
||
4824 | * @param int $exercise_id |
||
4825 | * @param int $courseId |
||
4826 | * @param int $session_id |
||
4827 | * @param string $question_type |
||
4828 | * @param string $correct_answer |
||
4829 | * @param string $current_answer |
||
4830 | * |
||
4831 | * @return int |
||
4832 | */ |
||
4833 | public static function get_number_students_answer_count( |
||
4834 | $answer_id, |
||
4835 | $question_id, |
||
4836 | $exercise_id, |
||
4837 | $courseId, |
||
4838 | $session_id, |
||
4839 | $question_type = null, |
||
4840 | $correct_answer = null, |
||
4841 | $current_answer = null |
||
4842 | ) { |
||
4843 | $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
4844 | $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
4845 | $courseTable = Database::get_main_table(TABLE_MAIN_COURSE); |
||
4846 | $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); |
||
4847 | $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); |
||
4848 | |||
4849 | $question_id = (int) $question_id; |
||
4850 | $answer_id = (int) $answer_id; |
||
4851 | $exercise_id = (int) $exercise_id; |
||
4852 | $courseId = (int) $courseId; |
||
4853 | $session_id = (int) $session_id; |
||
4854 | |||
4855 | switch ($question_type) { |
||
4856 | case FILL_IN_BLANKS: |
||
4857 | case FILL_IN_BLANKS_COMBINATION: |
||
4858 | $answer_condition = ''; |
||
4859 | $select_condition = ' e.exe_id, answer '; |
||
4860 | break; |
||
4861 | case MATCHING: |
||
4862 | case MATCHING_COMBINATION: |
||
4863 | case MATCHING_DRAGGABLE: |
||
4864 | case MATCHING_DRAGGABLE_COMBINATION: |
||
4865 | default: |
||
4866 | $answer_condition = " answer = $answer_id AND "; |
||
4867 | $select_condition = ' DISTINCT exe_user_id '; |
||
4868 | } |
||
4869 | |||
4870 | if (empty($session_id)) { |
||
4871 | $courseCondition = " |
||
4872 | INNER JOIN $courseUser cu |
||
4873 | ON cu.c_id = c.id AND cu.user_id = exe_user_id"; |
||
4874 | $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT; |
||
4875 | } else { |
||
4876 | $courseCondition = " |
||
4877 | INNER JOIN $courseUserSession cu |
||
4878 | ON (cu.c_id = a.c_id AND cu.user_id = e.exe_user_id AND e.session_id = cu.session_id)"; |
||
4879 | $courseConditionWhere = ' AND cu.status = 0 '; |
||
4880 | } |
||
4881 | |||
4882 | $sql = "SELECT $select_condition |
||
4883 | FROM $track_exercises e |
||
4884 | INNER JOIN $track_attempt a |
||
4885 | ON ( |
||
4886 | a.exe_id = e.exe_id AND |
||
4887 | e.c_id = a.c_id AND |
||
4888 | e.session_id = a.session_id |
||
4889 | ) |
||
4890 | INNER JOIN $courseTable c |
||
4891 | ON c.id = a.c_id |
||
4892 | $courseCondition |
||
4893 | WHERE |
||
4894 | exe_exo_id = $exercise_id AND |
||
4895 | a.c_id = $courseId AND |
||
4896 | e.session_id = $session_id AND |
||
4897 | $answer_condition |
||
4898 | question_id = $question_id AND |
||
4899 | e.status = '' |
||
4900 | $courseConditionWhere |
||
4901 | "; |
||
4902 | $result = Database::query($sql); |
||
4903 | $return = 0; |
||
4904 | if ($result) { |
||
0 ignored issues
–
show
|
|||
4905 | $good_answers = 0; |
||
4906 | switch ($question_type) { |
||
4907 | case FILL_IN_BLANKS: |
||
4908 | case FILL_IN_BLANKS_COMBINATION: |
||
4909 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||
4910 | $fill_blank = self::check_fill_in_blanks( |
||
4911 | $correct_answer, |
||
4912 | $row['answer'], |
||
4913 | $current_answer |
||
4914 | ); |
||
4915 | if (isset($fill_blank[$current_answer]) && $fill_blank[$current_answer] == 1) { |
||
4916 | $good_answers++; |
||
4917 | } |
||
4918 | } |
||
4919 | |||
4920 | return $good_answers; |
||
4921 | 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 ![]() |
|||
4922 | case MATCHING: |
||
4923 | case MATCHING_COMBINATION: |
||
4924 | case MATCHING_DRAGGABLE: |
||
4925 | case MATCHING_DRAGGABLE_COMBINATION: |
||
4926 | default: |
||
4927 | $return = Database::num_rows($result); |
||
4928 | } |
||
4929 | } |
||
4930 | |||
4931 | return $return; |
||
4932 | } |
||
4933 | |||
4934 | /** |
||
4935 | * @param array $answer |
||
4936 | * @param string $user_answer |
||
4937 | * |
||
4938 | * @return array |
||
4939 | */ |
||
4940 | public static function check_fill_in_blanks($answer, $user_answer, $current_answer) |
||
4941 | { |
||
4942 | // the question is encoded like this |
||
4943 | // [A] B [C] D [E] F::10,10,10@1 |
||
4944 | // number 1 before the "@" means that is a switchable fill in blank question |
||
4945 | // [A] B [C] D [E] F::10,10,10@ or [A] B [C] D [E] F::10,10,10 |
||
4946 | // means that is a normal fill blank question |
||
4947 | // first we explode the "::" |
||
4948 | $pre_array = explode('::', $answer); |
||
4949 | // is switchable fill blank or not |
||
4950 | $last = count($pre_array) - 1; |
||
4951 | $is_set_switchable = explode('@', $pre_array[$last]); |
||
4952 | $switchable_answer_set = false; |
||
4953 | if (isset($is_set_switchable[1]) && $is_set_switchable[1] == 1) { |
||
4954 | $switchable_answer_set = true; |
||
4955 | } |
||
4956 | $answer = ''; |
||
4957 | for ($k = 0; $k < $last; $k++) { |
||
4958 | $answer .= $pre_array[$k]; |
||
4959 | } |
||
4960 | // splits weightings that are joined with a comma |
||
4961 | $answerWeighting = explode(',', $is_set_switchable[0]); |
||
4962 | |||
4963 | // we save the answer because it will be modified |
||
4964 | //$temp = $answer; |
||
4965 | $temp = $answer; |
||
4966 | |||
4967 | $answer = ''; |
||
4968 | $j = 0; |
||
4969 | //initialise answer tags |
||
4970 | $user_tags = $correct_tags = $real_text = []; |
||
4971 | // the loop will stop at the end of the text |
||
4972 | while (1) { |
||
4973 | // quits the loop if there are no more blanks (detect '[') |
||
4974 | if (($pos = api_strpos($temp, '[')) === false) { |
||
4975 | // adds the end of the text |
||
4976 | $answer = $temp; |
||
4977 | $real_text[] = $answer; |
||
4978 | break; //no more "blanks", quit the loop |
||
4979 | } |
||
4980 | // adds the piece of text that is before the blank |
||
4981 | //and ends with '[' into a general storage array |
||
4982 | $real_text[] = api_substr($temp, 0, $pos + 1); |
||
4983 | $answer .= api_substr($temp, 0, $pos + 1); |
||
4984 | //take the string remaining (after the last "[" we found) |
||
4985 | $temp = api_substr($temp, $pos + 1); |
||
4986 | // quit the loop if there are no more blanks, and update $pos to the position of next ']' |
||
4987 | if (($pos = api_strpos($temp, ']')) === false) { |
||
4988 | // adds the end of the text |
||
4989 | $answer .= $temp; |
||
4990 | break; |
||
4991 | } |
||
4992 | |||
4993 | $str = $user_answer; |
||
4994 | |||
4995 | preg_match_all('#\[([^[]*)\]#', $str, $arr); |
||
4996 | $str = str_replace('\r\n', '', $str); |
||
4997 | $choices = $arr[1]; |
||
4998 | $choice = []; |
||
4999 | $check = false; |
||
5000 | $i = 0; |
||
5001 | foreach ($choices as $item) { |
||
5002 | if ($current_answer === $item) { |
||
5003 | $check = true; |
||
5004 | } |
||
5005 | if ($check) { |
||
5006 | $choice[] = $item; |
||
5007 | $i++; |
||
5008 | } |
||
5009 | if ($i == 3) { |
||
5010 | break; |
||
5011 | } |
||
5012 | } |
||
5013 | $tmp = api_strrpos($choice[$j], ' / '); |
||
5014 | |||
5015 | if ($tmp !== false) { |
||
5016 | $choice[$j] = api_substr($choice[$j], 0, $tmp); |
||
5017 | } |
||
5018 | |||
5019 | $choice[$j] = trim($choice[$j]); |
||
5020 | |||
5021 | //Needed to let characters ' and " to work as part of an answer |
||
5022 | $choice[$j] = stripslashes($choice[$j]); |
||
5023 | |||
5024 | $user_tags[] = api_strtolower($choice[$j]); |
||
5025 | //put the contents of the [] answer tag into correct_tags[] |
||
5026 | $correct_tags[] = api_strtolower(api_substr($temp, 0, $pos)); |
||
5027 | $j++; |
||
5028 | $temp = api_substr($temp, $pos + 1); |
||
5029 | } |
||
5030 | |||
5031 | $answer = ''; |
||
5032 | $real_correct_tags = $correct_tags; |
||
5033 | $chosen_list = []; |
||
5034 | $good_answer = []; |
||
5035 | |||
5036 | 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
}
![]() |
|||
5037 | if (!$switchable_answer_set) { |
||
5038 | //needed to parse ' and " characters |
||
5039 | $user_tags[$i] = stripslashes($user_tags[$i]); |
||
5040 | if ($correct_tags[$i] == $user_tags[$i]) { |
||
5041 | $good_answer[$correct_tags[$i]] = 1; |
||
5042 | } elseif (!empty($user_tags[$i])) { |
||
5043 | $good_answer[$correct_tags[$i]] = 0; |
||
5044 | } else { |
||
5045 | $good_answer[$correct_tags[$i]] = 0; |
||
5046 | } |
||
5047 | } else { |
||
5048 | // switchable fill in the blanks |
||
5049 | if (in_array($user_tags[$i], $correct_tags)) { |
||
5050 | $correct_tags = array_diff($correct_tags, $chosen_list); |
||
5051 | $good_answer[$correct_tags[$i]] = 1; |
||
5052 | } elseif (!empty($user_tags[$i])) { |
||
5053 | $good_answer[$correct_tags[$i]] = 0; |
||
5054 | } else { |
||
5055 | $good_answer[$correct_tags[$i]] = 0; |
||
5056 | } |
||
5057 | } |
||
5058 | // adds the correct word, followed by ] to close the blank |
||
5059 | $answer .= ' / <font color="green"><b>'.$real_correct_tags[$i].'</b></font>]'; |
||
5060 | if (isset($real_text[$i + 1])) { |
||
5061 | $answer .= $real_text[$i + 1]; |
||
5062 | } |
||
5063 | } |
||
5064 | |||
5065 | return $good_answer; |
||
5066 | } |
||
5067 | |||
5068 | /** |
||
5069 | * It gets the number of users who finishing the exercise. |
||
5070 | * |
||
5071 | * @param int $exerciseId |
||
5072 | * @param int $courseId |
||
5073 | * @param int $sessionId |
||
5074 | * |
||
5075 | * @return int |
||
5076 | */ |
||
5077 | public static function getNumberStudentsFinishExercise( |
||
5078 | $exerciseId, |
||
5079 | $courseId, |
||
5080 | $sessionId |
||
5081 | ) { |
||
5082 | $tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
5083 | $tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
5084 | |||
5085 | $exerciseId = (int) $exerciseId; |
||
5086 | $courseId = (int) $courseId; |
||
5087 | $sessionId = (int) $sessionId; |
||
5088 | |||
5089 | $sql = "SELECT DISTINCT exe_user_id |
||
5090 | FROM $tblTrackExercises e |
||
5091 | INNER JOIN $tblTrackAttempt a |
||
5092 | ON (a.exe_id = e.exe_id) |
||
5093 | WHERE |
||
5094 | exe_exo_id = $exerciseId AND |
||
5095 | e.c_id = $courseId AND |
||
5096 | e.session_id = $sessionId AND |
||
5097 | status = ''"; |
||
5098 | $result = Database::query($sql); |
||
5099 | $return = 0; |
||
5100 | if ($result) { |
||
0 ignored issues
–
show
|
|||
5101 | $return = Database::num_rows($result); |
||
5102 | } |
||
5103 | |||
5104 | return $return; |
||
5105 | } |
||
5106 | |||
5107 | /** |
||
5108 | * Return an HTML select menu with the student groups. |
||
5109 | * |
||
5110 | * @param string $name is the name and the id of the <select> |
||
5111 | * @param string $default default value for option |
||
5112 | * @param string $onchange |
||
5113 | * |
||
5114 | * @return string the html code of the <select> |
||
5115 | */ |
||
5116 | public static function displayGroupMenu($name, $default, $onchange = "") |
||
5117 | { |
||
5118 | // check the default value of option |
||
5119 | $tabSelected = [$default => " selected='selected' "]; |
||
5120 | $res = ""; |
||
5121 | $res .= "<select name='$name' id='$name' onchange='".$onchange."' >"; |
||
5122 | $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang( |
||
5123 | 'AllGroups' |
||
5124 | )." --</option>"; |
||
5125 | $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang( |
||
5126 | 'NotInAGroup' |
||
5127 | )." -</option>"; |
||
5128 | $tabGroups = GroupManager::get_group_list(); |
||
5129 | $currentCatId = 0; |
||
5130 | $countGroups = count($tabGroups); |
||
5131 | for ($i = 0; $i < $countGroups; $i++) { |
||
5132 | $tabCategory = GroupManager::get_category_from_group( |
||
5133 | $tabGroups[$i]['iid'] |
||
5134 | ); |
||
5135 | if ($tabCategory['iid'] != $currentCatId) { |
||
5136 | $res .= "<option value='-1' disabled='disabled'>".$tabCategory['title']."</option>"; |
||
5137 | $currentCatId = $tabCategory['iid']; |
||
5138 | } |
||
5139 | $res .= "<option ".$tabSelected[$tabGroups[$i]['iid']]."style='margin-left:40px' value='". |
||
5140 | $tabGroups[$i]['iid']."'>". |
||
5141 | $tabGroups[$i]['name']. |
||
5142 | "</option>"; |
||
5143 | } |
||
5144 | $res .= "</select>"; |
||
5145 | |||
5146 | return $res; |
||
5147 | } |
||
5148 | |||
5149 | /** |
||
5150 | * @param int $exe_id |
||
5151 | */ |
||
5152 | public static function create_chat_exercise_session($exe_id) |
||
5153 | { |
||
5154 | if (!isset($_SESSION['current_exercises'])) { |
||
5155 | $_SESSION['current_exercises'] = []; |
||
5156 | } |
||
5157 | $_SESSION['current_exercises'][$exe_id] = true; |
||
5158 | } |
||
5159 | |||
5160 | /** |
||
5161 | * @param int $exe_id |
||
5162 | */ |
||
5163 | public static function delete_chat_exercise_session($exe_id) |
||
5164 | { |
||
5165 | if (isset($_SESSION['current_exercises'])) { |
||
5166 | $_SESSION['current_exercises'][$exe_id] = false; |
||
5167 | } |
||
5168 | } |
||
5169 | |||
5170 | /** |
||
5171 | * Display the exercise results. |
||
5172 | * |
||
5173 | * @param Exercise $objExercise |
||
5174 | * @param int $exeId |
||
5175 | * @param bool $save_user_result save users results (true) or just show the results (false) |
||
5176 | * @param string $remainingMessage |
||
5177 | * @param bool $allowSignature |
||
5178 | * @param bool $allowExportPdf |
||
5179 | * @param bool $isExport |
||
5180 | */ |
||
5181 | public static function displayQuestionListByAttempt( |
||
5182 | $objExercise, |
||
5183 | $exeId, |
||
5184 | $save_user_result = false, |
||
5185 | $remainingMessage = '', |
||
5186 | $allowSignature = false, |
||
5187 | $allowExportPdf = false, |
||
5188 | $isExport = false |
||
5189 | ) { |
||
5190 | $origin = api_get_origin(); |
||
5191 | $courseId = api_get_course_int_id(); |
||
5192 | $courseCode = api_get_course_id(); |
||
5193 | $sessionId = api_get_session_id(); |
||
5194 | |||
5195 | // Getting attempt info |
||
5196 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||
5197 | |||
5198 | // Getting question list |
||
5199 | $question_list = []; |
||
5200 | $studentInfo = []; |
||
5201 | if (!empty($exercise_stat_info['data_tracking'])) { |
||
5202 | $studentInfo = api_get_user_info($exercise_stat_info['exe_user_id']); |
||
5203 | $question_list = explode(',', $exercise_stat_info['data_tracking']); |
||
5204 | } else { |
||
5205 | // Try getting the question list only if save result is off |
||
5206 | if ($save_user_result == false) { |
||
5207 | $question_list = $objExercise->get_validated_question_list(); |
||
5208 | } |
||
5209 | if (in_array( |
||
5210 | $objExercise->getFeedbackType(), |
||
5211 | [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP] |
||
5212 | )) { |
||
5213 | $question_list = $objExercise->get_validated_question_list(); |
||
5214 | } |
||
5215 | } |
||
5216 | |||
5217 | if ($objExercise->getResultAccess()) { |
||
5218 | if ($objExercise->hasResultsAccess($exercise_stat_info) === false) { |
||
5219 | echo Display::return_message( |
||
5220 | sprintf(get_lang('YouPassedTheLimitOfXMinutesToSeeTheResults'), $objExercise->getResultsAccess()) |
||
5221 | ); |
||
5222 | |||
5223 | return false; |
||
5224 | } |
||
5225 | |||
5226 | if (!empty($objExercise->getResultAccess())) { |
||
5227 | $url = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$objExercise->iid; |
||
5228 | echo $objExercise->returnTimeLeftDiv(); |
||
5229 | echo $objExercise->showSimpleTimeControl( |
||
5230 | $objExercise->getResultAccessTimeDiff($exercise_stat_info), |
||
5231 | $url |
||
5232 | ); |
||
5233 | } |
||
5234 | } |
||
5235 | |||
5236 | $counter = 1; |
||
5237 | $total_score = $total_weight = 0; |
||
5238 | $exercise_content = null; |
||
5239 | // Hide results |
||
5240 | $show_results = false; |
||
5241 | $show_only_score = false; |
||
5242 | if (in_array($objExercise->results_disabled, |
||
5243 | [ |
||
5244 | RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, |
||
5245 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS, |
||
5246 | RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, |
||
5247 | ] |
||
5248 | )) { |
||
5249 | $show_results = true; |
||
5250 | } |
||
5251 | |||
5252 | if (in_array( |
||
5253 | $objExercise->results_disabled, |
||
5254 | [ |
||
5255 | RESULT_DISABLE_SHOW_SCORE_ONLY, |
||
5256 | RESULT_DISABLE_SHOW_FINAL_SCORE_ONLY_WITH_CATEGORIES, |
||
5257 | RESULT_DISABLE_RANKING, |
||
5258 | ] |
||
5259 | ) |
||
5260 | ) { |
||
5261 | $show_only_score = true; |
||
5262 | } |
||
5263 | |||
5264 | // Not display expected answer, but score, and feedback |
||
5265 | $show_all_but_expected_answer = false; |
||
5266 | if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY && |
||
5267 | $objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_END |
||
5268 | ) { |
||
5269 | $show_all_but_expected_answer = true; |
||
5270 | $show_results = true; |
||
5271 | $show_only_score = false; |
||
5272 | } |
||
5273 | |||
5274 | $showTotalScoreAndUserChoicesInLastAttempt = true; |
||
5275 | $showTotalScore = true; |
||
5276 | $showQuestionScore = true; |
||
5277 | $attemptResult = []; |
||
5278 | |||
5279 | if (in_array( |
||
5280 | $objExercise->results_disabled, |
||
5281 | [ |
||
5282 | RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT, |
||
5283 | RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK, |
||
5284 | RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK, |
||
5285 | ]) |
||
5286 | ) { |
||
5287 | $show_only_score = true; |
||
5288 | $show_results = true; |
||
5289 | $numberAttempts = 0; |
||
5290 | if ($objExercise->attempts > 0) { |
||
5291 | $attempts = Event::getExerciseResultsByUser( |
||
5292 | api_get_user_id(), |
||
5293 | $objExercise->iid, |
||
5294 | $courseId, |
||
5295 | $sessionId, |
||
5296 | $exercise_stat_info['orig_lp_id'], |
||
5297 | $exercise_stat_info['orig_lp_item_id'], |
||
5298 | 'desc' |
||
5299 | ); |
||
5300 | if ($attempts) { |
||
5301 | $numberAttempts = count($attempts); |
||
5302 | } |
||
5303 | |||
5304 | if ($save_user_result) { |
||
5305 | $numberAttempts++; |
||
5306 | } |
||
5307 | |||
5308 | $showTotalScore = false; |
||
5309 | if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) { |
||
5310 | $showTotalScore = true; |
||
5311 | } |
||
5312 | $showTotalScoreAndUserChoicesInLastAttempt = false; |
||
5313 | if ($numberAttempts >= $objExercise->attempts) { |
||
5314 | $showTotalScore = true; |
||
5315 | $show_results = true; |
||
5316 | $show_only_score = false; |
||
5317 | $showTotalScoreAndUserChoicesInLastAttempt = true; |
||
5318 | } |
||
5319 | |||
5320 | if ($objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT_NO_FEEDBACK) { |
||
5321 | $showTotalScore = true; |
||
5322 | $show_results = true; |
||
5323 | $show_only_score = false; |
||
5324 | $showTotalScoreAndUserChoicesInLastAttempt = false; |
||
5325 | if ($numberAttempts >= $objExercise->attempts) { |
||
5326 | $showTotalScoreAndUserChoicesInLastAttempt = true; |
||
5327 | } |
||
5328 | |||
5329 | // Check if the current attempt is the last. |
||
5330 | /*if (false === $save_user_result && !empty($attempts)) { |
||
5331 | $showTotalScoreAndUserChoicesInLastAttempt = false; |
||
5332 | $position = 1; |
||
5333 | foreach ($attempts as $attempt) { |
||
5334 | if ($exeId == $attempt['exe_id']) { |
||
5335 | break; |
||
5336 | } |
||
5337 | $position++; |
||
5338 | } |
||
5339 | |||
5340 | if ($position == $objExercise->attempts) { |
||
5341 | $showTotalScoreAndUserChoicesInLastAttempt = true; |
||
5342 | } |
||
5343 | }*/ |
||
5344 | } |
||
5345 | } |
||
5346 | |||
5347 | if ($objExercise->results_disabled == |
||
5348 | RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK |
||
5349 | ) { |
||
5350 | $show_only_score = false; |
||
5351 | $show_results = true; |
||
5352 | $show_all_but_expected_answer = false; |
||
5353 | $showTotalScore = false; |
||
5354 | $showQuestionScore = false; |
||
5355 | if ($numberAttempts >= $objExercise->attempts) { |
||
5356 | $showTotalScore = true; |
||
5357 | $showQuestionScore = true; |
||
5358 | } |
||
5359 | } |
||
5360 | } |
||
5361 | |||
5362 | // When exporting to PDF hide feedback/comment/score show warning in hotspot. |
||
5363 | if ($allowExportPdf && $isExport) { |
||
5364 | $showTotalScore = false; |
||
5365 | $showQuestionScore = false; |
||
5366 | $objExercise->feedback_type = 2; |
||
5367 | $objExercise->hideComment = true; |
||
5368 | $objExercise->hideNoAnswer = true; |
||
5369 | $objExercise->results_disabled = 0; |
||
5370 | $objExercise->hideExpectedAnswer = true; |
||
5371 | $show_results = true; |
||
5372 | } |
||
5373 | |||
5374 | if ('embeddable' !== $origin && |
||
5375 | !empty($exercise_stat_info['exe_user_id']) && |
||
5376 | !empty($studentInfo) |
||
5377 | ) { |
||
5378 | // Shows exercise header. |
||
5379 | echo $objExercise->showExerciseResultHeader( |
||
5380 | $studentInfo, |
||
5381 | $exercise_stat_info, |
||
5382 | $save_user_result, |
||
5383 | $allowSignature, |
||
5384 | $allowExportPdf |
||
5385 | ); |
||
5386 | } |
||
5387 | |||
5388 | $question_list_answers = []; |
||
5389 | $category_list = []; |
||
5390 | $loadChoiceFromSession = false; |
||
5391 | $fromDatabase = true; |
||
5392 | $exerciseResult = null; |
||
5393 | $exerciseResultCoordinates = null; |
||
5394 | $delineationResults = null; |
||
5395 | if (true === $save_user_result && in_array( |
||
5396 | $objExercise->getFeedbackType(), |
||
5397 | [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP] |
||
5398 | )) { |
||
5399 | $loadChoiceFromSession = true; |
||
5400 | $fromDatabase = false; |
||
5401 | $exerciseResult = Session::read('exerciseResult'); |
||
5402 | $exerciseResultCoordinates = Session::read('exerciseResultCoordinates'); |
||
5403 | $delineationResults = Session::read('hotspot_delineation_result'); |
||
5404 | $delineationResults = $delineationResults[$objExercise->iid] ?? null; |
||
5405 | } |
||
5406 | |||
5407 | $countPendingQuestions = 0; |
||
5408 | $result = []; |
||
5409 | // Loop over all question to show results for each of them, one by one |
||
5410 | if (!empty($question_list)) { |
||
5411 | foreach ($question_list as $questionId) { |
||
5412 | // Creates a temporary Question object |
||
5413 | $objQuestionTmp = Question::read($questionId, $objExercise->course); |
||
5414 | // This variable came from exercise_submit_modal.php |
||
5415 | ob_start(); |
||
5416 | $choice = null; |
||
5417 | $delineationChoice = null; |
||
5418 | if ($loadChoiceFromSession) { |
||
5419 | $choice = $exerciseResult[$questionId] ?? null; |
||
5420 | $delineationChoice = $delineationResults[$questionId] ?? null; |
||
5421 | } |
||
5422 | |||
5423 | // We're inside *one* question. Go through each possible answer for this question |
||
5424 | $result = $objExercise->manage_answer( |
||
5425 | $exeId, |
||
5426 | $questionId, |
||
5427 | $choice, |
||
5428 | 'exercise_result', |
||
5429 | $exerciseResultCoordinates, |
||
5430 | $save_user_result, |
||
5431 | $fromDatabase, |
||
5432 | $show_results, |
||
5433 | $objExercise->selectPropagateNeg(), |
||
5434 | $delineationChoice, |
||
5435 | $showTotalScoreAndUserChoicesInLastAttempt |
||
5436 | ); |
||
5437 | |||
5438 | if (empty($result)) { |
||
5439 | continue; |
||
5440 | } |
||
5441 | |||
5442 | $total_score += (float) $result['score']; |
||
5443 | $total_weight += (float) $result['weight']; |
||
5444 | |||
5445 | $question_list_answers[] = [ |
||
5446 | 'question' => $result['open_question'], |
||
5447 | 'answer' => $result['open_answer'], |
||
5448 | 'answer_type' => $result['answer_type'], |
||
5449 | 'generated_oral_file' => $result['generated_oral_file'], |
||
5450 | ]; |
||
5451 | |||
5452 | $my_total_score = $result['score']; |
||
5453 | $my_total_weight = $result['weight']; |
||
5454 | $scorePassed = self::scorePassed($my_total_score, $my_total_weight); |
||
5455 | |||
5456 | // Category report |
||
5457 | $category_was_added_for_this_test = false; |
||
5458 | if (isset($objQuestionTmp->category) && !empty($objQuestionTmp->category)) { |
||
5459 | if (!isset($category_list[$objQuestionTmp->category]['score'])) { |
||
5460 | $category_list[$objQuestionTmp->category]['score'] = 0; |
||
5461 | } |
||
5462 | if (!isset($category_list[$objQuestionTmp->category]['total'])) { |
||
5463 | $category_list[$objQuestionTmp->category]['total'] = 0; |
||
5464 | } |
||
5465 | if (!isset($category_list[$objQuestionTmp->category]['total_questions'])) { |
||
5466 | $category_list[$objQuestionTmp->category]['total_questions'] = 0; |
||
5467 | } |
||
5468 | if (!isset($category_list[$objQuestionTmp->category]['passed'])) { |
||
5469 | $category_list[$objQuestionTmp->category]['passed'] = 0; |
||
5470 | } |
||
5471 | if (!isset($category_list[$objQuestionTmp->category]['wrong'])) { |
||
5472 | $category_list[$objQuestionTmp->category]['wrong'] = 0; |
||
5473 | } |
||
5474 | if (!isset($category_list[$objQuestionTmp->category]['no_answer'])) { |
||
5475 | $category_list[$objQuestionTmp->category]['no_answer'] = 0; |
||
5476 | } |
||
5477 | |||
5478 | $category_list[$objQuestionTmp->category]['score'] += $my_total_score; |
||
5479 | $category_list[$objQuestionTmp->category]['total'] += $my_total_weight; |
||
5480 | if ($scorePassed) { |
||
5481 | // Only count passed if score is not empty |
||
5482 | if (!empty($my_total_score)) { |
||
5483 | $category_list[$objQuestionTmp->category]['passed']++; |
||
5484 | } |
||
5485 | } else { |
||
5486 | if ($result['user_answered']) { |
||
5487 | $category_list[$objQuestionTmp->category]['wrong']++; |
||
5488 | } else { |
||
5489 | $category_list[$objQuestionTmp->category]['no_answer']++; |
||
5490 | } |
||
5491 | } |
||
5492 | |||
5493 | $category_list[$objQuestionTmp->category]['total_questions']++; |
||
5494 | $category_was_added_for_this_test = true; |
||
5495 | } |
||
5496 | if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) { |
||
5497 | foreach ($objQuestionTmp->category_list as $category_id) { |
||
5498 | $category_list[$category_id]['score'] += $my_total_score; |
||
5499 | $category_list[$category_id]['total'] += $my_total_weight; |
||
5500 | $category_was_added_for_this_test = true; |
||
5501 | } |
||
5502 | } |
||
5503 | |||
5504 | // No category for this question! |
||
5505 | if ($category_was_added_for_this_test == false) { |
||
5506 | if (!isset($category_list['none']['score'])) { |
||
5507 | $category_list['none']['score'] = 0; |
||
5508 | } |
||
5509 | if (!isset($category_list['none']['total'])) { |
||
5510 | $category_list['none']['total'] = 0; |
||
5511 | } |
||
5512 | |||
5513 | $category_list['none']['score'] += $my_total_score; |
||
5514 | $category_list['none']['total'] += $my_total_weight; |
||
5515 | } |
||
5516 | |||
5517 | if ($objExercise->selectPropagateNeg() == 0 && $my_total_score < 0) { |
||
5518 | $my_total_score = 0; |
||
5519 | } |
||
5520 | |||
5521 | $comnt = null; |
||
5522 | if ($show_results) { |
||
5523 | $comnt = Event::get_comments($exeId, $questionId); |
||
5524 | $teacherAudio = self::getOralFeedbackAudio( |
||
5525 | $exeId, |
||
5526 | $questionId, |
||
5527 | api_get_user_id() |
||
5528 | ); |
||
5529 | |||
5530 | if (!empty($comnt) || $teacherAudio) { |
||
5531 | echo '<b>'.get_lang('Feedback').'</b>'; |
||
5532 | } |
||
5533 | |||
5534 | if (!empty($comnt)) { |
||
5535 | echo self::getFeedbackText($comnt); |
||
5536 | } |
||
5537 | |||
5538 | if ($teacherAudio) { |
||
5539 | echo $teacherAudio; |
||
5540 | } |
||
5541 | } |
||
5542 | |||
5543 | $calculatedScore = [ |
||
5544 | 'result' => self::show_score( |
||
5545 | $my_total_score, |
||
5546 | $my_total_weight, |
||
5547 | false |
||
5548 | ), |
||
5549 | 'pass' => $scorePassed, |
||
5550 | 'score' => $my_total_score, |
||
5551 | 'weight' => $my_total_weight, |
||
5552 | 'comments' => $comnt, |
||
5553 | 'user_answered' => $result['user_answered'], |
||
5554 | ]; |
||
5555 | |||
5556 | $score = []; |
||
5557 | if ($show_results) { |
||
5558 | $score = $calculatedScore; |
||
5559 | } |
||
5560 | if (in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION, ANNOTATION, UPLOAD_ANSWER, ANSWER_IN_OFFICE_DOC])) { |
||
5561 | $reviewScore = [ |
||
5562 | 'score' => $my_total_score, |
||
5563 | 'comments' => Event::get_comments($exeId, $questionId), |
||
5564 | ]; |
||
5565 | $check = $objQuestionTmp->isQuestionWaitingReview($reviewScore); |
||
5566 | if (false === $check) { |
||
5567 | $countPendingQuestions++; |
||
5568 | } |
||
5569 | } |
||
5570 | |||
5571 | $contents = ob_get_clean(); |
||
5572 | |||
5573 | // Hide correct answers. |
||
5574 | if ($scorePassed && false === $objExercise->disableHideCorrectAnsweredQuestions) { |
||
5575 | // Skip correct answers. |
||
5576 | $hide = (int) $objExercise->getPageConfigurationAttribute('hide_correct_answered_questions'); |
||
5577 | if (1 === $hide) { |
||
5578 | continue; |
||
5579 | } |
||
5580 | } |
||
5581 | |||
5582 | $question_content = ''; |
||
5583 | if ($show_results) { |
||
5584 | $question_content = '<div class="question_row_answer">'; |
||
5585 | if (false === $showQuestionScore) { |
||
5586 | $score = []; |
||
5587 | } |
||
5588 | |||
5589 | // Shows question title an description |
||
5590 | $question_content .= $objQuestionTmp->return_header( |
||
5591 | $objExercise, |
||
5592 | $counter, |
||
5593 | $score |
||
5594 | ); |
||
5595 | } |
||
5596 | $counter++; |
||
5597 | $question_content .= $contents; |
||
5598 | if ($show_results) { |
||
5599 | $question_content .= '</div>'; |
||
5600 | } |
||
5601 | |||
5602 | $calculatedScore['question_content'] = $question_content; |
||
5603 | $attemptResult[] = $calculatedScore; |
||
5604 | |||
5605 | if ($objExercise->showExpectedChoice()) { |
||
5606 | $exercise_content .= Display::div( |
||
5607 | Display::panel($question_content), |
||
5608 | ['class' => 'question-panel'] |
||
5609 | ); |
||
5610 | } else { |
||
5611 | // $show_all_but_expected_answer should not happen at |
||
5612 | // the same time as $show_results |
||
5613 | if ($show_results && !$show_only_score) { |
||
5614 | $exercise_content .= Display::div( |
||
5615 | Display::panel($question_content), |
||
5616 | ['class' => 'question-panel'] |
||
5617 | ); |
||
5618 | } |
||
5619 | } |
||
5620 | } |
||
5621 | } |
||
5622 | |||
5623 | // Display text when test is finished #4074 and for LP #4227 |
||
5624 | // Allows to do a remove_XSS for end text result of exercise with |
||
5625 | // user status COURSEMANAGERLOWSECURITY BT#20194 |
||
5626 | $finishMessage = $objExercise->getFinishText($total_score, $total_weight); |
||
5627 | if (true === api_get_configuration_value('exercise_result_end_text_html_strict_filtering')) { |
||
5628 | $endOfMessage = Security::remove_XSS($finishMessage, COURSEMANAGERLOWSECURITY); |
||
5629 | } else { |
||
5630 | $endOfMessage = Security::remove_XSS($finishMessage); |
||
5631 | } |
||
5632 | if (!empty($endOfMessage)) { |
||
5633 | echo Display::div( |
||
5634 | $endOfMessage, |
||
5635 | ['id' => 'quiz_end_message'] |
||
5636 | ); |
||
5637 | } |
||
5638 | |||
5639 | $totalScoreText = null; |
||
5640 | $certificateBlock = ''; |
||
5641 | if (($show_results || $show_only_score) && $showTotalScore) { |
||
5642 | if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
5643 | echo '<h1 style="text-align : center; margin : 20px 0;">'.get_lang('YourResults').'</h1><br />'; |
||
5644 | } |
||
5645 | $totalScoreText .= '<div class="question_row_score">'; |
||
5646 | if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
5647 | $totalScoreText .= self::getQuestionDiagnosisRibbon( |
||
5648 | $objExercise, |
||
5649 | $total_score, |
||
5650 | $total_weight, |
||
5651 | true |
||
5652 | ); |
||
5653 | } else { |
||
5654 | $pluginEvaluation = QuestionOptionsEvaluationPlugin::create(); |
||
5655 | if ('true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE)) { |
||
5656 | $formula = $pluginEvaluation->getFormulaForExercise($objExercise->selectId()); |
||
5657 | |||
5658 | if (!empty($formula)) { |
||
5659 | $total_score = $pluginEvaluation->getResultWithFormula($exeId, $formula); |
||
5660 | $total_weight = $pluginEvaluation->getMaxScore(); |
||
5661 | } |
||
5662 | } |
||
5663 | |||
5664 | $totalScoreText .= self::getTotalScoreRibbon( |
||
5665 | $objExercise, |
||
5666 | $total_score, |
||
5667 | $total_weight, |
||
5668 | true, |
||
5669 | $countPendingQuestions |
||
5670 | ); |
||
5671 | } |
||
5672 | $totalScoreText .= '</div>'; |
||
5673 | |||
5674 | if (!empty($studentInfo)) { |
||
5675 | $certificateBlock = self::generateAndShowCertificateBlock( |
||
5676 | $total_score, |
||
5677 | $total_weight, |
||
5678 | $objExercise, |
||
5679 | $studentInfo['id'], |
||
5680 | $courseCode, |
||
5681 | $sessionId |
||
5682 | ); |
||
5683 | } |
||
5684 | } |
||
5685 | |||
5686 | if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||
5687 | $chartMultiAnswer = MultipleAnswerTrueFalseDegreeCertainty::displayStudentsChartResults( |
||
5688 | $exeId, |
||
5689 | $objExercise |
||
5690 | ); |
||
5691 | echo $chartMultiAnswer; |
||
5692 | } |
||
5693 | |||
5694 | if (!empty($category_list) && |
||
5695 | ($show_results || $show_only_score || RESULT_DISABLE_RADAR == $objExercise->results_disabled) |
||
5696 | ) { |
||
5697 | // Adding total |
||
5698 | $category_list['total'] = [ |
||
5699 | 'score' => $total_score, |
||
5700 | 'total' => $total_weight, |
||
5701 | ]; |
||
5702 | echo TestCategory::get_stats_table_by_attempt($objExercise, $category_list); |
||
5703 | } |
||
5704 | |||
5705 | if ($show_all_but_expected_answer) { |
||
5706 | $exercise_content .= Display::return_message(get_lang('ExerciseWithFeedbackWithoutCorrectionComment')); |
||
5707 | } |
||
5708 | |||
5709 | // Remove audio auto play from questions on results page - refs BT#7939 |
||
5710 | $exercise_content = preg_replace( |
||
5711 | ['/autoplay[\=\".+\"]+/', '/autostart[\=\".+\"]+/'], |
||
5712 | '', |
||
5713 | $exercise_content |
||
5714 | ); |
||
5715 | |||
5716 | echo $totalScoreText; |
||
5717 | echo $certificateBlock; |
||
5718 | |||
5719 | // Ofaj change BT#11784 |
||
5720 | if (api_get_configuration_value('quiz_show_description_on_results_page') && |
||
5721 | !empty($objExercise->description) |
||
5722 | ) { |
||
5723 | echo Display::div(Security::remove_XSS($objExercise->description), ['class' => 'exercise_description']); |
||
5724 | } |
||
5725 | |||
5726 | echo $exercise_content; |
||
5727 | if (!$show_only_score) { |
||
5728 | echo $totalScoreText; |
||
5729 | } |
||
5730 | |||
5731 | if ($save_user_result) { |
||
5732 | // Tracking of results |
||
5733 | if ($exercise_stat_info) { |
||
5734 | $learnpath_id = $exercise_stat_info['orig_lp_id']; |
||
5735 | $learnpath_item_id = $exercise_stat_info['orig_lp_item_id']; |
||
5736 | $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id']; |
||
5737 | |||
5738 | if (api_is_allowed_to_session_edit()) { |
||
5739 | Event::updateEventExercise( |
||
5740 | $exercise_stat_info['exe_id'], |
||
5741 | $objExercise->selectId(), |
||
5742 | $total_score, |
||
5743 | $total_weight, |
||
5744 | $sessionId, |
||
5745 | $learnpath_id, |
||
5746 | $learnpath_item_id, |
||
5747 | $learnpath_item_view_id, |
||
5748 | $exercise_stat_info['exe_duration'], |
||
5749 | $question_list |
||
5750 | ); |
||
5751 | |||
5752 | $allowStats = api_get_configuration_value('allow_gradebook_stats'); |
||
5753 | if ($allowStats) { |
||
5754 | $objExercise->generateStats( |
||
5755 | $objExercise->selectId(), |
||
5756 | api_get_course_info(), |
||
5757 | $sessionId |
||
5758 | ); |
||
5759 | } |
||
5760 | } |
||
5761 | } |
||
5762 | |||
5763 | // Send notification at the end |
||
5764 | if (!api_is_allowed_to_edit(null, true) && |
||
5765 | !api_is_excluded_user_type() |
||
5766 | ) { |
||
5767 | $objExercise->send_mail_notification_for_exam( |
||
5768 | 'end', |
||
5769 | $question_list_answers, |
||
5770 | $origin, |
||
5771 | $exeId, |
||
5772 | $total_score, |
||
5773 | $total_weight |
||
5774 | ); |
||
5775 | } |
||
5776 | } |
||
5777 | |||
5778 | if (in_array( |
||
5779 | $objExercise->selectResultsDisabled(), |
||
5780 | [RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING] |
||
5781 | )) { |
||
5782 | echo Display::page_header(get_lang('Ranking'), null, 'h4'); |
||
5783 | echo self::displayResultsInRanking( |
||
5784 | $objExercise, |
||
5785 | api_get_user_id(), |
||
5786 | $courseId, |
||
5787 | $sessionId |
||
5788 | ); |
||
5789 | } |
||
5790 | |||
5791 | if (!empty($remainingMessage)) { |
||
5792 | echo Display::return_message($remainingMessage, 'normal', false); |
||
5793 | } |
||
5794 | |||
5795 | $failedAnswersCount = 0; |
||
5796 | $wrongQuestionHtml = ''; |
||
5797 | $all = ''; |
||
5798 | foreach ($attemptResult as $item) { |
||
5799 | if (false === $item['pass']) { |
||
5800 | $failedAnswersCount++; |
||
5801 | $wrongQuestionHtml .= $item['question_content'].'<br />'; |
||
5802 | } |
||
5803 | $all .= $item['question_content'].'<br />'; |
||
5804 | } |
||
5805 | |||
5806 | $passed = self::isPassPercentageAttemptPassed( |
||
5807 | $objExercise, |
||
5808 | $total_score, |
||
5809 | $total_weight |
||
5810 | ); |
||
5811 | |||
5812 | if ($save_user_result |
||
5813 | && !$passed |
||
5814 | && true === api_get_configuration_value('exercise_subscribe_session_when_finished_failure') |
||
5815 | ) { |
||
5816 | self::subscribeSessionWhenFinishedFailure($objExercise->iid); |
||
5817 | } |
||
5818 | |||
5819 | $percentage = 0; |
||
5820 | if (!empty($total_weight)) { |
||
5821 | $percentage = ($total_score / $total_weight) * 100; |
||
5822 | } |
||
5823 | |||
5824 | return [ |
||
5825 | 'category_list' => $category_list, |
||
5826 | 'attempts_result_list' => $attemptResult, // array of results |
||
5827 | 'exercise_passed' => $passed, // boolean |
||
5828 | 'total_answers_count' => count($attemptResult), // int |
||
5829 | 'failed_answers_count' => $failedAnswersCount, // int |
||
5830 | 'failed_answers_html' => $wrongQuestionHtml, |
||
5831 | 'all_answers_html' => $all, |
||
5832 | 'total_score' => $total_score, |
||
5833 | 'total_weight' => $total_weight, |
||
5834 | 'total_percentage' => $percentage, |
||
5835 | 'count_pending_questions' => $countPendingQuestions, |
||
5836 | ]; |
||
5837 | } |
||
5838 | |||
5839 | public static function getSessionWhenFinishedFailure(int $exerciseId): ?SessionEntity |
||
5840 | { |
||
5841 | $objExtraField = new ExtraField('exercise'); |
||
5842 | $objExtraFieldValue = new ExtraFieldValue('exercise'); |
||
5843 | |||
5844 | $subsSessionWhenFailureField = $objExtraField->get_handler_field_info_by_field_variable( |
||
5845 | 'subscribe_session_when_finished_failure' |
||
5846 | ); |
||
5847 | $subsSessionWhenFailureValue = $objExtraFieldValue->get_values_by_handler_and_field_id( |
||
5848 | $exerciseId, |
||
5849 | $subsSessionWhenFailureField['id'] |
||
5850 | ); |
||
5851 | |||
5852 | if (!empty($subsSessionWhenFailureValue['value'])) { |
||
5853 | return api_get_session_entity((int) $subsSessionWhenFailureValue['value']); |
||
5854 | } |
||
5855 | |||
5856 | return null; |
||
5857 | } |
||
5858 | |||
5859 | /** |
||
5860 | * It validates unique score when all user answers are correct by question. |
||
5861 | * It is used for global questions. |
||
5862 | * |
||
5863 | * @param $answerType |
||
5864 | * @param $listCorrectAnswers |
||
5865 | * @param $exeId |
||
5866 | * @param $questionId |
||
5867 | * @param $questionWeighting |
||
5868 | * @param array $choice |
||
5869 | * @param int $nbrAnswers |
||
5870 | * |
||
5871 | * @return int|mixed |
||
5872 | */ |
||
5873 | public static function getUserQuestionScoreGlobal( |
||
5874 | $answerType, |
||
5875 | $listCorrectAnswers, |
||
5876 | $exeId, |
||
5877 | $questionId, |
||
5878 | $questionWeighting, |
||
5879 | $choice = [], |
||
5880 | $nbrAnswers = 0 |
||
5881 | ) { |
||
5882 | $nbrCorrect = 0; |
||
5883 | $nbrOptions = 0; |
||
5884 | switch ($answerType) { |
||
5885 | case FILL_IN_BLANKS_COMBINATION: |
||
5886 | if (!empty($listCorrectAnswers)) { |
||
5887 | foreach ($listCorrectAnswers['student_score'] as $idx => $val) { |
||
5888 | if (1 === (int) $val) { |
||
5889 | $nbrCorrect++; |
||
5890 | } |
||
5891 | } |
||
5892 | $nbrOptions = (int) $listCorrectAnswers['words_count']; |
||
5893 | } |
||
5894 | break; |
||
5895 | case HOT_SPOT_COMBINATION: |
||
5896 | if (!empty($listCorrectAnswers)) { |
||
5897 | foreach ($listCorrectAnswers as $idx => $val) { |
||
5898 | if (1 === (int) $choice[$idx]) { |
||
5899 | $nbrCorrect++; |
||
5900 | } |
||
5901 | } |
||
5902 | } else { |
||
5903 | // We get the user answers from database |
||
5904 | $TBL_TRACK_HOTSPOT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT); |
||
5905 | $sql = "SELECT count(hotspot_id) as ct |
||
5906 | FROM $TBL_TRACK_HOTSPOT |
||
5907 | WHERE |
||
5908 | hotspot_exe_id = '".Database::escape_string($exeId)."' AND |
||
5909 | hotspot_question_id = '".Database::escape_string($questionId)."' AND |
||
5910 | hotspot_correct = 1"; |
||
5911 | $result = Database::query($sql); |
||
5912 | $nbrCorrect = (int) Database::result($result, 0, 0); |
||
5913 | } |
||
5914 | $nbrOptions = $nbrAnswers; |
||
5915 | break; |
||
5916 | case MATCHING_COMBINATION: |
||
5917 | case MATCHING_DRAGGABLE_COMBINATION: |
||
5918 | if (isset($listCorrectAnswers['form_values'])) { |
||
5919 | if (isset($listCorrectAnswers['form_values']['correct'])) { |
||
5920 | $nbrCorrect = count($listCorrectAnswers['form_values']['correct']); |
||
5921 | $nbrOptions = (int) $listCorrectAnswers['form_values']['count_options']; |
||
5922 | } |
||
5923 | } else { |
||
5924 | if (isset($listCorrectAnswers['from_database'])) { |
||
5925 | if (isset($listCorrectAnswers['from_database']['correct'])) { |
||
5926 | $nbrCorrect = count($listCorrectAnswers['from_database']['correct']); |
||
5927 | $nbrOptions = (int) $listCorrectAnswers['from_database']['count_options']; |
||
5928 | } |
||
5929 | } |
||
5930 | } |
||
5931 | break; |
||
5932 | } |
||
5933 | |||
5934 | $questionScore = 0; |
||
5935 | if ($nbrCorrect > 0 && $nbrCorrect == $nbrOptions) { |
||
5936 | $questionScore = $questionWeighting; |
||
5937 | } |
||
5938 | |||
5939 | return $questionScore; |
||
5940 | } |
||
5941 | |||
5942 | /** |
||
5943 | * Display the ranking of results in a exercise. |
||
5944 | * |
||
5945 | * @param Exercise $exercise |
||
5946 | * @param int $currentUserId |
||
5947 | * @param int $courseId |
||
5948 | * @param int $sessionId |
||
5949 | * |
||
5950 | * @return string |
||
5951 | */ |
||
5952 | public static function displayResultsInRanking($exercise, $currentUserId, $courseId, $sessionId = 0) |
||
5953 | { |
||
5954 | $exerciseId = $exercise->iid; |
||
5955 | $data = self::exerciseResultsInRanking($exerciseId, $courseId, $sessionId); |
||
5956 | |||
5957 | $table = new HTML_Table(['class' => 'table table-hover table-striped table-bordered']); |
||
5958 | $table->setHeaderContents(0, 0, get_lang('Position'), ['class' => 'text-right']); |
||
5959 | $table->setHeaderContents(0, 1, get_lang('Username')); |
||
5960 | $table->setHeaderContents(0, 2, get_lang('Score'), ['class' => 'text-right']); |
||
5961 | $table->setHeaderContents(0, 3, get_lang('Date'), ['class' => 'text-center']); |
||
5962 | |||
5963 | foreach ($data as $r => $item) { |
||
5964 | if (!isset($item[1])) { |
||
5965 | continue; |
||
5966 | } |
||
5967 | $selected = $item[1]->getId() == $currentUserId; |
||
5968 | |||
5969 | foreach ($item as $c => $value) { |
||
5970 | $table->setCellContents($r + 1, $c, $value); |
||
5971 | |||
5972 | $attrClass = ''; |
||
5973 | |||
5974 | if (in_array($c, [0, 2])) { |
||
5975 | $attrClass = 'text-right'; |
||
5976 | } elseif (3 == $c) { |
||
5977 | $attrClass = 'text-center'; |
||
5978 | } |
||
5979 | |||
5980 | if ($selected) { |
||
5981 | $attrClass .= ' warning'; |
||
5982 | } |
||
5983 | |||
5984 | $table->setCellAttributes($r + 1, $c, ['class' => $attrClass]); |
||
5985 | } |
||
5986 | } |
||
5987 | |||
5988 | return $table->toHtml(); |
||
5989 | } |
||
5990 | |||
5991 | /** |
||
5992 | * Get the ranking for results in a exercise. |
||
5993 | * Function used internally by ExerciseLib::displayResultsInRanking. |
||
5994 | * |
||
5995 | * @param int $exerciseId |
||
5996 | * @param int $courseId |
||
5997 | * @param int $sessionId |
||
5998 | * |
||
5999 | * @return array |
||
6000 | */ |
||
6001 | public static function exerciseResultsInRanking($exerciseId, $courseId, $sessionId = 0) |
||
6002 | { |
||
6003 | $em = Database::getManager(); |
||
6004 | |||
6005 | $dql = 'SELECT DISTINCT te.exeUserId FROM ChamiloCoreBundle:TrackEExercises te WHERE te.exeExoId = :id AND te.cId = :cId'; |
||
6006 | $dql .= api_get_session_condition($sessionId, true, false, 'te.sessionId'); |
||
6007 | |||
6008 | $result = $em |
||
6009 | ->createQuery($dql) |
||
6010 | ->setParameters(['id' => $exerciseId, 'cId' => $courseId]) |
||
6011 | ->getScalarResult(); |
||
6012 | |||
6013 | $data = []; |
||
6014 | /** @var TrackEExercises $item */ |
||
6015 | foreach ($result as $item) { |
||
6016 | $data[] = self::get_best_attempt_by_user($item['exeUserId'], $exerciseId, $courseId, $sessionId); |
||
6017 | } |
||
6018 | |||
6019 | usort( |
||
6020 | $data, |
||
6021 | function ($a, $b) { |
||
6022 | if ($a['exe_result'] != $b['exe_result']) { |
||
6023 | return $a['exe_result'] > $b['exe_result'] ? -1 : 1; |
||
6024 | } |
||
6025 | |||
6026 | if ($a['exe_date'] != $b['exe_date']) { |
||
6027 | return $a['exe_date'] < $b['exe_date'] ? -1 : 1; |
||
6028 | } |
||
6029 | |||
6030 | return 0; |
||
6031 | } |
||
6032 | ); |
||
6033 | |||
6034 | // flags to display the same position in case of tie |
||
6035 | $lastScore = $data[0]['exe_result']; |
||
6036 | $position = 1; |
||
6037 | $data = array_map( |
||
6038 | function ($item) use (&$lastScore, &$position) { |
||
6039 | if ($item['exe_result'] < $lastScore) { |
||
6040 | $position++; |
||
6041 | } |
||
6042 | |||
6043 | $lastScore = $item['exe_result']; |
||
6044 | |||
6045 | return [ |
||
6046 | $position, |
||
6047 | api_get_user_entity($item['exe_user_id']), |
||
6048 | self::show_score($item['exe_result'], $item['exe_weighting'], true, true, true), |
||
6049 | api_convert_and_format_date($item['exe_date'], DATE_TIME_FORMAT_SHORT), |
||
6050 | ]; |
||
6051 | }, |
||
6052 | $data |
||
6053 | ); |
||
6054 | |||
6055 | return $data; |
||
6056 | } |
||
6057 | |||
6058 | /** |
||
6059 | * Get a special ribbon on top of "degree of certainty" questions ( |
||
6060 | * variation from getTotalScoreRibbon() for other question types). |
||
6061 | * |
||
6062 | * @param Exercise $objExercise |
||
6063 | * @param float $score |
||
6064 | * @param float $weight |
||
6065 | * @param bool $checkPassPercentage |
||
6066 | * |
||
6067 | * @return string |
||
6068 | */ |
||
6069 | public static function getQuestionDiagnosisRibbon($objExercise, $score, $weight, $checkPassPercentage = false) |
||
6070 | { |
||
6071 | $displayChartDegree = true; |
||
6072 | $ribbon = $displayChartDegree ? '<div class="ribbon">' : ''; |
||
6073 | |||
6074 | if ($checkPassPercentage) { |
||
6075 | $passPercentage = $objExercise->selectPassPercentage(); |
||
6076 | $isSuccess = self::isSuccessExerciseResult($score, $weight, $passPercentage); |
||
6077 | // Color the final test score if pass_percentage activated |
||
6078 | $ribbonTotalSuccessOrError = ''; |
||
6079 | if (self::isPassPercentageEnabled($passPercentage)) { |
||
6080 | if ($isSuccess) { |
||
6081 | $ribbonTotalSuccessOrError = ' ribbon-total-success'; |
||
6082 | } else { |
||
6083 | $ribbonTotalSuccessOrError = ' ribbon-total-error'; |
||
6084 | } |
||
6085 | } |
||
6086 | $ribbon .= $displayChartDegree ? '<div class="rib rib-total '.$ribbonTotalSuccessOrError.'">' : ''; |
||
6087 | } else { |
||
6088 | $ribbon .= $displayChartDegree ? '<div class="rib rib-total">' : ''; |
||
6089 | } |
||
6090 | |||
6091 | if ($displayChartDegree) { |
||
6092 | $ribbon .= '<h3>'.get_lang('YourTotalScore').': '; |
||
6093 | $ribbon .= self::show_score($score, $weight, false, true); |
||
6094 | $ribbon .= '</h3>'; |
||
6095 | $ribbon .= '</div>'; |
||
6096 | } |
||
6097 | |||
6098 | if ($checkPassPercentage) { |
||
6099 | $ribbon .= self::showSuccessMessage( |
||
6100 | $score, |
||
6101 | $weight, |
||
6102 | $objExercise->selectPassPercentage() |
||
6103 | ); |
||
6104 | } |
||
6105 | |||
6106 | $ribbon .= $displayChartDegree ? '</div>' : ''; |
||
6107 | |||
6108 | return $ribbon; |
||
6109 | } |
||
6110 | |||
6111 | public static function isPassPercentageAttemptPassed($objExercise, $score, $weight) |
||
6112 | { |
||
6113 | $passPercentage = $objExercise->selectPassPercentage(); |
||
6114 | |||
6115 | return self::isSuccessExerciseResult($score, $weight, $passPercentage); |
||
6116 | } |
||
6117 | |||
6118 | /** |
||
6119 | * @param float $score |
||
6120 | * @param float $weight |
||
6121 | * @param bool $checkPassPercentage |
||
6122 | * @param int $countPendingQuestions |
||
6123 | * |
||
6124 | * @return string |
||
6125 | */ |
||
6126 | public static function getTotalScoreRibbon( |
||
6127 | Exercise $objExercise, |
||
6128 | $score, |
||
6129 | $weight, |
||
6130 | $checkPassPercentage = false, |
||
6131 | $countPendingQuestions = 0 |
||
6132 | ) { |
||
6133 | $hide = (int) $objExercise->getPageConfigurationAttribute('hide_total_score'); |
||
6134 | if (1 === $hide) { |
||
6135 | return ''; |
||
6136 | } |
||
6137 | |||
6138 | $passPercentage = $objExercise->selectPassPercentage(); |
||
6139 | $ribbon = '<div class="title-score">'; |
||
6140 | if ($checkPassPercentage) { |
||
6141 | $isSuccess = self::isSuccessExerciseResult( |
||
6142 | $score, |
||
6143 | $weight, |
||
6144 | $passPercentage |
||
6145 | ); |
||
6146 | // Color the final test score if pass_percentage activated |
||
6147 | $class = ''; |
||
6148 | if (self::isPassPercentageEnabled($passPercentage)) { |
||
6149 | if ($isSuccess) { |
||
6150 | $class = ' ribbon-total-success'; |
||
6151 | } else { |
||
6152 | $class = ' ribbon-total-error'; |
||
6153 | } |
||
6154 | } |
||
6155 | $ribbon .= '<div class="total '.$class.'">'; |
||
6156 | } else { |
||
6157 | $ribbon .= '<div class="total">'; |
||
6158 | } |
||
6159 | $ribbon .= '<h3>'.get_lang('YourTotalScore').': '; |
||
6160 | $ribbon .= self::show_score($score, $weight, false, true); |
||
6161 | $ribbon .= '</h3>'; |
||
6162 | $ribbon .= '</div>'; |
||
6163 | if ($checkPassPercentage) { |
||
6164 | $ribbon .= self::showSuccessMessage( |
||
6165 | $score, |
||
6166 | $weight, |
||
6167 | $passPercentage |
||
6168 | ); |
||
6169 | } |
||
6170 | $ribbon .= '</div>'; |
||
6171 | |||
6172 | if (!empty($countPendingQuestions)) { |
||
6173 | $ribbon .= '<br />'; |
||
6174 | $ribbon .= Display::return_message( |
||
6175 | sprintf( |
||
6176 | get_lang('TempScoreXQuestionsNotCorrectedYet'), |
||
6177 | $countPendingQuestions |
||
6178 | ), |
||
6179 | 'warning' |
||
6180 | ); |
||
6181 | } |
||
6182 | |||
6183 | return $ribbon; |
||
6184 | } |
||
6185 | |||
6186 | /** |
||
6187 | * @param int $countLetter |
||
6188 | * |
||
6189 | * @return mixed |
||
6190 | */ |
||
6191 | public static function detectInputAppropriateClass($countLetter) |
||
6192 | { |
||
6193 | $limits = [ |
||
6194 | 0 => 'input-mini', |
||
6195 | 10 => 'input-mini', |
||
6196 | 15 => 'input-medium', |
||
6197 | 20 => 'input-xlarge', |
||
6198 | 40 => 'input-xlarge', |
||
6199 | 60 => 'input-xxlarge', |
||
6200 | 100 => 'input-xxlarge', |
||
6201 | 200 => 'input-xxlarge', |
||
6202 | ]; |
||
6203 | |||
6204 | foreach ($limits as $size => $item) { |
||
6205 | if ($countLetter <= $size) { |
||
6206 | return $item; |
||
6207 | } |
||
6208 | } |
||
6209 | |||
6210 | return $limits[0]; |
||
6211 | } |
||
6212 | |||
6213 | /** |
||
6214 | * @param int $senderId |
||
6215 | * @param array $course_info |
||
6216 | * @param string $test |
||
6217 | * @param string $url |
||
6218 | * |
||
6219 | * @return string |
||
6220 | */ |
||
6221 | public static function getEmailNotification($senderId, $course_info, $test, $url) |
||
6222 | { |
||
6223 | $teacher_info = api_get_user_info($senderId); |
||
6224 | $from_name = api_get_person_name( |
||
6225 | $teacher_info['firstname'], |
||
6226 | $teacher_info['lastname'], |
||
6227 | null, |
||
6228 | PERSON_NAME_EMAIL_ADDRESS |
||
6229 | ); |
||
6230 | |||
6231 | $view = new Template('', false, false, false, false, false, false); |
||
6232 | $view->assign('course_title', Security::remove_XSS($course_info['name'])); |
||
6233 | $view->assign('test_title', Security::remove_XSS($test)); |
||
6234 | $view->assign('url', $url); |
||
6235 | $view->assign('teacher_name', $from_name); |
||
6236 | $template = $view->get_template('mail/exercise_result_alert_body.tpl'); |
||
6237 | |||
6238 | return $view->fetch($template); |
||
6239 | } |
||
6240 | |||
6241 | /** |
||
6242 | * @return string |
||
6243 | */ |
||
6244 | public static function getNotCorrectedYetText() |
||
6245 | { |
||
6246 | return Display::return_message(get_lang('notCorrectedYet'), 'warning'); |
||
6247 | } |
||
6248 | |||
6249 | /** |
||
6250 | * @param string $message |
||
6251 | * |
||
6252 | * @return string |
||
6253 | */ |
||
6254 | public static function getFeedbackText($message) |
||
6255 | { |
||
6256 | return Display::return_message($message, 'warning', false); |
||
6257 | } |
||
6258 | |||
6259 | /** |
||
6260 | * Get the recorder audio component for save a teacher audio feedback. |
||
6261 | * |
||
6262 | * @param Template $template |
||
6263 | * @param int $attemptId |
||
6264 | * @param int $questionId |
||
6265 | * @param int $userId |
||
6266 | * |
||
6267 | * @return string |
||
6268 | */ |
||
6269 | public static function getOralFeedbackForm($template, $attemptId, $questionId, $userId) |
||
6270 | { |
||
6271 | $template->assign('user_id', $userId); |
||
6272 | $template->assign('question_id', $questionId); |
||
6273 | $template->assign('directory', "/../exercises/teacher_audio/$attemptId/"); |
||
6274 | $template->assign('file_name', "{$questionId}_{$userId}"); |
||
6275 | |||
6276 | return $template->fetch($template->get_template('exercise/oral_expression.tpl')); |
||
6277 | } |
||
6278 | |||
6279 | /** |
||
6280 | * Get the audio componen for a teacher audio feedback. |
||
6281 | * |
||
6282 | * @param int $attemptId |
||
6283 | * @param int $questionId |
||
6284 | * @param int $userId |
||
6285 | * |
||
6286 | * @return string |
||
6287 | */ |
||
6288 | public static function getOralFeedbackAudio($attemptId, $questionId, $userId) |
||
6289 | { |
||
6290 | $courseInfo = api_get_course_info(); |
||
6291 | $sessionId = api_get_session_id(); |
||
6292 | $groupId = api_get_group_id(); |
||
6293 | $sysCourseDir = api_get_path(SYS_COURSE_PATH).$courseInfo['path']; |
||
6294 | $webCourseDir = api_get_path(WEB_COURSE_PATH).$courseInfo['path']; |
||
6295 | $fileName = "{$questionId}_{$userId}".DocumentManager::getDocumentSuffix($courseInfo, $sessionId, $groupId); |
||
6296 | $filePath = null; |
||
6297 | |||
6298 | $relFilePath = "/exercises/teacher_audio/$attemptId/$fileName"; |
||
6299 | |||
6300 | if (file_exists($sysCourseDir.$relFilePath.'.ogg')) { |
||
6301 | $filePath = $webCourseDir.$relFilePath.'.ogg'; |
||
6302 | } elseif (file_exists($sysCourseDir.$relFilePath.'.wav.wav')) { |
||
6303 | $filePath = $webCourseDir.$relFilePath.'.wav.wav'; |
||
6304 | } elseif (file_exists($sysCourseDir.$relFilePath.'.wav')) { |
||
6305 | $filePath = $webCourseDir.$relFilePath.'.wav'; |
||
6306 | } |
||
6307 | |||
6308 | if (!$filePath) { |
||
6309 | return ''; |
||
6310 | } |
||
6311 | |||
6312 | return Display::tag( |
||
6313 | 'audio', |
||
6314 | null, |
||
6315 | ['src' => $filePath] |
||
6316 | ); |
||
6317 | } |
||
6318 | |||
6319 | /** |
||
6320 | * @return array |
||
6321 | */ |
||
6322 | public static function getNotificationSettings() |
||
6323 | { |
||
6324 | $emailAlerts = [ |
||
6325 | 2 => get_lang('SendEmailToTeacherWhenStudentStartQuiz'), |
||
6326 | 1 => get_lang('SendEmailToTeacherWhenStudentEndQuiz'), // default |
||
6327 | 3 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOpenQuestion'), |
||
6328 | 4 => get_lang('SendEmailToTeacherWhenStudentEndQuizOnlyIfOralQuestion'), |
||
6329 | ]; |
||
6330 | |||
6331 | return $emailAlerts; |
||
6332 | } |
||
6333 | |||
6334 | /** |
||
6335 | * Get the additional actions added in exercise_additional_teacher_modify_actions configuration. |
||
6336 | * |
||
6337 | * @param int $exerciseId |
||
6338 | * @param int $iconSize |
||
6339 | * |
||
6340 | * @return string |
||
6341 | */ |
||
6342 | public static function getAdditionalTeacherActions($exerciseId, $iconSize = ICON_SIZE_SMALL) |
||
6343 | { |
||
6344 | $additionalActions = api_get_configuration_value('exercise_additional_teacher_modify_actions') ?: []; |
||
6345 | $actions = []; |
||
6346 | |||
6347 | foreach ($additionalActions as $additionalAction) { |
||
6348 | $actions[] = call_user_func( |
||
6349 | $additionalAction, |
||
6350 | $exerciseId, |
||
6351 | $iconSize |
||
6352 | ); |
||
6353 | } |
||
6354 | |||
6355 | return implode(PHP_EOL, $actions); |
||
6356 | } |
||
6357 | |||
6358 | /** |
||
6359 | * @param int $userId |
||
6360 | * @param int $courseId |
||
6361 | * @param int $sessionId |
||
6362 | * |
||
6363 | * @throws \Doctrine\ORM\Query\QueryException |
||
6364 | * |
||
6365 | * @return int |
||
6366 | */ |
||
6367 | public static function countAnsweredQuestionsByUserAfterTime(DateTime $time, $userId, $courseId, $sessionId) |
||
6368 | { |
||
6369 | $em = Database::getManager(); |
||
6370 | |||
6371 | $time = api_get_utc_datetime($time->format('Y-m-d H:i:s'), false, true); |
||
6372 | |||
6373 | $result = $em |
||
6374 | ->createQuery(' |
||
6375 | SELECT COUNT(ea) FROM ChamiloCoreBundle:TrackEAttempt ea |
||
6376 | WHERE ea.userId = :user AND ea.cId = :course AND ea.sessionId = :session |
||
6377 | AND ea.tms > :time |
||
6378 | ') |
||
6379 | ->setParameters(['user' => $userId, 'course' => $courseId, 'session' => $sessionId, 'time' => $time]) |
||
6380 | ->getSingleScalarResult(); |
||
6381 | |||
6382 | return $result; |
||
6383 | } |
||
6384 | |||
6385 | /** |
||
6386 | * @param int $userId |
||
6387 | * @param int $numberOfQuestions |
||
6388 | * @param int $courseId |
||
6389 | * @param int $sessionId |
||
6390 | * |
||
6391 | * @throws \Doctrine\ORM\Query\QueryException |
||
6392 | * |
||
6393 | * @return bool |
||
6394 | */ |
||
6395 | public static function isQuestionsLimitPerDayReached($userId, $numberOfQuestions, $courseId, $sessionId) |
||
6396 | { |
||
6397 | $questionsLimitPerDay = (int) api_get_course_setting('quiz_question_limit_per_day'); |
||
6398 | |||
6399 | if ($questionsLimitPerDay <= 0) { |
||
6400 | return false; |
||
6401 | } |
||
6402 | |||
6403 | $midnightTime = ChamiloApi::getServerMidnightTime(); |
||
6404 | |||
6405 | $answeredQuestionsCount = self::countAnsweredQuestionsByUserAfterTime( |
||
6406 | $midnightTime, |
||
6407 | $userId, |
||
6408 | $courseId, |
||
6409 | $sessionId |
||
6410 | ); |
||
6411 | |||
6412 | return ($answeredQuestionsCount + $numberOfQuestions) > $questionsLimitPerDay; |
||
6413 | } |
||
6414 | |||
6415 | /** |
||
6416 | * By default, allowed types are unique-answer (and image) or multiple-answer questions. |
||
6417 | * Types can be extended by the configuration setting "exercise_embeddable_extra_types". |
||
6418 | */ |
||
6419 | public static function getEmbeddableTypes(): array |
||
6420 | { |
||
6421 | $allowedTypes = [ |
||
6422 | UNIQUE_ANSWER, |
||
6423 | MULTIPLE_ANSWER, |
||
6424 | FILL_IN_BLANKS, |
||
6425 | MATCHING, |
||
6426 | FREE_ANSWER, |
||
6427 | MULTIPLE_ANSWER_COMBINATION, |
||
6428 | UNIQUE_ANSWER_NO_OPTION, |
||
6429 | MULTIPLE_ANSWER_TRUE_FALSE, |
||
6430 | MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE, |
||
6431 | ORAL_EXPRESSION, |
||
6432 | GLOBAL_MULTIPLE_ANSWER, |
||
6433 | CALCULATED_ANSWER, |
||
6434 | UNIQUE_ANSWER_IMAGE, |
||
6435 | READING_COMPREHENSION, |
||
6436 | MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY, |
||
6437 | UPLOAD_ANSWER, |
||
6438 | ANSWER_IN_OFFICE_DOC, |
||
6439 | MATCHING_COMBINATION, |
||
6440 | FILL_IN_BLANKS_COMBINATION, |
||
6441 | MULTIPLE_ANSWER_DROPDOWN, |
||
6442 | MULTIPLE_ANSWER_DROPDOWN_COMBINATION, |
||
6443 | ]; |
||
6444 | $defaultTypes = [UNIQUE_ANSWER, MULTIPLE_ANSWER, UNIQUE_ANSWER_IMAGE]; |
||
6445 | $types = $defaultTypes; |
||
6446 | |||
6447 | $extraTypes = api_get_configuration_value('exercise_embeddable_extra_types'); |
||
6448 | |||
6449 | if (false !== $extraTypes && !empty($extraTypes['types'])) { |
||
6450 | $types = array_merge($defaultTypes, $extraTypes['types']); |
||
6451 | } |
||
6452 | |||
6453 | return array_filter( |
||
6454 | array_unique($types), |
||
6455 | function ($type) use ($allowedTypes) { |
||
6456 | return in_array($type, $allowedTypes); |
||
6457 | } |
||
6458 | ); |
||
6459 | } |
||
6460 | |||
6461 | /** |
||
6462 | * Check if an exercise complies with the requirements to be embedded in the mobile app or a video. |
||
6463 | * By making sure it is set on one question per page, and that the exam does not have immediate feedback, |
||
6464 | * and it only contains allowed types. |
||
6465 | * |
||
6466 | * @see Exercise::getEmbeddableTypes() |
||
6467 | */ |
||
6468 | public static function isQuizEmbeddable(array $exercise): bool |
||
6469 | { |
||
6470 | $exercise['iid'] = isset($exercise['iid']) ? (int) $exercise['iid'] : 0; |
||
6471 | |||
6472 | if (ONE_PER_PAGE != $exercise['type'] || |
||
6473 | in_array($exercise['feedback_type'], [EXERCISE_FEEDBACK_TYPE_DIRECT, EXERCISE_FEEDBACK_TYPE_POPUP]) |
||
6474 | ) { |
||
6475 | return false; |
||
6476 | } |
||
6477 | |||
6478 | $questionRepository = Database::getManager()->getRepository(CQuizQuestion::class); |
||
6479 | |||
6480 | $countAll = $questionRepository->countQuestionsInExercise($exercise['iid']); |
||
6481 | $countAllowed = $questionRepository->countEmbeddableQuestionsInExercise($exercise['iid']); |
||
6482 | |||
6483 | return $countAll === $countAllowed; |
||
6484 | } |
||
6485 | |||
6486 | /** |
||
6487 | * Generate a certificate linked to current quiz and. |
||
6488 | * Return the HTML block with links to download and view the certificate. |
||
6489 | * |
||
6490 | * @param float $totalScore |
||
6491 | * @param float $totalWeight |
||
6492 | * @param int $studentId |
||
6493 | * @param string $courseCode |
||
6494 | * @param int $sessionId |
||
6495 | * |
||
6496 | * @return string |
||
6497 | */ |
||
6498 | public static function generateAndShowCertificateBlock( |
||
6499 | $totalScore, |
||
6500 | $totalWeight, |
||
6501 | Exercise $objExercise, |
||
6502 | $studentId, |
||
6503 | $courseCode, |
||
6504 | $sessionId = 0 |
||
6505 | ) { |
||
6506 | if (!api_get_configuration_value('quiz_generate_certificate_ending') || |
||
6507 | !self::isSuccessExerciseResult($totalScore, $totalWeight, $objExercise->selectPassPercentage()) |
||
6508 | ) { |
||
6509 | return ''; |
||
6510 | } |
||
6511 | |||
6512 | /** @var Category $category */ |
||
6513 | $category = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id'); |
||
6514 | |||
6515 | if (empty($category)) { |
||
6516 | return ''; |
||
6517 | } |
||
6518 | |||
6519 | /** @var Category $category */ |
||
6520 | $category = $category[0]; |
||
6521 | $categoryId = $category->get_id(); |
||
6522 | $link = LinkFactory::load( |
||
6523 | null, |
||
6524 | null, |
||
6525 | $objExercise->selectId(), |
||
6526 | null, |
||
6527 | $courseCode, |
||
6528 | $categoryId |
||
6529 | ); |
||
6530 | |||
6531 | if (empty($link)) { |
||
6532 | return ''; |
||
6533 | } |
||
6534 | |||
6535 | $resourceDeletedMessage = $category->show_message_resource_delete($courseCode); |
||
6536 | |||
6537 | if (false !== $resourceDeletedMessage || api_is_allowed_to_edit() || api_is_excluded_user_type()) { |
||
6538 | return ''; |
||
6539 | } |
||
6540 | |||
6541 | $certificate = Category::generateUserCertificate($categoryId, $studentId); |
||
6542 | |||
6543 | if (!is_array($certificate)) { |
||
6544 | return ''; |
||
6545 | } |
||
6546 | |||
6547 | return Category::getDownloadCertificateBlock($certificate); |
||
6548 | } |
||
6549 | |||
6550 | /** |
||
6551 | * @param int $exerciseId |
||
6552 | */ |
||
6553 | public static function getExerciseTitleById($exerciseId) |
||
6554 | { |
||
6555 | $em = Database::getManager(); |
||
6556 | |||
6557 | return $em |
||
6558 | ->createQuery('SELECT cq.title |
||
6559 | FROM ChamiloCourseBundle:CQuiz cq |
||
6560 | WHERE cq.iid = :iid' |
||
6561 | ) |
||
6562 | ->setParameter('iid', $exerciseId) |
||
6563 | ->getSingleScalarResult(); |
||
6564 | } |
||
6565 | |||
6566 | /** |
||
6567 | * @param int $exeId ID from track_e_exercises |
||
6568 | * @param int $userId User ID |
||
6569 | * @param int $exerciseId Exercise ID |
||
6570 | * @param int $courseId Optional. Coure ID. |
||
6571 | * |
||
6572 | * @return TrackEExercises|null |
||
6573 | */ |
||
6574 | public static function recalculateResult($exeId, $userId, $exerciseId, $courseId = 0) |
||
6575 | { |
||
6576 | if (empty($userId) || empty($exerciseId)) { |
||
6577 | return null; |
||
6578 | } |
||
6579 | |||
6580 | $em = Database::getManager(); |
||
6581 | /** @var TrackEExercises $trackedExercise */ |
||
6582 | $trackedExercise = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId); |
||
6583 | |||
6584 | if (empty($trackedExercise)) { |
||
6585 | return null; |
||
6586 | } |
||
6587 | |||
6588 | if ($trackedExercise->getExeUserId() != $userId || |
||
6589 | $trackedExercise->getExeExoId() != $exerciseId |
||
6590 | ) { |
||
6591 | return null; |
||
6592 | } |
||
6593 | |||
6594 | $questionList = $trackedExercise->getDataTracking(); |
||
6595 | |||
6596 | if (empty($questionList)) { |
||
6597 | return null; |
||
6598 | } |
||
6599 | |||
6600 | $questionList = explode(',', $questionList); |
||
6601 | |||
6602 | $exercise = new Exercise($courseId); |
||
6603 | $courseInfo = $courseId ? api_get_course_info_by_id($courseId) : []; |
||
6604 | |||
6605 | if ($exercise->read($exerciseId) === false) { |
||
6606 | return null; |
||
6607 | } |
||
6608 | |||
6609 | $totalScore = 0; |
||
6610 | $totalWeight = 0; |
||
6611 | |||
6612 | $pluginEvaluation = QuestionOptionsEvaluationPlugin::create(); |
||
6613 | |||
6614 | $formula = 'true' === $pluginEvaluation->get(QuestionOptionsEvaluationPlugin::SETTING_ENABLE) |
||
6615 | ? $pluginEvaluation->getFormulaForExercise($exerciseId) |
||
6616 | : 0; |
||
6617 | |||
6618 | if (empty($formula)) { |
||
6619 | foreach ($questionList as $questionId) { |
||
6620 | $question = Question::read($questionId, $courseInfo); |
||
6621 | |||
6622 | if (false === $question) { |
||
6623 | continue; |
||
6624 | } |
||
6625 | |||
6626 | $totalWeight += $question->selectWeighting(); |
||
6627 | |||
6628 | // We're inside *one* question. Go through each possible answer for this question |
||
6629 | $result = $exercise->manage_answer( |
||
6630 | $exeId, |
||
6631 | $questionId, |
||
6632 | [], |
||
6633 | 'exercise_result', |
||
6634 | [], |
||
6635 | false, |
||
6636 | true, |
||
6637 | false, |
||
6638 | $exercise->selectPropagateNeg(), |
||
6639 | [], |
||
6640 | [], |
||
6641 | true |
||
6642 | ); |
||
6643 | |||
6644 | // Adding the new score. |
||
6645 | $totalScore += $result['score']; |
||
6646 | } |
||
6647 | } else { |
||
6648 | $totalScore = $pluginEvaluation->getResultWithFormula($exeId, $formula); |
||
6649 | $totalWeight = $pluginEvaluation->getMaxScore(); |
||
6650 | } |
||
6651 | |||
6652 | $trackedExercise |
||
6653 | ->setExeResult($totalScore) |
||
6654 | ->setExeWeighting($totalWeight); |
||
6655 | |||
6656 | $em->persist($trackedExercise); |
||
6657 | $em->flush(); |
||
6658 | |||
6659 | return $trackedExercise; |
||
6660 | } |
||
6661 | |||
6662 | public static function getTotalQuestionAnswered($courseId, $exerciseId, $questionId, $sessionId = 0, $groups = [], $users = []) |
||
6663 | { |
||
6664 | $courseId = (int) $courseId; |
||
6665 | $exerciseId = (int) $exerciseId; |
||
6666 | $questionId = (int) $questionId; |
||
6667 | $sessionId = (int) $sessionId; |
||
6668 | |||
6669 | $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
6670 | $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
6671 | |||
6672 | $userCondition = ''; |
||
6673 | $allUsers = []; |
||
6674 | if (!empty($groups)) { |
||
6675 | foreach ($groups as $groupId) { |
||
6676 | $groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId); |
||
6677 | if (!empty($groupUsers)) { |
||
6678 | $allUsers = array_merge($allUsers, $groupUsers); |
||
6679 | } |
||
6680 | } |
||
6681 | } |
||
6682 | |||
6683 | if (!empty($users)) { |
||
6684 | $allUsers = array_merge($allUsers, $users); |
||
6685 | } |
||
6686 | |||
6687 | if (!empty($allUsers)) { |
||
6688 | $allUsers = array_map('intval', $allUsers); |
||
6689 | $usersToString = implode("', '", $allUsers); |
||
6690 | $userCondition = " AND user_id IN ('$usersToString') "; |
||
6691 | } |
||
6692 | |||
6693 | $sessionCondition = ''; |
||
6694 | if (!empty($sessionId)) { |
||
6695 | $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id'); |
||
6696 | } |
||
6697 | |||
6698 | $sql = "SELECT count(te.exe_id) total |
||
6699 | FROM $attemptTable t |
||
6700 | INNER JOIN $trackTable te |
||
6701 | ON (te.c_id = t.c_id AND t.exe_id = te.exe_id) |
||
6702 | WHERE |
||
6703 | t.c_id = $courseId AND |
||
6704 | exe_exo_id = $exerciseId AND |
||
6705 | t.question_id = $questionId AND |
||
6706 | status != 'incomplete' |
||
6707 | $sessionCondition |
||
6708 | $userCondition |
||
6709 | "; |
||
6710 | $queryTotal = Database::query($sql); |
||
6711 | $totalRow = Database::fetch_array($queryTotal, 'ASSOC'); |
||
6712 | $total = 0; |
||
6713 | if ($totalRow) { |
||
6714 | $total = (int) $totalRow['total']; |
||
6715 | } |
||
6716 | |||
6717 | return $total; |
||
6718 | } |
||
6719 | |||
6720 | public static function getWrongQuestionResults($courseId, $exerciseId, $sessionId = 0, $groups = [], $users = []) |
||
6721 | { |
||
6722 | $courseId = (int) $courseId; |
||
6723 | $exerciseId = (int) $exerciseId; |
||
6724 | |||
6725 | $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
6726 | $attemptTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
6727 | $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
6728 | |||
6729 | $sessionCondition = ''; |
||
6730 | if (!empty($sessionId)) { |
||
6731 | $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id'); |
||
6732 | } |
||
6733 | |||
6734 | $userCondition = ''; |
||
6735 | $allUsers = []; |
||
6736 | if (!empty($groups)) { |
||
6737 | foreach ($groups as $groupId) { |
||
6738 | $groupUsers = GroupManager::get_users($groupId, null, null, null, false, $courseId); |
||
6739 | if (!empty($groupUsers)) { |
||
6740 | $allUsers = array_merge($allUsers, $groupUsers); |
||
6741 | } |
||
6742 | } |
||
6743 | } |
||
6744 | |||
6745 | if (!empty($users)) { |
||
6746 | $allUsers = array_merge($allUsers, $users); |
||
6747 | } |
||
6748 | |||
6749 | if (!empty($allUsers)) { |
||
6750 | $allUsers = array_map('intval', $allUsers); |
||
6751 | $usersToString = implode("', '", $allUsers); |
||
6752 | $userCondition .= " AND user_id IN ('$usersToString') "; |
||
6753 | } |
||
6754 | |||
6755 | $sql = "SELECT q.question, question_id, count(q.iid) count |
||
6756 | FROM $attemptTable t |
||
6757 | INNER JOIN $questionTable q |
||
6758 | ON q.iid = t.question_id |
||
6759 | INNER JOIN $trackTable te |
||
6760 | ON t.exe_id = te.exe_id |
||
6761 | WHERE |
||
6762 | t.c_id = $courseId AND |
||
6763 | t.marks != q.ponderation AND |
||
6764 | exe_exo_id = $exerciseId AND |
||
6765 | status != 'incomplete' |
||
6766 | $sessionCondition |
||
6767 | $userCondition |
||
6768 | GROUP BY q.iid |
||
6769 | ORDER BY count DESC |
||
6770 | "; |
||
6771 | |||
6772 | $result = Database::query($sql); |
||
6773 | |||
6774 | return Database::store_result($result, 'ASSOC'); |
||
6775 | } |
||
6776 | |||
6777 | public static function getExerciseResultsCount($type, $courseId, Exercise $exercise, $sessionId = 0) |
||
6778 | { |
||
6779 | $courseId = (int) $courseId; |
||
6780 | $exerciseId = (int) $exercise->iid; |
||
6781 | |||
6782 | $trackTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
6783 | |||
6784 | $sessionCondition = ''; |
||
6785 | if (!empty($sessionId)) { |
||
6786 | $sessionCondition = api_get_session_condition($sessionId, true, false, 'te.session_id'); |
||
6787 | } |
||
6788 | |||
6789 | $passPercentage = $exercise->selectPassPercentage(); |
||
6790 | $minPercentage = 100; |
||
6791 | if (!empty($passPercentage)) { |
||
6792 | $minPercentage = $passPercentage; |
||
6793 | } |
||
6794 | |||
6795 | $selectCount = 'count(DISTINCT te.exe_id)'; |
||
6796 | $scoreCondition = ''; |
||
6797 | switch ($type) { |
||
6798 | case 'correct_student': |
||
6799 | $selectCount = 'count(DISTINCT te.exe_user_id)'; |
||
6800 | $scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage "; |
||
6801 | break; |
||
6802 | case 'wrong_student': |
||
6803 | $selectCount = 'count(DISTINCT te.exe_user_id)'; |
||
6804 | $scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage "; |
||
6805 | break; |
||
6806 | case 'correct': |
||
6807 | $scoreCondition = " AND (exe_result/exe_weighting*100) >= $minPercentage "; |
||
6808 | break; |
||
6809 | case 'wrong': |
||
6810 | $scoreCondition = " AND (exe_result/exe_weighting*100) < $minPercentage "; |
||
6811 | break; |
||
6812 | } |
||
6813 | |||
6814 | $sql = "SELECT $selectCount count |
||
6815 | FROM $trackTable te |
||
6816 | WHERE |
||
6817 | c_id = $courseId AND |
||
6818 | exe_exo_id = $exerciseId AND |
||
6819 | status != 'incomplete' |
||
6820 | $scoreCondition |
||
6821 | $sessionCondition |
||
6822 | "; |
||
6823 | $result = Database::query($sql); |
||
6824 | $totalRow = Database::fetch_array($result, 'ASSOC'); |
||
6825 | $total = 0; |
||
6826 | if ($totalRow) { |
||
6827 | $total = (int) $totalRow['count']; |
||
6828 | } |
||
6829 | |||
6830 | return $total; |
||
6831 | } |
||
6832 | |||
6833 | public static function parseContent($content, $stats, Exercise $exercise, $trackInfo, $currentUserId = 0) |
||
6834 | { |
||
6835 | $wrongAnswersCount = $stats['failed_answers_count']; |
||
6836 | $attemptDate = substr($trackInfo['exe_date'], 0, 10); |
||
6837 | $exeId = $trackInfo['exe_id']; |
||
6838 | $resultsStudentUrl = api_get_path(WEB_CODE_PATH). |
||
6839 | 'exercise/result.php?id='.$exeId.'&'.api_get_cidreq(); |
||
6840 | $resultsTeacherUrl = api_get_path(WEB_CODE_PATH). |
||
6841 | 'exercise/exercise_show.php?action=edit&id='.$exeId.'&'.api_get_cidreq(true, true, 'teacher'); |
||
6842 | |||
6843 | $content = str_replace( |
||
6844 | [ |
||
6845 | '((exercise_error_count))', |
||
6846 | '((all_answers_html))', |
||
6847 | '((all_answers_teacher_html))', |
||
6848 | '((exercise_title))', |
||
6849 | '((exercise_attempt_date))', |
||
6850 | '((link_to_test_result_page_student))', |
||
6851 | '((link_to_test_result_page_teacher))', |
||
6852 | ], |
||
6853 | [ |
||
6854 | $wrongAnswersCount, |
||
6855 | $stats['all_answers_html'], |
||
6856 | $stats['all_answers_teacher_html'], |
||
6857 | $exercise->get_formated_title(), |
||
6858 | $attemptDate, |
||
6859 | $resultsStudentUrl, |
||
6860 | $resultsTeacherUrl, |
||
6861 | ], |
||
6862 | $content |
||
6863 | ); |
||
6864 | |||
6865 | $currentUserId = empty($currentUserId) ? api_get_user_id() : (int) $currentUserId; |
||
6866 | |||
6867 | $content = AnnouncementManager::parseContent( |
||
6868 | $currentUserId, |
||
6869 | $content, |
||
6870 | api_get_course_id(), |
||
6871 | api_get_session_id() |
||
6872 | ); |
||
6873 | |||
6874 | return $content; |
||
6875 | } |
||
6876 | |||
6877 | public static function sendNotification( |
||
6878 | $currentUserId, |
||
6879 | $objExercise, |
||
6880 | $exercise_stat_info, |
||
6881 | $courseInfo, |
||
6882 | $attemptCountToSend, |
||
6883 | $stats, |
||
6884 | $statsTeacher |
||
6885 | ) { |
||
6886 | $notifications = api_get_configuration_value('exercise_finished_notification_settings'); |
||
6887 | if (empty($notifications)) { |
||
6888 | return false; |
||
6889 | } |
||
6890 | |||
6891 | $studentId = $exercise_stat_info['exe_user_id']; |
||
6892 | $exerciseExtraFieldValue = new ExtraFieldValue('exercise'); |
||
6893 | $wrongAnswersCount = $stats['failed_answers_count']; |
||
6894 | $exercisePassed = $stats['exercise_passed']; |
||
6895 | $countPendingQuestions = $stats['count_pending_questions']; |
||
6896 | $stats['all_answers_teacher_html'] = $statsTeacher['all_answers_html']; |
||
6897 | |||
6898 | // If there are no pending questions (Open questions). |
||
6899 | if (0 === $countPendingQuestions) { |
||
6900 | /*$extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6901 | $objExercise->iid, |
||
6902 | 'signature_mandatory' |
||
6903 | ); |
||
6904 | |||
6905 | if ($extraFieldData && isset($extraFieldData['value']) && 1 === (int) $extraFieldData['value']) { |
||
6906 | if (ExerciseSignaturePlugin::exerciseHasSignatureActivated($objExercise)) { |
||
6907 | $signature = ExerciseSignaturePlugin::getSignature($studentId, $exercise_stat_info); |
||
6908 | if (false !== $signature) { |
||
6909 | //return false; |
||
6910 | } |
||
6911 | } |
||
6912 | }*/ |
||
6913 | |||
6914 | // Notifications. |
||
6915 | $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6916 | $objExercise->iid, |
||
6917 | 'notifications' |
||
6918 | ); |
||
6919 | $exerciseNotification = ''; |
||
6920 | if ($extraFieldData && isset($extraFieldData['value'])) { |
||
6921 | $exerciseNotification = $extraFieldData['value']; |
||
6922 | } |
||
6923 | |||
6924 | $subject = sprintf(get_lang('WrongAttemptXInCourseX'), $attemptCountToSend, $courseInfo['title']); |
||
6925 | if ($exercisePassed) { |
||
6926 | $subject = sprintf(get_lang('ExerciseValidationInCourseX'), $courseInfo['title']); |
||
6927 | } |
||
6928 | |||
6929 | if ($exercisePassed) { |
||
6930 | $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6931 | $objExercise->iid, |
||
6932 | 'MailSuccess' |
||
6933 | ); |
||
6934 | } else { |
||
6935 | $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6936 | $objExercise->iid, |
||
6937 | 'MailAttempt'.$attemptCountToSend |
||
6938 | ); |
||
6939 | } |
||
6940 | |||
6941 | // Blocking exercise. |
||
6942 | $blockPercentageExtra = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6943 | $objExercise->iid, |
||
6944 | 'blocking_percentage' |
||
6945 | ); |
||
6946 | $blockPercentage = false; |
||
6947 | if ($blockPercentageExtra && isset($blockPercentageExtra['value']) && $blockPercentageExtra['value']) { |
||
6948 | $blockPercentage = $blockPercentageExtra['value']; |
||
6949 | } |
||
6950 | if ($blockPercentage) { |
||
6951 | $passBlock = $stats['total_percentage'] > $blockPercentage; |
||
6952 | if (false === $passBlock) { |
||
6953 | $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
6954 | $objExercise->iid, |
||
6955 | 'MailIsBlockByPercentage' |
||
6956 | ); |
||
6957 | } |
||
6958 | } |
||
6959 | |||
6960 | $extraFieldValueUser = new ExtraFieldValue('user'); |
||
6961 | |||
6962 | if ($extraFieldData && isset($extraFieldData['value'])) { |
||
6963 | $content = $extraFieldData['value']; |
||
6964 | $content = self::parseContent($content, $stats, $objExercise, $exercise_stat_info, $studentId); |
||
6965 | //if (false === $exercisePassed) { |
||
6966 | if (0 !== $wrongAnswersCount) { |
||
6967 | $content .= $stats['failed_answers_html']; |
||
6968 | } |
||
6969 | |||
6970 | $sendMessage = true; |
||
6971 | if (!empty($exerciseNotification)) { |
||
6972 | foreach ($notifications as $name => $notificationList) { |
||
6973 | if ($exerciseNotification !== $name) { |
||
6974 | continue; |
||
6975 | } |
||
6976 | foreach ($notificationList as $notificationName => $attemptData) { |
||
6977 | if ('student_check' === $notificationName) { |
||
6978 | $sendMsgIfInList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : ''; |
||
6979 | if (!empty($sendMsgIfInList)) { |
||
6980 | foreach ($sendMsgIfInList as $skipVariable => $skipValues) { |
||
6981 | $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable( |
||
6982 | $studentId, |
||
6983 | $skipVariable |
||
6984 | ); |
||
6985 | |||
6986 | if (empty($userExtraFieldValue)) { |
||
6987 | $sendMessage = false; |
||
6988 | break; |
||
6989 | } else { |
||
6990 | $sendMessage = false; |
||
6991 | if (isset($userExtraFieldValue['value']) && |
||
6992 | in_array($userExtraFieldValue['value'], $skipValues) |
||
6993 | ) { |
||
6994 | $sendMessage = true; |
||
6995 | break; |
||
6996 | } |
||
6997 | } |
||
6998 | } |
||
6999 | } |
||
7000 | break; |
||
7001 | } |
||
7002 | } |
||
7003 | } |
||
7004 | } |
||
7005 | |||
7006 | // Send to student. |
||
7007 | if ($sendMessage) { |
||
7008 | MessageManager::send_message($currentUserId, $subject, $content); |
||
7009 | } |
||
7010 | } |
||
7011 | |||
7012 | if (!empty($exerciseNotification)) { |
||
7013 | foreach ($notifications as $name => $notificationList) { |
||
7014 | if ($exerciseNotification !== $name) { |
||
7015 | continue; |
||
7016 | } |
||
7017 | foreach ($notificationList as $attemptData) { |
||
7018 | $skipNotification = false; |
||
7019 | $skipNotificationList = isset($attemptData['send_notification_if_user_in_extra_field']) ? $attemptData['send_notification_if_user_in_extra_field'] : []; |
||
7020 | if (!empty($skipNotificationList)) { |
||
7021 | foreach ($skipNotificationList as $skipVariable => $skipValues) { |
||
7022 | $userExtraFieldValue = $extraFieldValueUser->get_values_by_handler_and_field_variable( |
||
7023 | $studentId, |
||
7024 | $skipVariable |
||
7025 | ); |
||
7026 | |||
7027 | if (empty($userExtraFieldValue)) { |
||
7028 | $skipNotification = true; |
||
7029 | break; |
||
7030 | } else { |
||
7031 | if (isset($userExtraFieldValue['value'])) { |
||
7032 | if (!in_array($userExtraFieldValue['value'], $skipValues)) { |
||
7033 | $skipNotification = true; |
||
7034 | break; |
||
7035 | } |
||
7036 | } else { |
||
7037 | $skipNotification = true; |
||
7038 | break; |
||
7039 | } |
||
7040 | } |
||
7041 | } |
||
7042 | } |
||
7043 | |||
7044 | if ($skipNotification) { |
||
7045 | continue; |
||
7046 | } |
||
7047 | |||
7048 | $email = isset($attemptData['email']) ? $attemptData['email'] : ''; |
||
7049 | $emailList = explode(',', $email); |
||
7050 | if (empty($emailList)) { |
||
7051 | continue; |
||
7052 | } |
||
7053 | $attempts = isset($attemptData['attempts']) ? $attemptData['attempts'] : []; |
||
7054 | foreach ($attempts as $attempt) { |
||
7055 | $sendMessage = false; |
||
7056 | if (isset($attempt['attempt']) && $attemptCountToSend !== (int) $attempt['attempt']) { |
||
7057 | continue; |
||
7058 | } |
||
7059 | |||
7060 | if (!isset($attempt['status'])) { |
||
7061 | continue; |
||
7062 | } |
||
7063 | |||
7064 | if ($blockPercentage && isset($attempt['is_block_by_percentage'])) { |
||
7065 | if ($attempt['is_block_by_percentage']) { |
||
7066 | if ($passBlock) { |
||
7067 | continue; |
||
7068 | } |
||
7069 | } else { |
||
7070 | if (false === $passBlock) { |
||
7071 | continue; |
||
7072 | } |
||
7073 | } |
||
7074 | } |
||
7075 | |||
7076 | switch ($attempt['status']) { |
||
7077 | case 'passed': |
||
7078 | if ($exercisePassed) { |
||
7079 | $sendMessage = true; |
||
7080 | } |
||
7081 | break; |
||
7082 | case 'failed': |
||
7083 | if (false === $exercisePassed) { |
||
7084 | $sendMessage = true; |
||
7085 | } |
||
7086 | break; |
||
7087 | case 'all': |
||
7088 | $sendMessage = true; |
||
7089 | break; |
||
7090 | } |
||
7091 | |||
7092 | if ($sendMessage) { |
||
7093 | $attachments = []; |
||
7094 | if (isset($attempt['add_pdf']) && $attempt['add_pdf']) { |
||
7095 | // Get pdf content |
||
7096 | $pdfExtraData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
7097 | $objExercise->iid, |
||
7098 | $attempt['add_pdf'] |
||
7099 | ); |
||
7100 | |||
7101 | if ($pdfExtraData && isset($pdfExtraData['value'])) { |
||
7102 | $pdfContent = self::parseContent( |
||
7103 | $pdfExtraData['value'], |
||
7104 | $stats, |
||
7105 | $objExercise, |
||
7106 | $exercise_stat_info, |
||
7107 | $studentId |
||
7108 | ); |
||
7109 | |||
7110 | @$pdf = new PDF(); |
||
7111 | $filename = get_lang('Exercise'); |
||
7112 | $cssFile = api_get_path(SYS_CSS_PATH).'themes/chamilo/default.css'; |
||
7113 | $pdfPath = @$pdf->content_to_pdf( |
||
7114 | "<html><body>$pdfContent</body></html>", |
||
7115 | file_get_contents($cssFile), |
||
7116 | $filename, |
||
7117 | api_get_course_id(), |
||
7118 | 'F', |
||
7119 | false, |
||
7120 | null, |
||
7121 | false, |
||
7122 | true |
||
7123 | ); |
||
7124 | $attachments[] = ['filename' => $filename, 'path' => $pdfPath]; |
||
7125 | } |
||
7126 | } |
||
7127 | |||
7128 | $content = isset($attempt['content_default']) ? $attempt['content_default'] : ''; |
||
7129 | if (isset($attempt['content'])) { |
||
7130 | $extraFieldData = $exerciseExtraFieldValue->get_values_by_handler_and_field_variable( |
||
7131 | $objExercise->iid, |
||
7132 | $attempt['content'] |
||
7133 | ); |
||
7134 | if ($extraFieldData && isset($extraFieldData['value']) && !empty($extraFieldData['value'])) { |
||
7135 | $content = $extraFieldData['value']; |
||
7136 | } |
||
7137 | } |
||
7138 | |||
7139 | if (!empty($content)) { |
||
7140 | $content = self::parseContent( |
||
7141 | $content, |
||
7142 | $stats, |
||
7143 | $objExercise, |
||
7144 | $exercise_stat_info, |
||
7145 | $studentId |
||
7146 | ); |
||
7147 | $extraParameters = []; |
||
7148 | if (api_get_configuration_value('mail_header_from_custom_course_logo') == true) { |
||
7149 | $extraParameters = ['logo' => CourseManager::getCourseEmailPicture($courseInfo)]; |
||
7150 | } |
||
7151 | foreach ($emailList as $email) { |
||
7152 | if (empty($email)) { |
||
7153 | continue; |
||
7154 | } |
||
7155 | |||
7156 | api_mail_html( |
||
7157 | null, |
||
7158 | $email, |
||
7159 | $subject, |
||
7160 | $content, |
||
7161 | null, |
||
7162 | null, |
||
7163 | [], |
||
7164 | $attachments, |
||
7165 | false, |
||
7166 | $extraParameters, |
||
7167 | '' |
||
7168 | ); |
||
7169 | } |
||
7170 | } |
||
7171 | |||
7172 | if (isset($attempt['post_actions'])) { |
||
7173 | foreach ($attempt['post_actions'] as $action => $params) { |
||
7174 | switch ($action) { |
||
7175 | case 'subscribe_student_to_courses': |
||
7176 | foreach ($params as $code) { |
||
7177 | CourseManager::subscribeUser($currentUserId, $code); |
||
7178 | break; |
||
7179 | } |
||
7180 | break; |
||
7181 | } |
||
7182 | } |
||
7183 | } |
||
7184 | } |
||
7185 | } |
||
7186 | } |
||
7187 | } |
||
7188 | } |
||
7189 | } |
||
7190 | } |
||
7191 | |||
7192 | /** |
||
7193 | * Delete an exercise attempt. |
||
7194 | * |
||
7195 | * Log the exe_id deleted with the exe_user_id related. |
||
7196 | * |
||
7197 | * @param int $exeId |
||
7198 | */ |
||
7199 | public static function deleteExerciseAttempt($exeId) |
||
7200 | { |
||
7201 | $exeId = (int) $exeId; |
||
7202 | |||
7203 | $trackExerciseInfo = self::get_exercise_track_exercise_info($exeId); |
||
7204 | |||
7205 | if (empty($trackExerciseInfo)) { |
||
7206 | return; |
||
7207 | } |
||
7208 | |||
7209 | $tblTrackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||
7210 | $tblTrackAttempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
7211 | |||
7212 | Database::query("DELETE FROM $tblTrackExercises WHERE exe_id = $exeId"); |
||
7213 | Database::query("DELETE FROM $tblTrackAttempt WHERE exe_id = $exeId"); |
||
7214 | |||
7215 | Event::addEvent( |
||
7216 | LOG_EXERCISE_ATTEMPT_DELETE, |
||
7217 | LOG_EXERCISE_ATTEMPT, |
||
7218 | $exeId, |
||
7219 | api_get_utc_datetime() |
||
7220 | ); |
||
7221 | Event::addEvent( |
||
7222 | LOG_EXERCISE_ATTEMPT_DELETE, |
||
7223 | LOG_EXERCISE_AND_USER_ID, |
||
7224 | $exeId.'-'.$trackExerciseInfo['exe_user_id'], |
||
7225 | api_get_utc_datetime() |
||
7226 | ); |
||
7227 | } |
||
7228 | |||
7229 | public static function scorePassed($score, $total) |
||
7230 | { |
||
7231 | $compareResult = bccomp($score, $total, 3); |
||
7232 | $scorePassed = 1 === $compareResult || 0 === $compareResult; |
||
7233 | if (false === $scorePassed) { |
||
7234 | $epsilon = 0.00001; |
||
7235 | if (abs($score - $total) < $epsilon) { |
||
7236 | $scorePassed = true; |
||
7237 | } |
||
7238 | } |
||
7239 | |||
7240 | return $scorePassed; |
||
7241 | } |
||
7242 | |||
7243 | public static function logPingForCheckingConnection() |
||
7244 | { |
||
7245 | $action = $_REQUEST['a'] ?? ''; |
||
7246 | |||
7247 | if ('ping' !== $action) { |
||
7248 | return; |
||
7249 | } |
||
7250 | |||
7251 | if (!empty(api_get_user_id())) { |
||
7252 | return; |
||
7253 | } |
||
7254 | |||
7255 | $exeId = $_REQUEST['exe_id'] ?? 0; |
||
7256 | |||
7257 | error_log("Exercise ping received: exe_id = $exeId. _user not found in session."); |
||
7258 | } |
||
7259 | |||
7260 | public static function saveFileExerciseResultPdf( |
||
7261 | int $exeId, |
||
7262 | int $courseId, |
||
7263 | int $sessionId |
||
7264 | ) { |
||
7265 | $courseInfo = api_get_course_info_by_id($courseId); |
||
7266 | $courseCode = $courseInfo['code']; |
||
7267 | $cidReq = 'cidReq='.$courseCode.'&id_session='.$sessionId.'&gidReq=0&gradebook=0'; |
||
7268 | |||
7269 | $url = api_get_path(WEB_PATH).'main/exercise/exercise_show.php?'.$cidReq.'&id='.$exeId.'&action=export&export_type=all_results'; |
||
7270 | $ch = curl_init(); |
||
7271 | curl_setopt($ch, CURLOPT_URL, $url); |
||
7272 | curl_setopt($ch, CURLOPT_COOKIE, session_id()); |
||
7273 | curl_setopt($ch, CURLOPT_AUTOREFERER, true); |
||
7274 | curl_setopt($ch, CURLOPT_COOKIESESSION, true); |
||
7275 | curl_setopt($ch, CURLOPT_FAILONERROR, false); |
||
7276 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); |
||
7277 | curl_setopt($ch, CURLOPT_HEADER, true); |
||
7278 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); |
||
7279 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); |
||
7280 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||
7281 | |||
7282 | $result = curl_exec($ch); |
||
7283 | |||
7284 | if (false === $result) { |
||
7285 | error_log('saveFileExerciseResultPdf error: '.curl_error($ch)); |
||
7286 | } |
||
7287 | |||
7288 | curl_close($ch); |
||
7289 | } |
||
7290 | |||
7291 | /** |
||
7292 | * Export all results of all exercises to a ZIP file (including one zip for each exercise). |
||
7293 | * |
||
7294 | * @return false|void |
||
7295 | */ |
||
7296 | public static function exportAllExercisesResultsZip( |
||
7297 | int $sessionId, |
||
7298 | int $courseId, |
||
7299 | array $filterDates = [] |
||
7300 | ) { |
||
7301 | $exercises = self::get_all_exercises_for_course_id( |
||
7302 | null, |
||
7303 | $sessionId, |
||
7304 | $courseId, |
||
7305 | false |
||
7306 | ); |
||
7307 | |||
7308 | $exportOk = false; |
||
7309 | if (!empty($exercises)) { |
||
7310 | $exportName = 'S'.$sessionId.'-C'.$courseId.'-ALL'; |
||
7311 | $baseDir = api_get_path(SYS_ARCHIVE_PATH); |
||
7312 | $folderName = 'pdfexport-'.$exportName; |
||
7313 | $exportFolderPath = $baseDir.$folderName; |
||
7314 | |||
7315 | if (!is_dir($exportFolderPath)) { |
||
7316 | @mkdir($exportFolderPath); |
||
7317 | } |
||
7318 | |||
7319 | foreach ($exercises as $exercise) { |
||
7320 | $exerciseId = $exercise['iid']; |
||
7321 | self::exportExerciseAllResultsZip($sessionId, $courseId, $exerciseId, $filterDates, $exportFolderPath); |
||
7322 | } |
||
7323 | |||
7324 | // If export folder is not empty will be zipped. |
||
7325 | $isFolderPathEmpty = (file_exists($exportFolderPath) && 2 == count(scandir($exportFolderPath))); |
||
7326 | if (is_dir($exportFolderPath) && !$isFolderPathEmpty) { |
||
7327 | $exportOk = true; |
||
7328 | $exportFilePath = $baseDir.$exportName.'.zip'; |
||
7329 | $zip = new \PclZip($exportFilePath); |
||
7330 | $zip->create($exportFolderPath, PCLZIP_OPT_REMOVE_PATH, $exportFolderPath); |
||
7331 | rmdirr($exportFolderPath); |
||
7332 | |||
7333 | DocumentManager::file_send_for_download($exportFilePath, true, $exportName.'.zip'); |
||
7334 | exit; |
||
0 ignored issues
–
show
|
|||
7335 | } |
||
7336 | } |
||
7337 | |||
7338 | if (!$exportOk) { |
||
7339 | Display::addFlash( |
||
7340 | Display::return_message( |
||
7341 | get_lang('ExportExerciseNoResult'), |
||
7342 | 'warning', |
||
7343 | false |
||
7344 | ) |
||
7345 | ); |
||
7346 | } |
||
7347 | |||
7348 | return false; |
||
7349 | } |
||
7350 | |||
7351 | /** |
||
7352 | * Export all results of *one* exercise to a ZIP file containing individual PDFs. |
||
7353 | * |
||
7354 | * @return false|void |
||
7355 | */ |
||
7356 | public static function exportExerciseAllResultsZip( |
||
7357 | int $sessionId, |
||
7358 | int $courseId, |
||
7359 | int $exerciseId, |
||
7360 | array $filterDates = [], |
||
7361 | string $mainPath = '' |
||
7362 | ) { |
||
7363 | $objExerciseTmp = new Exercise($courseId); |
||
7364 | $exeResults = $objExerciseTmp->getExerciseAndResult( |
||
7365 | $courseId, |
||
7366 | $sessionId, |
||
7367 | $exerciseId, |
||
7368 | true, |
||
7369 | $filterDates |
||
7370 | ); |
||
7371 | |||
7372 | $exportOk = false; |
||
7373 | if (!empty($exeResults)) { |
||
7374 | $exportName = 'S'.$sessionId.'-C'.$courseId.'-T'.$exerciseId; |
||
7375 | $baseDir = api_get_path(SYS_ARCHIVE_PATH); |
||
7376 | $folderName = 'pdfexport-'.$exportName; |
||
7377 | $exportFolderPath = $baseDir.$folderName; |
||
7378 | |||
7379 | // 1. Cleans the export folder if it exists. |
||
7380 | if (is_dir($exportFolderPath)) { |
||
7381 | rmdirr($exportFolderPath); |
||
7382 | } |
||
7383 | |||
7384 | // 2. Create the pdfs inside a new export folder path. |
||
7385 | if (!empty($exeResults)) { |
||
7386 | foreach ($exeResults as $exeResult) { |
||
7387 | $exeId = (int) $exeResult['exe_id']; |
||
7388 | ExerciseLib::saveFileExerciseResultPdf($exeId, $courseId, $sessionId); |
||
7389 | } |
||
7390 | } |
||
7391 | |||
7392 | // 3. If export folder is not empty will be zipped. |
||
7393 | $isFolderPathEmpty = (file_exists($exportFolderPath) && 2 == count(scandir($exportFolderPath))); |
||
7394 | if (is_dir($exportFolderPath) && !$isFolderPathEmpty) { |
||
7395 | $exportOk = true; |
||
7396 | $exportFilePath = $baseDir.$exportName.'.zip'; |
||
7397 | $zip = new \PclZip($exportFilePath); |
||
7398 | $zip->create($exportFolderPath, PCLZIP_OPT_REMOVE_PATH, $exportFolderPath); |
||
7399 | rmdirr($exportFolderPath); |
||
7400 | |||
7401 | if (!empty($mainPath) && file_exists($exportFilePath)) { |
||
7402 | @rename($exportFilePath, $mainPath.'/'.$exportName.'.zip'); |
||
7403 | } else { |
||
7404 | DocumentManager::file_send_for_download($exportFilePath, true, $exportName.'.zip'); |
||
7405 | exit; |
||
0 ignored issues
–
show
|
|||
7406 | } |
||
7407 | } |
||
7408 | } |
||
7409 | |||
7410 | if (empty($mainPath) && !$exportOk) { |
||
7411 | Display::addFlash( |
||
7412 | Display::return_message( |
||
7413 | get_lang('ExportExerciseNoResult'), |
||
7414 | 'warning', |
||
7415 | false |
||
7416 | ) |
||
7417 | ); |
||
7418 | } |
||
7419 | |||
7420 | return false; |
||
7421 | } |
||
7422 | |||
7423 | /** |
||
7424 | * Get formatted feedback comments for an exam attempt. |
||
7425 | */ |
||
7426 | public static function getFeedbackComments(int $examId): string |
||
7427 | { |
||
7428 | $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||
7429 | $TBL_QUIZ_QUESTION = Database::get_course_table(TABLE_QUIZ_QUESTION); |
||
7430 | |||
7431 | $sql = "SELECT ta.question_id, ta.teacher_comment, q.question AS title |
||
7432 | FROM $TBL_TRACK_ATTEMPT ta |
||
7433 | INNER JOIN $TBL_QUIZ_QUESTION q ON ta.question_id = q.iid |
||
7434 | WHERE ta.exe_id = $examId |
||
7435 | AND ta.teacher_comment IS NOT NULL |
||
7436 | AND ta.teacher_comment != '' |
||
7437 | GROUP BY ta.question_id |
||
7438 | ORDER BY q.position ASC, ta.id ASC"; |
||
7439 | |||
7440 | $result = Database::query($sql); |
||
7441 | $commentsByQuestion = []; |
||
7442 | |||
7443 | while ($row = Database::fetch_array($result)) { |
||
7444 | $questionId = $row['question_id']; |
||
7445 | $questionTitle = Security::remove_XSS($row['title']); |
||
7446 | $comment = Security::remove_XSS(trim(strip_tags($row['teacher_comment']))); |
||
7447 | |||
7448 | if (!empty($comment)) { |
||
7449 | if (!isset($commentsByQuestion[$questionId])) { |
||
7450 | $commentsByQuestion[$questionId] = [ |
||
7451 | 'title' => $questionTitle, |
||
7452 | 'comments' => [], |
||
7453 | ]; |
||
7454 | } |
||
7455 | $commentsByQuestion[$questionId]['comments'][] = $comment; |
||
7456 | } |
||
7457 | } |
||
7458 | |||
7459 | if (empty($commentsByQuestion)) { |
||
7460 | return "<p>".get_lang('NoAdditionalComments')."</p>"; |
||
7461 | } |
||
7462 | |||
7463 | $output = "<h3>".get_lang('TeacherFeedback')."</h3>"; |
||
7464 | $output .= "<table border='1' cellpadding='5' cellspacing='0' width='100%' style='border-collapse: collapse;'>"; |
||
7465 | |||
7466 | foreach ($commentsByQuestion as $questionId => $data) { |
||
7467 | $output .= "<tr> |
||
7468 | <td><b>".get_lang('Question')." #$questionId:</b> ".$data['title']."</td> |
||
7469 | </tr>"; |
||
7470 | foreach ($data['comments'] as $comment) { |
||
7471 | $output .= "<tr> |
||
7472 | <td style='padding-left: 20px;'><i>".get_lang('Feedback').":</i> $comment</td> |
||
7473 | </tr>"; |
||
7474 | } |
||
7475 | } |
||
7476 | |||
7477 | $output .= "</table>"; |
||
7478 | |||
7479 | return $output; |
||
7480 | } |
||
7481 | |||
7482 | private static function subscribeSessionWhenFinishedFailure(int $exerciseId): void |
||
7483 | { |
||
7484 | $failureSession = self::getSessionWhenFinishedFailure($exerciseId); |
||
7485 | |||
7486 | if ($failureSession) { |
||
7487 | SessionManager::subscribeUsersToSession( |
||
7488 | $failureSession->getId(), |
||
7489 | [api_get_user_id()], |
||
7490 | SESSION_VISIBLE_READ_ONLY, |
||
7491 | false |
||
7492 | ); |
||
7493 | } |
||
7494 | } |
||
7495 | |||
7496 | public static function replaceTermsInContent(string $search, string $replace): array |
||
7497 | { |
||
7498 | $replacements = [ |
||
7499 | Database::get_course_table(TABLE_QUIZ_TEST) => [ |
||
7500 | 'iid' => ['title', 'description', 'sound'], |
||
7501 | ], |
||
7502 | Database::get_course_table(TABLE_QUIZ_QUESTION) => [ |
||
7503 | 'iid' => ['question', 'description'], |
||
7504 | ], |
||
7505 | Database::get_course_table(TABLE_QUIZ_ANSWER) => [ |
||
7506 | 'iid' => ['answer', 'comment'], |
||
7507 | ], |
||
7508 | Database::get_course_table(TABLE_ANNOUNCEMENT) => [ |
||
7509 | 'iid' => ['title', 'content'], |
||
7510 | ], |
||
7511 | Database::get_course_table(TABLE_ANNOUNCEMENT_ATTACHMENT) => [ |
||
7512 | 'iid' => ['path', 'comment'], |
||
7513 | ], |
||
7514 | Database::get_course_table(TABLE_ATTENDANCE) => [ |
||
7515 | 'iid' => ['name', 'description', 'attendance_qualify_title'], |
||
7516 | ], |
||
7517 | Database::get_course_table(TABLE_BLOGS) => [ |
||
7518 | 'iid' => ['blog_name', 'blog_subtitle'], |
||
7519 | ], |
||
7520 | Database::get_course_table(TABLE_BLOGS_ATTACHMENT) => [ |
||
7521 | 'iid' => ['path', 'comment'], |
||
7522 | ], |
||
7523 | Database::get_course_table(TABLE_BLOGS_COMMENTS) => [ |
||
7524 | 'iid' => ['title', 'comment'], |
||
7525 | ], |
||
7526 | Database::get_course_table(TABLE_BLOGS_POSTS) => [ |
||
7527 | 'iid' => ['title', 'full_text'], |
||
7528 | ], |
||
7529 | Database::get_course_table(TABLE_BLOGS_TASKS) => [ |
||
7530 | 'iid' => ['title', 'description'], |
||
7531 | ], |
||
7532 | Database::get_course_table(TABLE_AGENDA) => [ |
||
7533 | 'iid' => ['title', 'content', 'comment'], |
||
7534 | ], |
||
7535 | Database::get_course_table(TABLE_AGENDA_ATTACHMENT) => [ |
||
7536 | 'iid' => ['path', 'comment', 'filename'], |
||
7537 | ], |
||
7538 | Database::get_course_table(TABLE_COURSE_DESCRIPTION) => [ |
||
7539 | 'iid' => ['title', 'content'], |
||
7540 | ], |
||
7541 | Database::get_course_table(TABLE_DOCUMENT) => [ |
||
7542 | 'iid' => ['path', 'comment'], |
||
7543 | ], |
||
7544 | Database::get_course_table(TABLE_DROPBOX_FEEDBACK) => [ |
||
7545 | 'iid' => ['feedback'], |
||
7546 | ], |
||
7547 | Database::get_course_table(TABLE_DROPBOX_FILE) => [ |
||
7548 | 'iid' => ['title', 'description'], |
||
7549 | ], |
||
7550 | Database::get_course_table(TABLE_DROPBOX_POST) => [ |
||
7551 | 'iid' => ['feedback'], |
||
7552 | ], |
||
7553 | Database::get_course_table(TABLE_FORUM_ATTACHMENT) => [ |
||
7554 | 'iid' => ['path', 'comment', 'filename'], |
||
7555 | ], |
||
7556 | Database::get_course_table(TABLE_FORUM_CATEGORY) => [ |
||
7557 | 'iid' => ['cat_title', 'cat_comment'], |
||
7558 | ], |
||
7559 | Database::get_course_table(TABLE_FORUM) => [ |
||
7560 | 'iid' => ['forum_title', 'forum_comment', 'forum_image'], |
||
7561 | ], |
||
7562 | Database::get_course_table(TABLE_FORUM_POST) => [ |
||
7563 | 'iid' => ['post_title', 'post_text', 'poster_name'], |
||
7564 | ], |
||
7565 | Database::get_course_table(TABLE_FORUM_THREAD) => [ |
||
7566 | 'iid' => ['thread_title', 'thread_poster_name', 'thread_title_qualify'], |
||
7567 | ], |
||
7568 | Database::get_course_table(TABLE_GLOSSARY) => [ |
||
7569 | 'iid' => ['name', 'description'], |
||
7570 | ], |
||
7571 | Database::get_course_table(TABLE_GROUP_CATEGORY) => [ |
||
7572 | 'iid' => ['title', 'description'], |
||
7573 | ], |
||
7574 | Database::get_course_table(TABLE_GROUP) => [ |
||
7575 | 'iid' => ['name', 'description', 'secret_directory'], |
||
7576 | ], |
||
7577 | Database::get_course_table(TABLE_LINK) => [ |
||
7578 | 'iid' => ['description'], |
||
7579 | ], |
||
7580 | Database::get_course_table(TABLE_LINK_CATEGORY) => [ |
||
7581 | 'iid' => ['category_title', 'description'], |
||
7582 | ], |
||
7583 | Database::get_course_table(TABLE_LP_MAIN) => [ |
||
7584 | 'iid' => ['name', 'ref', 'description', 'path', 'content_license', 'preview_image', 'theme'], |
||
7585 | ], |
||
7586 | Database::get_course_table(TABLE_LP_CATEGORY) => [ |
||
7587 | 'iid' => ['name'], |
||
7588 | ], |
||
7589 | Database::get_course_table(TABLE_LP_ITEM) => [ |
||
7590 | 'iid' => ['prerequisite', 'description', 'title', 'parameters', 'launch_data', 'terms'], |
||
7591 | ], |
||
7592 | Database::get_course_table(TABLE_LP_ITEM_VIEW) => [ |
||
7593 | 'iid' => ['suspend_data', 'lesson_location'], |
||
7594 | ], |
||
7595 | Database::get_course_table(TABLE_NOTEBOOK) => [ |
||
7596 | 'iid' => ['title', 'description'], |
||
7597 | ], |
||
7598 | Database::get_course_table(TABLE_ONLINE_LINK) => [ |
||
7599 | 'iid' => ['name'], |
||
7600 | ], |
||
7601 | Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY) => [ |
||
7602 | 'iid' => ['title', 'description'], |
||
7603 | ], |
||
7604 | Database::get_course_table(TABLE_ROLE) => [ |
||
7605 | 'iid' => ['role_name', 'role_comment'], |
||
7606 | ], |
||
7607 | Database::get_course_table(TABLE_STUDENT_PUBLICATION) => [ |
||
7608 | 'iid' => ['title', 'title_correction', 'description'], |
||
7609 | ], |
||
7610 | Database::get_course_table(TABLE_STUDENT_PUBLICATION_ASSIGNMENT_COMMENT) => [ |
||
7611 | 'iid' => ['comment', 'file'], |
||
7612 | ], |
||
7613 | Database::get_course_table(TABLE_SURVEY) => [ |
||
7614 | 'iid' => ['title', 'subtitle', 'surveythanks', 'invite_mail', 'reminder_mail', 'mail_subject', 'access_condition', 'form_fields'], |
||
7615 | ], |
||
7616 | Database::get_course_table(TABLE_SURVEY_QUESTION_GROUP) => [ |
||
7617 | 'iid' => ['name', 'description'], |
||
7618 | ], |
||
7619 | Database::get_course_table(TABLE_SURVEY_QUESTION) => [ |
||
7620 | 'iid' => ['survey_question', 'survey_question_comment'], |
||
7621 | ], |
||
7622 | Database::get_course_table(TABLE_SURVEY_QUESTION_OPTION) => [ |
||
7623 | 'iid' => ['option_text'], |
||
7624 | ], |
||
7625 | Database::get_course_table(TABLE_THEMATIC) => [ |
||
7626 | 'iid' => ['content', 'title'], |
||
7627 | ], |
||
7628 | Database::get_course_table(TABLE_THEMATIC_ADVANCE) => [ |
||
7629 | 'iid' => ['content'], |
||
7630 | ], |
||
7631 | Database::get_course_table(TABLE_THEMATIC_PLAN) => [ |
||
7632 | 'iid' => ['description'], |
||
7633 | ], |
||
7634 | Database::get_course_table(TABLE_TOOL_LIST) => [ |
||
7635 | 'iid' => ['description'], |
||
7636 | ], |
||
7637 | Database::get_course_table(TABLE_TOOL_INTRO) => [ |
||
7638 | 'iid' => ['intro_text'], |
||
7639 | ], |
||
7640 | Database::get_course_table(TABLE_USER_INFO_DEF) => [ |
||
7641 | 'iid' => ['comment'], |
||
7642 | ], |
||
7643 | Database::get_course_table(TABLE_WIKI) => [ |
||
7644 | 'iid' => ['title', 'content', 'comment', 'progress', 'linksto'], |
||
7645 | ], |
||
7646 | Database::get_course_table(TABLE_WIKI_CONF) => [ |
||
7647 | 'iid' => ['feedback1', 'feedback2', 'feedback3'], |
||
7648 | ], |
||
7649 | Database::get_course_table(TABLE_WIKI_DISCUSS) => [ |
||
7650 | 'iid' => ['comment'], |
||
7651 | ], |
||
7652 | Database::get_main_table(TABLE_CAREER) => [ |
||
7653 | 'id' => ['name', 'description'], |
||
7654 | ], |
||
7655 | Database::get_main_table(TABLE_MAIN_CHAT) => [ |
||
7656 | 'id' => ['message'], |
||
7657 | ], |
||
7658 | Database::get_main_table(TABLE_MAIN_CLASS) => [ |
||
7659 | 'id' => ['name'], |
||
7660 | ], |
||
7661 | Database::get_main_table(TABLE_MAIN_COURSE_REQUEST) => [ |
||
7662 | 'id' => ['description', 'title', 'objetives', 'target_audience'], |
||
7663 | ], |
||
7664 | 'course_type' => [ |
||
7665 | 'id' => ['description'], |
||
7666 | ], |
||
7667 | Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE) => [ |
||
7668 | 'id' => ['message', 'subject', 'event_type_name'], |
||
7669 | ], |
||
7670 | Database::get_main_table(TABLE_GRADE_MODEL) => [ |
||
7671 | 'id' => ['name', 'description'], |
||
7672 | ], |
||
7673 | Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY) => [ |
||
7674 | 'id' => ['name', 'description'], |
||
7675 | ], |
||
7676 | Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE) => [ |
||
7677 | 'id' => ['path_certificate'], |
||
7678 | ], |
||
7679 | Database::get_main_table(TABLE_MAIN_GRADEBOOK_EVALUATION) => [ |
||
7680 | 'id' => ['description', 'name'], |
||
7681 | ], |
||
7682 | Database::get_main_table(TABLE_MAIN_GRADEBOOK_LINKEVAL_LOG) => [ |
||
7683 | 'id' => ['name', 'description'], |
||
7684 | ], |
||
7685 | Database::get_main_table(TABLE_MAIN_LEGAL) => [ |
||
7686 | 'id' => ['content', 'changes'], |
||
7687 | ], |
||
7688 | Database::get_main_table(TABLE_MESSAGE) => [ |
||
7689 | 'id' => ['content'], |
||
7690 | ], |
||
7691 | Database::get_main_table(TABLE_MESSAGE_ATTACHMENT) => [ |
||
7692 | 'id' => ['path', 'comment', 'filename'], |
||
7693 | ], |
||
7694 | Database::get_main_table(TABLE_NOTIFICATION) => [ |
||
7695 | 'id' => ['content'], |
||
7696 | ], |
||
7697 | Database::get_main_table(TABLE_PERSONAL_AGENDA) => [ |
||
7698 | 'id' => ['title', 'text'], |
||
7699 | ], |
||
7700 | Database::get_main_table(TABLE_PROMOTION) => [ |
||
7701 | 'id' => ['description'], |
||
7702 | ], |
||
7703 | 'room' => [ |
||
7704 | 'id' => ['description'], |
||
7705 | ], |
||
7706 | 'sequence_condition' => [ |
||
7707 | 'id' => ['description'], |
||
7708 | ], |
||
7709 | 'sequence_method' => [ |
||
7710 | 'id' => ['description', 'formula'], |
||
7711 | ], |
||
7712 | 'sequence_rule' => [ |
||
7713 | 'id' => ['description'], |
||
7714 | ], |
||
7715 | 'sequence_type_entity' => [ |
||
7716 | 'id' => ['description'], |
||
7717 | ], |
||
7718 | 'sequence_variable' => [ |
||
7719 | 'id' => ['description'], |
||
7720 | ], |
||
7721 | Database::get_main_table(TABLE_MAIN_SESSION) => [ |
||
7722 | 'id' => ['description'], |
||
7723 | ], |
||
7724 | Database::get_main_table(TABLE_MAIN_SHARED_SURVEY) => [ |
||
7725 | 'survey_id' => ['subtitle', 'surveythanks', 'intro'], |
||
7726 | ], |
||
7727 | Database::get_main_table(TABLE_MAIN_SHARED_SURVEY_QUESTION) => [ |
||
7728 | 'question_id' => ['survey_question', 'survey_question_comment'], |
||
7729 | ], |
||
7730 | Database::get_main_table(TABLE_MAIN_SHARED_SURVEY_QUESTION_OPTION) => [ |
||
7731 | 'question_option_id' => ['option_text'], |
||
7732 | ], |
||
7733 | Database::get_main_table(TABLE_MAIN_SKILL) => [ |
||
7734 | 'id' => ['name', 'description', 'criteria'], |
||
7735 | ], |
||
7736 | Database::get_main_table(TABLE_MAIN_SKILL_PROFILE) => [ |
||
7737 | 'id' => ['description'], |
||
7738 | ], |
||
7739 | Database::get_main_table(TABLE_MAIN_SKILL_REL_USER) => [ |
||
7740 | 'id' => ['argumentation'], |
||
7741 | ], |
||
7742 | 'skill_rel_user_comment' => [ |
||
7743 | 'id' => ['feedback_text'], |
||
7744 | ], |
||
7745 | Database::get_main_table(TABLE_MAIN_SYSTEM_ANNOUNCEMENTS) => [ |
||
7746 | 'id' => ['content'], |
||
7747 | ], |
||
7748 | Database::get_main_table(TABLE_MAIN_SYSTEM_CALENDAR) => [ |
||
7749 | 'id' => ['content'], |
||
7750 | ], |
||
7751 | Database::get_main_table(TABLE_MAIN_SYSTEM_TEMPLATE) => [ |
||
7752 | 'id' => ['comment', 'content'], |
||
7753 | ], |
||
7754 | Database::get_main_table(TABLE_MAIN_TEMPLATES) => [ |
||
7755 | 'id' => ['description', 'image'], |
||
7756 | ], |
||
7757 | Database::get_main_table(TABLE_TICKET_CATEGORY) => [ |
||
7758 | 'id' => ['description'], |
||
7759 | ], |
||
7760 | Database::get_main_table(TABLE_TICKET_MESSAGE) => [ |
||
7761 | 'id' => ['message'], |
||
7762 | ], |
||
7763 | Database::get_main_table(TABLE_TICKET_MESSAGE_ATTACHMENTS) => [ |
||
7764 | 'id' => ['filename', 'path'], |
||
7765 | ], |
||
7766 | Database::get_main_table(TABLE_TICKET_PRIORITY) => [ |
||
7767 | 'id' => ['description'], |
||
7768 | ], |
||
7769 | Database::get_main_table(TABLE_TICKET_PROJECT) => [ |
||
7770 | 'id' => ['description'], |
||
7771 | ], |
||
7772 | Database::get_main_table(TABLE_TICKET_STATUS) => [ |
||
7773 | 'id' => ['description'], |
||
7774 | ], |
||
7775 | Database::get_main_table(TABLE_TICKET_TICKET) => [ |
||
7776 | 'id' => ['message'], |
||
7777 | ], |
||
7778 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT) => [ |
||
7779 | 'id' => ['answer', 'teacher_comment', 'filename'], |
||
7780 | ], |
||
7781 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING) => [ |
||
7782 | 'id' => ['teacher_comment'], |
||
7783 | ], |
||
7784 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT) => [ |
||
7785 | 'default_id' => ['default_value'], |
||
7786 | ], |
||
7787 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES) => [ |
||
7788 | 'exe_id' => ['data_tracking', 'questions_to_check'], |
||
7789 | ], |
||
7790 | Database::get_main_table(TABLE_STATISTIC_TRACK_E_ITEM_PROPERTY) => [ |
||
7791 | 'id' => ['content'], |
||
7792 | ], |
||
7793 | 'track_e_open' => [ |
||
7794 | 'open_id' => ['open_remote_host', 'open_agent', 'open_referer'], |
||
7795 | ], |
||
7796 | Database::get_main_table(TABLE_TRACK_STORED_VALUES) => [ |
||
7797 | 'id' => ['sv_value'], |
||
7798 | ], |
||
7799 | Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK) => [ |
||
7800 | 'id' => ['sv_value'], |
||
7801 | ], |
||
7802 | Database::get_main_table(TABLE_MAIN_USER_API_KEY) => [ |
||
7803 | 'id' => ['description'], |
||
7804 | ], |
||
7805 | Database::get_main_table(TABLE_USERGROUP) => [ |
||
7806 | 'id' => ['name', 'description', 'picture', 'url'], |
||
7807 | ], |
||
7808 | Database::get_main_table(TABLE_MAIN_BLOCK) => [ |
||
7809 | 'id' => ['name', 'description', 'path'], |
||
7810 | ] |
||
7811 | ]; |
||
7812 | |||
7813 | if (api_get_configuration_value('attendance_allow_comments')) { |
||
7814 | $replacements['c_attendance_result_comment'] = [ |
||
7815 | 'iid' => ['comment'], |
||
7816 | ]; |
||
7817 | } |
||
7818 | |||
7819 | if (api_get_configuration_value('exercise_text_when_finished_failure')) { |
||
7820 | $replacements[Database::get_course_table(TABLE_QUIZ_TEST)]['iid'][] = 'text_when_finished_failure'; |
||
7821 | } |
||
7822 | |||
7823 | $changes = array_map( |
||
7824 | fn($table) => 0, |
||
7825 | $replacements |
||
7826 | ); |
||
7827 | |||
7828 | foreach ($replacements as $table => $replacement) { |
||
7829 | foreach ($replacement as $idColumn => $columns) { |
||
7830 | $keys = array_map(fn($column) => "$column LIKE %?%", $columns); |
||
7831 | $values = array_fill(0, count($columns), $search); |
||
7832 | |||
7833 | $result = Database::select( |
||
7834 | [$idColumn, ...$columns], |
||
7835 | $table, |
||
7836 | [ |
||
7837 | 'where' => [ |
||
7838 | implode(' OR ', $keys) => $values, |
||
7839 | ], |
||
7840 | 'order' => "$idColumn ASC" |
||
7841 | ] |
||
7842 | ); |
||
7843 | |||
7844 | foreach ($result as $row) { |
||
7845 | $attributes = array_combine( |
||
7846 | $columns, |
||
7847 | array_map( |
||
7848 | fn($column) => preg_replace('#'.$search.'#', $replace, $row[$column]), |
||
7849 | $columns |
||
7850 | ) |
||
7851 | ); |
||
7852 | |||
7853 | try { |
||
7854 | Database::update( |
||
7855 | $table, |
||
7856 | $attributes, |
||
7857 | ["$idColumn = ?" => $row[$idColumn]] |
||
7858 | ); |
||
7859 | } catch (Exception $e) { |
||
7860 | Database::handleError($e); |
||
7861 | } |
||
7862 | |||
7863 | $changes[$table]++; |
||
7864 | } |
||
7865 | } |
||
7866 | } |
||
7867 | |||
7868 | return $changes; |
||
7869 | } |
||
7870 | } |
||
7871 |