Passed
Push — 1.11.x ( 1353b8...defa11 )
by Julito
10:30
created

Event::updateEventExercise()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 73
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 45
nc 17
nop 13
dl 0
loc 73
rs 8.2666
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* See license terms in /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
6
use ChamiloSession as Session;
7
8
/**
9
 * Class Event
10
 * Functions of this library are used to record informations when some kind
11
 * of event occur. Each event has his own types of informations then each event
12
 * use its own function.
13
 */
14
class Event
15
{
16
    /**
17
     * @author Sebastien Piraux <[email protected]>
18
     * @desc Record information for open event (when homepage is opened)
19
     */
20
    public static function open()
21
    {
22
        global $_configuration;
23
        global $TABLETRACK_OPEN;
24
25
        // @getHostByAddr($_SERVER['REMOTE_ADDR']) : will provide host and country information
26
        // $_SERVER['HTTP_USER_AGENT'] :  will provide browser and os information
27
        // $_SERVER['HTTP_REFERER'] : provide information about refering url
28
        if (isset($_SERVER['HTT_REFERER'])) {
29
            $referer = Database::escape_string($_SERVER['HTTP_REFERER']);
30
        } else {
31
            $referer = '';
32
        }
33
        // record informations only if user comes from another site
34
        //if(!eregi($_configuration['root_web'],$referer))
35
        $pos = strpos($referer, $_configuration['root_web']);
36
        if ($pos === false && $referer != '') {
37
            $ip = api_get_real_ip();
38
            $remhost = @gethostbyaddr($ip);
39
            if ($remhost == $ip) {
40
                $remhost = "Unknown";
41
            } // don't change this
42
            $reallyNow = api_get_utc_datetime();
43
            $params = [
44
                'open_remote_host' => Database::escape_string($remhost),
45
                'open_agent' => $_SERVER['HTTP_USER_AGENT'],
46
                'open_referer' => $referer,
47
                'open_date' => $reallyNow,
48
            ];
49
            Database::insert($TABLETRACK_OPEN, $params);
50
        }
51
52
        return 1;
53
    }
54
55
    /**
56
     * @author Sebastien Piraux <[email protected]> old code
57
     * @author Julio Montoya
58
     *
59
     * @param int $userId
60
     *
61
     * @return bool
62
     * @desc Record information for login event when an user identifies himself with username & password
63
     */
64
    public static function eventLogin($userId)
65
    {
66
        $userInfo = api_get_user_info($userId);
67
        $userId = (int) $userId;
68
69
        if (empty($userInfo)) {
70
            return false;
71
        }
72
73
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
74
        $reallyNow = api_get_utc_datetime();
75
        $userIp = Database::escape_string(api_get_real_ip());
76
77
        $sql = "INSERT INTO $table (login_user_id, user_ip, login_date, logout_date) VALUES
78
                    ($userId,
79
                    '$userIp',
80
                    '$reallyNow',
81
                    '$reallyNow'
82
                )";
83
        Database::query($sql);
84
85
        // Auto subscribe
86
        $status = 'student';
87
        if ($userInfo['status'] == SESSIONADMIN) {
88
            $status = 'sessionadmin';
89
        }
90
        if ($userInfo['status'] == COURSEMANAGER) {
91
            $status = 'teacher';
92
        }
93
        if ($userInfo['status'] == DRH) {
94
            $status = 'DRH';
95
        }
96
97
        $autoSubscribe = api_get_setting($status.'_autosubscribe');
98
        if ($autoSubscribe) {
99
            $autoSubscribe = explode('|', $autoSubscribe);
100
            foreach ($autoSubscribe as $code) {
101
                if (CourseManager::course_exists($code)) {
102
                    CourseManager::subscribeUser($userId, $code);
103
                }
104
            }
105
        }
106
107
        return true;
108
    }
109
110
    /**
111
     * @param int $sessionId
112
     *
113
     * @return bool
114
     */
115
    public static function isSessionLogNeedToBeSave($sessionId)
116
    {
117
        if (!empty($sessionId)) {
118
            $visibility = api_get_session_visibility($sessionId);
119
            if (!empty($visibility) && $visibility != SESSION_AVAILABLE) {
120
                $extraFieldValue = new ExtraFieldValue('session');
121
                $value = $extraFieldValue->get_values_by_handler_and_field_variable(
122
                    $sessionId,
123
                    'disable_log_after_session_ends'
124
                );
125
                if (!empty($value) && isset($value['value']) && (int) $value['value'] == 1) {
126
                    return false;
127
                }
128
            }
129
        }
130
131
        return true;
132
    }
133
134
    /**
135
     * @author Sebastien Piraux <[email protected]>
136
     * @desc Record information for access event for courses
137
     *
138
     * @return bool
139
     */
140
    public static function accessCourse()
141
    {
142
        if (Session::read('login_as')) {
143
            return false;
144
        }
145
146
        $TABLETRACK_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
147
        // For "what's new" notification
148
        $TABLETRACK_LASTACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
149
150
        $sessionId = api_get_session_id();
151
        $now = api_get_utc_datetime();
152
        $courseId = api_get_course_int_id();
153
        $userId = api_get_user_id();
154
        $ip = Database::escape_string(api_get_real_ip());
155
156
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
157
            return false;
158
        }
159
160
        if ($userId) {
161
            $userId = $userId;
162
        } else {
163
            $userId = '0'; // no one
164
        }
165
        $sql = "INSERT INTO $TABLETRACK_ACCESS  (user_ip, access_user_id, c_id, access_date, access_session_id)
166
                VALUES ('$ip', $userId, $courseId, '$now', $sessionId)";
167
168
        Database::query($sql);
169
170
        // added for "what's new" notification
171
        $sql = "UPDATE $TABLETRACK_LASTACCESS  SET access_date = '$now'
172
                WHERE
173
                  access_user_id = $userId AND
174
                  c_id = $courseId AND
175
                  access_tool IS NULL AND
176
                  access_session_id = $sessionId";
177
        $result = Database::query($sql);
178
179
        if (Database::affected_rows($result) == 0) {
180
            $sql = "INSERT INTO $TABLETRACK_LASTACCESS (access_user_id, c_id, access_date, access_session_id)
181
                    VALUES ($userId, $courseId, '$now', $sessionId)";
182
            Database::query($sql);
183
        }
184
185
        return true;
186
    }
187
188
    /**
189
     * @param string $tool name of the tool
190
     *
191
     * @author Sebastien Piraux <[email protected]>
192
     * @desc Record information for access event for tools
193
     *
194
     *  $tool can take this values :
195
     *  Links, Calendar, Document, Announcements,
196
     *  Group, Video, Works, Users, Exercises, Course Desc
197
     *  ...
198
     *  Values can be added if new modules are created (15char max)
199
     *  I encourage to use $nameTool as $tool when calling this function
200
     *
201
     * Functionality for "what's new" notification is added by Toon Van Hoecke
202
     *
203
     * @return bool
204
     */
205
    public static function event_access_tool($tool)
206
    {
207
        if (Session::read('login_as')) {
208
            return false;
209
        }
210
211
        $tool = Database::escape_string($tool);
212
213
        if (empty($tool)) {
214
            return false;
215
        }
216
217
        $courseInfo = api_get_course_info();
218
        $sessionId = api_get_session_id();
219
        $reallyNow = api_get_utc_datetime();
220
        $userId = api_get_user_id();
221
222
        if (empty($courseInfo)) {
223
            return false;
224
        }
225
226
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
227
            return false;
228
        }
229
230
        $courseId = $courseInfo['real_id'];
231
232
        $tableAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
233
        //for "what's new" notification
234
        $tableLastAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
235
236
        // record information
237
        // only if user comes from the course $_cid
238
        //if( eregi($_configuration['root_web'].$_cid,$_SERVER['HTTP_REFERER'] ) )
239
        //$pos = strpos($_SERVER['HTTP_REFERER'],$_configuration['root_web'].$_cid);
240
        $coursePath = isset($courseInfo['path']) ? $courseInfo['path'] : null;
241
242
        $pos = isset($_SERVER['HTTP_REFERER']) ? strpos(strtolower($_SERVER['HTTP_REFERER']), strtolower(api_get_path(WEB_COURSE_PATH).$coursePath)) : false;
243
        // added for "what's new" notification
244
        $pos2 = isset($_SERVER['HTTP_REFERER']) ? strpos(strtolower($_SERVER['HTTP_REFERER']), strtolower(api_get_path(WEB_PATH)."index")) : false;
245
246
        // end "what's new" notification
247
        if ($pos !== false || $pos2 !== false) {
248
            $params = [
249
                'access_user_id' => $userId,
250
                'c_id' => $courseId,
251
                'access_tool' => $tool,
252
                'access_date' => $reallyNow,
253
                'access_session_id' => $sessionId,
254
                'user_ip' => Database::escape_string(api_get_real_ip()),
255
            ];
256
            Database::insert($tableAccess, $params);
257
        }
258
259
        // "what's new" notification
260
        $sql = "UPDATE $tableLastAccess
261
                SET access_date = '$reallyNow'
262
                WHERE
263
                    access_user_id = $userId AND
264
                    c_id = $courseId AND
265
                    access_tool = '$tool' AND
266
                    access_session_id = $sessionId";
267
        $result = Database::query($sql);
268
269
        if (Database::affected_rows($result) == 0) {
270
            $params = [
271
                'access_user_id' => $userId,
272
                'c_id' => $courseId,
273
                'access_tool' => $tool,
274
                'access_date' => $reallyNow,
275
                'access_session_id' => $sessionId,
276
            ];
277
            Database::insert($tableLastAccess, $params);
278
        }
279
280
        return true;
281
    }
282
283
    /**
284
     * Record information for download event (when an user click to d/l a
285
     * document) it will be used in a redirection page.
286
     *
287
     * @param string $documentUrl
288
     *
289
     * @return int
290
     *
291
     * @author Sebastien Piraux <[email protected]>
292
     * @author Evie Embrechts (bug fixed: The user id is put in single quotes)
293
     */
294
    public static function event_download($documentUrl)
295
    {
296
        if (Session::read('login_as')) {
297
            return false;
298
        }
299
300
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
301
        $documentUrl = Database::escape_string($documentUrl);
302
303
        $reallyNow = api_get_utc_datetime();
304
        $userId = api_get_user_id();
305
        $courseId = api_get_course_int_id();
306
        $sessionId = api_get_session_id();
307
308
        return Database::insert(
309
            $table,
310
            [
311
                'down_user_id' => $userId,
312
                'c_id' => $courseId,
313
                'down_doc_path' => $documentUrl,
314
                'down_date' => $reallyNow,
315
                'down_session_id' => $sessionId,
316
            ]
317
        );
318
    }
319
320
    /**
321
     * @param int $documentId of document (id in mainDb.document table)
322
     *
323
     * @author Sebastien Piraux <[email protected]>
324
     * @desc Record information for upload event
325
     * used in the works tool to record informations when
326
     * an user upload 1 work
327
     *
328
     * @return int
329
     */
330
    public static function event_upload($documentId)
331
    {
332
        if (Session::read('login_as')) {
333
            return false;
334
        }
335
336
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_UPLOADS);
337
        $courseId = api_get_course_int_id();
338
        $reallyNow = api_get_utc_datetime();
339
        $userId = api_get_user_id();
340
        $documentId = (int) $documentId;
