Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

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