Issues (2037)

main/inc/ajax/exercise.ajax.php (1 issue)

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);
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);
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(
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(
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(
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) {
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);
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
Security Best Practice introduced by
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 ignore-unhandled  annotation

1236
                        /** @scrutinizer ignore-unhandled */ @unlink($file['tmp_name']);

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...
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