341
        $sessionId = api_get_session_id();
342
343
        $sql = "INSERT INTO $table
344
                ( upload_user_id,
345
                  c_id,
346
                  upload_cours_id,
347
                  upload_work_id,
348
                  upload_date,
349
                  upload_session_id
350
                )
351
                VALUES (
352
                 $userId,
353
                 $courseId,
354
                 '',
355
                 $documentId,
356
                 '$reallyNow',
357
                 $sessionId
358
                )";
359
        Database::query($sql);
360
361
        return 1;
362
    }
363
364
    /**
365
     * Record information for link event (when an user click on an added link)
366
     * it will be used in a redirection page.
367
     *
368
     * @param int $linkId (id in c_link table)
369
     *
370
     * @return int
371
     *
372
     * @author Sebastien Piraux <[email protected]>
373
     */
374
    public static function event_link($linkId)
375
    {
376
        if (Session::read('login_as')) {
377
            return false;
378
        }
379
380
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
381
        $reallyNow = api_get_utc_datetime();
382
        $userId = api_get_user_id();
383
        $courseId = api_get_course_int_id();
384
        $linkId = (int) $linkId;
385
        $sessionId = api_get_session_id();
386
        $sql = "INSERT INTO ".$table."
387
                    ( links_user_id,
388
                     c_id,
389
                     links_link_id,
390
                     links_date,
391
                     links_session_id
392
                    ) VALUES (
393
                     $userId,
394
                     $courseId,
395
                     $linkId,
396
                     '$reallyNow',
397
                     $sessionId
398
                    )";
399
        Database::query($sql);
400
401
        return 1;
402
    }
403
404
    /**
405
     * Update the TRACK_E_EXERCICES exercises.
406
     * Record result of user when an exercise was done.
407
     *
408
     * @param int    $exeId
409
     * @param int    $exoId
410
     * @param mixed  $score
411
     * @param int    $weighting
412
     * @param int    $sessionId
413
     * @param int    $learnpathId
414
     * @param int    $learnpathItemId
415
     * @param int    $learnpathItemViewId
416
     * @param int    $duration
417
     * @param array  $questionsList
418
     * @param string $status
419
     * @param array  $remindList
420
     * @param null   $endDate
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $endDate is correct as it would always require null to be passed?
Loading history...
421
     *
422
     * @return bool
423
     *
424
     * @author Sebastien Piraux <[email protected]>
425
     * @author Julio Montoya Armas <[email protected]> Reworked 2010
426
     */
427
    public static function updateEventExercise(
428
        $exeId,
429
        $exoId,
430
        $score,
431
        $weighting,
432
        $sessionId,
433
        $learnpathId = 0,
434
        $learnpathItemId = 0,
435
        $learnpathItemViewId = 0,
436
        $duration = 0,
437
        $questionsList = [],
438
        $status = '',
439
        $remindList = [],
440
        $endDate = null
441
    ) {
442
        if (empty($exeId)) {
443
            return false;
444
        }
445
        if (!isset($status) || empty($status)) {
446
            $status = '';
447
        } else {
448
            $status = Database::escape_string($status);
449
        }
450
451
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
452
453
        if (!empty($questionsList)) {
454
            $questionsList = array_map('intval', $questionsList);
455
        }
456
457
        if (!empty($remindList)) {
458
            $remindList = array_map('intval', $remindList);
459
            $remindList = array_filter($remindList);
460
            $remindList = implode(",", $remindList);
461
        } else {
462
            $remindList = '';
463
        }
464
465
        if (empty($endDate)) {
466
            $endDate = api_get_utc_datetime();
467
        }
468
        $exoId = (int) $exoId;
469
        $sessionId = (int) $sessionId;
470
        $learnpathId = (int) $learnpathId;
471
        $learnpathItemId = (int) $learnpathItemId;
472
        $learnpathItemViewId = (int) $learnpathItemViewId;
473
        $duration = (int) $duration;
474
        $exeId = (int) $exeId;
475
        $score = Database::escape_string($score);
476
        $weighting = Database::escape_string($weighting);
477
        $questions = implode(',', $questionsList);
478
        $userIp = Database::escape_string(api_get_real_ip());
479
480
        $sql = "UPDATE $table SET
481
               exe_exo_id = $exoId,
482
               exe_result = '$score',
483
               exe_weighting = '$weighting',
484
               session_id = $sessionId,
485
               orig_lp_id = $learnpathId,
486
               orig_lp_item_id = $learnpathItemId,
487
               orig_lp_item_view_id = $learnpathItemViewId,
488
               exe_duration = $duration,
489
               exe_date = '$endDate',
490
               status = '$status',
491
               questions_to_check = '$remindList',
492
               data_tracking = '$questions',
493
               user_ip = '$userIp'
494
             WHERE exe_id = $exeId";
495
        Database::query($sql);
496
497
        //Deleting control time session track
498
        //ExerciseLib::exercise_time_control_delete($exo_id);
499
        return true;
500
    }
501
502
    /**
503
     * Record an event for this attempt at answering an exercise.
504
     *
505
     * @param float  $score             Score achieved
506
     * @param string $answer            Answer given
507
     * @param int    $question_id
508
     * @param int    $exe_id            Exercise attempt ID a.k.a exe_id (from track_e_exercise)
509
     * @param int    $position
510
     * @param int    $exercise_id       From c_quiz
511
     * @param bool   $updateResults
512
     * @param int    $duration          Time spent in seconds
513
     * @param string $fileName          Filename (for audio answers - using nanogong)
514
     * @param int    $user_id           The user who's going to get this score.
515
     * @param int    $course_id         Default value of null means "get from context".
516
     * @param int    $session_id        Default value of null means "get from context".
517
     * @param int    $learnpath_id      (from c_lp table). Default value of null means "get from context".
518
     * @param int    $learnpath_item_id (from the c_lp_item table). Default value of null means "get from context".
519
     *
520
     * @return bool Result of the insert query
521
     */
522
    public static function saveQuestionAttempt(
523
        $score,
524
        $answer,
525
        $question_id,
526
        $exe_id,
527
        $position,
528
        $exercise_id = 0,
529
        $updateResults = false,
530
        $questionDuration = 0,
531
        $fileName = null,
532
        $user_id = null,
533
        $course_id = null,
534
        $session_id = null,
535
        $learnpath_id = null,
536
        $learnpath_item_id = null
537
    ) {
538
        global $debug;
539
        $questionDuration = (int) $questionDuration;
540
        $question_id = (int) $question_id;
541
        $exe_id = (int) $exe_id;
542
        $position = (int) $position;
543
        $course_id = (int) $course_id;
544
        $now = api_get_utc_datetime();
545
        $recording = api_get_configuration_value('quiz_answer_extra_recording');
546
547
        // check user_id or get from context
548
        if (empty($user_id)) {
549
            $user_id = api_get_user_id();
550
            // anonymous
551
            if (empty($user_id)) {
552
                $user_id = api_get_anonymous_id();
553
            }
554
        }
555
        // check course_id or get from context
556
        if (empty($course_id)) {
557
            $course_id = api_get_course_int_id();
558
        }
559
        // check session_id or get from context
560
        $session_id = (int) $session_id;
561
        if (empty($session_id)) {
562
            $session_id = api_get_session_id();
563
        }
564
        // check learnpath_id or get from context
565
        if (empty($learnpath_id)) {
566
            global $learnpath_id;
567
        }
568
        // check learnpath_item_id or get from context
569
        if (empty($learnpath_item_id)) {
570
            global $learnpath_item_id;
571
        }
572
573
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
574
575
        if ($debug) {
576
            error_log("----- entering saveQuestionAttempt() function ------");
577
            error_log("answer: $answer");
578
            error_log("score: $score");
579
            error_log("question_id : $question_id");
580
            error_log("position: $position");
581
        }
582
583
        // Validation in case of fraud with active control time.
584
        if (!ExerciseLib::exercise_time_control_is_valid($exercise_id, $learnpath_id, $learnpath_item_id)) {
585
            if ($debug) {
586
                error_log("exercise_time_control_is_valid is false");
587
            }
588
            $score = 0;
589
            $answer = 0;
590
        }
591
592
        if (empty($question_id) || empty($exe_id) || empty($user_id)) {
593
            return false;
594
        }
595
596
        if (null === $answer) {
597
            $answer = '';
598
        }
599
600
        if (null === $score) {
601
            $score = 0;
602
        }
603
604
        $attempt = [
605
            'user_id' => $user_id,
606
            'question_id' => $question_id,
607
            'answer' => $answer,
608
            'marks' => $score,
609
            'c_id' => $course_id,
610
            'session_id' => $session_id,
611
            'position' => $position,
612
            'tms' => $now,
613
            'filename' => !empty($fileName) ? basename($fileName) : $fileName,
614
            'teacher_comment' => '',
615
        ];
616
617
        if (api_get_configuration_value('allow_time_per_question')) {
618
            $attempt['seconds_spent'] = $questionDuration;
619
        }
620
621
        // Check if attempt exists.
622
        $sql = "SELECT exe_id FROM $TBL_TRACK_ATTEMPT
623
                WHERE
624
                    c_id = $course_id AND
625
                    session_id = $session_id AND
626
                    exe_id = $exe_id AND
627
                    user_id = $user_id AND
628
                    question_id = $question_id AND
629
                    position = $position";
630
        $result = Database::query($sql);
631
        $attemptData = [];
632
        if (Database::num_rows($result)) {
633
            $attemptData = Database::fetch_array($result, 'ASSOC');
634
            if ($updateResults == false) {
635
                // The attempt already exist do not update use  update_event_exercise() instead
636
                return false;
637
            }
638
        } else {
639
            $attempt['exe_id'] = $exe_id;
640
        }
641
642
        if ($debug) {
643
            error_log("updateResults : $updateResults");
644
            error_log("Saving question attempt: ");
645
            error_log($sql);
646
        }
647
648
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
649
        if ($updateResults == false) {
650
            $attempt_id = Database::insert($TBL_TRACK_ATTEMPT, $attempt);
651
            if ($recording) {
652
                $attempt_recording = [
653
                    'exe_id' => $exe_id,
654
                    'question_id' => $question_id,
655
                    'answer' => $answer,
656
                    'marks' => $score,
657
                    'insert_date' => $now,
658
                    'session_id' => $session_id,
659
                ];
660
                Database::insert($recording_table, $attempt_recording);
661
            }
662
        } else {
663
            if (api_get_configuration_value('allow_time_per_question')) {
664
                $attempt['seconds_spent'] = $questionDuration + (int) $attemptData['seconds_spent'];
665
            }
666
            Database::update(
667
                $TBL_TRACK_ATTEMPT,
668
                $attempt,
669
                [
670
                    'exe_id = ? AND question_id = ? AND user_id = ? ' => [
671
                        $exe_id,
672
                        $question_id,
673
                        $user_id,
674
                    ],
675
                ]
676
            );
677
678
            if ($recording) {
679
                $attempt_recording = [
680
                    'exe_id' => $exe_id,
681
                    'question_id' => $question_id,
682
                    'answer' => $answer,
683
                    'marks' => $score,
684
                    'insert_date' => $now,
685
                    'session_id' => $session_id,
686
                ];
687
688
                Database::update(
689
                    $recording_table,
690
                    $attempt_recording,
691
                    [
692
                        'exe_id = ? AND question_id = ? AND session_id = ? ' => [
693
                            $exe_id,
694
                            $question_id,
695
                            $session_id,
696
                        ],
697
                    ]
698
                );
699
            }
700
            $attempt_id = $exe_id;
701
        }
702
703
        return $attempt_id;
704
    }
