1 | <?php |
||||||
2 | |||||||
3 | /* For licensing terms, see /license.txt */ |
||||||
4 | |||||||
5 | use Chamilo\CoreBundle\Entity\TrackEExerciseConfirmation; |
||||||
6 | use Chamilo\CoreBundle\Entity\TrackEExercises; |
||||||
7 | use Chamilo\CourseBundle\Entity\CQuizQuestion; |
||||||
8 | use ChamiloSession as Session; |
||||||
9 | |||||||
10 | require_once __DIR__.'/../global.inc.php'; |
||||||
11 | $current_course_tool = TOOL_QUIZ; |
||||||
12 | $debug = false; |
||||||
13 | |||||||
14 | ExerciseLib::logPingForCheckingConnection(); |
||||||
15 | |||||||
16 | // Check if the user has access to the contextual course/session |
||||||
17 | api_protect_course_script(true); |
||||||
18 | |||||||
19 | $action = $_REQUEST['a']; |
||||||
20 | $course_id = api_get_course_int_id(); |
||||||
21 | $session_id = isset($_REQUEST['session_id']) ? (int) $_REQUEST['session_id'] : api_get_session_id(); |
||||||
22 | $course_code = isset($_REQUEST['cidReq']) ? $_REQUEST['cidReq'] : api_get_course_id(); |
||||||
23 | $currentUserId = api_get_user_id(); |
||||||
24 | $exeId = isset($_REQUEST['exe_id']) ? $_REQUEST['exe_id'] : 0; |
||||||
25 | |||||||
26 | switch ($action) { |
||||||
27 | case 'get_exercise_by_course': |
||||||
28 | $course_id = (isset($_GET['course_id']) && !empty($_GET['course_id'])) ? (int) $_GET['course_id'] : 0; |
||||||
29 | $session_id = (!empty($_GET['session_id'])) ? (int) $_GET['session_id'] : 0; |
||||||
30 | $data = []; |
||||||
31 | $onlyActiveExercises = !(api_is_platform_admin(true) || api_is_course_admin()); |
||||||
32 | $results = ExerciseLib::get_all_exercises_for_course_id( |
||||||
33 | null, |
||||||
34 | $session_id, |
||||||
35 | $course_id, |
||||||
36 | $onlyActiveExercises |
||||||
37 | ); |
||||||
38 | |||||||
39 | if (!empty($results)) { |
||||||
40 | foreach ($results as $exercise) { |
||||||
41 | $data[] = ['id' => $exercise['iid'], 'text' => html_entity_decode($exercise['title'])]; |
||||||
42 | } |
||||||
43 | } |
||||||
44 | |||||||
45 | echo json_encode($data); |
||||||
46 | break; |
||||||
47 | case 'update_duration': |
||||||
48 | if (Session::read('login_as')) { |
||||||
49 | if ($debug) { |
||||||
50 | error_log("User is 'login as' don't update duration time."); |
||||||
51 | } |
||||||
52 | exit; |
||||||
53 | } |
||||||
54 | |||||||
55 | if (empty($exeId)) { |
||||||
56 | if ($debug) { |
||||||
57 | error_log('Exe id not provided.'); |
||||||
58 | } |
||||||
59 | exit; |
||||||
60 | } |
||||||
61 | |||||||
62 | /** @var Exercise $exerciseInSession */ |
||||||
63 | $exerciseInSession = Session::read('objExercise'); |
||||||
64 | |||||||
65 | if (empty($exerciseInSession)) { |
||||||
66 | if ($debug) { |
||||||
67 | error_log('Exercise obj not provided.'); |
||||||
68 | } |
||||||
69 | exit; |
||||||
70 | } |
||||||
71 | |||||||
72 | // If exercise was updated x seconds before, then don't updated duration. |
||||||
73 | $onlyUpdateValue = 10; |
||||||
74 | |||||||
75 | $em = Database::getManager(); |
||||||
76 | /** @var TrackEExercises $attempt */ |
||||||
77 | $attempt = $em->getRepository('ChamiloCoreBundle:TrackEExercises')->find($exeId); |
||||||
78 | |||||||
79 | if (empty($attempt)) { |
||||||
80 | if ($debug) { |
||||||
81 | error_log("Attempt #$exeId doesn't exists."); |
||||||
82 | } |
||||||
83 | exit; |
||||||
84 | } |
||||||
85 | |||||||
86 | $nowObject = api_get_utc_datetime(null, false, true); |
||||||
87 | $now = $nowObject->getTimestamp(); |
||||||
88 | $exerciseId = $attempt->getExeExoId(); |
||||||
89 | $userId = $attempt->getExeUserId(); |
||||||
90 | |||||||
91 | if ($userId != $currentUserId) { |
||||||
92 | if ($debug) { |
||||||
93 | error_log("User $currentUserId trying to change time for user $userId"); |
||||||
94 | } |
||||||
95 | exit; |
||||||
96 | } |
||||||
97 | |||||||
98 | if ($exerciseInSession->iid != $exerciseId) { |
||||||
99 | if ($debug) { |
||||||
100 | error_log("Cannot update, exercise are different."); |
||||||
101 | } |
||||||
102 | exit; |
||||||
103 | } |
||||||
104 | |||||||
105 | if ($attempt->getStatus() != 'incomplete') { |
||||||
106 | if ($debug) { |
||||||
107 | error_log('Cannot update exercise is already completed.'); |
||||||
108 | } |
||||||
109 | exit; |
||||||
110 | } |
||||||
111 | |||||||
112 | // Check if we are dealing with the same exercise. |
||||||
113 | $timeWithOutUpdate = $now - $attempt->getExeDate()->getTimestamp(); |
||||||
114 | if ($timeWithOutUpdate > $onlyUpdateValue) { |
||||||
115 | $key = ExerciseLib::get_time_control_key( |
||||||
116 | $exerciseId, |
||||||
117 | $attempt->getOrigLpId(), |
||||||
118 | $attempt->getOrigLpItemId() |
||||||
119 | ); |
||||||
120 | $durationFromObject = $attempt->getExeDuration(); |
||||||
121 | $previousTime = Session::read('duration_time_previous'); |
||||||
122 | if (isset($previousTime[$key]) && |
||||||
123 | !empty($previousTime[$key]) |
||||||
124 | ) { |
||||||
125 | $sessionTime = $previousTime[$key]; |
||||||
126 | $duration = $sessionTime = $now - $sessionTime; |
||||||
127 | if (!empty($durationFromObject)) { |
||||||
128 | $duration += $durationFromObject; |
||||||
129 | } |
||||||
130 | $duration = (int) $duration; |
||||||
131 | if (!empty($duration)) { |
||||||
132 | if ($debug) { |
||||||
133 | error_log("Exe_id: #".$exeId); |
||||||
134 | error_log("Key: $key"); |
||||||
135 | error_log("Exercise to update: #$exerciseId of user: #$userId"); |
||||||
136 | error_log("Duration time found in DB before update: $durationFromObject"); |
||||||
137 | error_log("Current spent time $sessionTime before an update"); |
||||||
138 | error_log("Accumulate duration to save in DB: $duration"); |
||||||
139 | error_log("End date (UTC) before update: ".$attempt->getExeDate()->format('Y-m-d H:i:s')); |
||||||
140 | error_log("End date (UTC) to be save in DB: ".$nowObject->format('Y-m-d H:i:s')); |
||||||
141 | } |
||||||
142 | $attempt |
||||||
143 | ->setExeDuration($duration) |
||||||
144 | ->setExeDate($nowObject); |
||||||
145 | $em->merge($attempt); |
||||||
146 | $em->flush(); |
||||||
147 | } |
||||||
148 | } else { |
||||||
149 | if ($debug) { |
||||||
150 | error_log("Nothing to update, 'duration_time_previous' session not set"); |
||||||
151 | error_log("Key: $key"); |
||||||
152 | } |
||||||
153 | } |
||||||
154 | } else { |
||||||
155 | if ($debug) { |
||||||
156 | error_log("Can't update, time was already updated $timeWithOutUpdate seconds ago"); |
||||||
157 | } |
||||||
158 | } |
||||||
159 | |||||||
160 | break; |
||||||
161 | case 'get_live_stats': |
||||||
162 | if (!api_is_allowed_to_edit(null, true)) { |
||||||
163 | break; |
||||||
164 | } |
||||||
165 | |||||||
166 | // 1. Setting variables needed by jqgrid |
||||||
167 | $exercise_id = (int) $_GET['exercise_id']; |
||||||
168 | $page = (int) $_REQUEST['page']; //page |
||||||
169 | $limit = (int) $_REQUEST['rows']; //quantity of rows |
||||||
170 | $sidx = $_REQUEST['sidx']; //index to filter |
||||||
171 | $sord = $_REQUEST['sord']; //asc or desc |
||||||
172 | |||||||
173 | if (!in_array($sidx, ['firstname', 'lastname', 'start_date'])) { |
||||||
174 | $sidx = 1; |
||||||
175 | } |
||||||
176 | |||||||
177 | if (!in_array($sord, ['asc', 'desc'])) { |
||||||
178 | $sord = 'desc'; |
||||||
179 | } |
||||||
180 | // get index row - i.e. user click to sort $sord = $_GET['sord']; |
||||||
181 | // get the direction |
||||||
182 | if (!$sidx) { |
||||||
183 | $sidx = 1; |
||||||
184 | } |
||||||
185 | |||||||
186 | $track_exercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); |
||||||
187 | $user_table = Database::get_main_table(TABLE_MAIN_USER); |
||||||
188 | $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); |
||||||
189 | |||||||
190 | $minutes = (int) $_REQUEST['minutes']; |
||||||
191 | $now = time() - 60 * $minutes; |
||||||
192 | $now = api_get_utc_datetime($now); |
||||||
193 | |||||||
194 | $where_condition = " orig_lp_id = 0 AND exe_exo_id = $exercise_id AND start_date > '$now' "; |
||||||
195 | $sql = "SELECT COUNT(DISTINCT exe_id) |
||||||
196 | FROM $track_exercise |
||||||
197 | WHERE $where_condition "; |
||||||
198 | $result = Database::query($sql); |
||||||
199 | $count = Database::fetch_row($result); |
||||||
200 | $count = $count[0]; |
||||||
201 | |||||||
202 | //3. Calculating first, end, etc |
||||||
203 | $total_pages = 0; |
||||||
204 | if ($count > 0) { |
||||||
205 | if (!empty($limit)) { |
||||||
206 | $total_pages = ceil($count / $limit); |
||||||
207 | } |
||||||
208 | } |
||||||
209 | |||||||
210 | if ($page > $total_pages) { |
||||||
211 | $page = $total_pages; |
||||||
212 | } |
||||||
213 | |||||||
214 | $start = $limit * $page - $limit; |
||||||
215 | if ($start < 0) { |
||||||
216 | $start = 0; |
||||||
217 | } |
||||||
218 | |||||||
219 | $sql = "SELECT |
||||||
220 | exe_id, |
||||||
221 | exe_user_id, |
||||||
222 | firstname, |
||||||
223 | lastname, |
||||||
224 | aa.status, |
||||||
225 | start_date, |
||||||
226 | exe_result, |
||||||
227 | exe_weighting, |
||||||
228 | exe_result/exe_weighting as score, |
||||||
229 | exe_duration, |
||||||
230 | questions_to_check, |
||||||
231 | orig_lp_id |
||||||
232 | FROM $user_table u |
||||||
233 | INNER JOIN ( |
||||||
234 | SELECT |
||||||
235 | t.exe_id, |
||||||
236 | t.exe_user_id, |
||||||
237 | status, |
||||||
238 | start_date, |
||||||
239 | exe_result, |
||||||
240 | exe_weighting, |
||||||
241 | exe_result/exe_weighting as score, |
||||||
242 | exe_duration, |
||||||
243 | questions_to_check, |
||||||
244 | orig_lp_id |
||||||
245 | FROM $track_exercise t |
||||||
246 | LEFT JOIN $track_attempt a |
||||||
247 | ON (a.exe_id = t.exe_id AND t.exe_user_id = a.user_id) |
||||||
248 | WHERE t.status = 'incomplete' AND $where_condition |
||||||
249 | GROUP BY exe_user_id |
||||||
250 | ) as aa |
||||||
251 | ON aa.exe_user_id = user_id |
||||||
252 | ORDER BY $sidx $sord |
||||||
253 | LIMIT $start, $limit"; |
||||||
254 | |||||||
255 | $result = Database::query($sql); |
||||||
256 | $results = []; |
||||||
257 | while ($row = Database::fetch_array($result, 'ASSOC')) { |
||||||
258 | $results[] = $row; |
||||||
259 | } |
||||||
260 | |||||||
261 | $oExe = new Exercise(); |
||||||
262 | $oExe->read($exercise_id); |
||||||
263 | |||||||
264 | $response = new stdClass(); |
||||||
265 | $response->page = $page; |
||||||
266 | $response->total = $total_pages; |
||||||
267 | $response->records = $count; |
||||||
268 | $i = 0; |
||||||
269 | |||||||
270 | if (!empty($results)) { |
||||||
271 | foreach ($results as $row) { |
||||||
272 | $sql = "SELECT SUM(count_question_id) as count_question_id |
||||||
273 | FROM ( |
||||||
274 | SELECT 1 as count_question_id |
||||||
275 | FROM $track_attempt a |
||||||
276 | WHERE |
||||||
277 | user_id = {$row['exe_user_id']} AND |
||||||
278 | exe_id = {$row['exe_id']} |
||||||
279 | GROUP by question_id |
||||||
280 | ) as count_table"; |
||||||
281 | $result_count = Database::query($sql); |
||||||
282 | $count_questions = Database::fetch_array( |
||||||
283 | $result_count, |
||||||
284 | 'ASSOC' |
||||||
285 | ); |
||||||
286 | $count_questions = $count_questions['count_question_id']; |
||||||
287 | |||||||
288 | $row['count_questions'] = $count_questions; |
||||||
289 | |||||||
290 | $response->rows[$i]['id'] = $row['exe_id']; |
||||||
291 | if (!empty($oExe->expired_time)) { |
||||||
292 | $remaining = strtotime($row['start_date']) + |
||||||
293 | ($oExe->expired_time * 60) - |
||||||
294 | strtotime(api_get_utc_datetime(time())); |
||||||
295 | $h = floor($remaining / 3600); |
||||||
296 | $m = floor(($remaining - ($h * 3600)) / 60); |
||||||
297 | $s = ($remaining - ($h * 3600) - ($m * 60)); |
||||||
298 | $timeInfo = api_convert_and_format_date( |
||||||
299 | $row['start_date'], |
||||||
300 | DATE_TIME_FORMAT_LONG |
||||||
301 | ).' ['.($h > 0 ? $h.':' : '').sprintf("%02d", $m).':'.sprintf("%02d", $s).']'; |
||||||
302 | } else { |
||||||
303 | $timeInfo = api_convert_and_format_date( |
||||||
304 | $row['start_date'], |
||||||
305 | DATE_TIME_FORMAT_LONG |
||||||
306 | ); |
||||||
307 | } |
||||||
308 | $array = [ |
||||||
309 | $row['firstname'], |
||||||
310 | $row['lastname'], |
||||||
311 | $timeInfo, |
||||||
312 | $row['count_questions'], |
||||||
313 | round($row['score'] * 100).'%', |
||||||
314 | ]; |
||||||
315 | $response->rows[$i]['cell'] = $array; |
||||||
316 | $i++; |
||||||
317 | } |
||||||
318 | } |
||||||
319 | echo json_encode($response); |
||||||
320 | break; |
||||||
321 | case 'update_exercise_list_order': |
||||||
322 | if (api_is_allowed_to_edit(null, true)) { |
||||||
323 | $new_list = $_REQUEST['exercise_list']; |
||||||
324 | $table = Database::get_course_table(TABLE_QUIZ_ORDER); |
||||||
325 | $counter = 1; |
||||||
326 | //Drop all |
||||||
327 | $sql = "DELETE FROM $table WHERE session_id = $session_id AND c_id = $course_id"; |
||||||
328 | Database::query($sql); |
||||||
329 | // Insert all |
||||||
330 | foreach ($new_list as $new_order_id) { |
||||||
331 | Database::insert( |
||||||
332 | $table, |
||||||
333 | [ |
||||||
334 | 'exercise_order' => $counter, |
||||||
335 | 'session_id' => $session_id, |
||||||
336 | 'exercise_id' => (int) $new_order_id, |
||||||
337 | 'c_id' => $course_id, |
||||||
338 | ] |
||||||
339 | ); |
||||||
340 | $counter++; |
||||||
341 | } |
||||||
342 | echo Display::return_message(get_lang('Saved'), 'confirmation'); |
||||||
343 | } |
||||||
344 | break; |
||||||
345 | case 'update_question_order': |
||||||
346 | $course_info = api_get_course_info_by_id($course_id); |
||||||
347 | $course_id = $course_info['real_id']; |
||||||
348 | $exercise_id = isset($_REQUEST['exercise_id']) ? (int) $_REQUEST['exercise_id'] : null; |
||||||
349 | |||||||
350 | if (empty($exercise_id)) { |
||||||
351 | return Display::return_message(get_lang('Error'), 'error'); |
||||||
352 | } |
||||||
353 | if (api_is_allowed_to_edit(null, true)) { |
||||||
354 | $new_question_list = $_POST['question_id_list']; |
||||||
355 | $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); |
||||||
356 | $counter = 1; |
||||||
357 | foreach ($new_question_list as $new_order_id) { |
||||||
358 | Database::update( |
||||||
359 | $TBL_QUESTIONS, |
||||||
360 | ['question_order' => $counter], |
||||||
361 | [ |
||||||
362 | 'question_id = ? AND c_id = ? AND exercice_id = ? ' => [ |
||||||
363 | (int) $new_order_id, |
||||||
364 | $course_id, |
||||||
365 | $exercise_id, |
||||||
366 | ], |
||||||
367 | ] |
||||||
368 | ) |
||||||
369 | ; |
||||||
370 | $counter++; |
||||||
371 | } |
||||||
372 | echo Display::return_message(get_lang('Saved'), 'confirmation'); |
||||||
373 | } |
||||||
374 | break; |
||||||
375 | case 'add_question_to_reminder': |
||||||
376 | /** @var Exercise $objExercise */ |
||||||
377 | $objExercise = Session::read('objExercise'); |
||||||
378 | |||||||
379 | if (empty($objExercise) || empty($exeId)) { |
||||||
380 | echo 0; |
||||||
381 | exit; |
||||||
382 | } |
||||||
383 | |||||||
384 | $option = isset($_GET['option']) ? $_GET['option'] : ''; |
||||||
385 | switch ($option) { |
||||||
386 | case 'add_all': |
||||||
387 | $questionListInSession = Session::read('questionList'); |
||||||
388 | $objExercise->addAllQuestionToRemind($exeId, $questionListInSession); |
||||||
389 | break; |
||||||
390 | case 'remove_all': |
||||||
391 | $objExercise->removeAllQuestionToRemind($exeId); |
||||||
392 | break; |
||||||
393 | default: |
||||||
394 | $objExercise->editQuestionToRemind( |
||||||
395 | $exeId, |
||||||
396 | $_REQUEST['question_id'], |
||||||
397 | $_REQUEST['action'] |
||||||
398 | ); |
||||||
399 | break; |
||||||
400 | } |
||||||
401 | |||||||
402 | echo 1; |
||||||
403 | exit; |
||||||
404 | |||||||
405 | break; |
||||||
406 | case 'check_answers': |
||||||
407 | if (false === api_is_allowed_to_session_edit()) { |
||||||
408 | echo 'error'; |
||||||
409 | exit; |
||||||
410 | } |
||||||
411 | |||||||
412 | /** @var Exercise $objExercise */ |
||||||
413 | $objExercise = Session::read('objExercise'); |
||||||
414 | $questionList = Session::read('questionList'); |
||||||
415 | $exeId = Session::read('exe_id'); |
||||||
416 | |||||||
417 | // If exercise or question is not set then exit. |
||||||
418 | if (empty($questionList) || empty($objExercise)) { |
||||||
419 | echo 'error'; |
||||||
420 | exit; |
||||||
421 | } |
||||||
422 | |||||||
423 | $statInfo = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||||||
424 | |||||||
425 | echo Display::page_subheader(get_lang('VerificationOfAnsweredQuestions')); |
||||||
426 | echo $objExercise->getReminderTable($questionList, $statInfo, true); |
||||||
427 | break; |
||||||
428 | case 'save_question_description': |
||||||
429 | if (!api_get_configuration_value('allow_quick_question_description_popup')) { |
||||||
430 | exit; |
||||||
431 | } |
||||||
432 | if (!api_is_allowed_to_edit(null, true)) { |
||||||
433 | exit; |
||||||
434 | } |
||||||
435 | |||||||
436 | /** @var \Exercise $objExercise */ |
||||||
437 | $objExercise = Session::read('objExercise'); |
||||||
438 | if (empty($objExercise)) { |
||||||
439 | exit; |
||||||
440 | } |
||||||
441 | |||||||
442 | $questionId = isset($_REQUEST['question_id']) ? (int) $_REQUEST['question_id'] : null; |
||||||
443 | $image = isset($_REQUEST['image']) ? $_REQUEST['image'] : ''; |
||||||
444 | |||||||
445 | $questionList = $objExercise->getQuestionList(); |
||||||
446 | |||||||
447 | if (!in_array($questionId, $questionList)) { |
||||||
448 | echo '0'; |
||||||
449 | exit; |
||||||
450 | } |
||||||
451 | |||||||
452 | $em = Database::getManager(); |
||||||
453 | $repo = $em->getRepository(CQuizQuestion::class); |
||||||
454 | /** @var CQuizQuestion $question */ |
||||||
455 | $question = $repo->find($questionId); |
||||||
456 | if (null !== $question) { |
||||||
457 | $question->setDescription('<img src="'.$image.'" />'); |
||||||
458 | $em->persist($question); |
||||||
459 | $em->flush(); |
||||||
460 | echo 1; |
||||||
461 | exit; |
||||||
462 | } |
||||||
463 | echo 0; |
||||||
464 | exit; |
||||||
465 | break; |
||||||
466 | case 'save_exercise_by_now': |
||||||
467 | header('Content-Type: application/json'); |
||||||
468 | |||||||
469 | $course_info = api_get_course_info_by_id($course_id); |
||||||
470 | |||||||
471 | if (empty($course_info)) { |
||||||
472 | echo json_encode(['error' => true]); |
||||||
473 | exit; |
||||||
474 | } |
||||||
475 | |||||||
476 | $course_id = $course_info['real_id']; |
||||||
477 | |||||||
478 | // Use have permissions to edit exercises results now? |
||||||
479 | if (false === api_is_allowed_to_session_edit()) { |
||||||
480 | echo json_encode(['error' => true]); |
||||||
481 | if ($debug) { |
||||||
482 | error_log( |
||||||
483 | 'Exercises attempt '.$exeId.': Failed saving question(s) in course/session '. |
||||||
484 | $course_id.'/'.$session_id. |
||||||
485 | ': The user ('.api_get_user_id().') does not have the permission to access this session now' |
||||||
486 | ); |
||||||
487 | } |
||||||
488 | exit; |
||||||
489 | } |
||||||
490 | |||||||
491 | // "all" or "simple" strings means that there's one or all questions exercise type |
||||||
492 | $type = isset($_REQUEST['type']) ? $_REQUEST['type'] : null; |
||||||
493 | |||||||
494 | // Questions choices. |
||||||
495 | $choice = isset($_REQUEST['choice']) ? $_REQUEST['choice'] : []; |
||||||
496 | |||||||
497 | // certainty degree choice |
||||||
498 | $choiceDegreeCertainty = isset($_REQUEST['choiceDegreeCertainty']) ? $_REQUEST['choiceDegreeCertainty'] : []; |
||||||
499 | |||||||
500 | // Hot spot coordinates from all questions. |
||||||
501 | $hot_spot_coordinates = isset($_REQUEST['hotspot']) ? $_REQUEST['hotspot'] : []; |
||||||
502 | |||||||
503 | // the filenames in upload answer type |
||||||
504 | $uploadAnswerFileNames = isset($_REQUEST['uploadChoice']) ? $_REQUEST['uploadChoice'] : []; |
||||||
505 | |||||||
506 | // There is a reminder? |
||||||
507 | $remind_list = isset($_REQUEST['remind_list']) && !empty($_REQUEST['remind_list']) |
||||||
508 | ? array_keys($_REQUEST['remind_list']) : []; |
||||||
509 | |||||||
510 | // Needed in manage_answer. |
||||||
511 | $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0; |
||||||
512 | $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0; |
||||||
513 | |||||||
514 | if ($debug) { |
||||||
515 | error_log("exe_id = $exeId"); |
||||||
516 | error_log("type = $type"); |
||||||
517 | error_log("choice = ".print_r($choice, 1)." "); |
||||||
518 | error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates, 1)); |
||||||
519 | error_log("remind_list = ".print_r($remind_list, 1)); |
||||||
520 | error_log("uploadAnswerFileNames = ".print_r($uploadAnswerFileNames, 1)); |
||||||
521 | error_log("--------------------------------"); |
||||||
522 | } |
||||||
523 | |||||||
524 | /** @var Exercise $objExercise */ |
||||||
525 | $objExercise = Session::read('objExercise'); |
||||||
526 | |||||||
527 | // Question info. |
||||||
528 | $question_id = isset($_REQUEST['question_id']) ? (int) $_REQUEST['question_id'] : null; |
||||||
529 | $question_list = Session::read('questionList'); |
||||||
530 | |||||||
531 | // If exercise or question is not set then exit. |
||||||
532 | if (empty($question_list) || empty($objExercise)) { |
||||||
533 | echo json_encode(['error' => true]); |
||||||
534 | if ($debug) { |
||||||
535 | if (empty($question_list)) { |
||||||
536 | error_log("question_list is empty"); |
||||||
537 | } |
||||||
538 | if (empty($objExercise)) { |
||||||
539 | error_log("objExercise is empty"); |
||||||
540 | } |
||||||
541 | } |
||||||
542 | exit; |
||||||
543 | } |
||||||
544 | |||||||
545 | if (WhispeakAuthPlugin::questionRequireAuthentify($question_id)) { |
||||||
546 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
547 | echo json_encode(['type' => 'one_per_page']); |
||||||
548 | exit; |
||||||
549 | } |
||||||
550 | |||||||
551 | echo json_encode(['ok' => true]); |
||||||
552 | exit; |
||||||
553 | } else { |
||||||
554 | ChamiloSession::erase(WhispeakAuthPlugin::SESSION_QUIZ_QUESTION); |
||||||
555 | } |
||||||
556 | |||||||
557 | // Getting information of the current exercise. |
||||||
558 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||||||
559 | $exercise_id = $exercise_stat_info['exe_exo_id']; |
||||||
560 | $attemptList = []; |
||||||
561 | // First time here we create an attempt (getting the exe_id). |
||||||
562 | if (!empty($exercise_stat_info)) { |
||||||
563 | // We know the user we get the exe_id. |
||||||
564 | $exeId = $exercise_stat_info['exe_id']; |
||||||
565 | $total_score = $exercise_stat_info['exe_result']; |
||||||
566 | // Getting the list of attempts |
||||||
567 | $attemptList = Event::getAllExerciseEventByExeId($exeId); |
||||||
0 ignored issues
–
show
|
|||||||
568 | } |
||||||
569 | |||||||
570 | // No exe id? Can't save answer. |
||||||
571 | if (empty($exeId)) { |
||||||
572 | // Fires an error. |
||||||
573 | echo json_encode(['error' => true]); |
||||||
574 | if ($debug) { |
||||||
575 | error_log('exe_id is empty'); |
||||||
576 | } |
||||||
577 | exit; |
||||||
578 | } |
||||||
579 | |||||||
580 | Session::write('exe_id', $exeId); |
||||||
581 | |||||||
582 | // Updating Reminder algorithm. |
||||||
583 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
584 | $bd_reminder_list = explode(',', $exercise_stat_info['questions_to_check']); |
||||||
585 | if (empty($remind_list)) { |
||||||
586 | $remind_list = $bd_reminder_list; |
||||||
587 | $new_list = []; |
||||||
588 | foreach ($bd_reminder_list as $item) { |
||||||
589 | if ($item != $question_id) { |
||||||
590 | $new_list[] = $item; |
||||||
591 | } |
||||||
592 | } |
||||||
593 | $remind_list = $new_list; |
||||||
594 | } else { |
||||||
595 | if (isset($remind_list[0])) { |
||||||
596 | if (!in_array($remind_list[0], $bd_reminder_list)) { |
||||||
597 | array_push($bd_reminder_list, $remind_list[0]); |
||||||
598 | } |
||||||
599 | $remind_list = $bd_reminder_list; |
||||||
600 | } |
||||||
601 | } |
||||||
602 | } |
||||||
603 | |||||||
604 | // Getting the total weight if the request is simple. |
||||||
605 | $total_weight = 0; |
||||||
606 | if ($type === 'simple') { |
||||||
607 | foreach ($question_list as $my_question_id) { |
||||||
608 | $objQuestionTmp = Question::read($my_question_id, $objExercise->course); |
||||||
609 | $total_weight += $objQuestionTmp->selectWeighting(); |
||||||
610 | } |
||||||
611 | } |
||||||
612 | unset($objQuestionTmp); |
||||||
613 | |||||||
614 | if ($debug) { |
||||||
615 | error_log('Starting questions loop in save_exercise_by_now'); |
||||||
616 | } |
||||||
617 | |||||||
618 | $now = time(); |
||||||
619 | if ('all' === $type) { |
||||||
620 | // Check we have at least one non-empty answer in the array |
||||||
621 | // provided by the user's click on the "Finish test" button. |
||||||
622 | $atLeastOneAnswer = false; |
||||||
623 | foreach ($question_list as $my_question_id) { |
||||||
624 | if (!empty($choice[$my_question_id])) { |
||||||
625 | $atLeastOneAnswer = true; |
||||||
626 | break; |
||||||
627 | } |
||||||
628 | } |
||||||
629 | |||||||
630 | if (!$atLeastOneAnswer) { |
||||||
631 | // Check if time is over. |
||||||
632 | if ($objExercise->expired_time != 0) { |
||||||
633 | $clockExpiredTime = ExerciseLib::get_session_time_control_key( |
||||||
634 | $objExercise->iid, |
||||||
635 | $learnpath_id, |
||||||
636 | $learnpath_item_id |
||||||
637 | ); |
||||||
638 | if (!empty($clockExpiredTime)) { |
||||||
639 | $timeLeft = api_strtotime($clockExpiredTime, 'UTC') - $now; |
||||||
640 | if ($timeLeft <= 0) { |
||||||
641 | // There's no time, but still no answers ... |
||||||
642 | echo json_encode(['ok' => true, 'savedAnswerMessage' => '']); |
||||||
643 | exit; |
||||||
644 | } |
||||||
645 | } |
||||||
646 | } |
||||||
647 | |||||||
648 | error_log( |
||||||
649 | 'In '.__FILE__.'::action save_exercise_by_now,'. |
||||||
650 | ' from user '.api_get_user_id(). |
||||||
651 | ' for track_e_exercises.exe_id = '.$exeId. |
||||||
652 | ', we received an empty set of answers.'. |
||||||
653 | 'Preventing submission to avoid overwriting w/ null.' |
||||||
654 | ); |
||||||
655 | echo json_encode(['error' => true]); |
||||||
656 | exit; |
||||||
657 | } |
||||||
658 | } |
||||||
659 | |||||||
660 | // Looping the question list from database (not from the user answer) |
||||||
661 | foreach ($question_list as $my_question_id) { |
||||||
662 | if ($type === 'simple' && $question_id != $my_question_id) { |
||||||
663 | continue; |
||||||
664 | } |
||||||
665 | $my_choice = isset($choice[$my_question_id]) ? $choice[$my_question_id] : null; |
||||||
666 | $objQuestionTmp = Question::read($my_question_id, $objExercise->course); |
||||||
667 | $myChoiceDegreeCertainty = null; |
||||||
668 | if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||||||
669 | if (isset($choiceDegreeCertainty[$my_question_id])) { |
||||||
670 | $myChoiceDegreeCertainty = $choiceDegreeCertainty[$my_question_id]; |
||||||
671 | } |
||||||
672 | } |
||||||
673 | if ($objQuestionTmp->type === UPLOAD_ANSWER) { |
||||||
674 | $my_choice = ''; |
||||||
675 | if (!empty($uploadAnswerFileNames)) { |
||||||
676 | // Clean user upload_answer folder |
||||||
677 | $userUploadAnswerSyspath = UserManager::getUserPathById(api_get_user_id(), 'system').'my_files'.'/upload_answer/'.$exeId.'/'.$my_question_id.'/*'; |
||||||
678 | foreach (glob($userUploadAnswerSyspath) as $file) { |
||||||
679 | $filename = basename($file); |
||||||
680 | if (!in_array($filename, $uploadAnswerFileNames[$my_question_id])) { |
||||||
681 | unlink($file); |
||||||
682 | } |
||||||
683 | } |
||||||
684 | $my_choice = implode('|', $uploadAnswerFileNames[$my_question_id]); |
||||||
685 | } |
||||||
686 | } |
||||||
687 | // Getting free choice data. |
||||||
688 | if ('all' === $type && in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION])) { |
||||||
689 | $my_choice = isset($_REQUEST['free_choice'][$my_question_id]) && !empty($_REQUEST['free_choice'][$my_question_id]) |
||||||
690 | ? $_REQUEST['free_choice'][$my_question_id] |
||||||
691 | : null; |
||||||
692 | } |
||||||
693 | |||||||
694 | if ($type === 'all') { |
||||||
695 | // If saving the whole exercise (not only one question), |
||||||
696 | // record the sum of individual max scores (called |
||||||
697 | // "exe_weighting" in track_e_exercises) |
||||||
698 | $total_weight += $objQuestionTmp->selectWeighting(); |
||||||
699 | } |
||||||
700 | |||||||
701 | // This variable came from exercise_submit_modal.php. |
||||||
702 | $hotspot_delineation_result = null; |
||||||
703 | if (isset($_SESSION['hotspot_delineation_result']) && |
||||||
704 | isset($_SESSION['hotspot_delineation_result'][$objExercise->selectId()]) |
||||||
705 | ) { |
||||||
706 | $hotspot_delineation_result = $_SESSION['hotspot_delineation_result'][$objExercise->selectId()][$my_question_id]; |
||||||
707 | } |
||||||
708 | |||||||
709 | if ('simple' === $type) { |
||||||
710 | // Getting old attempt in order to decrease the total score. |
||||||
711 | $old_result = $objExercise->manage_answer( |
||||||
712 | $exeId, |
||||||
713 | $my_question_id, |
||||||
714 | null, |
||||||
715 | 'exercise_show', |
||||||
716 | [], |
||||||
717 | false, |
||||||
718 | true, |
||||||
719 | false, |
||||||
720 | $objExercise->selectPropagateNeg() |
||||||
721 | ); |
||||||
722 | // Removing old score. |
||||||
723 | $total_score = $total_score - $old_result['score']; |
||||||
724 | } |
||||||
725 | |||||||
726 | $questionDuration = 0; |
||||||
727 | if (api_get_configuration_value('allow_time_per_question')) { |
||||||
728 | $extraFieldValue = new ExtraFieldValue('question'); |
||||||
729 | $value = $extraFieldValue->get_values_by_handler_and_field_variable($objQuestionTmp->iid, 'time'); |
||||||
730 | if (!empty($value) && isset($value['value']) && !empty($value['value'])) { |
||||||
731 | $questionDuration = Event::getAttemptQuestionDuration($exeId, $objQuestionTmp->iid); |
||||||
0 ignored issues
–
show
The method
getAttemptQuestionDuration() does not exist on Event .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
732 | if (empty($questionDuration)) { |
||||||
733 | echo json_encode(['error' => true]); |
||||||
734 | if ($debug) { |
||||||
735 | error_log("Question duration = 0, in exeId: $exeId, question_id: $my_question_id"); |
||||||
736 | } |
||||||
737 | exit; |
||||||
738 | } |
||||||
739 | } |
||||||
740 | } |
||||||
741 | |||||||
742 | // Deleting old attempt. |
||||||
743 | if (isset($attemptList) && !empty($attemptList[$my_question_id])) { |
||||||
744 | if ($debug) { |
||||||
745 | error_log("delete_attempt exe_id : $exeId, my_question_id: $my_question_id"); |
||||||
746 | } |
||||||
747 | Event::delete_attempt( |
||||||
0 ignored issues
–
show
The method
delete_attempt() does not exist on Event .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
748 | $exeId, |
||||||
749 | api_get_user_id(), |
||||||
750 | $course_id, |
||||||
751 | $session_id, |
||||||
752 | $my_question_id |
||||||
753 | ); |
||||||
754 | if (in_array($objQuestionTmp->type, [HOT_SPOT, HOT_SPOT_COMBINATION])) { |
||||||
755 | Event::delete_attempt_hotspot( |
||||||
0 ignored issues
–
show
The method
delete_attempt_hotspot() does not exist on Event .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
756 | $exeId, |
||||||
757 | api_get_user_id(), |
||||||
758 | $course_id, |
||||||
759 | $session_id, |
||||||
760 | $my_question_id |
||||||
761 | ); |
||||||
762 | } |
||||||
763 | |||||||
764 | if (isset($attemptList[$my_question_id]) && |
||||||
765 | isset($attemptList[$my_question_id]['marks']) |
||||||
766 | ) { |
||||||
767 | $total_score -= $attemptList[$my_question_id]['marks']; |
||||||
768 | } |
||||||
769 | } |
||||||
770 | |||||||
771 | // We're inside *one* question. Go through each possible answer for this question |
||||||
772 | if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||||||
773 | $myChoiceTmp = []; |
||||||
774 | $myChoiceTmp['choice'] = $my_choice; |
||||||
775 | $myChoiceTmp['choiceDegreeCertainty'] = $myChoiceDegreeCertainty; |
||||||
776 | $result = $objExercise->manage_answer( |
||||||
777 | $exeId, |
||||||
778 | $my_question_id, |
||||||
779 | $myChoiceTmp, |
||||||
780 | 'exercise_result', |
||||||
781 | $hot_spot_coordinates, |
||||||
782 | true, |
||||||
783 | false, |
||||||
784 | false, |
||||||
785 | $objExercise->selectPropagateNeg(), |
||||||
786 | $hotspot_delineation_result, |
||||||
787 | true, |
||||||
788 | false, |
||||||
789 | false, |
||||||
790 | $questionDuration |
||||||
791 | ); |
||||||
792 | } else { |
||||||
793 | $result = $objExercise->manage_answer( |
||||||
794 | $exeId, |
||||||
795 | $my_question_id, |
||||||
796 | $my_choice, |
||||||
797 | 'exercise_result', |
||||||
798 | $hot_spot_coordinates, |
||||||
799 | true, |
||||||
800 | false, |
||||||
801 | false, |
||||||
802 | $objExercise->selectPropagateNeg(), |
||||||
803 | $hotspot_delineation_result, |
||||||
804 | true, |
||||||
805 | false, |
||||||
806 | false, |
||||||
807 | $questionDuration |
||||||
808 | ); |
||||||
809 | } |
||||||
810 | |||||||
811 | // Adding the new score. |
||||||
812 | $total_score += $result['score']; |
||||||
813 | |||||||
814 | if ($debug) { |
||||||
815 | error_log("total_score: $total_score "); |
||||||
816 | error_log("total_weight: $total_weight "); |
||||||
817 | } |
||||||
818 | |||||||
819 | $duration = 0; |
||||||
820 | if ($type === 'all') { |
||||||
821 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||||||
822 | } |
||||||
823 | |||||||
824 | $key = ExerciseLib::get_time_control_key( |
||||||
825 | $exercise_id, |
||||||
826 | $exercise_stat_info['orig_lp_id'], |
||||||
827 | $exercise_stat_info['orig_lp_item_id'] |
||||||
828 | ); |
||||||
829 | |||||||
830 | $durationTime = Session::read('duration_time'); |
||||||
831 | if (isset($durationTime[$key]) && !empty($durationTime[$key])) { |
||||||
832 | if ($debug) { |
||||||
833 | error_log('Session time: '.$durationTime[$key]); |
||||||
834 | } |
||||||
835 | $duration = $now - $durationTime[$key]; |
||||||
836 | if (!empty($exercise_stat_info['exe_duration'])) { |
||||||
837 | $duration += $exercise_stat_info['exe_duration']; |
||||||
838 | } |
||||||
839 | $duration = (int) $duration; |
||||||
840 | } else { |
||||||
841 | if (!empty($exercise_stat_info['exe_duration'])) { |
||||||
842 | $duration = $exercise_stat_info['exe_duration']; |
||||||
843 | } |
||||||
844 | } |
||||||
845 | |||||||
846 | if ($debug) { |
||||||
847 | error_log('duration to save in DB:'.$duration); |
||||||
848 | } |
||||||
849 | Session::write('duration_time', [$key => $now]); |
||||||
850 | Event::updateEventExercise( |
||||||
0 ignored issues
–
show
The method
updateEventExercise() does not exist on Event .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
851 | $exeId, |
||||||
852 | $objExercise->selectId(), |
||||||
853 | $total_score, |
||||||
854 | $total_weight, |
||||||
855 | $session_id, |
||||||
856 | $exercise_stat_info['orig_lp_id'], |
||||||
857 | $exercise_stat_info['orig_lp_item_id'], |
||||||
858 | $exercise_stat_info['orig_lp_item_view_id'], |
||||||
859 | $duration, |
||||||
860 | $question_list, |
||||||
861 | 'incomplete', |
||||||
862 | $remind_list |
||||||
863 | ); |
||||||
864 | |||||||
865 | if (api_get_configuration_value('allow_time_per_question')) { |
||||||
866 | $questionStart = Session::read('question_start', []); |
||||||
867 | if (!empty($questionStart)) { |
||||||
868 | if (isset($questionStart[$my_question_id])) { |
||||||
869 | unset($questionStart[$my_question_id]); |
||||||
870 | } |
||||||
871 | array_filter($questionStart); |
||||||
872 | Session::write('question_start', $questionStart); |
||||||
873 | } |
||||||
874 | } |
||||||
875 | |||||||
876 | HookQuizQuestionAnswered::create() |
||||||
877 | ->setEventData( |
||||||
878 | [ |
||||||
879 | 'exe_id' => (int) $exeId, |
||||||
880 | 'quiz' => [ |
||||||
881 | 'id' => (int) $objExercise->iid, |
||||||
882 | 'title' => $objExercise->selectTitle(true), |
||||||
883 | ], |
||||||
884 | 'question' => [ |
||||||
885 | 'id' => (int) $my_question_id, |
||||||
886 | 'weight' => (float) $result['weight'], |
||||||
887 | ], |
||||||
888 | ] |
||||||
889 | ) |
||||||
890 | ->notifyQuizQuestionAnswered(); |
||||||
891 | |||||||
892 | // Destruction of the Question object |
||||||
893 | unset($objQuestionTmp); |
||||||
894 | if ($debug) { |
||||||
895 | error_log("---------- end question ------------"); |
||||||
896 | } |
||||||
897 | } |
||||||
898 | if ($debug) { |
||||||
899 | error_log('Finished questions loop in save_exercise_by_now'); |
||||||
900 | } |
||||||
901 | |||||||
902 | $questionsCount = count(explode(',', $exercise_stat_info['data_tracking'])); |
||||||
903 | $savedAnswersCount = $objExercise->countUserAnswersSavedInExercise($exeId); |
||||||
904 | |||||||
905 | if ($savedAnswersCount !== $questionsCount) { |
||||||
906 | $savedQuestionsMessage = Display::span( |
||||||
907 | sprintf(get_lang('XAnswersSavedByUsersFromXTotal'), $savedAnswersCount, $questionsCount), |
||||||
908 | ['class' => 'text-warning'] |
||||||
909 | ); |
||||||
910 | } else { |
||||||
911 | $savedQuestionsMessage = Display::span( |
||||||
912 | sprintf(get_lang('XAnswersSavedByUsersFromXTotal'), $savedAnswersCount, $questionsCount), |
||||||
913 | ['class' => 'text-success'] |
||||||
914 | ); |
||||||
915 | } |
||||||
916 | |||||||
917 | if ($type === 'all') { |
||||||
918 | if ($debug) { |
||||||
919 | error_log("result: ok - all"); |
||||||
920 | error_log(" ------ end ajax call ------- "); |
||||||
921 | } |
||||||
922 | echo json_encode(['ok' => true, 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
923 | exit; |
||||||
924 | } |
||||||
925 | |||||||
926 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
927 | if ($debug) { |
||||||
928 | error_log("result: one_per_page"); |
||||||
929 | error_log(" ------ end ajax call ------- "); |
||||||
930 | } |
||||||
931 | echo json_encode(['type' => 'one_per_page', 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
932 | exit; |
||||||
933 | } |
||||||
934 | if ($debug) { |
||||||
935 | error_log("result: ok"); |
||||||
936 | error_log(" ------ end ajax call ------- "); |
||||||
937 | } |
||||||
938 | echo json_encode(['ok' => true, 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
939 | break; |
||||||
940 | case 'show_question_attempt': |
||||||
941 | $isAllowedToEdit = api_is_allowed_to_edit(null, true, false, false); |
||||||
942 | |||||||
943 | if (!$isAllowedToEdit) { |
||||||
944 | api_not_allowed(true); |
||||||
945 | exit; |
||||||
946 | } |
||||||
947 | |||||||
948 | $questionId = isset($_GET['question']) ? (int) $_GET['question'] : 0; |
||||||
949 | $exerciseId = isset($_REQUEST['exercise']) ? (int) $_REQUEST['exercise'] : 0; |
||||||
950 | |||||||
951 | if (!$questionId || !$exerciseId) { |
||||||
952 | break; |
||||||
953 | } |
||||||
954 | |||||||
955 | $objExercise = new Exercise(); |
||||||
956 | $objExercise->read($exerciseId); |
||||||
957 | $objQuestion = Question::read($questionId); |
||||||
958 | $id = ''; |
||||||
959 | if (api_get_configuration_value('show_question_id')) { |
||||||
960 | $id = '<h4>#'.$objQuestion->course['code'].'-'.$objQuestion->iid.'</h4>'; |
||||||
961 | } |
||||||
962 | echo $id; |
||||||
963 | echo '<p class="lead">'.$objQuestion->get_question_type_name().'</p>'; |
||||||
964 | if (in_array($objQuestion->type, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||||||
965 | echo '<script> |
||||||
966 | $(function() { |
||||||
967 | $(".selectpicker").selectpicker({}); |
||||||
968 | }); |
||||||
969 | </script>'; |
||||||
970 | } |
||||||
971 | |||||||
972 | // Allows render MathJax elements in a ajax call |
||||||
973 | if (api_get_setting('include_asciimathml_script') === 'true') { |
||||||
974 | echo '<script> MathJax.Hub.Queue(["Typeset",MathJax.Hub]);</script>'; |
||||||
975 | } |
||||||
976 | |||||||
977 | if (in_array($objQuestion->type, [HOT_SPOT, HOT_SPOT_COMBINATION])) { |
||||||
978 | echo '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>'; |
||||||
979 | } |
||||||
980 | |||||||
981 | $attemptList = []; |
||||||
982 | if (!empty($exeId)) { |
||||||
983 | $attemptList = Event::getAllExerciseEventByExeId($exeId); |
||||||
984 | } |
||||||
985 | |||||||
986 | $userChoice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null; |
||||||
987 | |||||||
988 | ExerciseLib::showQuestion( |
||||||
989 | $objExercise, |
||||||
990 | $questionId, |
||||||
991 | false, |
||||||
992 | null, |
||||||
993 | null, |
||||||
994 | false, |
||||||
995 | true, |
||||||
996 | $userChoice, |
||||||
997 | true |
||||||
998 | ); |
||||||
999 | break; |
||||||
1000 | case 'show_question': |
||||||
1001 | $isAllowedToEdit = api_is_allowed_to_edit(null, true, false, false); |
||||||
1002 | |||||||
1003 | if (!$isAllowedToEdit) { |
||||||
1004 | api_not_allowed(true); |
||||||
1005 | exit; |
||||||
1006 | } |
||||||
1007 | |||||||
1008 | $questionId = isset($_GET['question']) ? (int) $_GET['question'] : 0; |
||||||
1009 | $exerciseId = isset($_REQUEST['exercise']) ? (int) $_REQUEST['exercise'] : 0; |
||||||
1010 | |||||||
1011 | if (!$questionId || !$exerciseId) { |
||||||
1012 | break; |
||||||
1013 | } |
||||||
1014 | |||||||
1015 | $objExercise = new Exercise(); |
||||||
1016 | $objExercise->read($exerciseId); |
||||||
1017 | $objQuestion = Question::read($questionId); |
||||||
1018 | $id = ''; |
||||||
1019 | if (api_get_configuration_value('show_question_id')) { |
||||||
1020 | $id = '<h4>#'.$objQuestion->course['code'].'-'.$objQuestion->iid.'</h4>'; |
||||||
1021 | } |
||||||
1022 | echo $id; |
||||||
1023 | echo '<p class="lead">'.$objQuestion->get_question_type_name().'</p>'; |
||||||
1024 | if (in_array($objQuestion->type, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||||||
1025 | echo '<script> |
||||||
1026 | $(function() { |
||||||
1027 | $(".selectpicker").selectpicker({}); |
||||||
1028 | }); |
||||||
1029 | </script>'; |
||||||
1030 | } |
||||||
1031 | |||||||
1032 | // Allows render MathJax elements in a ajax call |
||||||
1033 | if (api_get_setting('include_asciimathml_script') === 'true') { |
||||||
1034 | echo '<script> MathJax.Hub.Queue(["Typeset",MathJax.Hub]);</script>'; |
||||||
1035 | } |
||||||
1036 | |||||||
1037 | ExerciseLib::showQuestion( |
||||||
1038 | $objExercise, |
||||||
1039 | $questionId, |
||||||
1040 | false, |
||||||
1041 | null, |
||||||
1042 | null, |
||||||
1043 | false, |
||||||
1044 | true, |
||||||
1045 | false, |
||||||
1046 | true, |
||||||
1047 | true |
||||||
1048 | ); |
||||||
1049 | break; |
||||||
1050 | case 'get_quiz_embeddable': |
||||||
1051 | $exercises = ExerciseLib::get_all_exercises_for_course_id( |
||||||
1052 | api_get_course_info(), |
||||||
1053 | api_get_session_id(), |
||||||
1054 | api_get_course_int_id(), |
||||||
1055 | false |
||||||
1056 | ); |
||||||
1057 | |||||||
1058 | $exercises = array_filter( |
||||||
1059 | $exercises, |
||||||
1060 | function (array $exercise) { |
||||||
1061 | return ExerciseLib::isQuizEmbeddable($exercise); |
||||||
1062 | } |
||||||
1063 | ); |
||||||
1064 | |||||||
1065 | $result = []; |
||||||
1066 | |||||||
1067 | $codePath = api_get_path(WEB_CODE_PATH); |
||||||
1068 | |||||||
1069 | foreach ($exercises as $exercise) { |
||||||
1070 | $title = Security::remove_XSS(api_html_entity_decode($exercise['title'])); |
||||||
1071 | |||||||
1072 | $result[] = [ |
||||||
1073 | 'id' => $exercise['iid'], |
||||||
1074 | 'title' => strip_tags($title), |
||||||
1075 | ]; |
||||||
1076 | } |
||||||
1077 | |||||||
1078 | header('Content-Type: application/json'); |
||||||
1079 | echo json_encode($result); |
||||||
1080 | break; |
||||||
1081 | case 'browser_test': |
||||||
1082 | $quizCheckButtonEnabled = api_get_configuration_value('quiz_check_button_enable'); |
||||||
1083 | |||||||
1084 | if ($quizCheckButtonEnabled) { |
||||||
1085 | if (isset($_POST['sleep'])) { |
||||||
1086 | sleep(2); |
||||||
1087 | } |
||||||
1088 | |||||||
1089 | echo 'ok'; |
||||||
1090 | } |
||||||
1091 | |||||||
1092 | break; |
||||||
1093 | case 'quiz_confirm_saved_answers': |
||||||
1094 | if (false === api_get_configuration_value('quiz_confirm_saved_answers')) { |
||||||
1095 | break; |
||||||
1096 | } |
||||||
1097 | |||||||
1098 | $trackConfirmationId = isset($_POST['tc_id']) ? (int) $_POST['tc_id'] : 0; |
||||||
1099 | $cId = api_get_course_int_id(); |
||||||
1100 | $sessionId = api_get_session_id(); |
||||||
1101 | $userId = api_get_user_id(); |
||||||
1102 | $confirmed = !empty($_POST['quiz_confirm_saved_answers_check']); |
||||||
1103 | |||||||
1104 | $em = Database::getManager(); |
||||||
1105 | $repo = $em->getRepository('ChamiloCoreBundle:TrackEExerciseConfirmation'); |
||||||
1106 | |||||||
1107 | try { |
||||||
1108 | if (!$trackConfirmationId) { |
||||||
1109 | throw new Exception(get_lang('ErrorOccurred')); |
||||||
1110 | } |
||||||
1111 | |||||||
1112 | /** @var TrackEExerciseConfirmation $trackConfirmation */ |
||||||
1113 | $trackConfirmation = $repo->findOneBy( |
||||||
1114 | [ |
||||||
1115 | 'id' => $trackConfirmationId, |
||||||
1116 | 'userId' => $userId, |
||||||
1117 | 'courseId' => $cId, |
||||||
1118 | 'sessionId' => $sessionId, |
||||||
1119 | ], |
||||||
1120 | ['createdAt' => 'DESC'] |
||||||
1121 | ); |
||||||
1122 | |||||||
1123 | if (!$trackConfirmation) { |
||||||
0 ignored issues
–
show
|
|||||||
1124 | throw new Exception(get_lang('NotFound')); |
||||||
1125 | } |
||||||
1126 | |||||||
1127 | $trackConfirmation |
||||||
1128 | ->setConfirmed($confirmed) |
||||||
1129 | ->setUpdatedAt(api_get_utc_datetime(null, false, true)); |
||||||
1130 | |||||||
1131 | $em->persist($trackConfirmation); |
||||||
1132 | $em->flush(); |
||||||
1133 | |||||||
1134 | http_response_code(200); |
||||||
1135 | } catch (Exception $exception) { |
||||||
1136 | http_response_code(500); |
||||||
1137 | |||||||
1138 | echo Display::return_message($exception->getMessage(), 'error'); |
||||||
1139 | } |
||||||
1140 | |||||||
1141 | break; |
||||||
1142 | case 'sign_attempt': |
||||||
1143 | api_block_anonymous_users(); |
||||||
1144 | if ('true' !== api_get_plugin_setting('exercise_signature', 'tool_enable')) { |
||||||
1145 | exit; |
||||||
1146 | } |
||||||
1147 | |||||||
1148 | $file = isset($_REQUEST['file']) ? $_REQUEST['file'] : ''; |
||||||
1149 | if (empty($exeId) || empty($file)) { |
||||||
1150 | echo 0; |
||||||
1151 | exit; |
||||||
1152 | } |
||||||
1153 | |||||||
1154 | $file = str_replace(' ', '+', $file); |
||||||
1155 | $track = ExerciseLib::get_exercise_track_exercise_info($exeId); |
||||||
1156 | if ($track) { |
||||||
1157 | $result = ExerciseSignaturePlugin::saveSignature($currentUserId, $track, $file); |
||||||
1158 | if ($result) { |
||||||
1159 | echo 1; |
||||||
1160 | exit; |
||||||
1161 | } |
||||||
1162 | } |
||||||
1163 | echo 0; |
||||||
1164 | break; |
||||||
1165 | case 'upload_answer': |
||||||
1166 | api_block_anonymous_users(); |
||||||
1167 | |||||||
1168 | if (isset($_REQUEST['chunkAction']) && 'send' === $_REQUEST['chunkAction']) { |
||||||
1169 | // It uploads the files in chunks |
||||||
1170 | if (!empty($_FILES)) { |
||||||
1171 | $tempDirectory = api_get_path(SYS_ARCHIVE_PATH); |
||||||
1172 | $files = $_FILES['files']; |
||||||
1173 | $fileList = []; |
||||||
1174 | foreach ($files as $name => $array) { |
||||||
1175 | $counter = 0; |
||||||
1176 | foreach ($array as $data) { |
||||||
1177 | $fileList[$counter][$name] = $data; |
||||||
1178 | $counter++; |
||||||
1179 | } |
||||||
1180 | } |
||||||
1181 | if (!empty($fileList)) { |
||||||
1182 | foreach ($fileList as $n => $file) { |
||||||
1183 | $tmpFile = disable_dangerous_file( |
||||||
1184 | api_replace_dangerous_char($file['name']) |
||||||
1185 | ); |
||||||
1186 | |||||||
1187 | file_put_contents( |
||||||
1188 | $tempDirectory.$tmpFile, |
||||||
1189 | fopen($file['tmp_name'], 'r'), |
||||||
1190 | FILE_APPEND |
||||||
1191 | ); |
||||||
1192 | } |
||||||
1193 | } |
||||||
1194 | } |
||||||
1195 | echo json_encode([ |
||||||
1196 | 'files' => $_FILES, |
||||||
1197 | 'errorStatus' => 0, |
||||||
1198 | ]); |
||||||
1199 | exit; |
||||||
1200 | } else { |
||||||
1201 | if (!empty($_FILES)) { |
||||||
1202 | $currentDirectory = Security::remove_XSS($_REQUEST['curdirpath']); |
||||||
1203 | $userId = api_get_user_id(); |
||||||
1204 | |||||||
1205 | // Upload answer path is created inside user personal folder my_files/upload_answer/[exe_id]/[question_id] |
||||||
1206 | $syspath = UserManager::getUserPathById($userId, 'system').'my_files'.$currentDirectory; |
||||||
1207 | @mkdir($syspath, api_get_permissions_for_new_directories(), true); |
||||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
mkdir() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||||
1208 | $webpath = UserManager::getUserPathById($userId, 'web').'my_files'.$currentDirectory; |
||||||
1209 | |||||||
1210 | $files = $_FILES['files']; |
||||||
1211 | $fileList = []; |
||||||
1212 | foreach ($files as $name => $array) { |
||||||
1213 | $counter = 0; |
||||||
1214 | foreach ($array as $data) { |
||||||
1215 | $fileList[$counter][$name] = $data; |
||||||
1216 | $counter++; |
||||||
1217 | } |
||||||
1218 | } |
||||||
1219 | $resultList = []; |
||||||
1220 | foreach ($fileList as $file) { |
||||||
1221 | $json = []; |
||||||
1222 | |||||||
1223 | if (isset($_REQUEST['chunkAction']) && 'done' === $_REQUEST['chunkAction']) { |
||||||
1224 | // to rename and move the finished file |
||||||
1225 | $chunkedFile = api_get_path(SYS_ARCHIVE_PATH).$file['name']; |
||||||
1226 | $file['tmp_name'] = $chunkedFile; |
||||||
1227 | $file['size'] = filesize($chunkedFile); |
||||||
1228 | $file['copy_file'] = true; |
||||||
1229 | } |
||||||
1230 | |||||||
1231 | $filename = api_replace_dangerous_char($file['name']); |
||||||
1232 | $filename = disable_dangerous_file($filename); |
||||||
1233 | |||||||
1234 | if (isset($file['copy_file']) && $file['copy_file']) { |
||||||
1235 | $uploaded = copy($file['tmp_name'], $syspath.$filename); |
||||||
1236 | @unlink($file['tmp_name']); |
||||||
0 ignored issues
–
show
It seems like you do not handle an error condition for
unlink() . This can introduce security issues, and is generally not recommended.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||||||
1237 | } else { |
||||||
1238 | $uploaded = move_uploaded_file($file['tmp_name'], $syspath.$filename); |
||||||
1239 | } |
||||||
1240 | |||||||
1241 | if ($uploaded) { |
||||||
1242 | $title = $filename; |
||||||
1243 | $url = $webpath.$filename; |
||||||
1244 | $json['name'] = api_htmlentities($title); |
||||||
1245 | $json['link'] = Display::url( |
||||||
1246 | api_htmlentities($title), |
||||||
1247 | api_htmlentities($url), |
||||||
1248 | ['target' => '_blank'] |
||||||
1249 | ); |
||||||
1250 | $json['url'] = $url; |
||||||
1251 | $json['size'] = format_file_size($file['size']); |
||||||
1252 | $json['type'] = api_htmlentities($file['type']); |
||||||
1253 | $json['result'] = Display::return_icon( |
||||||
1254 | 'accept.png', |
||||||
1255 | get_lang('Uploaded') |
||||||
1256 | ); |
||||||
1257 | } else { |
||||||
1258 | $json['name'] = isset($file['name']) ? $filename : get_lang('Unknown'); |
||||||
1259 | $json['url'] = ''; |
||||||
1260 | $json['error'] = get_lang('Error'); |
||||||
1261 | } |
||||||
1262 | $resultList[] = $json; |
||||||
1263 | } |
||||||
1264 | echo json_encode(['files' => $resultList]); |
||||||
1265 | exit; |
||||||
1266 | } |
||||||
1267 | } |
||||||
1268 | break; |
||||||
1269 | default: |
||||||
1270 | echo ''; |
||||||
1271 | } |
||||||
1272 | exit; |
||||||
1273 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.