chamilo /
chamilo-lms
| 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 = $_REQUEST['type'] ?? null; |
||||||
| 493 | |||||||
| 494 | // Questions choices. |
||||||
| 495 | $choice = $_REQUEST['choice'] ?? []; |
||||||
| 496 | |||||||
| 497 | // certainty degree choice |
||||||
| 498 | $choiceDegreeCertainty = $_REQUEST['choiceDegreeCertainty'] ?? []; |
||||||
| 499 | |||||||
| 500 | // Hot spot coordinates from all questions. |
||||||
| 501 | $hot_spot_coordinates = $_REQUEST['hotspot'] ?? []; |
||||||
| 502 | |||||||
| 503 | // the filenames in upload answer type |
||||||
| 504 | $uploadAnswerFileNames = $_REQUEST['uploadChoice'] ?? []; |
||||||
| 505 | |||||||
| 506 | // There is a reminder? |
||||||
| 507 | $remind_list = !empty($_REQUEST['remind_list']) ? array_keys($_REQUEST['remind_list']) : []; |
||||||
| 508 | |||||||
| 509 | // Needed in manage_answer. |
||||||
| 510 | $learnpath_id = isset($_REQUEST['learnpath_id']) ? (int) $_REQUEST['learnpath_id'] : 0; |
||||||
| 511 | $learnpath_item_id = isset($_REQUEST['learnpath_item_id']) ? (int) $_REQUEST['learnpath_item_id'] : 0; |
||||||
| 512 | |||||||
| 513 | if ($debug) { |
||||||
| 514 | error_log("exe_id = $exeId"); |
||||||
| 515 | error_log("type = $type"); |
||||||
| 516 | error_log("choice = ".print_r($choice, 1)." "); |
||||||
| 517 | error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates, 1)); |
||||||
| 518 | error_log("remind_list = ".print_r($remind_list, 1)); |
||||||
| 519 | error_log("uploadAnswerFileNames = ".print_r($uploadAnswerFileNames, 1)); |
||||||
| 520 | error_log("--------------------------------"); |
||||||
| 521 | } |
||||||
| 522 | |||||||
| 523 | /** @var Exercise $objExercise */ |
||||||
| 524 | $objExercise = Session::read('objExercise'); |
||||||
| 525 | |||||||
| 526 | // Question info. |
||||||
| 527 | $question_id = isset($_REQUEST['question_id']) ? (int) $_REQUEST['question_id'] : null; |
||||||
| 528 | $question_list = Session::read('questionList'); |
||||||
| 529 | |||||||
| 530 | // If exercise or question is not set then exit. |
||||||
| 531 | if (empty($question_list) || empty($objExercise)) { |
||||||
| 532 | echo json_encode(['error' => true]); |
||||||
| 533 | if ($debug) { |
||||||
| 534 | if (empty($question_list)) { |
||||||
| 535 | error_log("question_list is empty"); |
||||||
| 536 | } |
||||||
| 537 | if (empty($objExercise)) { |
||||||
| 538 | error_log("objExercise is empty"); |
||||||
| 539 | } |
||||||
| 540 | } |
||||||
| 541 | exit; |
||||||
| 542 | } |
||||||
| 543 | |||||||
| 544 | if (WhispeakAuthPlugin::questionRequireAuthentify($question_id)) { |
||||||
| 545 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
| 546 | echo json_encode(['type' => 'one_per_page']); |
||||||
| 547 | exit; |
||||||
| 548 | } |
||||||
| 549 | |||||||
| 550 | echo json_encode(['ok' => true]); |
||||||
| 551 | exit; |
||||||
| 552 | } else { |
||||||
| 553 | ChamiloSession::erase(WhispeakAuthPlugin::SESSION_QUIZ_QUESTION); |
||||||
| 554 | } |
||||||
| 555 | |||||||
| 556 | // Getting information of the current exercise. |
||||||
| 557 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||||||
| 558 | $exercise_id = $exercise_stat_info['exe_exo_id']; |
||||||
| 559 | $attemptList = []; |
||||||
| 560 | // First time here we create an attempt (getting the exe_id). |
||||||
| 561 | if (!empty($exercise_stat_info)) { |
||||||
| 562 | // We know the user we get the exe_id. |
||||||
| 563 | $exeId = $exercise_stat_info['exe_id']; |
||||||
| 564 | $total_score = $exercise_stat_info['exe_result']; |
||||||
| 565 | // Getting the list of attempts |
||||||
| 566 | $attemptList = Event::getAllExerciseEventByExeId($exeId); |
||||||
|
0 ignored issues
–
show
|
|||||||
| 567 | } |
||||||
| 568 | |||||||
| 569 | // No exe id? Can't save answer. |
||||||
| 570 | if (empty($exeId)) { |
||||||
| 571 | // Fires an error. |
||||||
| 572 | echo json_encode(['error' => true]); |
||||||
| 573 | if ($debug) { |
||||||
| 574 | error_log('exe_id is empty'); |
||||||
| 575 | } |
||||||
| 576 | exit; |
||||||
| 577 | } |
||||||
| 578 | |||||||
| 579 | Session::write('exe_id', $exeId); |
||||||
| 580 | |||||||
| 581 | // Updating Reminder algorithm. |
||||||
| 582 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
| 583 | $bd_reminder_list = explode(',', $exercise_stat_info['questions_to_check']); |
||||||
| 584 | if (empty($remind_list)) { |
||||||
| 585 | $remind_list = $bd_reminder_list; |
||||||
| 586 | $new_list = []; |
||||||
| 587 | foreach ($bd_reminder_list as $item) { |
||||||
| 588 | if ($item != $question_id) { |
||||||
| 589 | $new_list[] = $item; |
||||||
| 590 | } |
||||||
| 591 | } |
||||||
| 592 | $remind_list = $new_list; |
||||||
| 593 | } else { |
||||||
| 594 | if (isset($remind_list[0])) { |
||||||
| 595 | if (!in_array($remind_list[0], $bd_reminder_list)) { |
||||||
| 596 | array_push($bd_reminder_list, $remind_list[0]); |
||||||
| 597 | } |
||||||
| 598 | $remind_list = $bd_reminder_list; |
||||||
| 599 | } |
||||||
| 600 | } |
||||||
| 601 | } |
||||||
| 602 | |||||||
| 603 | // Getting the total weight if the request is simple. |
||||||
| 604 | $total_weight = 0; |
||||||
| 605 | if ($type === 'simple') { |
||||||
| 606 | foreach ($question_list as $my_question_id) { |
||||||
| 607 | $objQuestionTmp = Question::read($my_question_id, $objExercise->course); |
||||||
| 608 | $total_weight += $objQuestionTmp->selectWeighting(); |
||||||
| 609 | } |
||||||
| 610 | } |
||||||
| 611 | unset($objQuestionTmp); |
||||||
| 612 | |||||||
| 613 | if ($debug) { |
||||||
| 614 | error_log('Starting questions loop in save_exercise_by_now'); |
||||||
| 615 | } |
||||||
| 616 | |||||||
| 617 | $now = time(); |
||||||
| 618 | if ('all' === $type) { |
||||||
| 619 | // Check we have at least one non-empty answer in the array |
||||||
| 620 | // provided by the user's click on the "Finish test" button. |
||||||
| 621 | $atLeastOneAnswer = false; |
||||||
| 622 | foreach ($question_list as $my_question_id) { |
||||||
| 623 | if (!empty($choice[$my_question_id])) { |
||||||
| 624 | $atLeastOneAnswer = true; |
||||||
| 625 | break; |
||||||
| 626 | } |
||||||
| 627 | } |
||||||
| 628 | |||||||
| 629 | if (!$atLeastOneAnswer) { |
||||||
| 630 | // Check if time is over. |
||||||
| 631 | if ($objExercise->expired_time != 0) { |
||||||
| 632 | $clockExpiredTime = ExerciseLib::get_session_time_control_key( |
||||||
| 633 | $objExercise->iid, |
||||||
| 634 | $learnpath_id, |
||||||
| 635 | $learnpath_item_id |
||||||
| 636 | ); |
||||||
| 637 | if (!empty($clockExpiredTime)) { |
||||||
| 638 | $timeLeft = api_strtotime($clockExpiredTime, 'UTC') - $now; |
||||||
| 639 | if ($timeLeft <= 0) { |
||||||
| 640 | // There's no time, but still no answers ... |
||||||
| 641 | echo json_encode(['ok' => true, 'savedAnswerMessage' => '']); |
||||||
| 642 | exit; |
||||||
| 643 | } |
||||||
| 644 | } |
||||||
| 645 | } |
||||||
| 646 | |||||||
| 647 | error_log( |
||||||
| 648 | 'In '.__FILE__.'::action save_exercise_by_now,'. |
||||||
| 649 | ' from user '.api_get_user_id(). |
||||||
| 650 | ' for track_e_exercises.exe_id = '.$exeId. |
||||||
| 651 | ', we received an empty set of answers.'. |
||||||
| 652 | 'Preventing submission to avoid overwriting w/ null.' |
||||||
| 653 | ); |
||||||
| 654 | echo json_encode(['error' => true]); |
||||||
| 655 | exit; |
||||||
| 656 | } |
||||||
| 657 | } |
||||||
| 658 | |||||||
| 659 | // Looping the question list from database (not from the user answer) |
||||||
| 660 | foreach ($question_list as $my_question_id) { |
||||||
| 661 | if ($type === 'simple' && $question_id != $my_question_id) { |
||||||
| 662 | continue; |
||||||
| 663 | } |
||||||
| 664 | $my_choice = $choice[$my_question_id] ?? null; |
||||||
| 665 | $objQuestionTmp = Question::read($my_question_id, $objExercise->course); |
||||||
| 666 | $myChoiceDegreeCertainty = null; |
||||||
| 667 | if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||||||
| 668 | if (isset($choiceDegreeCertainty[$my_question_id])) { |
||||||
| 669 | $myChoiceDegreeCertainty = $choiceDegreeCertainty[$my_question_id]; |
||||||
| 670 | } |
||||||
| 671 | } |
||||||
| 672 | if ($objQuestionTmp->type === UPLOAD_ANSWER) { |
||||||
| 673 | $my_choice = ''; |
||||||
| 674 | if (!empty($uploadAnswerFileNames)) { |
||||||
| 675 | // Clean user upload_answer folder |
||||||
| 676 | $userUploadAnswerSyspath = UserManager::getUserPathById(api_get_user_id(), 'system').'my_files'.'/upload_answer/'.$exeId.'/'.$my_question_id.'/*'; |
||||||
| 677 | foreach (glob($userUploadAnswerSyspath) as $file) { |
||||||
| 678 | $filename = basename($file); |
||||||
| 679 | if (!in_array($filename, $uploadAnswerFileNames[$my_question_id])) { |
||||||
| 680 | unlink($file); |
||||||
| 681 | } |
||||||
| 682 | } |
||||||
| 683 | $my_choice = implode('|', $uploadAnswerFileNames[$my_question_id]); |
||||||
| 684 | } |
||||||
| 685 | } |
||||||
| 686 | // Getting free choice data. |
||||||
| 687 | if ('all' === $type && in_array($objQuestionTmp->type, [FREE_ANSWER, ORAL_EXPRESSION])) { |
||||||
| 688 | $my_choice = isset($_REQUEST['free_choice'][$my_question_id]) && !empty($_REQUEST['free_choice'][$my_question_id]) |
||||||
| 689 | ? $_REQUEST['free_choice'][$my_question_id] |
||||||
| 690 | : null; |
||||||
| 691 | } |
||||||
| 692 | |||||||
| 693 | if ($type === 'all') { |
||||||
| 694 | // If saving the whole exercise (not only one question), |
||||||
| 695 | // record the sum of individual max scores (called |
||||||
| 696 | // "exe_weighting" in track_e_exercises) |
||||||
| 697 | $total_weight += $objQuestionTmp->selectWeighting(); |
||||||
| 698 | } |
||||||
| 699 | |||||||
| 700 | // This variable came from exercise_submit_modal.php. |
||||||
| 701 | $hotspot_delineation_result = null; |
||||||
| 702 | if (isset($_SESSION['hotspot_delineation_result'][$objExercise->selectId()]) |
||||||
| 703 | ) { |
||||||
| 704 | $hotspot_delineation_result = $_SESSION['hotspot_delineation_result'][$objExercise->selectId()][$my_question_id]; |
||||||
| 705 | } |
||||||
| 706 | |||||||
| 707 | if ('simple' === $type) { |
||||||
| 708 | // Getting old attempt in order to decrease the total score. |
||||||
| 709 | $old_result = $objExercise->manage_answer( |
||||||
| 710 | $exeId, |
||||||
| 711 | $my_question_id, |
||||||
| 712 | null, |
||||||
| 713 | 'exercise_show', |
||||||
| 714 | [], |
||||||
| 715 | false, |
||||||
| 716 | true, |
||||||
| 717 | false, |
||||||
| 718 | $objExercise->selectPropagateNeg() |
||||||
| 719 | ); |
||||||
| 720 | // Removing old score. |
||||||
| 721 | $total_score = $total_score - $old_result['score']; |
||||||
| 722 | } |
||||||
| 723 | |||||||
| 724 | $questionDuration = 0; |
||||||
| 725 | if (api_get_configuration_value('allow_time_per_question')) { |
||||||
| 726 | $extraFieldValue = new ExtraFieldValue('question'); |
||||||
| 727 | $value = $extraFieldValue->get_values_by_handler_and_field_variable($objQuestionTmp->iid, 'time'); |
||||||
| 728 | if (!empty($value) && isset($value['value']) && !empty($value['value'])) { |
||||||
| 729 | $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. Loading history...
|
|||||||
| 730 | if (empty($questionDuration)) { |
||||||
| 731 | echo json_encode(['error' => true]); |
||||||
| 732 | if ($debug) { |
||||||
| 733 | error_log("Question duration = 0, in exeId: $exeId, question_id: $my_question_id"); |
||||||
| 734 | } |
||||||
| 735 | exit; |
||||||
| 736 | } |
||||||
| 737 | } |
||||||
| 738 | } |
||||||
| 739 | |||||||
| 740 | // Deleting old attempt. |
||||||
| 741 | if (isset($attemptList) && !empty($attemptList[$my_question_id])) { |
||||||
| 742 | if ($debug) { |
||||||
| 743 | error_log("delete_attempt exe_id : $exeId, my_question_id: $my_question_id"); |
||||||
| 744 | } |
||||||
| 745 | 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. Loading history...
|
|||||||
| 746 | $exeId, |
||||||
| 747 | api_get_user_id(), |
||||||
| 748 | $course_id, |
||||||
| 749 | $session_id, |
||||||
| 750 | $my_question_id |
||||||
| 751 | ); |
||||||
| 752 | if (in_array($objQuestionTmp->type, [HOT_SPOT, HOT_SPOT_COMBINATION])) { |
||||||
| 753 | 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. Loading history...
|
|||||||
| 754 | $exeId, |
||||||
| 755 | api_get_user_id(), |
||||||
| 756 | $course_id, |
||||||
| 757 | $session_id, |
||||||
| 758 | $my_question_id |
||||||
| 759 | ); |
||||||
| 760 | } |
||||||
| 761 | |||||||
| 762 | if (isset($attemptList[$my_question_id]) && |
||||||
| 763 | isset($attemptList[$my_question_id]['marks']) |
||||||
| 764 | ) { |
||||||
| 765 | $total_score -= $attemptList[$my_question_id]['marks']; |
||||||
| 766 | } |
||||||
| 767 | } |
||||||
| 768 | |||||||
| 769 | // We're inside *one* question. Go through each possible answer for this question |
||||||
| 770 | if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { |
||||||
| 771 | $myChoiceTmp = []; |
||||||
| 772 | $myChoiceTmp['choice'] = $my_choice; |
||||||
| 773 | $myChoiceTmp['choiceDegreeCertainty'] = $myChoiceDegreeCertainty; |
||||||
| 774 | $result = $objExercise->manage_answer( |
||||||
| 775 | $exeId, |
||||||
| 776 | $my_question_id, |
||||||
| 777 | $myChoiceTmp, |
||||||
| 778 | 'exercise_result', |
||||||
| 779 | $hot_spot_coordinates, |
||||||
| 780 | true, |
||||||
| 781 | false, |
||||||
| 782 | false, |
||||||
| 783 | $objExercise->selectPropagateNeg(), |
||||||
| 784 | $hotspot_delineation_result, |
||||||
| 785 | true, |
||||||
| 786 | false, |
||||||
| 787 | false, |
||||||
| 788 | $questionDuration |
||||||
| 789 | ); |
||||||
| 790 | } else { |
||||||
| 791 | $result = $objExercise->manage_answer( |
||||||
| 792 | $exeId, |
||||||
| 793 | $my_question_id, |
||||||
| 794 | $my_choice, |
||||||
| 795 | 'exercise_result', |
||||||
| 796 | $hot_spot_coordinates, |
||||||
| 797 | true, |
||||||
| 798 | false, |
||||||
| 799 | false, |
||||||
| 800 | $objExercise->selectPropagateNeg(), |
||||||
| 801 | $hotspot_delineation_result, |
||||||
| 802 | true, |
||||||
| 803 | false, |
||||||
| 804 | false, |
||||||
| 805 | $questionDuration |
||||||
| 806 | ); |
||||||
| 807 | } |
||||||
| 808 | |||||||
| 809 | // Adding the new score. |
||||||
| 810 | $total_score += $result['score']; |
||||||
| 811 | |||||||
| 812 | if ($debug) { |
||||||
| 813 | error_log("total_score: $total_score "); |
||||||
| 814 | error_log("total_weight: $total_weight "); |
||||||
| 815 | } |
||||||
| 816 | |||||||
| 817 | $duration = 0; |
||||||
| 818 | if ($type === 'all') { |
||||||
| 819 | $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); |
||||||
| 820 | } |
||||||
| 821 | |||||||
| 822 | $key = ExerciseLib::get_time_control_key( |
||||||
| 823 | $exercise_id, |
||||||
| 824 | $exercise_stat_info['orig_lp_id'], |
||||||
| 825 | $exercise_stat_info['orig_lp_item_id'] |
||||||
| 826 | ); |
||||||
| 827 | |||||||
| 828 | $durationTime = Session::read('duration_time'); |
||||||
| 829 | if (isset($durationTime[$key]) && !empty($durationTime[$key])) { |
||||||
| 830 | if ($debug) { |
||||||
| 831 | error_log('Session time: '.$durationTime[$key]); |
||||||
| 832 | } |
||||||
| 833 | $duration = $now - $durationTime[$key]; |
||||||
| 834 | if (!empty($exercise_stat_info['exe_duration'])) { |
||||||
| 835 | $duration += $exercise_stat_info['exe_duration']; |
||||||
| 836 | } |
||||||
| 837 | $duration = (int) $duration; |
||||||
| 838 | } else { |
||||||
| 839 | if (!empty($exercise_stat_info['exe_duration'])) { |
||||||
| 840 | $duration = $exercise_stat_info['exe_duration']; |
||||||
| 841 | } |
||||||
| 842 | } |
||||||
| 843 | |||||||
| 844 | if ($debug) { |
||||||
| 845 | error_log('duration to save in DB:'.$duration); |
||||||
| 846 | } |
||||||
| 847 | Session::write('duration_time', [$key => $now]); |
||||||
| 848 | 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. Loading history...
|
|||||||
| 849 | $exeId, |
||||||
| 850 | $objExercise->selectId(), |
||||||
| 851 | $total_score, |
||||||
| 852 | $total_weight, |
||||||
| 853 | $session_id, |
||||||
| 854 | $exercise_stat_info['orig_lp_id'], |
||||||
| 855 | $exercise_stat_info['orig_lp_item_id'], |
||||||
| 856 | $exercise_stat_info['orig_lp_item_view_id'], |
||||||
| 857 | $duration, |
||||||
| 858 | $question_list, |
||||||
| 859 | 'incomplete', |
||||||
| 860 | $remind_list |
||||||
| 861 | ); |
||||||
| 862 | |||||||
| 863 | if (api_get_configuration_value('allow_time_per_question')) { |
||||||
| 864 | $questionStart = Session::read('question_start', []); |
||||||
| 865 | if (!empty($questionStart)) { |
||||||
| 866 | if (isset($questionStart[$my_question_id])) { |
||||||
| 867 | unset($questionStart[$my_question_id]); |
||||||
| 868 | } |
||||||
| 869 | array_filter($questionStart); |
||||||
| 870 | Session::write('question_start', $questionStart); |
||||||
| 871 | } |
||||||
| 872 | } |
||||||
| 873 | |||||||
| 874 | HookQuizQuestionAnswered::create() |
||||||
| 875 | ->setEventData( |
||||||
| 876 | [ |
||||||
| 877 | 'exe_id' => (int) $exeId, |
||||||
| 878 | 'quiz' => [ |
||||||
| 879 | 'id' => (int) $objExercise->iid, |
||||||
| 880 | 'title' => $objExercise->selectTitle(true), |
||||||
| 881 | ], |
||||||
| 882 | 'question' => [ |
||||||
| 883 | 'id' => (int) $my_question_id, |
||||||
| 884 | 'weight' => (float) $result['weight'], |
||||||
| 885 | ], |
||||||
| 886 | ] |
||||||
| 887 | ) |
||||||
| 888 | ->notifyQuizQuestionAnswered(); |
||||||
| 889 | |||||||
| 890 | // Destruction of the Question object |
||||||
| 891 | unset($objQuestionTmp); |
||||||
| 892 | if ($debug) { |
||||||
| 893 | error_log("---------- end question ------------"); |
||||||
| 894 | } |
||||||
| 895 | } |
||||||
| 896 | if ($debug) { |
||||||
| 897 | error_log('Finished questions loop in save_exercise_by_now'); |
||||||
| 898 | } |
||||||
| 899 | |||||||
| 900 | $questionsCount = count(explode(',', $exercise_stat_info['data_tracking'])); |
||||||
| 901 | $savedAnswersCount = $objExercise->countUserAnswersSavedInExercise($exeId); |
||||||
| 902 | |||||||
| 903 | if ($savedAnswersCount !== $questionsCount) { |
||||||
| 904 | $savedQuestionsMessage = Display::span( |
||||||
| 905 | sprintf(get_lang('XAnswersSavedByUsersFromXTotal'), $savedAnswersCount, $questionsCount), |
||||||
| 906 | ['class' => 'text-warning'] |
||||||
| 907 | ); |
||||||
| 908 | } else { |
||||||
| 909 | $savedQuestionsMessage = Display::span( |
||||||
| 910 | sprintf(get_lang('XAnswersSavedByUsersFromXTotal'), $savedAnswersCount, $questionsCount), |
||||||
| 911 | ['class' => 'text-success'] |
||||||
| 912 | ); |
||||||
| 913 | } |
||||||
| 914 | |||||||
| 915 | if ($type === 'all') { |
||||||
| 916 | if ($debug) { |
||||||
| 917 | error_log("result: ok - all"); |
||||||
| 918 | error_log(" ------ end ajax call ------- "); |
||||||
| 919 | } |
||||||
| 920 | echo json_encode(['ok' => true, 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
| 921 | exit; |
||||||
| 922 | } |
||||||
| 923 | |||||||
| 924 | if ($objExercise->type == ONE_PER_PAGE) { |
||||||
| 925 | if ($debug) { |
||||||
| 926 | error_log("result: one_per_page"); |
||||||
| 927 | error_log(" ------ end ajax call ------- "); |
||||||
| 928 | } |
||||||
| 929 | echo json_encode(['type' => 'one_per_page', 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
| 930 | exit; |
||||||
| 931 | } |
||||||
| 932 | if ($debug) { |
||||||
| 933 | error_log("result: ok"); |
||||||
| 934 | error_log(" ------ end ajax call ------- "); |
||||||
| 935 | } |
||||||
| 936 | echo json_encode(['ok' => true, 'savedAnswerMessage' => $savedQuestionsMessage]); |
||||||
| 937 | break; |
||||||
| 938 | case 'show_question_attempt': |
||||||
| 939 | $isAllowedToEdit = api_is_allowed_to_edit(null, true, false, false); |
||||||
| 940 | |||||||
| 941 | if (!$isAllowedToEdit) { |
||||||
| 942 | api_not_allowed(true); |
||||||
| 943 | exit; |
||||||
| 944 | } |
||||||
| 945 | |||||||
| 946 | $questionId = isset($_GET['question']) ? (int) $_GET['question'] : 0; |
||||||
| 947 | $exerciseId = isset($_REQUEST['exercise']) ? (int) $_REQUEST['exercise'] : 0; |
||||||
| 948 | |||||||
| 949 | if (!$questionId || !$exerciseId) { |
||||||
| 950 | break; |
||||||
| 951 | } |
||||||
| 952 | |||||||
| 953 | $objExercise = new Exercise(); |
||||||
| 954 | $objExercise->read($exerciseId); |
||||||
| 955 | $objQuestion = Question::read($questionId); |
||||||
| 956 | $id = ''; |
||||||
| 957 | if (api_get_configuration_value('show_question_id')) { |
||||||
| 958 | $id = '<h4>#'.$objQuestion->course['code'].'-'.$objQuestion->iid.'</h4>'; |
||||||
| 959 | } |
||||||
| 960 | echo $id; |
||||||
| 961 | echo '<p class="lead">'.$objQuestion->get_question_type_name().'</p>'; |
||||||
| 962 | if (in_array($objQuestion->type, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||||||
| 963 | echo '<script> |
||||||
| 964 | $(function() { |
||||||
| 965 | $(".selectpicker").selectpicker({}); |
||||||
| 966 | }); |
||||||
| 967 | </script>'; |
||||||
| 968 | } |
||||||
| 969 | |||||||
| 970 | // Allows render MathJax elements in a ajax call |
||||||
| 971 | if (api_get_setting('include_asciimathml_script') === 'true') { |
||||||
| 972 | echo '<script> MathJax.Hub.Queue(["Typeset",MathJax.Hub]);</script>'; |
||||||
| 973 | } |
||||||
| 974 | |||||||
| 975 | if (in_array($objQuestion->type, [HOT_SPOT, HOT_SPOT_COMBINATION])) { |
||||||
| 976 | echo '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'hotspot/js/hotspot.js"></script>'; |
||||||
| 977 | } |
||||||
| 978 | |||||||
| 979 | $attemptList = []; |
||||||
| 980 | if (!empty($exeId)) { |
||||||
| 981 | $attemptList = Event::getAllExerciseEventByExeId($exeId); |
||||||
| 982 | } |
||||||
| 983 | |||||||
| 984 | $userChoice = isset($attemptList[$questionId]) ? $attemptList[$questionId] : null; |
||||||
| 985 | |||||||
| 986 | ExerciseLib::showQuestion( |
||||||
| 987 | $objExercise, |
||||||
| 988 | $questionId, |
||||||
| 989 | false, |
||||||
| 990 | null, |
||||||
| 991 | null, |
||||||
| 992 | false, |
||||||
| 993 | true, |
||||||
| 994 | $userChoice, |
||||||
| 995 | true |
||||||
| 996 | ); |
||||||
| 997 | break; |
||||||
| 998 | case 'show_question': |
||||||
| 999 | $isAllowedToEdit = api_is_allowed_to_edit(null, true, false, false); |
||||||
| 1000 | |||||||
| 1001 | if (!$isAllowedToEdit) { |
||||||
| 1002 | api_not_allowed(true); |
||||||
| 1003 | exit; |
||||||
| 1004 | } |
||||||
| 1005 | |||||||
| 1006 | $questionId = isset($_GET['question']) ? (int) $_GET['question'] : 0; |
||||||
| 1007 | $exerciseId = isset($_REQUEST['exercise']) ? (int) $_REQUEST['exercise'] : 0; |
||||||
| 1008 | |||||||
| 1009 | if (!$questionId || !$exerciseId) { |
||||||
| 1010 | break; |
||||||
| 1011 | } |
||||||
| 1012 | |||||||
| 1013 | $objExercise = new Exercise(); |
||||||
| 1014 | $objExercise->read($exerciseId); |
||||||
| 1015 | $objQuestion = Question::read($questionId); |
||||||
| 1016 | $id = ''; |
||||||
| 1017 | if (api_get_configuration_value('show_question_id')) { |
||||||
| 1018 | $id = '<h4>#'.$objQuestion->course['code'].'-'.$objQuestion->iid.'</h4>'; |
||||||
| 1019 | } |
||||||
| 1020 | echo $id; |
||||||
| 1021 | echo '<p class="lead">'.$objQuestion->get_question_type_name().'</p>'; |
||||||
| 1022 | if (in_array($objQuestion->type, [FILL_IN_BLANKS, FILL_IN_BLANKS_COMBINATION])) { |
||||||
| 1023 | echo '<script> |
||||||
| 1024 | $(function() { |
||||||
| 1025 | $(".selectpicker").selectpicker({}); |
||||||
| 1026 | }); |
||||||
| 1027 | </script>'; |
||||||
| 1028 | } |
||||||
| 1029 | |||||||
| 1030 | // Allows render MathJax elements in a ajax call |
||||||
| 1031 | if (api_get_setting('include_asciimathml_script') === 'true') { |
||||||
| 1032 | echo '<script> MathJax.Hub.Queue(["Typeset",MathJax.Hub]);</script>'; |
||||||
| 1033 | } |
||||||
| 1034 | |||||||
| 1035 | ExerciseLib::showQuestion( |
||||||
| 1036 | $objExercise, |
||||||
| 1037 | $questionId, |
||||||
| 1038 | false, |
||||||
| 1039 | null, |
||||||
| 1040 | null, |
||||||
| 1041 | false, |
||||||
| 1042 | true, |
||||||
| 1043 | false, |
||||||
| 1044 | true, |
||||||
| 1045 | true |
||||||
| 1046 | ); |
||||||
| 1047 | break; |
||||||
| 1048 | case 'get_quiz_embeddable': |
||||||
| 1049 | $exercises = ExerciseLib::get_all_exercises_for_course_id( |
||||||
| 1050 | api_get_course_info(), |
||||||
| 1051 | api_get_session_id(), |
||||||
| 1052 | api_get_course_int_id(), |
||||||
| 1053 | false |
||||||
| 1054 | ); |
||||||
| 1055 | |||||||
| 1056 | $exercises = array_filter( |
||||||
| 1057 | $exercises, |
||||||
| 1058 | function (array $exercise) { |
||||||
| 1059 | return ExerciseLib::isQuizEmbeddable($exercise); |
||||||
| 1060 | } |
||||||
| 1061 | ); |
||||||
| 1062 | |||||||
| 1063 | $result = []; |
||||||
| 1064 | |||||||
| 1065 | $codePath = api_get_path(WEB_CODE_PATH); |
||||||
| 1066 | |||||||
| 1067 | foreach ($exercises as $exercise) { |
||||||
| 1068 | $title = Security::remove_XSS(api_html_entity_decode($exercise['title'])); |
||||||
| 1069 | |||||||
| 1070 | $result[] = [ |
||||||
| 1071 | 'id' => $exercise['iid'], |
||||||
| 1072 | 'title' => strip_tags($title), |
||||||
| 1073 | ]; |
||||||
| 1074 | } |
||||||
| 1075 | |||||||
| 1076 | header('Content-Type: application/json'); |
||||||
| 1077 | echo json_encode($result); |
||||||
| 1078 | break; |
||||||
| 1079 | case 'browser_test': |
||||||
| 1080 | $quizCheckButtonEnabled = api_get_configuration_value('quiz_check_button_enable'); |
||||||
| 1081 | |||||||
| 1082 | if ($quizCheckButtonEnabled) { |
||||||
| 1083 | if (isset($_POST['sleep'])) { |
||||||
| 1084 | sleep(2); |
||||||
| 1085 | } |
||||||
| 1086 | |||||||
| 1087 | echo 'ok'; |
||||||
| 1088 | } |
||||||
| 1089 | |||||||
| 1090 | break; |
||||||
| 1091 | case 'quiz_confirm_saved_answers': |
||||||
| 1092 | if (false === api_get_configuration_value('quiz_confirm_saved_answers')) { |
||||||
| 1093 | break; |
||||||
| 1094 | } |
||||||
| 1095 | |||||||
| 1096 | $trackConfirmationId = isset($_POST['tc_id']) ? (int) $_POST['tc_id'] : 0; |
||||||
| 1097 | $cId = api_get_course_int_id(); |
||||||
| 1098 | $sessionId = api_get_session_id(); |
||||||
| 1099 | $userId = api_get_user_id(); |
||||||
| 1100 | $confirmed = !empty($_POST['quiz_confirm_saved_answers_check']); |
||||||
| 1101 | |||||||
| 1102 | $em = Database::getManager(); |
||||||
| 1103 | $repo = $em->getRepository('ChamiloCoreBundle:TrackEExerciseConfirmation'); |
||||||
| 1104 | |||||||
| 1105 | try { |
||||||
| 1106 | if (!$trackConfirmationId) { |
||||||
| 1107 | throw new Exception(get_lang('ErrorOccurred')); |
||||||
| 1108 | } |
||||||
| 1109 | |||||||
| 1110 | /** @var TrackEExerciseConfirmation $trackConfirmation */ |
||||||
| 1111 | $trackConfirmation = $repo->findOneBy( |
||||||
| 1112 | [ |
||||||
| 1113 | 'id' => $trackConfirmationId, |
||||||
| 1114 | 'userId' => $userId, |
||||||
| 1115 | 'courseId' => $cId, |
||||||
| 1116 | 'sessionId' => $sessionId, |
||||||
| 1117 | ], |
||||||
| 1118 | ['createdAt' => 'DESC'] |
||||||
| 1119 | ); |
||||||
| 1120 | |||||||
| 1121 | if (!$trackConfirmation) { |
||||||
|
0 ignored issues
–
show
|
|||||||
| 1122 | throw new Exception(get_lang('NotFound')); |
||||||
| 1123 | } |
||||||
| 1124 | |||||||
| 1125 | $trackConfirmation |
||||||
| 1126 | ->setConfirmed($confirmed) |
||||||
| 1127 | ->setUpdatedAt(api_get_utc_datetime(null, false, true)); |
||||||
| 1128 | |||||||
| 1129 | $em->persist($trackConfirmation); |
||||||
| 1130 | $em->flush(); |
||||||
| 1131 | |||||||
| 1132 | http_response_code(200); |
||||||
| 1133 | } catch (Exception $exception) { |
||||||
| 1134 | http_response_code(500); |
||||||
| 1135 | |||||||
| 1136 | echo Display::return_message($exception->getMessage(), 'error'); |
||||||
| 1137 | } |
||||||
| 1138 | |||||||
| 1139 | break; |
||||||
| 1140 | case 'sign_attempt': |
||||||
| 1141 | api_block_anonymous_users(); |
||||||
| 1142 | if ('true' !== api_get_plugin_setting('exercise_signature', 'tool_enable')) { |
||||||
| 1143 | exit; |
||||||
| 1144 | } |
||||||
| 1145 | |||||||
| 1146 | $file = isset($_REQUEST['file']) ? $_REQUEST['file'] : ''; |
||||||
| 1147 | if (empty($exeId) || empty($file)) { |
||||||
| 1148 | echo 0; |
||||||
| 1149 | exit; |
||||||
| 1150 | } |
||||||
| 1151 | |||||||
| 1152 | $file = str_replace(' ', '+', $file); |
||||||
| 1153 | $track = ExerciseLib::get_exercise_track_exercise_info($exeId); |
||||||
| 1154 | if ($track) { |
||||||
| 1155 | $result = ExerciseSignaturePlugin::saveSignature($currentUserId, $track, $file); |
||||||
| 1156 | if ($result) { |
||||||
| 1157 | echo 1; |
||||||
| 1158 | exit; |
||||||
| 1159 | } |
||||||
| 1160 | } |
||||||
| 1161 | echo 0; |
||||||
| 1162 | break; |
||||||
| 1163 | case 'upload_answer': |
||||||
| 1164 | api_block_anonymous_users(); |
||||||
| 1165 | |||||||
| 1166 | if (isset($_REQUEST['chunkAction']) && 'send' === $_REQUEST['chunkAction']) { |
||||||
| 1167 | // It uploads the files in chunks |
||||||
| 1168 | if (!empty($_FILES)) { |
||||||
| 1169 | $tempDirectory = api_get_path(SYS_ARCHIVE_PATH); |
||||||
| 1170 | $files = $_FILES['files']; |
||||||
| 1171 | $fileList = []; |
||||||
| 1172 | foreach ($files as $name => $array) { |
||||||
| 1173 | $counter = 0; |
||||||
| 1174 | foreach ($array as $data) { |
||||||
| 1175 | $fileList[$counter][$name] = $data; |
||||||
| 1176 | $counter++; |
||||||
| 1177 | } |
||||||
| 1178 | } |
||||||
| 1179 | if (!empty($fileList)) { |
||||||
| 1180 | foreach ($fileList as $n => $file) { |
||||||
| 1181 | $tmpFile = disable_dangerous_file( |
||||||
| 1182 | api_replace_dangerous_char($file['name']) |
||||||
| 1183 | ); |
||||||
| 1184 | |||||||
| 1185 | file_put_contents( |
||||||
| 1186 | $tempDirectory.$tmpFile, |
||||||
| 1187 | fopen($file['tmp_name'], 'r'), |
||||||
| 1188 | FILE_APPEND |
||||||
| 1189 | ); |
||||||
| 1190 | } |
||||||
| 1191 | } |
||||||
| 1192 | } |
||||||
| 1193 | echo json_encode([ |
||||||
| 1194 | 'files' => $_FILES, |
||||||
| 1195 | 'errorStatus' => 0, |
||||||
| 1196 | ]); |
||||||
| 1197 | exit; |
||||||
| 1198 | } else { |
||||||
| 1199 | if (!empty($_FILES)) { |
||||||
| 1200 | $currentDirectory = Security::remove_XSS($_REQUEST['curdirpath']); |
||||||
| 1201 | $userId = api_get_user_id(); |
||||||
| 1202 | |||||||
| 1203 | // Upload answer path is created inside user personal folder my_files/upload_answer/[exe_id]/[question_id] |
||||||
| 1204 | $syspath = UserManager::getUserPathById($userId, 'system').'my_files'.$currentDirectory; |
||||||
| 1205 | @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.');
}
Loading history...
|
|||||||
| 1206 | $webpath = UserManager::getUserPathById($userId, 'web').'my_files'.$currentDirectory; |
||||||
| 1207 | |||||||
| 1208 | $files = $_FILES['files']; |
||||||
| 1209 | $fileList = []; |
||||||
| 1210 | foreach ($files as $name => $array) { |
||||||
| 1211 | $counter = 0; |
||||||
| 1212 | foreach ($array as $data) { |
||||||
| 1213 | $fileList[$counter][$name] = $data; |
||||||
| 1214 | $counter++; |
||||||
| 1215 | } |
||||||
| 1216 | } |
||||||
| 1217 | $resultList = []; |
||||||
| 1218 | foreach ($fileList as $file) { |
||||||
| 1219 | $json = []; |
||||||
| 1220 | |||||||
| 1221 | if (isset($_REQUEST['chunkAction']) && 'done' === $_REQUEST['chunkAction']) { |
||||||
| 1222 | // to rename and move the finished file |
||||||
| 1223 | $chunkedFile = api_get_path(SYS_ARCHIVE_PATH).$file['name']; |
||||||
| 1224 | $file['tmp_name'] = $chunkedFile; |
||||||
| 1225 | $file['size'] = filesize($chunkedFile); |
||||||
| 1226 | $file['copy_file'] = true; |
||||||
| 1227 | } |
||||||
| 1228 | |||||||
| 1229 | $filename = api_replace_dangerous_char($file['name']); |
||||||
| 1230 | $filename = disable_dangerous_file($filename); |
||||||
| 1231 | |||||||
| 1232 | if (isset($file['copy_file']) && $file['copy_file']) { |
||||||
| 1233 | $uploaded = copy($file['tmp_name'], $syspath.$filename); |
||||||
| 1234 | @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.');
}
Loading history...
|
|||||||
| 1235 | } else { |
||||||
| 1236 | $uploaded = move_uploaded_file($file['tmp_name'], $syspath.$filename); |
||||||
| 1237 | } |
||||||
| 1238 | |||||||
| 1239 | if ($uploaded) { |
||||||
| 1240 | $title = $filename; |
||||||
| 1241 | $url = $webpath.$filename; |
||||||
| 1242 | $json['name'] = api_htmlentities($title); |
||||||
| 1243 | $json['link'] = Display::url( |
||||||
| 1244 | api_htmlentities($title), |
||||||
| 1245 | api_htmlentities($url), |
||||||
| 1246 | ['target' => '_blank'] |
||||||
| 1247 | ); |
||||||
| 1248 | $json['url'] = $url; |
||||||
| 1249 | $json['size'] = format_file_size($file['size']); |
||||||
| 1250 | $json['type'] = api_htmlentities($file['type']); |
||||||
| 1251 | $json['result'] = Display::return_icon( |
||||||
| 1252 | 'accept.png', |
||||||
| 1253 | get_lang('Uploaded') |
||||||
| 1254 | ); |
||||||
| 1255 | } else { |
||||||
| 1256 | $json['name'] = isset($file['name']) ? $filename : get_lang('Unknown'); |
||||||
| 1257 | $json['url'] = ''; |
||||||
| 1258 | $json['error'] = get_lang('Error'); |
||||||
| 1259 | } |
||||||
| 1260 | $resultList[] = $json; |
||||||
| 1261 | } |
||||||
| 1262 | echo json_encode(['files' => $resultList]); |
||||||
| 1263 | exit; |
||||||
| 1264 | } |
||||||
| 1265 | } |
||||||
| 1266 | break; |
||||||
| 1267 | default: |
||||||
| 1268 | echo ''; |
||||||
| 1269 | } |
||||||
| 1270 | exit; |
||||||
| 1271 |
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.