705
706
    /**
707
     * Record an hotspot spot for this attempt at answering an hotspot question.
708
     *
709
     * @param int    $exeId
710
     * @param int    $questionId    Question ID
711
     * @param int    $answerId      Answer ID
712
     * @param int    $correct
713
     * @param string $coords        Coordinates of this point (e.g. 123;324)
714
     * @param bool   $updateResults
715
     * @param int    $exerciseId
716
     *
717
     * @return bool Result of the insert query
718
     *
719
     * @uses \Course code and user_id from global scope $_cid and $_user
720
     */
721
    public static function saveExerciseAttemptHotspot(
722
        $exeId,
723
        $questionId,
724
        $answerId,
725
        $correct,
726
        $coords,
727
        $updateResults = false,
728
        $exerciseId = 0
729
    ) {
730
        $debug = false;
731
        global $safe_lp_id, $safe_lp_item_id;
732
733
        if ($updateResults == false) {
734
            // Validation in case of fraud with activated control time
735
            if (!ExerciseLib::exercise_time_control_is_valid($exerciseId, $safe_lp_id, $safe_lp_item_id)) {
736
                if ($debug) {
737
                    error_log('Attempt is fraud');
738
                }
739
                $correct = 0;
740
            }
741
        }
742
743
        if (empty($exeId)) {
744
            if ($debug) {
745
                error_log('exe id is empty');
746
            }
747
748
            return false;
749
        }
750
751
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
752
        if ($updateResults) {
753
            if ($debug) {
754
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
755
            }
756
            $params = [
757
                'hotspot_correct' => $correct,
758
                'hotspot_coordinate' => $coords,
759
            ];
760
            Database::update(
761
                $table,
762
                $params,
763
                [
764
                    'hotspot_user_id = ? AND hotspot_exe_id = ? AND hotspot_question_id = ? AND hotspot_answer_id = ? ' => [
765
                        api_get_user_id(),
766
                        $exeId,
767
                        $questionId,
768
                        $answerId,
769
                    ],
770
                ]
771
            );
772
        } else {
773
            if ($debug) {
774
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
775
            }
776
777
            return Database::insert(
778
                $table,
779
                [
780
                    'hotspot_course_code' => api_get_course_id(),
781
                    'hotspot_user_id' => api_get_user_id(),
782
                    'c_id' => api_get_course_int_id(),
783
                    'hotspot_exe_id' => $exeId,
784
                    'hotspot_question_id' => $questionId,
785
                    'hotspot_answer_id' => $answerId,
786
                    'hotspot_correct' => $correct,
787
                    'hotspot_coordinate' => $coords,
788
                ]
789
            );
790
        }
791
    }
792
793
    /**
794
     * Records information for common (or admin) events (in the track_e_default table).
795
     *
796
     * @author Yannick Warnier <[email protected]>
797
     *
798
     * @param string $event_type       Type of event
799
     * @param string $event_value_type Type of value
800
     * @param mixed  $event_value      Value (string, or array in the case of user info)
801
     * @param string $datetime         Datetime (UTC) (defaults to null)
802
     * @param int    $user_id          User ID (defaults to null)
803
     * @param int    $course_id        Course ID (defaults to null)
804
     * @param int    $sessionId        Session ID
805
     *
806
     * @return bool
807
     * @assert ('','','') === false
808
     */
809
    public static function addEvent(
810
        $event_type,
811
        $event_value_type,
812
        $event_value,
813
        $datetime = null,
814
        $user_id = null,
815
        $course_id = null,
816
        $sessionId = 0
817
    ) {
818
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
819
820
        if (empty($event_type)) {
821
            return false;
822
        }
823
        $event_type = Database::escape_string($event_type);
824
        $event_value_type = Database::escape_string($event_value_type);
825
        if (!empty($course_id)) {
826
            $course_id = (int) $course_id;
827
        } else {
828
            $course_id = api_get_course_int_id();
829
        }
830
        if (!empty($sessionId)) {
831
            $sessionId = (int) $sessionId;
832
        } else {
833
            $sessionId = api_get_session_id();
834
        }
835
836
        //Clean the user_info
837
        if ($event_value_type == LOG_USER_OBJECT) {
838
            if (is_array($event_value)) {
839
                unset($event_value['complete_name']);
840
                unset($event_value['complete_name_with_username']);
841
                unset($event_value['firstName']);
842
                unset($event_value['lastName']);
843
                unset($event_value['avatar_small']);
844
                unset($event_value['avatar']);
845
                unset($event_value['mail']);
846
                unset($event_value['password']);
847
                unset($event_value['last_login']);
848
                unset($event_value['picture_uri']);
849
                $event_value = serialize($event_value);
850
            }
851
        }
852
        // If event is an array then the $event_value_type should finish with
853
        // the suffix _array for example LOG_WORK_DATA = work_data_array
854
        if (is_array($event_value)) {
855
            $event_value = serialize($event_value);
856
        }
857
858
        $event_value = Database::escape_string($event_value);
859
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
860
861
        if (!isset($datetime)) {
862
            $datetime = api_get_utc_datetime();
863
        }
864
865
        $datetime = Database::escape_string($datetime);
866
867
        if (!isset($user_id)) {
868
            $user_id = api_get_user_id();
869
        }
870
871
        $params = [
872
            'default_user_id' => $user_id,
873
            'c_id' => $course_id,
874
            'default_date' => $datetime,
875
            'default_event_type' => $event_type,
876
            'default_value_type' => $event_value_type,
877
            'default_value' => $event_value,
878
            'session_id' => $sessionId,
879
        ];
880
        Database::insert($table, $params);
881
882
        return true;
883
    }
884
885
    /**
886
     * Get every email stored in the database.
887
     *
888
     * @deprecated
889
     *
890
     * @return array
891
     * @assert () !== false
892
     */
893
    public static function get_all_event_types()
894
    {
895
        global $event_config;
896
897
        $sql = 'SELECT etm.id, event_type_name, activated, language_id, message, subject, dokeos_folder
898
                FROM '.Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE).' etm
899
                INNER JOIN '.Database::get_main_table(TABLE_MAIN_LANGUAGE).' l
900
                ON etm.language_id = l.id';
901
902
        $events_types = Database::store_result(Database::query($sql), 'ASSOC');
903
904
        $to_return = [];
905
        foreach ($events_types as $et) {
906
            $et['nameLangVar'] = $event_config[$et["event_type_name"]]["name_lang_var"];
907
            $et['descLangVar'] = $event_config[$et["event_type_name"]]["desc_lang_var"];
908
            $to_return[] = $et;
909
        }
910
911
        return $to_return;
912
    }
913
914
    /**
915
     * Get the users related to one event.
916
     *
917
     * @param string $event_name
918
     *
919
     * @return string
920
     */
921
    public static function get_event_users($event_name)
922
    {
923
        $event_name = Database::escape_string($event_name);
924
        $sql = 'SELECT user.user_id,  user.firstname, user.lastname
925
                FROM '.Database::get_main_table(TABLE_MAIN_USER).' user
926
                JOIN '.Database::get_main_table(TABLE_EVENT_TYPE_REL_USER).' relUser
927
                ON relUser.user_id = user.user_id
928
                WHERE user.status <> '.ANONYMOUS.' AND relUser.event_type_name = "'.$event_name.'"';
929
        $user_list = Database::store_result(Database::query($sql), 'ASSOC');
930
931
        return json_encode($user_list);
932
    }
933
934
    /**
935
     * @param int    $user_id
936
     * @param string $event_type
937
     *
938
     * @return array|bool
939
     */
940
    public static function get_events_by_user_and_type($user_id, $event_type)
941
    {
942
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
943
        $user_id = (int) $user_id;
944
        $event_type = Database::escape_string($event_type);
945
946
        $sql = "SELECT * FROM $table
947
                WHERE default_value_type = 'user_id' AND
948
                      default_value = $user_id AND
949
                      default_event_type = '$event_type'
950
                ORDER BY default_date ";
951
        $result = Database::query($sql);
952
        if ($result) {
953
            return Database::store_result($result, 'ASSOC');
954
        }
955
956
        return false;
957
    }
958
959
    /**
960
     * Save the new message for one event and for one language.
961
     *
962
     * @param string $event_name
963
     * @param array  $users
964
     * @param string $message
965
     * @param string $subject
966
     * @param string $event_message_language
967
     * @param int    $activated
968
     */
969
    public static function save_event_type_message(
970
        $event_name,
971
        $users,
972
        $message,
973
        $subject,
974
        $event_message_language,
975
        $activated
976
    ) {
977
        $event_name = Database::escape_string($event_name);
978
        $activated = (int) $activated;
979
        $event_message_language = Database::escape_string($event_message_language);
980
981
        // Deletes then re-adds the users linked to the event
982
        $sql = 'DELETE FROM '.Database::get_main_table(TABLE_EVENT_TYPE_REL_USER).'
983
                WHERE event_type_name = "'.$event_name.'"	';
984
        Database::query($sql);
985
986
        $eventTable = Database::get_main_table(TABLE_EVENT_TYPE_REL_USER);
987
988
        foreach ($users as $user) {
989
            $user = (int) $user;
990
            $sql = "INSERT INTO $eventTable (user_id,event_type_name)
991
                    VALUES($user,'$event_name')";
992
            Database::query($sql);
993
        }
994
        $language_id = api_get_language_id($event_message_language);
995
        // check if this template in this language already exists or not
996
        $eventMailTable = Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE);
997
        $sql = "SELECT COUNT(id) as total
998
                FROM $eventMailTable
999
                WHERE event_type_name = '$event_name' AND language_id = $language_id";
1000
1001
        $sql = Database::store_result(Database::query($sql), 'ASSOC');
1002
1003
        $languageTable = Database::get_main_table(TABLE_MAIN_LANGUAGE);
1004
        $message = Database::escape_string($message);
1005
        $subject = Database::escape_string($subject);
1006
        // if already exists, we update
1007
        if ($sql[0]["total"] > 0) {
1008
            $sql = "UPDATE $eventMailTable
1009
                SET message = '$message',
1010
                subject = '$subject',
1011
                activated = $activated
1012
                WHERE event_type_name = '$event_name' AND language_id = (
1013
                    SELECT id FROM $languageTable
1014
                    WHERE dokeos_folder = '$event_message_language'
1015
                )";
1016
            Database::query($sql);
1017
        } else { // else we create a new record
1018
            // gets the language_-_id
1019
            $lang_id = "(SELECT id FROM $languageTable
1020
                        WHERE dokeos_folder = '$event_message_language')";
1021
            $lang_id = Database::store_result(Database::query($lang_id), 'ASSOC');
1022
            $lang_id = $lang_id[0]['id'];
1023
1024
            if (!empty($lang_id[0]["id"])) {
1025
                $sql = "INSERT INTO $eventMailTable (event_type_name, language_id, message, subject, activated)
1026
                    VALUES ('$event_name', $lang_id, '$message', '$subject', $activated)";
1027
                Database::query($sql);
1028
            }
1029
        }
1030
1031
        // set activated at every save
1032
        $sql = "UPDATE $eventMailTable
1033
                SET activated = $activated
1034
                WHERE event_type_name = '$event_name'";
1035
        Database::query($sql);
1036
    }
1037
1038
    /**
1039
     * Gets the last attempt of an exercise based in the exe_id.
1040
     *
1041
     * @param int $exeId
1042
     *
1043
     * @return mixed
1044
     */
1045
    public static function getLastAttemptDateOfExercise($exeId)
1046
    {
1047
        $exeId = (int) $exeId;
1048
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1049
        $sql = "SELECT max(tms) as last_attempt_date
1050
                FROM $track_attempts
1051
                WHERE exe_id = $exeId";
1052
        $rs_last_attempt = Database::query($sql);
1053
        $row_last_attempt = Database::fetch_array($rs_last_attempt);
1054
        $date = $row_last_attempt['last_attempt_date']; //Get the date of last attempt
1055
1056
        return $date;
1057
    }
1058
1059
    /**
1060
     * Gets the last attempt of an exercise based in the exe_id.
1061
     *
1062
     * @param int $exeId
1063
     *
1064
     * @return mixed
1065
     */
1066
    public static function getLatestQuestionIdFromAttempt($exeId)
1067
    {
1068
        $exeId = (int) $exeId;
1069
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1070
        $sql = "SELECT question_id FROM $track_attempts
1071
                WHERE exe_id = $exeId
1072
                ORDER BY tms DESC
1073
                LIMIT 1";
1074
        $result = Database::query($sql);
1075
        if (Database::num_rows($result)) {
1076
            $row = Database::fetch_array($result);
1077
1078
            return $row['question_id'];
1079
        }
1080
1081
        return false;
1082
    }
1083
1084
    /**
1085
     * Gets how many attempts exists by user, exercise, learning path.
1086
     *
1087
     * @param   int user id
1088
     * @param   int exercise id
1089
     * @param   int lp id
1090
     * @param   int lp item id
1091
     * @param   int lp item view id
1092
     *
1093
     * @return int
1094
     */
1095
    public static function get_attempt_count(
1096
        $user_id,
1097
        $exerciseId,
1098
        $lp_id,
1099
        $lp_item_id,
1100
        $lp_item_view_id
1101
    ) {
1102
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1103
        $user_id = (int) $user_id;
1104
        $exerciseId = (int) $exerciseId;
1105
        $lp_id = (int) $lp_id;
1106
        $lp_item_id = (int) $lp_item_id;
1107
        $lp_item_view_id = (int) $lp_item_view_id;
1108
        $courseId = api_get_course_int_id();
1109
        $sessionId = api_get_session_id();
1110
1111
        $sql = "SELECT count(*) as count
1112
                FROM $table
1113
                WHERE
1114
                    exe_exo_id = $exerciseId AND
1115
                    exe_user_id = $user_id AND
1116
                    status != 'incomplete' AND
1117
                    orig_lp_id = $lp_id AND
1118
                    orig_lp_item_id = $lp_item_id AND
1119
                    orig_lp_item_view_id = $lp_item_view_id AND
1120
                    c_id = $courseId AND
1121
                    session_id = $sessionId";
1122
1123
        $result = Database::query($sql);
1124
        if (Database::num_rows($result) > 0) {
1125
            $attempt = Database::fetch_array($result, 'ASSOC');
1126
1127
            return (int) $attempt['count'];
1128
        }
1129
1130
        return 0;
1131
    }
1132
1133
    public static function getAttemptPosition(
1134
        $exeId,
1135
        $user_id,
1136
        $exerciseId,
1137
        $lp_id,
1138
        $lp_item_id,
1139
        $lp_item_view_id
1140
    ) {
1141
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1142
        $user_id = (int) $user_id;
1143
        $exerciseId = (int) $exerciseId;
1144
        $lp_id = (int) $lp_id;
1145
        $lp_item_id = (int) $lp_item_id;
1146
        $lp_item_view_id = (int) $lp_item_view_id;
1147
        $courseId = api_get_course_int_id();
1148
        $sessionId = api_get_session_id();
1149
1150
        $sql = "SELECT exe_id
1151
                FROM $table
1152
                WHERE
1153
                    exe_exo_id = $exerciseId AND
1154
                    exe_user_id = $user_id AND
1155
                    status = '' AND
1156
                    orig_lp_id = $lp_id AND
1157
                    orig_lp_item_id = $lp_item_id AND
1158
                    orig_lp_item_view_id = $lp_item_view_id AND
1159
                    c_id = $courseId AND
1160
                    session_id = $sessionId
1161
                ORDER by exe_id
1162
                ";
1163
1164
        $result = Database::query($sql);
1165
        if (Database::num_rows($result) > 0) {
1166
            $position = 1;
1167
            while ($row = Database::fetch_array($result, 'ASSOC')) {
1168
                if ($row['exe_id'] === $exeId) {
1169
                    break;
1170
                }
1171
                $position++;
1172
            }
1173
1174
            return $position;
1175
        }
1176
1177
        return 0;
1178
    }
1179
1180
    /**
1181
     * @param $user_id
1182
     * @param $exerciseId
1183
     * @param $lp_id
1184
     * @param $lp_item_id
1185
     *
1186
     * @return int
1187
     */
1188
    public static function get_attempt_count_not_finished(
1189
        $user_id,
1190
        $exerciseId,
1191
        $lp_id,
1192
        $lp_item_id
1193
    ) {
1194
        $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1195
        $user_id = (int) $user_id;
1196
        $exerciseId = (int) $exerciseId;
1197
        $lp_id = (int) $lp_id;
1198
        $lp_item_id = (int) $lp_item_id;
1199
        //$lp_item_view_id = (int) $lp_item_view_id;
1200
        $courseId = api_get_course_int_id();
1201
        $sessionId = api_get_session_id();
1202
1203
        $sql = "SELECT count(*) as count
1204
                FROM $stat_table
1205
                WHERE
1206
                    exe_exo_id 			= $exerciseId AND
1207
                    exe_user_id 		= $user_id AND
1208
                    status 				!= 'incomplete' AND
1209
                    orig_lp_id 			= $lp_id AND
1210
                    orig_lp_item_id 	= $lp_item_id AND
1211
                    c_id = $courseId AND
1212
                    session_id = $sessionId";
1213
1214
        $query = Database::query($sql);
1215
        if (Database::num_rows($query) > 0) {
1216
            $attempt = Database::fetch_array($query, 'ASSOC');
1217
1218
            return (int) $attempt['count'];
1219
        }
1220
1221
        return 0;
1222
    }
1223
1224
    /**
1225
     * @param int   $user_id
1226
     * @param int   $lp_id
1227
     * @param array $course
1228
     * @param int   $session_id
1229
     * @param bool  $disconnectExerciseResultsFromLp (Replace orig_lp_* variables to null)
1230
     *
1231
     * @return bool
1232
     */
1233
    public static function delete_student_lp_events(
1234
        $user_id,
1235
        $lp_id,
1236
        $course,
1237
        $session_id,
1238
        $disconnectExerciseResultsFromLp = false
1239
    ) {
1240
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
1241
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1242
        $lpInteraction = Database::get_course_table(TABLE_LP_IV_INTERACTION);
1243
        $lpObjective = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
1244
1245
        if (empty($course) || empty($user_id)) {
1246
            return false;
1247
        }
1248
1249
        $course_id = $course['real_id'];
1250
        $user_id = (int) $user_id;
1251
        $lp_id = (int) $lp_id;
1252
        $session_id = (int) $session_id;
1253
1254
        if (empty($course_id)) {
1255
            $course_id = api_get_course_int_id();
1256
        }
1257
1258
        $track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1259
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1260
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1261
1262
        // Make sure we have the exact lp_view_id
1263
        $sql = "SELECT id FROM $lp_view_table
1264
                WHERE
1265
                    c_id = $course_id AND
1266
                    user_id = $user_id AND
1267
                    lp_id = $lp_id AND
1268
                    session_id = $session_id";
1269
        $result = Database::query($sql);
1270
1271
        if (Database::num_rows($result)) {
1272
            $view = Database::fetch_array($result, 'ASSOC');
1273
            $lp_view_id = $view['id'];
1274
1275
            $sql = "DELETE FROM $lp_item_view_table
1276
                    WHERE c_id = $course_id AND lp_view_id = $lp_view_id";
1277
            Database::query($sql);
1278
1279
            $sql = "DELETE FROM $lpInteraction
1280
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1281
            Database::query($sql);
1282
1283
            $sql = "DELETE FROM $lpObjective
1284
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1285
            Database::query($sql);
1286
        }
1287
1288
        if (api_get_configuration_value('lp_minimum_time')) {
1289
            $sql = "DELETE FROM track_e_access_complete
1290
                    WHERE
1291
                        tool = 'learnpath' AND
1292
                        c_id = $course_id AND
1293
                        tool_id = $lp_id AND
1294
                        user_id = $user_id AND
1295
                        session_id = $session_id
1296
                    ";
1297
            Database::query($sql);
1298
        }
1299
1300
        $sql = "SELECT exe_id FROM $track_e_exercises
1301
                WHERE
1302
                    exe_user_id = $user_id AND
1303
                    session_id = $session_id AND
1304
                    c_id = $course_id AND
1305
                    orig_lp_id = $lp_id";
1306
        $result = Database::query($sql);
1307
        $exeList = [];
1308
        while ($row = Database::fetch_array($result, 'ASSOC')) {
1309
            $exeList[] = $row['exe_id'];
1310
        }
1311
1312
        if (!empty($exeList) && count($exeList) > 0) {
1313
            $exeListString = implode(',', $exeList);
1314
            if ($disconnectExerciseResultsFromLp) {
1315
                $sql = "UPDATE $track_e_exercises
1316
                        SET orig_lp_id = null,
1317
                            orig_lp_item_id = null,
1318
                            orig_lp_item_view_id = null
1319
                        WHERE exe_id IN ($exeListString)";
1320
                Database::query($sql);
1321
            } else {
1322
                $sql = "DELETE FROM $track_e_exercises
1323
                        WHERE exe_id IN ($exeListString)";
1324
                Database::query($sql);
1325
1326
                $sql = "DELETE FROM $track_attempts
1327
                        WHERE exe_id IN ($exeListString)";
1328
                Database::query($sql);
1329
1330
                $sql = "DELETE FROM $recording_table
1331
                        WHERE exe_id IN ($exeListString)";
1332
                Database::query($sql);
1333
            }
1334
        }
1335
1336
        $sql = "DELETE FROM $lp_view_table
1337
                WHERE
1338
                    c_id = $course_id AND
1339
                    user_id = $user_id AND
1340
                    lp_id= $lp_id AND
1341
                    session_id = $session_id
1342
            ";
1343
        Database::query($sql);
1344
1345
        self::addEvent(
1346
            LOG_LP_ATTEMPT_DELETE,
1347
            LOG_LP_ID,
1348
            $lp_id,
1349
            null,
1350
            null,
1351
            $course_id,
1352
            $session_id
1353
        );
1354
1355
        return true;
1356
    }
1357
1358
    /**
1359
     * Delete all exercise attempts (included in LP or not).
1360
     *
1361
     * @param int user id
1362
     * @param int exercise id
1363
     * @param int $course_id
1364
     * @param int session id
1365
     */
1366
    public static function delete_all_incomplete_attempts(
1367
        $user_id,
1368
        $exercise_id,
1369
        $course_id,
1370
        $session_id = 0
1371
    ) {
1372
        $user_id = (int) $user_id;
1373
        $exercise_id = (int) $exercise_id;
1374
        $course_id = (int) $course_id;
1375
        $session_id = (int) $session_id;
1376
1377
        if (!empty($user_id) && !empty($exercise_id) && !empty($course_id)) {
1378
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1379
            $sql = "DELETE FROM $table
1380
                    WHERE
1381
                        exe_user_id = $user_id AND
1382
                        exe_exo_id = $exercise_id AND
1383
                        c_id = $course_id AND
1384
                        session_id = $session_id AND
1385
                        status = 'incomplete' ";
1386
            Database::query($sql);
1387
            self::addEvent(
1388
                LOG_EXERCISE_RESULT_DELETE,
1389
                LOG_EXERCISE_AND_USER_ID,
1390
                $exercise_id.'-'.$user_id,
1391
                null,
1392
                null,
1393
                $course_id,
1394
                $session_id
1395
            );
1396
        }
1397
    }
1398
1399
    /**
1400
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1401
     *
1402
     * @param int $exercise_id
1403
     * @param int $courseId
1404
     * @param int $session_id
1405
     *
1406
     * @return array with the results
1407
     */
1408
    public static function get_all_exercise_results(
1409
        $exercise_id,
1410
        $courseId,
1411
        $session_id = 0,
1412
        $load_question_list = true,
1413
        $user_id = null
1414
    ) {
1415
        $TABLETRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1416
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1417
        $courseId = (int) $courseId;
1418
        $exercise_id = (int) $exercise_id;
1419
        $session_id = (int) $session_id;
1420
1421
        $user_condition = null;
1422
        if (!empty($user_id)) {
1423
            $user_id = (int) $user_id;
1424
            $user_condition = "AND exe_user_id = $user_id ";
1425
        }
1426
        $sql = "SELECT * FROM $TABLETRACK_EXERCICES
1427
                WHERE
1428
                    status = ''  AND
1429
                    c_id = $courseId AND
1430
                    exe_exo_id = $exercise_id AND
1431
                    session_id = $session_id  AND
1432
                    orig_lp_id =0 AND
1433
                    orig_lp_item_id = 0
1434
                    $user_condition
1435
                ORDER BY exe_id";
1436
        $res = Database::query($sql);
1437
        $list = [];
1438
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1439
            $list[$row['exe_id']] = $row;
1440
            if ($load_question_list) {
1441
                $sql = "SELECT * FROM $TBL_TRACK_ATTEMPT
1442
                        WHERE exe_id = {$row['exe_id']}";
1443
                $res_question = Database::query($sql);
1444
                while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1445
                    $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1446
                }
1447
            }
1448
        }
1449
1450
        return $list;
1451
    }
1452
1453
    /**
1454
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1455
     *
1456
     * @param int  $courseId
1457
     * @param int  $session_id
1458
     * @param bool $get_count
1459
     *
1460
     * @return array with the results
1461
     */
1462
    public static function get_all_exercise_results_by_course(
1463
        $courseId,
1464
        $session_id = 0,
1465
        $get_count = true
1466
    ) {
1467
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1468
        $courseId = (int) $courseId;
1469
        $session_id = (int) $session_id;
1470
1471
        $select = '*';
1472
        if ($get_count) {
1473
            $select = 'count(*) as count';
1474
        }
1475
        $sql = "SELECT $select FROM $table_track_exercises
1476
                WHERE   status = ''  AND
1477
                        c_id = $courseId AND
1478
                        session_id = $session_id  AND
1479
                        orig_lp_id = 0 AND
1480
                        orig_lp_item_id = 0
1481
                ORDER BY exe_id";
1482
        $res = Database::query($sql);
1483
        if ($get_count) {
1484
            $row = Database::fetch_array($res, 'ASSOC');
1485
1486
            return $row['count'];
1487
        } else {
1488
            $list = [];
1489
            while ($row = Database::fetch_array($res, 'ASSOC')) {
1490
                $list[$row['exe_id']] = $row;
1491
            }
1492
1493
            return $list;
1494
        }
1495
    }
1496
1497
    /**
1498
     * Gets all exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1499
     *
1500
     * @param int $user_id
1501
     * @param int $courseId
1502
     * @param int $session_id
1503
     *
1504
     * @return array with the results
1505
     */
1506
    public static function get_all_exercise_results_by_user(
1507
        $user_id,
1508
        $courseId,
1509
        $session_id = 0
1510
    ) {
1511
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1512
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1513
        $courseId = (int) $courseId;
1514
        $session_id = (int) $session_id;
1515
        $user_id = (int) $user_id;
1516
1517
        $sql = "SELECT * FROM $table_track_exercises
1518
                WHERE
1519
                    status = '' AND
1520
                    exe_user_id = $user_id AND
1521
                    c_id = $courseId AND
1522
                    session_id = $session_id AND
1523
                    orig_lp_id = 0 AND
1524
                    orig_lp_item_id = 0
1525
                ORDER by exe_id";
1526
1527
        $res = Database::query($sql);
1528
        $list = [];
1529
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1530
            $list[$row['exe_id']] = $row;
1531
            $sql = "SELECT * FROM $table_track_attempt
1532
                    WHERE exe_id = {$row['exe_id']}";
1533
            $res_question = Database::query($sql);
1534
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1535
                $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1536
            }
1537
        }
1538
1539
        return $list;
1540
    }
1541
1542
    /**
1543
     * Gets exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1544
     *
1545
     * @param int    $exe_id attempt id
1546
     * @param string $status
1547
     *
1548
     * @return array with the results
1549
     */
1550
    public static function get_exercise_results_by_attempt($exe_id, $status = null)
1551
    {
1552
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1553
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1554
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1555
        $exe_id = (int) $exe_id;
1556
1557
        $status = Database::escape_string($status);
1558
1559
        $sql = "SELECT * FROM $table_track_exercises
1560
                WHERE status = '$status' AND exe_id = $exe_id";
1561
1562
        $res = Database::query($sql);
1563
        $list = [];
1564
        if (Database::num_rows($res)) {
1565
            $row = Database::fetch_array($res, 'ASSOC');
1566
1567
            //Checking if this attempt was revised by a teacher
1568
            $sql_revised = "SELECT exe_id FROM $table_track_attempt_recording
1569
                            WHERE author != '' AND exe_id = $exe_id
1570
                            LIMIT 1";
1571
            $res_revised = Database::query($sql_revised);
1572
            $row['attempt_revised'] = 0;
1573
            if (Database::num_rows($res_revised) > 0) {
1574
                $row['attempt_revised'] = 1;
1575
            }
1576
            $list[$exe_id] = $row;
1577
            $sql = "SELECT * FROM $table_track_attempt
1578
                    WHERE exe_id = $exe_id
1579
                    ORDER BY tms ASC";
1580
            $res_question = Database::query($sql);
1581
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1582
                $list[$exe_id]['question_list'][$row_q['question_id']] = $row_q;
1583
            }
1584
        }
1585
1586
        return $list;
1587
    }
1588
1589
    /**
1590
     * Gets exercise results (NO Exercises in LPs) from a given user, exercise id, course, session, lp_id, lp_item_id.
1591
     *
1592
     * @param   int     user id
1593
     * @param   int     exercise id
1594
     * @param   int     course id
1595
     * @param   int     session id
1596
     * @param   int     lp id
1597
     * @param   int     lp item id
1598
     * @param   string order asc or desc
1599
     *
1600
     * @return array with the results
1601
     */
1602
    public static function getExerciseResultsByUser(
1603
        $user_id,
1604
        $exercise_id,
1605
        $courseId,
1606
        $session_id = 0,
1607
        $lp_id = 0,
1608
        $lp_item_id = 0,
1609
        $order = null
1610
    ) {
1611
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1612
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1613
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1614
        $courseId = (int) $courseId;
1615
        $exercise_id = (int) $exercise_id;
1616
        $session_id = (int) $session_id;
1617
        $user_id = (int) $user_id;
1618
        $lp_id = (int) $lp_id;
1619
        $lp_item_id = (int) $lp_item_id;
1620
1621
        if (!in_array(strtolower($order), ['asc', 'desc'])) {
1622
            $order = 'asc';
1623
        }
1624
1625
        $sql = "SELECT * FROM $table_track_exercises
1626
                WHERE
1627
                    status 			= '' AND
1628
                    exe_user_id 	= $user_id AND
1629
                    c_id 	        = $courseId AND
1630
                    exe_exo_id 		= $exercise_id AND
1631
                    session_id 		= $session_id AND
1632
                    orig_lp_id 		= $lp_id AND
1633
                    orig_lp_item_id = $lp_item_id
1634
                ORDER by exe_id $order ";
1635
1636
        $res = Database::query($sql);
1637
        $list = [];
1638
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1639
            // Checking if this attempt was revised by a teacher
1640
            $exeId = $row['exe_id'];
1641
            $sql = "SELECT exe_id FROM $table_track_attempt_recording
1642
                    WHERE author != '' AND exe_id = $exeId
1643
                    LIMIT 1";
1644
            $res_revised = Database::query($sql);
1645
            $row['attempt_revised'] = 0;
1646
            if (Database::num_rows($res_revised) > 0) {
1647
                $row['attempt_revised'] = 1;
1648
            }
1649
            $row['total_percentage'] = ($row['exe_result'] / $row['exe_weighting']) * 100;
1650
1651
            $list[$row['exe_id']] = $row;
1652
            $sql = "SELECT * FROM $table_track_attempt
1653
                    WHERE exe_id = $exeId";
1654
            $res_question = Database::query($sql);
1655
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1656
                $list[$row['exe_id']]['question_list'][$row_q['question_id']][] = $row_q;
1657
            }
1658
        }
1659
1660
        return $list;
1661
    }
1662
1663
    /**
1664
     * Count exercise attempts (NO Exercises in LPs ) from a given exercise id, course, session.
1665
     *
1666
     * @param int $user_id
1667
     * @param int $exercise_id
1668
     * @param int $courseId
1669
     * @param int $session_id
1670
     *
1671
     * @return array with the results
1672
     */
1673
    public static function count_exercise_attempts_by_user(
1674
        $user_id,
1675
        $exercise_id,
1676
        $courseId,
1677
        $session_id = 0
1678
    ) {
1679
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1680
        $courseId = (int) $courseId;
1681
        $exercise_id = (int) $exercise_id;
1682
        $session_id = (int) $session_id;
1683
        $user_id = (int) $user_id;
1684
1685
        $sql = "SELECT count(*) as count
1686
                FROM $table
1687
                WHERE status = ''  AND
1688
                    exe_user_id = $user_id AND
1689
                    c_id = $courseId AND
1690
                    exe_exo_id = $exercise_id AND
1691
                    session_id = $session_id AND
1692
                    orig_lp_id =0 AND
1693
                    orig_lp_item_id = 0
1694
                ORDER BY exe_id";
1695
        $res = Database::query($sql);
1696
        $result = 0;
1697
        if (Database::num_rows($res) > 0) {
1698
            $row = Database::fetch_array($res, 'ASSOC');
1699
            $result = $row['count'];
1700
        }
1701
1702
        return $result;
1703
    }
1704
1705
    /**
1706
     * Gets all exercise BEST results attempts (NO Exercises in LPs)
1707
     * from a given exercise id, course, session per user.
1708
     *
1709
     * @param int $exercise_id
1710
     * @param int $courseId
1711
     * @param int $session_id
1712
     * @param int $userId
1713
     *
1714
     * @return array with the results
1715
     *
1716
     * @todo rename this function
1717
     */
1718
    public static function get_best_exercise_results_by_user(
1719
        $exercise_id,
1720
        $courseId,
1721
        $session_id = 0,
1722
        $userId = 0
1723
    ) {
1724
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1725
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1726
        $courseId = (int) $courseId;
1727
        $exercise_id = (int) $exercise_id;
1728
        $session_id = (int) $session_id;
1729
1730
        $sql = "SELECT * FROM $table_track_exercises
1731
                WHERE
1732
                    status = '' AND
1733
                    c_id = $courseId AND
1734
                    exe_exo_id = $exercise_id AND
1735
                    session_id = $session_id AND
1736
                    orig_lp_id = 0 AND
1737
                    orig_lp_item_id = 0";
1738
1739
        if (!empty($userId)) {
1740
            $userId = (int) $userId;
1741
            $sql .= " AND exe_user_id = $userId ";
1742
        }
1743
        $sql .= ' ORDER BY exe_id';
1744
1745
        $res = Database::query($sql);
1746
        $list = [];
1747
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1748
            $list[$row['exe_id']] = $row;
1749
            $exeId = $row['exe_id'];
1750
            $sql = "SELECT * FROM $table_track_attempt
1751
                    WHERE exe_id = $exeId";
1752
            $res_question = Database::query($sql);
1753
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1754
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1755
            }
1756
        }
1757
1758
        // Getting the best results of every student
1759
        $best_score_return = [];
1760
        foreach ($list as $student_result) {
1761
            $user_id = $student_result['exe_user_id'];
1762
            $current_best_score[$user_id] = $student_result['exe_result'];
1763
            if (!isset($best_score_return[$user_id]['exe_result'])) {
1764
                $best_score_return[$user_id] = $student_result;
1765
            }
1766
1767
            if ($current_best_score[$user_id] > $best_score_return[$user_id]['exe_result']) {
1768
                $best_score_return[$user_id] = $student_result;
1769
            }
1770
        }
1771
1772
        return $best_score_return;
1773
    }
1774
1775
    /**
1776
     * Get the last best result from all attempts in exercises per user (out of learning paths).
1777
     *
1778
     * @param int  $user_id
1779
     * @param int  $exercise_id
1780
     * @param int  $courseId
1781
     * @param int  $session_id
1782
     * @param bool $skipLpResults
1783
     *
1784
     * @return array
1785
     */
1786
    public static function get_best_attempt_exercise_results_per_user(
1787
        $user_id,
1788
        $exercise_id,
1789
        $courseId,
1790
        $session_id = 0,
1791
        $skipLpResults = true
1792
    ) {
1793
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1794
        $courseId = (int) $courseId;
1795
        $exercise_id = (int) $exercise_id;
1796
        $session_id = (int) $session_id;
1797
        $user_id = (int) $user_id;
1798
1799
        $sql = "SELECT * FROM $table
1800
                WHERE
1801
                    status = ''  AND
1802
                    c_id = $courseId AND
1803
                    exe_exo_id = $exercise_id AND
1804
                    session_id = $session_id  AND
1805
                    exe_user_id = $user_id
1806
                ";
1807
1808
        if ($skipLpResults) {
1809
            $sql .= ' AND
1810
                orig_lp_id = 0 AND
1811
                orig_lp_item_id = 0 ';
1812
        }
1813
1814
        $sql .= ' ORDER BY exe_id ';
1815
1816
        $res = Database::query($sql);
1817
        $list = [];
1818
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1819
            $list[$row['exe_id']] = $row;
1820
        }
1821
        // Getting the best results of every student.
1822
        $best_score_return = [];
1823
        $best_score_return['exe_result'] = 0;
1824
1825
        foreach ($list as $result) {
1826
            $current_best_score = $result;
1827
            if ($current_best_score['exe_result'] > $best_score_return['exe_result']) {
1828
                $best_score_return = $result;
1829
            }
1830
        }
1831
        if (!isset($best_score_return['exe_weighting'])) {
1832
            $best_score_return = [];
1833
        }
1834
1835
        return $best_score_return;
1836
    }
1837
1838
    /**
1839
     * @param int $exercise_id
1840
     * @param int $courseId
1841
     * @param int $session_id
1842
     *
1843
     * @return mixed
1844
     */
1845
    public static function count_exercise_result_not_validated(
1846
        $exercise_id,
1847
        $courseId,
1848
        $session_id = 0
1849
    ) {
1850
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1851
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1852
        $courseId = (int) $courseId;
1853
        $session_id = (int) $session_id;
1854
        $exercise_id = (int) $exercise_id;
1855
1856
        $sql = "SELECT count(e.exe_id) as count
1857
                FROM $table_track_exercises e
1858
                LEFT JOIN $table_track_attempt a
1859
                ON e.exe_id = a.exe_id
1860
                WHERE
1861
                    exe_exo_id = $exercise_id AND
1862
                    c_id = $courseId AND
1863
                    e.session_id = $session_id  AND
1864
                    orig_lp_id = 0 AND
1865
                    marks IS NULL AND
1866
                    status = '' AND
1867
                    orig_lp_item_id = 0
1868
                ORDER BY e.exe_id";
1869
        $res = Database::query($sql);
1870
        $row = Database::fetch_array($res, 'ASSOC');
1871
1872
        return $row['count'];
1873
    }
1874
1875
    /**
1876
     * Gets all exercise events from a Learning Path within a Course    nd Session.
1877
     *
1878
     * @param int $exercise_id
1879
     * @param int $courseId
1880
     * @param int $session_id
1881
     *
1882
     * @return array
1883
     */
1884
    public static function get_all_exercise_event_from_lp(
1885
        $exercise_id,
1886
        $courseId,
1887
        $session_id = 0
1888
    ) {
1889
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1890
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1891
        $courseId = (int) $courseId;
1892
        $exercise_id = (int) $exercise_id;
1893
        $session_id = (int) $session_id;
1894
1895
        $sql = "SELECT * FROM $table_track_exercises
1896
                WHERE
1897
                    status = '' AND
1898
                    c_id = $courseId AND
1899
                    exe_exo_id = $exercise_id AND
1900
                    session_id = $session_id AND
1901
                    orig_lp_id !=0 AND
1902
                    orig_lp_item_id != 0";
1903
1904
        $res = Database::query($sql);
1905
        $list = [];
1906
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1907
            $exeId = $row['exe_id'];
1908
            $list[$exeId] = $row;
1909
            $sql = "SELECT * FROM $table_track_attempt
1910
                    WHERE exe_id = $exeId";
1911
            $res_question = Database::query($sql);
1912
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1913
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1914
            }
1915
        }
1916
1917
        return $list;
1918
    }
1919
1920
    /**
1921
     * Get a list of all the exercises in a given learning path.
1922
     *
1923
     * @param int $lp_id
1924
     * @param int $course_id This parameter is probably deprecated as lp_id now is a global iid
1925
     *
1926
     * @return array
1927
     */
1928
    public static function get_all_exercises_from_lp($lp_id, $course_id)
1929
    {
1930
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
1931
        $course_id = (int) $course_id;
1932
        $lp_id = (int) $lp_id;
1933
        $sql = "SELECT * FROM $lp_item_table
1934
                WHERE
1935
                    c_id = $course_id AND
1936
                    lp_id = $lp_id AND
1937
                    item_type = 'quiz'
1938
                ORDER BY parent_item_id, display_order";
1939
        $res = Database::query($sql);
1940
1941
        $list = [];
1942
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1943
            $list[] = $row;
1944
        }
1945
1946
        return $list;
1947
    }
1948
1949
    /**
1950
     * This function gets the comments of an exercise.
1951
     *
1952
     * @param int $exe_id
1953
     * @param int $question_id
1954
     *
1955
     * @return string the comment
1956
     */
1957
    public static function get_comments($exe_id, $question_id)
1958
    {
1959
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1960
        $exe_id = (int) $exe_id;
1961
        $question_id = (int) $question_id;
1962
        $sql = "SELECT teacher_comment
1963
                FROM $table
1964
                WHERE
1965
                    exe_id = $exe_id AND
1966
                    question_id = $question_id
1967
                ORDER by question_id";
1968
        $sqlres = Database::query($sql);
1969
        $comm = strval(Database::result($sqlres, 0, 'teacher_comment'));
1970
        $comm = trim($comm);
1971
1972
        return $comm;
1973
    }
1974
1975
    /**
1976
     * Get all the track_e_attempt records for a given
1977
     * track_e_exercises.exe_id (pk).
1978
     *
1979
     * @param int $exeId The exe_id from an exercise attempt record
1980
     *
1981
     * @return array The complete records from track_e_attempt that match the given exe_id
1982
     */
1983
    public static function getAllExerciseEventByExeId($exeId)
1984
    {
1985
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1986
        $exeId = (int) $exeId;
1987
1988
        $sql = "SELECT * FROM $table
1989
                WHERE exe_id = $exeId
1990
                ORDER BY position";
1991
        $res_question = Database::query($sql);
1992
        $list = [];
1993
        if (Database::num_rows($res_question)) {
1994
            while ($row = Database::fetch_array($res_question, 'ASSOC')) {
1995
                $list[$row['question_id']][] = $row;
1996
            }
1997
        }
1998
1999
        return $list;
2000
    }
2001
2002
    public static function getQuestionAttemptByExeIdAndQuestion($exeId, $questionId)
2003
    {
2004
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2005
        $exeId = (int) $exeId;
2006
        $questionId = (int) $questionId;
2007
2008
        $sql = "SELECT * FROM $table
2009
                WHERE
2010
                    exe_id = $exeId AND
2011
                    question_id = $questionId
2012
                ORDER BY position";
2013
        $result = Database::query($sql);
2014
        $attempt = [];
2015
        if (Database::num_rows($result)) {
2016
            $attempt = Database::fetch_array($result, 'ASSOC');
2017
        }
2018
2019
        return $attempt;
2020
    }
2021
2022
    /**
2023
     * Delete one record from the track_e_attempt table (recorded quiz answer)
2024
     * and register the deletion event (LOG_QUESTION_RESULT_DELETE) in
2025
     * track_e_default.
2026
     *
2027
     * @param int $exeId       The track_e_exercises.exe_id (primary key)
2028
     * @param int $user_id     The user who answered (already contained in exe_id)
2029
     * @param int $courseId    The course in which it happened (already contained in exe_id)
2030
     * @param int $session_id  The session in which it happened (already contained in exe_id)
2031
     * @param int $question_id The c_quiz_question.iid
2032
     */
2033
    public static function delete_attempt(
2034
        $exeId,
2035
        $user_id,
2036
        $courseId,
2037
        $session_id,
2038
        $question_id
2039
    ) {
2040
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2041
2042
        $exeId = (int) $exeId;
2043
        $user_id = (int) $user_id;
2044
        $courseId = (int) $courseId;
2045
        $session_id = (int) $session_id;
2046
        $question_id = (int) $question_id;
2047
2048
        $sql = "DELETE FROM $table
2049
                WHERE
2050
                    exe_id = $exeId AND
2051
                    user_id = $user_id AND
2052
                    c_id = $courseId AND
2053
                    session_id = $session_id AND
2054
                    question_id = $question_id ";
2055
        Database::query($sql);
2056
2057
        self::addEvent(
2058
            LOG_QUESTION_RESULT_DELETE,
2059
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2060
            $exeId.'-'.$question_id,
2061
            null,
2062
            null,
2063
            $courseId,
2064
            $session_id
2065
        );
2066
    }
2067
2068
    /**
2069
     * Delete one record from the track_e_hotspot table based on a given
2070
     * track_e_exercises.exe_id.
2071
     *
2072
     * @param $exeId
2073
     * @param $user_id
2074
     * @param int $courseId
2075
     * @param $question_id
2076
     * @param int $sessionId
2077
     */
2078
    public static function delete_attempt_hotspot(
2079
        $exeId,
2080
        $user_id,
2081
        $courseId,
2082
        $question_id,
2083
        $sessionId = null
2084
    ) {
2085
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
2086
2087
        $exeId = (int) $exeId;
2088
        $user_id = (int) $user_id;
2089
        $courseId = (int) $courseId;
2090
        $question_id = (int) $question_id;
2091
        if (!isset($sessionId)) {
2092
            $sessionId = api_get_session_id();
2093
        }
2094
2095
        $sql = "DELETE FROM $table
2096
                WHERE
2097
                    hotspot_exe_id = $exeId AND
2098
                    hotspot_user_id = $user_id AND
2099
                    c_id = $courseId AND
2100
                    hotspot_question_id = $question_id ";
2101
        Database::query($sql);
2102
        self::addEvent(
2103
            LOG_QUESTION_RESULT_DELETE,
2104
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2105
            $exeId.'-'.$question_id,
2106
            null,
2107
            null,
2108
            $courseId,
2109
            $sessionId
2110
        );
2111
    }
2112
2113
    /**
2114
     * Registers in track_e_course_access when user logs in for the first time to a course.
2115
     *
2116
     * @param int $courseId  ID of the course
2117
     * @param int $user_id   ID of the user
2118
     * @param int $sessionId ID of the session (if any)
2119
     *
2120
     * @return bool
2121
     */
2122
    public static function eventCourseLogin($courseId, $user_id, $sessionId)
2123
    {
2124
        if (Session::read('login_as')) {
2125
            return false;
2126
        }
2127
2128
        $sessionId = (int) $sessionId;
2129
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2130
            return false;
2131
        }
2132
2133
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2134
        $loginDate = $logoutDate = api_get_utc_datetime();
2135
2136
        // $counter represents the number of time this record has been refreshed
2137
        $counter = 1;
2138
        $courseId = (int) $courseId;
2139
        $user_id = (int) $user_id;
2140
        $ip = Database::escape_string(api_get_real_ip());
2141
2142
        $sql = "INSERT INTO $table(c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2143
                VALUES($courseId, '$ip', $user_id, '$loginDate', '$logoutDate', $counter, $sessionId)";
2144
        $courseAccessId = Database::query($sql);
2145
2146
        if ($courseAccessId) {
2147
            // Course catalog stats modifications see #4191
2148
            CourseManager::update_course_ranking(
2149
                null,
2150
                null,
2151
                null,
2152
                null,
2153
                true,
2154
                false
2155
            );
2156
2157
            return true;
2158
        }
2159
    }
2160
2161
    /**
2162
     * Updates the user - course - session every X minutes
2163
     * In order to avoid.
2164
     *
2165
     * @param int $courseId
2166
     * @param int $userId
2167
     * @param int $sessionId
2168
     * @param int $minutes
2169
     *
2170
     * @return bool
2171
     */
2172
    public static function eventCourseLoginUpdate(
2173
        $courseId,
2174
        $userId,
2175
        $sessionId,
2176
        $minutes = 5
2177
    ) {
2178
        if (Session::read('login_as')) {
2179
            return false;
2180
        }
2181
2182
        if (empty($courseId) || empty($userId)) {
2183
            return false;
2184
        }
2185
2186
        $sessionId = (int) $sessionId;
2187
2188
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2189
            return false;
2190
        }
2191
2192
        $courseId = (int) $courseId;
2193
        $userId = (int) $userId;
2194
2195
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2196
        $sql = "SELECT course_access_id, logout_course_date
2197
                FROM $table
2198
                WHERE
2199
                    c_id = $courseId AND
2200
                    session_id = $sessionId AND
2201
                    user_id = $userId
2202
                ORDER BY login_course_date DESC
2203
                LIMIT 1";
2204
2205
        $result = Database::query($sql);
2206
2207
        // Save every 5 minutes by default
2208
        $seconds = $minutes * 60;
2209
        $maxSeconds = 3600; // Only update if max diff is one hour
2210
        if (Database::num_rows($result)) {
2211
            $row = Database::fetch_array($result);
2212
            $id = $row['course_access_id'];
2213
            $logout = $row['logout_course_date'];
2214
            $now = time();
2215
            $logout = api_strtotime($logout, 'UTC');
2216
            if ($now - $logout > $seconds &&
2217
                $now - $logout < $maxSeconds
2218
            ) {
2219
                $now = api_get_utc_datetime();
2220
                $sql = "UPDATE $table SET
2221
                            logout_course_date = '$now',
2222
                            counter = counter + 1
2223
                        WHERE course_access_id = $id";
2224
                Database::query($sql);
2225
            }
2226
2227
            return true;
2228
        }
2229
2230
        return false;
2231
    }
2232
2233
    /**
2234
     * Register the logout of the course (usually when logging out of the platform)
2235
     * from the track_e_course_access table.
2236
     *
2237
     * @param array $logoutInfo Information stored by local.inc.php
2238
     *                          before new context ['uid'=> x, 'cid'=>y, 'sid'=>z]
2239
     *
2240
     * @return bool
2241
     */
2242
    public static function courseLogout($logoutInfo)
2243
    {
2244
        if (Session::read('login_as')) {
2245
            return false;
2246
        }
2247
2248
        if (empty($logoutInfo['uid']) || empty($logoutInfo['cid'])) {
2249
            return false;
2250
        }
2251
2252
        $sessionLifetime = api_get_configuration_value('session_lifetime');
2253
        /*
2254
         * When $_configuration['session_lifetime'] is larger than ~100 hours
2255
         * (in order to let users take exercises with no problems)
2256
         * the function Tracking::get_time_spent_on_the_course() returns larger values (200h) due the condition:
2257
         * login_course_date > now() - INTERVAL $session_lifetime SECOND
2258
         */
2259
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
2260
            $sessionLifetime = 3600; // 1 hour
2261
        }
2262
        if (!empty($logoutInfo) && !empty($logoutInfo['cid'])) {
2263
            $sessionId = 0;
2264
            if (!empty($logoutInfo['sid'])) {
2265
                $sessionId = (int) $logoutInfo['sid'];
2266
            }
2267
2268
            if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2269
                return false;
2270
            }
2271
2272
            $tableCourseAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2273
            $userId = (int) $logoutInfo['uid'];
2274
            $courseId = (int) $logoutInfo['cid'];
2275
2276
            $currentDate = api_get_utc_datetime();
2277
            // UTC time
2278
            $diff = time() - $sessionLifetime;
2279
            $time = api_get_utc_datetime($diff);
2280
            $sql = "SELECT course_access_id, logout_course_date
2281
                    FROM $tableCourseAccess
2282
                    WHERE
2283
                        user_id = $userId AND
2284
                        c_id = $courseId  AND
2285
                        session_id = $sessionId AND
2286
                        login_course_date > '$time'
2287
                    ORDER BY login_course_date DESC
2288
                    LIMIT 1";
2289
            $result = Database::query($sql);
2290
            $insert = false;
2291
            if (Database::num_rows($result) > 0) {
2292
                $row = Database::fetch_array($result, 'ASSOC');
2293
                $courseAccessId = $row['course_access_id'];
2294
                $sql = "UPDATE $tableCourseAccess SET
2295
                                logout_course_date = '$currentDate',
2296
                                counter = counter + 1
2297
                            WHERE course_access_id = $courseAccessId";
2298
                Database::query($sql);
2299
            } else {
2300
                $insert = true;
2301
            }
2302
2303
            if ($insert) {
2304
                $ip = Database::escape_string(api_get_real_ip());
2305
                $sql = "INSERT INTO $tableCourseAccess (c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2306
                        VALUES ($courseId, '$ip', $userId, '$currentDate', '$currentDate', 1, $sessionId)";
2307
                Database::query($sql);
2308
            }
2309
2310
            return true;
2311
        }
2312
    }
2313
2314
    /**
2315
     * Register a "fake" time spent on the platform, for example to match the
2316
     * estimated time he took to author an assignment/work, see configuration
2317
     * setting considered_working_time.
2318
     * This assumes there is already some connection of the student to the
2319
     * course, otherwise he wouldn't be able to upload an assignment.
2320
     * This works by creating a new record, copy of the current one, then
2321
     * updating the current one to be just the considered_working_time and
2322
     * end at the same second as the user connected to the course.
2323
     *
2324
     * @param int    $courseId    The course in which to add the time
2325
     * @param int    $userId      The user for whom to add the time
2326
     * @param int    $sessionId   The session in which to add the time (if any)
2327
     * @param string $virtualTime The amount of time to be added,
2328
     *                            in a hh:mm:ss format. If int, we consider it is expressed in hours.
2329
     * @param int    $workId      Student publication id result
2330
     *
2331
     * @return true on successful insertion, false otherwise
2332
     */
2333
    public static function eventAddVirtualCourseTime(
2334
        $courseId,
2335
        $userId,
2336
        $sessionId,
2337
        $virtualTime,
2338
        $workId
2339
    ) {
2340
        if (empty($virtualTime)) {
2341
            return false;
2342
        }
2343
2344
        $courseId = (int) $courseId;
2345
        $userId = (int) $userId;
2346
        $sessionId = (int) $sessionId;
2347
2348
        $logoutDate = api_get_utc_datetime();
2349
        $loginDate = ChamiloApi::addOrSubTimeToDateTime(
2350
            $virtualTime,
2351
            $logoutDate,
2352
            false
2353
        );
2354
2355
        $ip = api_get_real_ip();
2356
        $params = [
2357
            'login_course_date' => $loginDate,
2358
            'logout_course_date' => $logoutDate,
2359
            'session_id' => $sessionId,
2360
            'user_id' => $userId,
2361
            'counter' => 0,
2362
            'c_id' => $courseId,
2363
            'user_ip' => $ip,
2364
        ];
2365
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2366
        Database::insert($courseTrackingTable, $params);
2367
2368
        // Time should also be added to the track_e_login table so as to
2369
        // affect total time on the platform
2370
        $params = [
2371
            'login_user_id' => $userId,
2372
            'login_date' => $loginDate,
2373
            'user_ip' => $ip,
2374
            'logout_date' => $logoutDate,
2375
        ];
2376
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2377
        Database::insert($platformTrackingTable, $params);
2378
2379
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2380
            $workId = (int) $workId;
2381
            $uniqueId = time();
2382
            $logInfo = [
2383
                'c_id' => $courseId,
2384
                'session_id' => $sessionId,
2385
                'tool' => TOOL_STUDENTPUBLICATION,
2386
                'date_reg' => $loginDate,
2387
                'action' => 'add_work_start_'.$workId,
2388
                'action_details' => $virtualTime,
2389
                'user_id' => $userId,
2390
                'current_id' => $uniqueId,
2391
            ];
2392
            self::registerLog($logInfo);
2393
2394
            $logInfo = [
2395
                'c_id' => $courseId,
2396
                'session_id' => $sessionId,
2397
                'tool' => TOOL_STUDENTPUBLICATION,
2398
                'date_reg' => $logoutDate,
2399
                'action' => 'add_work_end_'.$workId,
2400
                'action_details' => $virtualTime,
2401
                'user_id' => $userId,
2402
                'current_id' => $uniqueId,
2403
            ];
2404
            self::registerLog($logInfo);
2405
        }
2406
2407
        return true;
2408
    }
2409
2410
    /**
2411
     * Removes a "fake" time spent on the platform, for example to match the
2412
     * estimated time he took to author an assignment/work, see configuration
2413
     * setting considered_working_time.
2414
     * This method should be called when something that generated a fake
2415
     * time record is removed. Given the database link is weak (no real
2416
     * relationship kept between the deleted item and this record), this
2417
     * method just looks for the latest record that has the same time as the
2418
     * item's fake time, is in the past and in this course+session. If such a
2419
     * record cannot be found, it doesn't do anything.
2420
     * The IP address is not considered a useful filter here.
2421
     *
2422
     * @param int    $courseId    The course in which to add the time
2423
     * @param int    $userId      The user for whom to add the time
2424
     * @param int    $sessionId   The session in which to add the time (if any)
2425
     * @param string $virtualTime The amount of time to be added, in a hh:mm:ss format. If int, we consider it is expressed in hours.
2426
     *
2427
     * @return true on successful removal, false otherwise
2428
     */
2429
    public static function eventRemoveVirtualCourseTime(
2430
        $courseId,
2431
        $userId,
2432
        $sessionId = 0,
2433
        $virtualTime,
2434
        $workId
2435
    ) {
2436
        if (empty($virtualTime)) {
2437
            return false;
2438
        }
2439
2440
        $originalVirtualTime = $virtualTime;
2441
2442
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2443
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2444
        $courseId = (int) $courseId;
2445
        $userId = (int) $userId;
2446
        $sessionId = (int) $sessionId;
2447
2448
        // Change $virtualTime format from hh:mm:ss to hhmmss which is the
2449
        // format returned by SQL for a subtraction of two datetime values
2450
        // @todo make sure this is portable between DBMSes
2451
        if (preg_match('/:/', $virtualTime)) {
2452
            list($h, $m, $s) = preg_split('/:/', $virtualTime);
2453
            $virtualTime = $h * 3600 + $m * 60 + $s;
2454
        } else {
2455
            $virtualTime *= 3600;
2456
        }
2457
2458
        // Get the current latest course connection register. We need that
2459
        // record to re-use the data and create a new record.
2460
        $sql = "SELECT course_access_id
2461
                FROM $courseTrackingTable
2462
                WHERE
2463
                    user_id = $userId AND
2464
                    c_id = $courseId  AND
2465
                    session_id  = $sessionId AND
2466
                    counter = 0 AND
2467
                    (UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) = '$virtualTime'
2468
                ORDER BY login_course_date DESC LIMIT 0,1";
2469
        $result = Database::query($sql);
2470
2471
        // Ignore if we didn't find any course connection record in the last
2472
        // hour. In this case it wouldn't be right to add a "fake" time record.
2473
        if (Database::num_rows($result) > 0) {
2474
            // Found the latest connection
2475
            $row = Database::fetch_row($result);
2476
            $courseAccessId = $row[0];
2477
            $sql = "DELETE FROM $courseTrackingTable
2478
                    WHERE course_access_id = $courseAccessId";
2479
            Database::query($sql);
2480
        }
2481
        $sql = "SELECT login_id
2482
                FROM $platformTrackingTable
2483
                WHERE
2484
                    login_user_id = $userId AND
2485
                    (UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date)) = '$virtualTime'
2486
                ORDER BY login_date DESC LIMIT 0,1";
2487
        $result = Database::query($sql);
2488
        if (Database::num_rows($result) > 0) {
2489
            // Found the latest connection
2490
            $row = Database::fetch_row($result);
2491
            $loginAccessId = $row[0];
2492
            $sql = "DELETE FROM $platformTrackingTable
2493
                    WHERE login_id = $loginAccessId";
2494
            Database::query($sql);
2495
        }
2496
2497
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2498
            $workId = (int) $workId;
2499
            $sql = "SELECT id FROM track_e_access_complete
2500
                    WHERE
2501
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2502
                        c_id = $courseId AND
2503
                        session_id = $sessionId AND
2504
                        user_id = $userId AND
2505
                        action_details = '$originalVirtualTime' AND
2506
                        action = 'add_work_start_$workId' ";
2507
            $result = Database::query($sql);
2508
            $result = Database::fetch_array($result);
2509
            if ($result) {
2510
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2511
                Database::query($sql);
2512
            }
2513
2514
            $sql = "SELECT id FROM track_e_access_complete
2515
                    WHERE
2516
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2517
                        c_id = $courseId AND
2518
                        session_id = $sessionId AND
2519
                        user_id = $userId AND
2520
                        action_details = '$originalVirtualTime' AND
2521
                        action = 'add_work_end_$workId' ";
2522
            $result = Database::query($sql);
2523
            $result = Database::fetch_array($result);
2524
            if ($result) {
2525
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2526
                Database::query($sql);
2527
            }
2528
        }
2529
2530
        return false;
2531
    }
2532
2533
    /**
2534
     * For the sake of cohesion, this function is a switch.
2535
     * It's called by EventsDispatcher and fires the right function
2536
     * with the right require_once.
2537
     *
2538
     * @deprecated
2539
     *
2540
     * @param string $event_name
2541
     * @param array  $params
2542
     */
2543
    public static function event_send_mail($event_name, $params)
2544
    {
2545
        EventsMail::send_mail($event_name, $params);
2546
    }
2547
2548
    /**
2549
     * Filter EventEmailTemplate Filters see the main/inc/conf/events.conf.dist.php.
2550
     */
2551
2552
    /**
2553
     * Basic template event message filter (to be used by other filters as default).
2554
     *
2555
     * @deprecated
2556
     *
2557
     * @param array $values (passing by reference)     *
2558
     *
2559
     * @return bool True if everything is OK, false otherwise
2560
     */
2561
    public function event_send_mail_filter_func(&$values)
2562
    {
2563
        return true;
2564
    }
2565
2566
    /**
2567
     * user_registration - send_mail filter.
2568
     *
2569
     * @deprecated
2570
     *
2571
     * @param array $values (passing by reference)
2572
     *
2573
     * @return bool True if everything is OK, false otherwise
2574
     */
2575
    public function user_registration_event_send_mail_filter_func(&$values)
2576
    {
2577
        $res = self::event_send_mail_filter_func($values);
2578
        // proper logic for this filter
2579
        return $res;
2580
    }
2581
2582
    /**
2583
     * portal_homepage_edited - send_mail filter.
2584
     *
2585
     * @deprecated
2586
     *
2587
     * @param array $values (passing by reference)
2588
     *
2589
     * @return bool True if everything is OK, false otherwise
2590
     */
2591
    public function portal_homepage_edited_event_send_mail_filter_func(&$values)
2592
    {
2593
        $res = self::event_send_mail_filter_func($values);
2594
        // proper logic for this filter
2595
        return $res;
2596
    }
2597
2598
    /**
2599
     * Register the logout of the course (usually when logging out of the platform)
2600
     * from the track_e_access_complete table.
2601
     *
2602
     * @param array $logInfo Information stored by local.inc.php
2603
     *
2604
     * @return bool
2605
     */
2606
    public static function registerLog($logInfo)
2607
    {
2608
        $sessionId = api_get_session_id();
2609
        $courseId = api_get_course_int_id();
2610
2611
        if (isset($logInfo['c_id']) && !empty($logInfo['c_id'])) {
2612
            $courseId = $logInfo['c_id'];
2613
        }
2614
2615
        if (isset($logInfo['session_id']) && !empty($logInfo['session_id'])) {
2616
            $sessionId = $logInfo['session_id'];
2617
        }
2618
2619
        if (!Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2620
            return false;
2621
        }
2622
2623
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2624
            return false;
2625
        }
2626
2627
        $loginAs = (int) Session::read('login_as') === true;
2628
2629
        $logInfo['user_id'] = isset($logInfo['user_id']) ? $logInfo['user_id'] : api_get_user_id();
2630
        $logInfo['date_reg'] = isset($logInfo['date_reg']) ? $logInfo['date_reg'] : api_get_utc_datetime();
2631
        $logInfo['tool'] = !empty($logInfo['tool']) ? $logInfo['tool'] : '';
2632
        $logInfo['tool_id'] = !empty($logInfo['tool_id']) ? (int) $logInfo['tool_id'] : 0;
2633
        $logInfo['tool_id_detail'] = !empty($logInfo['tool_id_detail']) ? (int) $logInfo['tool_id_detail'] : 0;
2634
        $logInfo['action'] = !empty($logInfo['action']) ? $logInfo['action'] : '';
2635
        $logInfo['action_details'] = !empty($logInfo['action_details']) ? $logInfo['action_details'] : '';
2636
        $logInfo['ip_user'] = api_get_real_ip();
2637
        $logInfo['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
2638
        $logInfo['session_id'] = $sessionId;
2639
        $logInfo['c_id'] = $courseId;
2640
        $logInfo['ch_sid'] = session_id();
2641
        $logInfo['login_as'] = $loginAs;
2642
        $logInfo['info'] = !empty($logInfo['info']) ? $logInfo['info'] : '';
2643
        $logInfo['url'] = $_SERVER['REQUEST_URI'];
2644
        $logInfo['current_id'] = isset($logInfo['current_id']) ? $logInfo['current_id'] : Session::read('last_id', 0);
2645
2646
        $id = Database::insert('track_e_access_complete', $logInfo);
2647
        if ($id && empty($logInfo['current_id'])) {
2648
            Session::write('last_id', $id);
2649
        }
2650
2651
        return true;
2652
    }
2653
2654
    public static function getAttemptQuestionDuration($exeId, $questionId)
2655
    {
2656
        // Check current attempt.
2657
        $questionAttempt = self::getQuestionAttemptByExeIdAndQuestion($exeId, $questionId);
2658
        $alreadySpent = 0;
2659
        if (!empty($questionAttempt) && $questionAttempt['seconds_spent']) {
2660
            $alreadySpent = $questionAttempt['seconds_spent'];
2661
        }
2662
        $now = time();
2663
        $questionStart = Session::read('question_start', []);
2664
        if (!empty($questionStart) &&
2665
            isset($questionStart[$questionId]) && !empty($questionStart[$questionId])
2666
        ) {
2667
            $time = $questionStart[$questionId];
2668
        } else {
2669
            $diff = 0;
2670
            if (!empty($alreadySpent)) {
2671
                $diff = $alreadySpent;
2672
            }
2673
            $time = $questionStart[$questionId] = $now - $diff;
2674
            Session::write('question_start', $questionStart);
2675
        }
2676
2677
        return $now - $time;
2678
    }
2679
}
2680