Completed
Push — 1.11.x ( 0adea3...e968b6 )
by Angel Fernando Quiroz
01:23 queued 37s
created

Event::logSubscribedUserInCourse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 16
nc 1
nop 2
dl 0
loc 21
rs 9.7333
c 0
b 0
f 0
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
        $lpId = 0,
730
        $lpItemId = 0
731
    ) {
732
        $debug = false;
733
734
        if ($updateResults == false) {
735
            // Validation in case of fraud with activated control time
736
            if (!ExerciseLib::exercise_time_control_is_valid($exerciseId, $lpId, $lpItemId)) {
737
                if ($debug) {
738
                    error_log('Attempt is fraud');
739
                }
740
                $correct = 0;
741
            }
742
        }
743
744
        if (empty($exeId)) {
745
            if ($debug) {
746
                error_log('exe id is empty');
747
            }
748
749
            return false;
750
        }
751
752
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
753
        if ($updateResults) {
754
            if ($debug) {
755
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
756
            }
757
            $params = [
758
                'hotspot_correct' => $correct,
759
                'hotspot_coordinate' => $coords,
760
            ];
761
            Database::update(
762
                $table,
763
                $params,
764
                [
765
                    'hotspot_user_id = ? AND hotspot_exe_id = ? AND hotspot_question_id = ? AND hotspot_answer_id = ? ' => [
766
                        api_get_user_id(),
767
                        $exeId,
768
                        $questionId,
769
                        $answerId,
770
                    ],
771
                ]
772
            );
773
        } else {
774
            if ($debug) {
775
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
776
            }
777
778
            return Database::insert(
779
                $table,
780
                [
781
                    'hotspot_course_code' => api_get_course_id(),
782
                    'hotspot_user_id' => api_get_user_id(),
783
                    'c_id' => api_get_course_int_id(),
784
                    'hotspot_exe_id' => $exeId,
785
                    'hotspot_question_id' => $questionId,
786
                    'hotspot_answer_id' => $answerId,
787
                    'hotspot_correct' => $correct,
788
                    'hotspot_coordinate' => $coords,
789
                ]
790
            );
791
        }
792
    }
793
794
    /**
795
     * Records information for common (or admin) events (in the track_e_default table).
796
     *
797
     * @author Yannick Warnier <[email protected]>
798
     *
799
     * @param string $event_type       Type of event
800
     * @param string $event_value_type Type of value
801
     * @param mixed  $event_value      Value (string, or array in the case of user info)
802
     * @param string $datetime         Datetime (UTC) (defaults to null)
803
     * @param int    $user_id          User ID (defaults to null)
804
     * @param int    $course_id        Course ID (defaults to null)
805
     * @param int    $sessionId        Session ID
806
     *
807
     * @return bool
808
     * @assert ('','','') === false
809
     */
810
    public static function addEvent(
811
        $event_type,
812
        $event_value_type,
813
        $event_value,
814
        $datetime = null,
815
        $user_id = null,
816
        $course_id = null,
817
        $sessionId = 0
818
    ) {
819
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
820
821
        if (empty($event_type)) {
822
            return false;
823
        }
824
        $event_type = Database::escape_string($event_type);
825
        $event_value_type = Database::escape_string($event_value_type);
826
        if (!empty($course_id)) {
827
            $course_id = (int) $course_id;
828
        } else {
829
            $course_id = api_get_course_int_id();
830
        }
831
        if (!empty($sessionId)) {
832
            $sessionId = (int) $sessionId;
833
        } else {
834
            $sessionId = api_get_session_id();
835
        }
836
837
        //Clean the user_info
838
        if ($event_value_type == LOG_USER_OBJECT) {
839
            if (is_array($event_value)) {
840
                unset($event_value['complete_name']);
841
                unset($event_value['complete_name_with_username']);
842
                unset($event_value['firstName']);
843
                unset($event_value['lastName']);
844
                unset($event_value['avatar_small']);
845
                unset($event_value['avatar']);
846
                unset($event_value['mail']);
847
                unset($event_value['password']);
848
                unset($event_value['last_login']);
849
                unset($event_value['picture_uri']);
850
                $event_value = serialize($event_value);
851
            }
852
        }
853
        // If event is an array then the $event_value_type should finish with
854
        // the suffix _array for example LOG_WORK_DATA = work_data_array
855
        if (is_array($event_value)) {
856
            $event_value = serialize($event_value);
857
        }
858
859
        $event_value = Database::escape_string($event_value);
860
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
861
862
        if (!isset($datetime)) {
863
            $datetime = api_get_utc_datetime();
864
        }
865
866
        $datetime = Database::escape_string($datetime);
867
868
        if (!isset($user_id)) {
869
            $user_id = api_get_user_id();
870
        }
871
872
        $params = [
873
            'default_user_id' => $user_id,
874
            'c_id' => $course_id,
875
            'default_date' => $datetime,
876
            'default_event_type' => $event_type,
877
            'default_value_type' => $event_value_type,
878
            'default_value' => $event_value,
879
            'session_id' => $sessionId,
880
        ];
881
        Database::insert($table, $params);
882
883
        return true;
884
    }
885
886
    public static function findUserSubscriptionToCourse(int $userId, int $courseId, int $sessionId = 0)
887
    {
888
        $tblTrackEDefault = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
889
890
        return Database::select(
891
            '*',
892
            $tblTrackEDefault,
893
            [
894
                'where' => [
895
                    'default_event_type = ? AND ' => LOG_SUBSCRIBE_USER_TO_COURSE,
896
                    'default_value_type = ? AND ' => LOG_USER_OBJECT,
897
                    'default_value LIKE ? AND ' => '%s:2:\\\\"id\\\\";i:'.$userId.'%',
898
                    'c_id = ? AND ' => $courseId,
899
                    'session_id = ?' => $sessionId,
900
                ],
901
            ],
902
            'first'
903
        );
904
    }
905
906
    /**
907
     * Get every email stored in the database.
908
     *
909
     * @deprecated
910
     *
911
     * @return array
912
     * @assert () !== false
913
     */
914
    public static function get_all_event_types()
915
    {
916
        global $event_config;
917
918
        $sql = 'SELECT etm.id, event_type_name, activated, language_id, message, subject, dokeos_folder
919
                FROM '.Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE).' etm
920
                INNER JOIN '.Database::get_main_table(TABLE_MAIN_LANGUAGE).' l
921
                ON etm.language_id = l.id';
922
923
        $events_types = Database::store_result(Database::query($sql), 'ASSOC');
924
925
        $to_return = [];
926
        foreach ($events_types as $et) {
927
            $et['nameLangVar'] = $event_config[$et["event_type_name"]]["name_lang_var"];
928
            $et['descLangVar'] = $event_config[$et["event_type_name"]]["desc_lang_var"];
929
            $to_return[] = $et;
930
        }
931
932
        return $to_return;
933
    }
934
935
    /**
936
     * Get the users related to one event.
937
     *
938
     * @param string $event_name
939
     *
940
     * @return string
941
     */
942
    public static function get_event_users($event_name)
943
    {
944
        $event_name = Database::escape_string($event_name);
945
        $sql = 'SELECT user.user_id,  user.firstname, user.lastname
946
                FROM '.Database::get_main_table(TABLE_MAIN_USER).' user
947
                JOIN '.Database::get_main_table(TABLE_EVENT_TYPE_REL_USER).' relUser
948
                ON relUser.user_id = user.user_id
949
                WHERE user.status <> '.ANONYMOUS.' AND relUser.event_type_name = "'.$event_name.'"';
950
        $user_list = Database::store_result(Database::query($sql), 'ASSOC');
951
952
        return json_encode($user_list);
953
    }
954
955
    /**
956
     * @param int    $user_id
957
     * @param string $event_type
958
     *
959
     * @return array|bool
960
     */
961
    public static function get_events_by_user_and_type($user_id, $event_type)
962
    {
963
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
964
        $user_id = (int) $user_id;
965
        $event_type = Database::escape_string($event_type);
966
967
        $sql = "SELECT * FROM $table
968
                WHERE default_value_type = 'user_id' AND
969
                      default_value = $user_id AND
970
                      default_event_type = '$event_type'
971
                ORDER BY default_date ";
972
        $result = Database::query($sql);
973
        if ($result) {
974
            return Database::store_result($result, 'ASSOC');
975
        }
976
977
        return false;
978
    }
979
980
    /**
981
     * Save the new message for one event and for one language.
982
     *
983
     * @param string $event_name
984
     * @param array  $users
985
     * @param string $message
986
     * @param string $subject
987
     * @param string $event_message_language
988
     * @param int    $activated
989
     */
990
    public static function save_event_type_message(
991
        $event_name,
992
        $users,
993
        $message,
994
        $subject,
995
        $event_message_language,
996
        $activated
997
    ) {
998
        $event_name = Database::escape_string($event_name);
999
        $activated = (int) $activated;
1000
        $event_message_language = Database::escape_string($event_message_language);
1001
1002
        // Deletes then re-adds the users linked to the event
1003
        $sql = 'DELETE FROM '.Database::get_main_table(TABLE_EVENT_TYPE_REL_USER).'
1004
                WHERE event_type_name = "'.$event_name.'"	';
1005
        Database::query($sql);
1006
1007
        $eventTable = Database::get_main_table(TABLE_EVENT_TYPE_REL_USER);
1008
1009
        foreach ($users as $user) {
1010
            $user = (int) $user;
1011
            $sql = "INSERT INTO $eventTable (user_id,event_type_name)
1012
                    VALUES($user,'$event_name')";
1013
            Database::query($sql);
1014
        }
1015
        $language_id = api_get_language_id($event_message_language);
1016
        // check if this template in this language already exists or not
1017
        $eventMailTable = Database::get_main_table(TABLE_EVENT_EMAIL_TEMPLATE);
1018
        $sql = "SELECT COUNT(id) as total
1019
                FROM $eventMailTable
1020
                WHERE event_type_name = '$event_name' AND language_id = $language_id";
1021
1022
        $sql = Database::store_result(Database::query($sql), 'ASSOC');
1023
1024
        $languageTable = Database::get_main_table(TABLE_MAIN_LANGUAGE);
1025
        $message = Database::escape_string($message);
1026
        $subject = Database::escape_string($subject);
1027
        // if already exists, we update
1028
        if ($sql[0]["total"] > 0) {
1029
            $sql = "UPDATE $eventMailTable
1030
                SET message = '$message',
1031
                subject = '$subject',
1032
                activated = $activated
1033
                WHERE event_type_name = '$event_name' AND language_id = (
1034
                    SELECT id FROM $languageTable
1035
                    WHERE dokeos_folder = '$event_message_language'
1036
                )";
1037
            Database::query($sql);
1038
        } else { // else we create a new record
1039
            // gets the language_-_id
1040
            $lang_id = "(SELECT id FROM $languageTable
1041
                        WHERE dokeos_folder = '$event_message_language')";
1042
            $lang_id = Database::store_result(Database::query($lang_id), 'ASSOC');
1043
            $lang_id = $lang_id[0]['id'];
1044
1045
            if (!empty($lang_id[0]["id"])) {
1046
                $sql = "INSERT INTO $eventMailTable (event_type_name, language_id, message, subject, activated)
1047
                    VALUES ('$event_name', $lang_id, '$message', '$subject', $activated)";
1048
                Database::query($sql);
1049
            }
1050
        }
1051
1052
        // set activated at every save
1053
        $sql = "UPDATE $eventMailTable
1054
                SET activated = $activated
1055
                WHERE event_type_name = '$event_name'";
1056
        Database::query($sql);
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 getLastAttemptDateOfExercise($exeId)
1067
    {
1068
        $exeId = (int) $exeId;
1069
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1070
        $sql = "SELECT max(tms) as last_attempt_date
1071
                FROM $track_attempts
1072
                WHERE exe_id = $exeId";
1073
        $rs_last_attempt = Database::query($sql);
1074
        $row_last_attempt = Database::fetch_array($rs_last_attempt);
1075
        $date = $row_last_attempt['last_attempt_date']; //Get the date of last attempt
1076
1077
        return $date;
1078
    }
1079
1080
    /**
1081
     * Gets the last attempt of an exercise based in the exe_id.
1082
     *
1083
     * @param int $exeId
1084
     *
1085
     * @return mixed
1086
     */
1087
    public static function getLatestQuestionIdFromAttempt($exeId)
1088
    {
1089
        $exeId = (int) $exeId;
1090
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1091
        $sql = "SELECT question_id FROM $track_attempts
1092
                WHERE exe_id = $exeId
1093
                ORDER BY tms DESC
1094
                LIMIT 1";
1095
        $result = Database::query($sql);
1096
        if (Database::num_rows($result)) {
1097
            $row = Database::fetch_array($result);
1098
1099
            return $row['question_id'];
1100
        }
1101
1102
        return false;
1103
    }
1104
1105
    /**
1106
     * Gets how many attempts exists by user, exercise, learning path.
1107
     *
1108
     * @param   int user id
1109
     * @param   int exercise id
1110
     * @param   int lp id
1111
     * @param   int lp item id
1112
     * @param   int lp item view id
1113
     *
1114
     * @return int
1115
     */
1116
    public static function get_attempt_count(
1117
        $user_id,
1118
        $exerciseId,
1119
        $lp_id,
1120
        $lp_item_id,
1121
        $lp_item_view_id
1122
    ) {
1123
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1124
        $user_id = (int) $user_id;
1125
        $exerciseId = (int) $exerciseId;
1126
        $lp_id = (int) $lp_id;
1127
        $lp_item_id = (int) $lp_item_id;
1128
        $lp_item_view_id = (int) $lp_item_view_id;
1129
        $courseId = api_get_course_int_id();
1130
        $sessionId = api_get_session_id();
1131
1132
        $sql = "SELECT count(*) as count
1133
                FROM $table
1134
                WHERE
1135
                    exe_exo_id = $exerciseId AND
1136
                    exe_user_id = $user_id AND
1137
                    status != 'incomplete' AND
1138
                    orig_lp_id = $lp_id AND
1139
                    orig_lp_item_id = $lp_item_id AND
1140
                    orig_lp_item_view_id = $lp_item_view_id AND
1141
                    c_id = $courseId AND
1142
                    session_id = $sessionId";
1143
1144
        $result = Database::query($sql);
1145
        if (Database::num_rows($result) > 0) {
1146
            $attempt = Database::fetch_array($result, 'ASSOC');
1147
1148
            return (int) $attempt['count'];
1149
        }
1150
1151
        return 0;
1152
    }
1153
1154
    public static function getAttemptPosition(
1155
        $exeId,
1156
        $user_id,
1157
        $exerciseId,
1158
        $lp_id,
1159
        $lp_item_id,
1160
        $lp_item_view_id
1161
    ) {
1162
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1163
        $user_id = (int) $user_id;
1164
        $exerciseId = (int) $exerciseId;
1165
        $lp_id = (int) $lp_id;
1166
        $lp_item_id = (int) $lp_item_id;
1167
        $lp_item_view_id = (int) $lp_item_view_id;
1168
        $courseId = api_get_course_int_id();
1169
        $sessionId = api_get_session_id();
1170
1171
        $sql = "SELECT exe_id
1172
                FROM $table
1173
                WHERE
1174
                    exe_exo_id = $exerciseId AND
1175
                    exe_user_id = $user_id AND
1176
                    status = '' AND
1177
                    orig_lp_id = $lp_id AND
1178
                    orig_lp_item_id = $lp_item_id AND
1179
                    orig_lp_item_view_id = $lp_item_view_id AND
1180
                    c_id = $courseId AND
1181
                    session_id = $sessionId
1182
                ORDER by exe_id
1183
                ";
1184
1185
        $result = Database::query($sql);
1186
        if (Database::num_rows($result) > 0) {
1187
            $position = 1;
1188
            while ($row = Database::fetch_array($result, 'ASSOC')) {
1189
                if ($row['exe_id'] === $exeId) {
1190
                    break;
1191
                }
1192
                $position++;
1193
            }
1194
1195
            return $position;
1196
        }
1197
1198
        return 0;
1199
    }
1200
1201
    /**
1202
     * @param $user_id
1203
     * @param $exerciseId
1204
     * @param $lp_id
1205
     * @param $lp_item_id
1206
     *
1207
     * @return int
1208
     */
1209
    public static function get_attempt_count_not_finished(
1210
        $user_id,
1211
        $exerciseId,
1212
        $lp_id,
1213
        $lp_item_id
1214
    ) {
1215
        $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1216
        $user_id = (int) $user_id;
1217
        $exerciseId = (int) $exerciseId;
1218
        $lp_id = (int) $lp_id;
1219
        $lp_item_id = (int) $lp_item_id;
1220
        //$lp_item_view_id = (int) $lp_item_view_id;
1221
        $courseId = api_get_course_int_id();
1222
        $sessionId = api_get_session_id();
1223
1224
        $sql = "SELECT count(*) as count
1225
                FROM $stat_table
1226
                WHERE
1227
                    exe_exo_id 			= $exerciseId AND
1228
                    exe_user_id 		= $user_id AND
1229
                    status 				!= 'incomplete' AND
1230
                    orig_lp_id 			= $lp_id AND
1231
                    orig_lp_item_id 	= $lp_item_id AND
1232
                    c_id = $courseId AND
1233
                    session_id = $sessionId";
1234
1235
        $query = Database::query($sql);
1236
        if (Database::num_rows($query) > 0) {
1237
            $attempt = Database::fetch_array($query, 'ASSOC');
1238
1239
            return (int) $attempt['count'];
1240
        }
1241
1242
        return 0;
1243
    }
1244
1245
    /**
1246
     * @param int   $user_id
1247
     * @param int   $lp_id
1248
     * @param array $course
1249
     * @param int   $session_id
1250
     * @param bool  $disconnectExerciseResultsFromLp (Replace orig_lp_* variables to null)
1251
     *
1252
     * @return bool
1253
     */
1254
    public static function delete_student_lp_events(
1255
        $user_id,
1256
        $lp_id,
1257
        $course,
1258
        $session_id,
1259
        $disconnectExerciseResultsFromLp = false
1260
    ) {
1261
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
1262
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1263
        $lpInteraction = Database::get_course_table(TABLE_LP_IV_INTERACTION);
1264
        $lpObjective = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
1265
1266
        if (empty($course) || empty($user_id)) {
1267
            return false;
1268
        }
1269
1270
        $course_id = $course['real_id'];
1271
        $user_id = (int) $user_id;
1272
        $lp_id = (int) $lp_id;
1273
        $session_id = (int) $session_id;
1274
1275
        if (empty($course_id)) {
1276
            $course_id = api_get_course_int_id();
1277
        }
1278
1279
        $track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1280
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1281
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1282
1283
        // Make sure we have the exact lp_view_id
1284
        $sql = "SELECT id FROM $lp_view_table
1285
                WHERE
1286
                    c_id = $course_id AND
1287
                    user_id = $user_id AND
1288
                    lp_id = $lp_id AND
1289
                    session_id = $session_id";
1290
        $result = Database::query($sql);
1291
1292
        if (Database::num_rows($result)) {
1293
            $view = Database::fetch_array($result, 'ASSOC');
1294
            $lp_view_id = $view['id'];
1295
1296
            $sql = "DELETE FROM $lp_item_view_table
1297
                    WHERE c_id = $course_id AND lp_view_id = $lp_view_id";
1298
            Database::query($sql);
1299
1300
            $sql = "DELETE FROM $lpInteraction
1301
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1302
            Database::query($sql);
1303
1304
            $sql = "DELETE FROM $lpObjective
1305
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1306
            Database::query($sql);
1307
        }
1308
1309
        if (api_get_configuration_value('lp_minimum_time')) {
1310
            $sql = "DELETE FROM track_e_access_complete
1311
                    WHERE
1312
                        tool = 'learnpath' AND
1313
                        c_id = $course_id AND
1314
                        tool_id = $lp_id AND
1315
                        user_id = $user_id AND
1316
                        session_id = $session_id
1317
                    ";
1318
            Database::query($sql);
1319
        }
1320
1321
        $sql = "SELECT exe_id FROM $track_e_exercises
1322
                WHERE
1323
                    exe_user_id = $user_id AND
1324
                    session_id = $session_id AND
1325
                    c_id = $course_id AND
1326
                    orig_lp_id = $lp_id";
1327
        $result = Database::query($sql);
1328
        $exeList = [];
1329
        while ($row = Database::fetch_array($result, 'ASSOC')) {
1330
            $exeList[] = $row['exe_id'];
1331
        }
1332
1333
        if (!empty($exeList) && count($exeList) > 0) {
1334
            $exeListString = implode(',', $exeList);
1335
            if ($disconnectExerciseResultsFromLp) {
1336
                $sql = "UPDATE $track_e_exercises
1337
                        SET orig_lp_id = null,
1338
                            orig_lp_item_id = null,
1339
                            orig_lp_item_view_id = null
1340
                        WHERE exe_id IN ($exeListString)";
1341
                Database::query($sql);
1342
            } else {
1343
                $sql = "DELETE FROM $track_e_exercises
1344
                        WHERE exe_id IN ($exeListString)";
1345
                Database::query($sql);
1346
1347
                $sql = "DELETE FROM $track_attempts
1348
                        WHERE exe_id IN ($exeListString)";
1349
                Database::query($sql);
1350
1351
                $sql = "DELETE FROM $recording_table
1352
                        WHERE exe_id IN ($exeListString)";
1353
                Database::query($sql);
1354
            }
1355
        }
1356
1357
        $sql = "DELETE FROM $lp_view_table
1358
                WHERE
1359
                    c_id = $course_id AND
1360
                    user_id = $user_id AND
1361
                    lp_id= $lp_id AND
1362
                    session_id = $session_id
1363
            ";
1364
        Database::query($sql);
1365
1366
        self::addEvent(
1367
            LOG_LP_ATTEMPT_DELETE,
1368
            LOG_LP_ID,
1369
            $lp_id,
1370
            null,
1371
            null,
1372
            $course_id,
1373
            $session_id
1374
        );
1375
1376
        return true;
1377
    }
1378
1379
    /**
1380
     * Delete all exercise attempts (included in LP or not).
1381
     *
1382
     * @param int user id
1383
     * @param int exercise id
1384
     * @param int $course_id
1385
     * @param int session id
1386
     */
1387
    public static function delete_all_incomplete_attempts(
1388
        $user_id,
1389
        $exercise_id,
1390
        $course_id,
1391
        $session_id = 0
1392
    ) {
1393
        $user_id = (int) $user_id;
1394
        $exercise_id = (int) $exercise_id;
1395
        $course_id = (int) $course_id;
1396
        $session_id = (int) $session_id;
1397
1398
        if (!empty($user_id) && !empty($exercise_id) && !empty($course_id)) {
1399
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1400
            $sql = "DELETE FROM $table
1401
                    WHERE
1402
                        exe_user_id = $user_id AND
1403
                        exe_exo_id = $exercise_id AND
1404
                        c_id = $course_id AND
1405
                        session_id = $session_id AND
1406
                        status = 'incomplete' ";
1407
            Database::query($sql);
1408
            self::addEvent(
1409
                LOG_EXERCISE_RESULT_DELETE,
1410
                LOG_EXERCISE_AND_USER_ID,
1411
                $exercise_id.'-'.$user_id,
1412
                null,
1413
                null,
1414
                $course_id,
1415
                $session_id
1416
            );
1417
        }
1418
    }
1419
1420
    /**
1421
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1422
     *
1423
     * @param int $exercise_id
1424
     * @param int $courseId
1425
     * @param int $session_id
1426
     *
1427
     * @return array with the results
1428
     */
1429
    public static function get_all_exercise_results(
1430
        $exercise_id,
1431
        $courseId,
1432
        $session_id = 0,
1433
        $load_question_list = true,
1434
        $user_id = null
1435
    ) {
1436
        $TABLETRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1437
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1438
        $courseId = (int) $courseId;
1439
        $exercise_id = (int) $exercise_id;
1440
        $session_id = (int) $session_id;
1441
1442
        $user_condition = null;
1443
        if (!empty($user_id)) {
1444
            $user_id = (int) $user_id;
1445
            $user_condition = "AND exe_user_id = $user_id ";
1446
        }
1447
        $sql = "SELECT * FROM $TABLETRACK_EXERCICES
1448
                WHERE
1449
                    status = ''  AND
1450
                    c_id = $courseId AND
1451
                    exe_exo_id = $exercise_id AND
1452
                    session_id = $session_id  AND
1453
                    orig_lp_id =0 AND
1454
                    orig_lp_item_id = 0
1455
                    $user_condition
1456
                ORDER BY exe_id";
1457
        $res = Database::query($sql);
1458
        $list = [];
1459
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1460
            $list[$row['exe_id']] = $row;
1461
            if ($load_question_list) {
1462
                $sql = "SELECT * FROM $TBL_TRACK_ATTEMPT
1463
                        WHERE exe_id = {$row['exe_id']}";
1464
                $res_question = Database::query($sql);
1465
                while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1466
                    $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1467
                }
1468
            }
1469
        }
1470
1471
        return $list;
1472
    }
1473
1474
    /**
1475
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1476
     *
1477
     * @param int  $courseId
1478
     * @param int  $session_id
1479
     * @param bool $get_count
1480
     *
1481
     * @return array with the results
1482
     */
1483
    public static function get_all_exercise_results_by_course(
1484
        $courseId,
1485
        $session_id = 0,
1486
        $get_count = true
1487
    ) {
1488
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1489
        $courseId = (int) $courseId;
1490
        $session_id = (int) $session_id;
1491
1492
        $select = '*';
1493
        if ($get_count) {
1494
            $select = 'count(*) as count';
1495
        }
1496
        $sql = "SELECT $select FROM $table_track_exercises
1497
                WHERE   status = ''  AND
1498
                        c_id = $courseId AND
1499
                        session_id = $session_id  AND
1500
                        orig_lp_id = 0 AND
1501
                        orig_lp_item_id = 0
1502
                ORDER BY exe_id";
1503
        $res = Database::query($sql);
1504
        if ($get_count) {
1505
            $row = Database::fetch_array($res, 'ASSOC');
1506
1507
            return $row['count'];
1508
        } else {
1509
            $list = [];
1510
            while ($row = Database::fetch_array($res, 'ASSOC')) {
1511
                $list[$row['exe_id']] = $row;
1512
            }
1513
1514
            return $list;
1515
        }
1516
    }
1517
1518
    /**
1519
     * Gets all exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1520
     *
1521
     * @param int $user_id
1522
     * @param int $courseId
1523
     * @param int $session_id
1524
     *
1525
     * @return array with the results
1526
     */
1527
    public static function get_all_exercise_results_by_user(
1528
        $user_id,
1529
        $courseId,
1530
        $session_id = 0
1531
    ) {
1532
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1533
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1534
        $courseId = (int) $courseId;
1535
        $session_id = (int) $session_id;
1536
        $user_id = (int) $user_id;
1537
1538
        $sql = "SELECT * FROM $table_track_exercises
1539
                WHERE
1540
                    status = '' AND
1541
                    exe_user_id = $user_id AND
1542
                    c_id = $courseId AND
1543
                    session_id = $session_id AND
1544
                    orig_lp_id = 0 AND
1545
                    orig_lp_item_id = 0
1546
                ORDER by exe_id";
1547
1548
        $res = Database::query($sql);
1549
        $list = [];
1550
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1551
            $list[$row['exe_id']] = $row;
1552
            $sql = "SELECT * FROM $table_track_attempt
1553
                    WHERE exe_id = {$row['exe_id']}";
1554
            $res_question = Database::query($sql);
1555
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1556
                $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1557
            }
1558
        }
1559
1560
        return $list;
1561
    }
1562
1563
    /**
1564
     * Gets exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1565
     *
1566
     * @param int    $exe_id attempt id
1567
     * @param string $status
1568
     *
1569
     * @return array with the results
1570
     */
1571
    public static function get_exercise_results_by_attempt($exe_id, $status = null)
1572
    {
1573
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1574
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1575
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1576
        $exe_id = (int) $exe_id;
1577
1578
        $status = Database::escape_string($status);
1579
1580
        $sql = "SELECT * FROM $table_track_exercises
1581
                WHERE status = '$status' AND exe_id = $exe_id";
1582
1583
        $res = Database::query($sql);
1584
        $list = [];
1585
        if (Database::num_rows($res)) {
1586
            $row = Database::fetch_array($res, 'ASSOC');
1587
1588
            //Checking if this attempt was revised by a teacher
1589
            $sql_revised = "SELECT exe_id FROM $table_track_attempt_recording
1590
                            WHERE author != '' AND exe_id = $exe_id
1591
                            LIMIT 1";
1592
            $res_revised = Database::query($sql_revised);
1593
            $row['attempt_revised'] = 0;
1594
            if (Database::num_rows($res_revised) > 0) {
1595
                $row['attempt_revised'] = 1;
1596
            }
1597
            $list[$exe_id] = $row;
1598
            $sql = "SELECT * FROM $table_track_attempt
1599
                    WHERE exe_id = $exe_id
1600
                    ORDER BY tms ASC";
1601
            $res_question = Database::query($sql);
1602
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1603
                $list[$exe_id]['question_list'][$row_q['question_id']] = $row_q;
1604
            }
1605
        }
1606
1607
        return $list;
1608
    }
1609
1610
    /**
1611
     * Gets exercise results (NO Exercises in LPs) from a given user, exercise id, course, session, lp_id, lp_item_id.
1612
     *
1613
     * @param   int     user id
1614
     * @param   int     exercise id
1615
     * @param   int     course id
1616
     * @param   int     session id
1617
     * @param   int     lp id
1618
     * @param   int     lp item id
1619
     * @param   string order asc or desc
1620
     *
1621
     * @return array with the results
1622
     */
1623
    public static function getExerciseResultsByUser(
1624
        $user_id,
1625
        $exercise_id,
1626
        $courseId,
1627
        $session_id = 0,
1628
        $lp_id = 0,
1629
        $lp_item_id = 0,
1630
        $order = null
1631
    ) {
1632
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1633
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1634
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1635
        $courseId = (int) $courseId;
1636
        $exercise_id = (int) $exercise_id;
1637
        $session_id = (int) $session_id;
1638
        $user_id = (int) $user_id;
1639
        $lp_id = (int) $lp_id;
1640
        $lp_item_id = (int) $lp_item_id;
1641
1642
        if (!in_array(strtolower($order), ['asc', 'desc'])) {
1643
            $order = 'asc';
1644
        }
1645
1646
        $sql = "SELECT * FROM $table_track_exercises
1647
                WHERE
1648
                    status 			= '' AND
1649
                    exe_user_id 	= $user_id AND
1650
                    c_id 	        = $courseId AND
1651
                    exe_exo_id 		= $exercise_id AND
1652
                    session_id 		= $session_id AND
1653
                    orig_lp_id 		= $lp_id AND
1654
                    orig_lp_item_id = $lp_item_id
1655
                ORDER by exe_id $order ";
1656
1657
        $res = Database::query($sql);
1658
        $list = [];
1659
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1660
            // Checking if this attempt was revised by a teacher
1661
            $exeId = $row['exe_id'];
1662
            $sql = "SELECT exe_id FROM $table_track_attempt_recording
1663
                    WHERE author != '' AND exe_id = $exeId
1664
                    LIMIT 1";
1665
            $res_revised = Database::query($sql);
1666
            $row['attempt_revised'] = 0;
1667
            if (Database::num_rows($res_revised) > 0) {
1668
                $row['attempt_revised'] = 1;
1669
            }
1670
            $row['total_percentage'] = ($row['exe_result'] / $row['exe_weighting']) * 100;
1671
1672
            $list[$row['exe_id']] = $row;
1673
            $sql = "SELECT * FROM $table_track_attempt
1674
                    WHERE exe_id = $exeId";
1675
            $res_question = Database::query($sql);
1676
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1677
                $list[$row['exe_id']]['question_list'][$row_q['question_id']][] = $row_q;
1678
            }
1679
        }
1680
1681
        return $list;
1682
    }
1683
1684
    /**
1685
     * Count exercise attempts (NO Exercises in LPs ) from a given exercise id, course, session.
1686
     *
1687
     * @param int $user_id
1688
     * @param int $exercise_id
1689
     * @param int $courseId
1690
     * @param int $session_id
1691
     *
1692
     * @return array with the results
1693
     */
1694
    public static function count_exercise_attempts_by_user(
1695
        $user_id,
1696
        $exercise_id,
1697
        $courseId,
1698
        $session_id = 0
1699
    ) {
1700
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1701
        $courseId = (int) $courseId;
1702
        $exercise_id = (int) $exercise_id;
1703
        $session_id = (int) $session_id;
1704
        $user_id = (int) $user_id;
1705
1706
        $sql = "SELECT count(*) as count
1707
                FROM $table
1708
                WHERE status = ''  AND
1709
                    exe_user_id = $user_id AND
1710
                    c_id = $courseId AND
1711
                    exe_exo_id = $exercise_id AND
1712
                    session_id = $session_id AND
1713
                    orig_lp_id =0 AND
1714
                    orig_lp_item_id = 0
1715
                ORDER BY exe_id";
1716
        $res = Database::query($sql);
1717
        $result = 0;
1718
        if (Database::num_rows($res) > 0) {
1719
            $row = Database::fetch_array($res, 'ASSOC');
1720
            $result = $row['count'];
1721
        }
1722
1723
        return $result;
1724
    }
1725
1726
    /**
1727
     * Gets all exercise BEST results attempts (NO Exercises in LPs)
1728
     * from a given exercise id, course, session per user.
1729
     *
1730
     * @param int $exercise_id
1731
     * @param int $courseId
1732
     * @param int $session_id
1733
     * @param int $userId
1734
     *
1735
     * @return array with the results
1736
     *
1737
     * @todo rename this function
1738
     */
1739
    public static function get_best_exercise_results_by_user(
1740
        $exercise_id,
1741
        $courseId,
1742
        $session_id = 0,
1743
        $userId = 0
1744
    ) {
1745
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1746
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1747
        $courseId = (int) $courseId;
1748
        $exercise_id = (int) $exercise_id;
1749
        $session_id = (int) $session_id;
1750
1751
        $sql = "SELECT * FROM $table_track_exercises
1752
                WHERE
1753
                    status = '' AND
1754
                    c_id = $courseId AND
1755
                    exe_exo_id = $exercise_id AND
1756
                    session_id = $session_id AND
1757
                    orig_lp_id = 0 AND
1758
                    orig_lp_item_id = 0";
1759
1760
        if (!empty($userId)) {
1761
            $userId = (int) $userId;
1762
            $sql .= " AND exe_user_id = $userId ";
1763
        }
1764
        $sql .= ' ORDER BY exe_id';
1765
1766
        $res = Database::query($sql);
1767
        $list = [];
1768
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1769
            $list[$row['exe_id']] = $row;
1770
            $exeId = $row['exe_id'];
1771
            $sql = "SELECT * FROM $table_track_attempt
1772
                    WHERE exe_id = $exeId";
1773
            $res_question = Database::query($sql);
1774
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1775
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1776
            }
1777
        }
1778
1779
        // Getting the best results of every student
1780
        $best_score_return = [];
1781
        foreach ($list as $student_result) {
1782
            $user_id = $student_result['exe_user_id'];
1783
            $current_best_score[$user_id] = $student_result['exe_result'];
1784
            if (!isset($best_score_return[$user_id]['exe_result'])) {
1785
                $best_score_return[$user_id] = $student_result;
1786
            }
1787
1788
            if ($current_best_score[$user_id] > $best_score_return[$user_id]['exe_result']) {
1789
                $best_score_return[$user_id] = $student_result;
1790
            }
1791
        }
1792
1793
        return $best_score_return;
1794
    }
1795
1796
    /**
1797
     * Get the last best result from all attempts in exercises per user (out of learning paths).
1798
     *
1799
     * @param int  $user_id
1800
     * @param int  $exercise_id
1801
     * @param int  $courseId
1802
     * @param int  $session_id
1803
     * @param bool $skipLpResults
1804
     *
1805
     * @return array
1806
     */
1807
    public static function get_best_attempt_exercise_results_per_user(
1808
        $user_id,
1809
        $exercise_id,
1810
        $courseId,
1811
        $session_id = 0,
1812
        $skipLpResults = true
1813
    ) {
1814
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1815
        $courseId = (int) $courseId;
1816
        $exercise_id = (int) $exercise_id;
1817
        $session_id = (int) $session_id;
1818
        $user_id = (int) $user_id;
1819
1820
        $sql = "SELECT * FROM $table
1821
                WHERE
1822
                    status = ''  AND
1823
                    c_id = $courseId AND
1824
                    exe_exo_id = $exercise_id AND
1825
                    session_id = $session_id  AND
1826
                    exe_user_id = $user_id
1827
                ";
1828
1829
        if ($skipLpResults) {
1830
            $sql .= ' AND
1831
                orig_lp_id = 0 AND
1832
                orig_lp_item_id = 0 ';
1833
        }
1834
1835
        $sql .= ' ORDER BY exe_id ';
1836
1837
        $res = Database::query($sql);
1838
        $list = [];
1839
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1840
            $list[$row['exe_id']] = $row;
1841
        }
1842
        // Getting the best results of every student.
1843
        $best_score_return = [];
1844
        $best_score_return['exe_result'] = 0;
1845
1846
        foreach ($list as $result) {
1847
            $current_best_score = $result;
1848
            if ($current_best_score['exe_result'] > $best_score_return['exe_result']) {
1849
                $best_score_return = $result;
1850
            }
1851
        }
1852
        if (!isset($best_score_return['exe_weighting'])) {
1853
            $best_score_return = [];
1854
        }
1855
1856
        return $best_score_return;
1857
    }
1858
1859
    /**
1860
     * @param int $exercise_id
1861
     * @param int $courseId
1862
     * @param int $session_id
1863
     *
1864
     * @return mixed
1865
     */
1866
    public static function count_exercise_result_not_validated(
1867
        $exercise_id,
1868
        $courseId,
1869
        $session_id = 0
1870
    ) {
1871
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1872
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1873
        $courseId = (int) $courseId;
1874
        $session_id = (int) $session_id;
1875
        $exercise_id = (int) $exercise_id;
1876
1877
        $sql = "SELECT count(e.exe_id) as count
1878
                FROM $table_track_exercises e
1879
                LEFT JOIN $table_track_attempt a
1880
                ON e.exe_id = a.exe_id
1881
                WHERE
1882
                    exe_exo_id = $exercise_id AND
1883
                    c_id = $courseId AND
1884
                    e.session_id = $session_id  AND
1885
                    orig_lp_id = 0 AND
1886
                    marks IS NULL AND
1887
                    status = '' AND
1888
                    orig_lp_item_id = 0
1889
                ORDER BY e.exe_id";
1890
        $res = Database::query($sql);
1891
        $row = Database::fetch_array($res, 'ASSOC');
1892
1893
        return $row['count'];
1894
    }
1895
1896
    /**
1897
     * Gets all exercise events from a Learning Path within a Course    nd Session.
1898
     *
1899
     * @param int $exercise_id
1900
     * @param int $courseId
1901
     * @param int $session_id
1902
     *
1903
     * @return array
1904
     */
1905
    public static function get_all_exercise_event_from_lp(
1906
        $exercise_id,
1907
        $courseId,
1908
        $session_id = 0
1909
    ) {
1910
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1911
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1912
        $courseId = (int) $courseId;
1913
        $exercise_id = (int) $exercise_id;
1914
        $session_id = (int) $session_id;
1915
1916
        $sql = "SELECT * FROM $table_track_exercises
1917
                WHERE
1918
                    status = '' AND
1919
                    c_id = $courseId AND
1920
                    exe_exo_id = $exercise_id AND
1921
                    session_id = $session_id AND
1922
                    orig_lp_id !=0 AND
1923
                    orig_lp_item_id != 0";
1924
1925
        $res = Database::query($sql);
1926
        $list = [];
1927
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1928
            $exeId = $row['exe_id'];
1929
            $list[$exeId] = $row;
1930
            $sql = "SELECT * FROM $table_track_attempt
1931
                    WHERE exe_id = $exeId";
1932
            $res_question = Database::query($sql);
1933
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1934
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1935
            }
1936
        }
1937
1938
        return $list;
1939
    }
1940
1941
    /**
1942
     * Get a list of all the exercises in a given learning path.
1943
     *
1944
     * @param int $lp_id
1945
     * @param int $course_id This parameter is probably deprecated as lp_id now is a global iid
1946
     *
1947
     * @return array
1948
     */
1949
    public static function get_all_exercises_from_lp($lp_id, $course_id)
1950
    {
1951
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
1952
        $course_id = (int) $course_id;
1953
        $lp_id = (int) $lp_id;
1954
        $sql = "SELECT * FROM $lp_item_table
1955
                WHERE
1956
                    c_id = $course_id AND
1957
                    lp_id = $lp_id AND
1958
                    item_type = 'quiz'
1959
                ORDER BY parent_item_id, display_order";
1960
        $res = Database::query($sql);
1961
1962
        $list = [];
1963
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1964
            $list[] = $row;
1965
        }
1966
1967
        return $list;
1968
    }
1969
1970
    /**
1971
     * This function gets the comments of an exercise.
1972
     *
1973
     * @param int $exe_id
1974
     * @param int $question_id
1975
     *
1976
     * @return string the comment
1977
     */
1978
    public static function get_comments($exe_id, $question_id)
1979
    {
1980
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1981
        $exe_id = (int) $exe_id;
1982
        $question_id = (int) $question_id;
1983
        $sql = "SELECT teacher_comment
1984
                FROM $table
1985
                WHERE
1986
                    exe_id = $exe_id AND
1987
                    question_id = $question_id
1988
                ORDER by question_id";
1989
        $sqlres = Database::query($sql);
1990
        $comm = strval(Database::result($sqlres, 0, 'teacher_comment'));
1991
        $comm = trim($comm);
1992
1993
        return $comm;
1994
    }
1995
1996
    /**
1997
     * Get all the track_e_attempt records for a given
1998
     * track_e_exercises.exe_id (pk).
1999
     *
2000
     * @param int $exeId The exe_id from an exercise attempt record
2001
     *
2002
     * @return array The complete records from track_e_attempt that match the given exe_id
2003
     */
2004
    public static function getAllExerciseEventByExeId($exeId)
2005
    {
2006
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2007
        $exeId = (int) $exeId;
2008
2009
        $sql = "SELECT * FROM $table
2010
                WHERE exe_id = $exeId
2011
                ORDER BY position";
2012
        $res_question = Database::query($sql);
2013
        $list = [];
2014
        if (Database::num_rows($res_question)) {
2015
            while ($row = Database::fetch_array($res_question, 'ASSOC')) {
2016
                $list[$row['question_id']][] = $row;
2017
            }
2018
        }
2019
2020
        return $list;
2021
    }
2022
2023
    public static function getQuestionAttemptByExeIdAndQuestion($exeId, $questionId)
2024
    {
2025
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2026
        $exeId = (int) $exeId;
2027
        $questionId = (int) $questionId;
2028
2029
        $sql = "SELECT * FROM $table
2030
                WHERE
2031
                    exe_id = $exeId AND
2032
                    question_id = $questionId
2033
                ORDER BY position";
2034
        $result = Database::query($sql);
2035
        $attempt = [];
2036
        if (Database::num_rows($result)) {
2037
            $attempt = Database::fetch_array($result, 'ASSOC');
2038
        }
2039
2040
        return $attempt;
2041
    }
2042
2043
    /**
2044
     * Delete one record from the track_e_attempt table (recorded quiz answer)
2045
     * and register the deletion event (LOG_QUESTION_RESULT_DELETE) in
2046
     * track_e_default.
2047
     *
2048
     * @param int $exeId       The track_e_exercises.exe_id (primary key)
2049
     * @param int $user_id     The user who answered (already contained in exe_id)
2050
     * @param int $courseId    The course in which it happened (already contained in exe_id)
2051
     * @param int $session_id  The session in which it happened (already contained in exe_id)
2052
     * @param int $question_id The c_quiz_question.iid
2053
     */
2054
    public static function delete_attempt(
2055
        $exeId,
2056
        $user_id,
2057
        $courseId,
2058
        $session_id,
2059
        $question_id
2060
    ) {
2061
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2062
2063
        $exeId = (int) $exeId;
2064
        $user_id = (int) $user_id;
2065
        $courseId = (int) $courseId;
2066
        $session_id = (int) $session_id;
2067
        $question_id = (int) $question_id;
2068
2069
        $sql = "DELETE FROM $table
2070
                WHERE
2071
                    exe_id = $exeId AND
2072
                    user_id = $user_id AND
2073
                    c_id = $courseId AND
2074
                    session_id = $session_id AND
2075
                    question_id = $question_id ";
2076
        Database::query($sql);
2077
2078
        self::addEvent(
2079
            LOG_QUESTION_RESULT_DELETE,
2080
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2081
            $exeId.'-'.$question_id,
2082
            null,
2083
            null,
2084
            $courseId,
2085
            $session_id
2086
        );
2087
    }
2088
2089
    /**
2090
     * Delete one record from the track_e_hotspot table based on a given
2091
     * track_e_exercises.exe_id.
2092
     *
2093
     * @param $exeId
2094
     * @param $user_id
2095
     * @param int $courseId
2096
     * @param $question_id
2097
     * @param int $sessionId
2098
     */
2099
    public static function delete_attempt_hotspot(
2100
        $exeId,
2101
        $user_id,
2102
        $courseId,
2103
        $question_id,
2104
        $sessionId = null
2105
    ) {
2106
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
2107
2108
        $exeId = (int) $exeId;
2109
        $user_id = (int) $user_id;
2110
        $courseId = (int) $courseId;
2111
        $question_id = (int) $question_id;
2112
        if (!isset($sessionId)) {
2113
            $sessionId = api_get_session_id();
2114
        }
2115
2116
        $sql = "DELETE FROM $table
2117
                WHERE
2118
                    hotspot_exe_id = $exeId AND
2119
                    hotspot_user_id = $user_id AND
2120
                    c_id = $courseId AND
2121
                    hotspot_question_id = $question_id ";
2122
        Database::query($sql);
2123
        self::addEvent(
2124
            LOG_QUESTION_RESULT_DELETE,
2125
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2126
            $exeId.'-'.$question_id,
2127
            null,
2128
            null,
2129
            $courseId,
2130
            $sessionId
2131
        );
2132
    }
2133
2134
    /**
2135
     * Registers in track_e_course_access when user logs in for the first time to a course.
2136
     *
2137
     * @param int $courseId  ID of the course
2138
     * @param int $user_id   ID of the user
2139
     * @param int $sessionId ID of the session (if any)
2140
     *
2141
     * @return bool
2142
     */
2143
    public static function eventCourseLogin($courseId, $user_id, $sessionId)
2144
    {
2145
        if (Session::read('login_as')) {
2146
            return false;
2147
        }
2148
2149
        $sessionId = (int) $sessionId;
2150
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2151
            return false;
2152
        }
2153
2154
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2155
        $loginDate = $logoutDate = api_get_utc_datetime();
2156
2157
        // $counter represents the number of time this record has been refreshed
2158
        $counter = 1;
2159
        $courseId = (int) $courseId;
2160
        $user_id = (int) $user_id;
2161
        $ip = Database::escape_string(api_get_real_ip());
2162
2163
        $sql = "INSERT INTO $table(c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2164
                VALUES($courseId, '$ip', $user_id, '$loginDate', '$logoutDate', $counter, $sessionId)";
2165
        $courseAccessId = Database::query($sql);
2166
2167
        if ($courseAccessId) {
2168
            // Course catalog stats modifications see #4191
2169
            CourseManager::update_course_ranking(
2170
                null,
2171
                null,
2172
                null,
2173
                null,
2174
                true,
2175
                false
2176
            );
2177
2178
            return true;
2179
        }
2180
    }
2181
2182
    /**
2183
     * Updates the user - course - session every X minutes
2184
     * In order to avoid.
2185
     *
2186
     * @param int $courseId
2187
     * @param int $userId
2188
     * @param int $sessionId
2189
     * @param int $minutes
2190
     *
2191
     * @return bool
2192
     */
2193
    public static function eventCourseLoginUpdate(
2194
        $courseId,
2195
        $userId,
2196
        $sessionId,
2197
        $minutes = 5
2198
    ) {
2199
        if (Session::read('login_as')) {
2200
            return false;
2201
        }
2202
2203
        if (empty($courseId) || empty($userId)) {
2204
            return false;
2205
        }
2206
2207
        $sessionId = (int) $sessionId;
2208
2209
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2210
            return false;
2211
        }
2212
2213
        $courseId = (int) $courseId;
2214
        $userId = (int) $userId;
2215
2216
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2217
        $sql = "SELECT course_access_id, logout_course_date
2218
                FROM $table
2219
                WHERE
2220
                    c_id = $courseId AND
2221
                    session_id = $sessionId AND
2222
                    user_id = $userId
2223
                ORDER BY login_course_date DESC
2224
                LIMIT 1";
2225
2226
        $result = Database::query($sql);
2227
2228
        // Save every 5 minutes by default
2229
        $seconds = $minutes * 60;
2230
        $maxSeconds = 3600; // Only update if max diff is one hour
2231
        if (Database::num_rows($result)) {
2232
            $row = Database::fetch_array($result);
2233
            $id = $row['course_access_id'];
2234
            $logout = $row['logout_course_date'];
2235
            $now = time();
2236
            $logout = api_strtotime($logout, 'UTC');
2237
            if ($now - $logout > $seconds &&
2238
                $now - $logout < $maxSeconds
2239
            ) {
2240
                $now = api_get_utc_datetime();
2241
                $sql = "UPDATE $table SET
2242
                            logout_course_date = '$now',
2243
                            counter = counter + 1
2244
                        WHERE course_access_id = $id";
2245
                Database::query($sql);
2246
            }
2247
2248
            return true;
2249
        }
2250
2251
        return false;
2252
    }
2253
2254
    /**
2255
     * Register the logout of the course (usually when logging out of the platform)
2256
     * from the track_e_course_access table.
2257
     *
2258
     * @param array $logoutInfo Information stored by local.inc.php
2259
     *                          before new context ['uid'=> x, 'cid'=>y, 'sid'=>z]
2260
     *
2261
     * @return bool
2262
     */
2263
    public static function courseLogout($logoutInfo)
2264
    {
2265
        if (Session::read('login_as')) {
2266
            return false;
2267
        }
2268
2269
        if (empty($logoutInfo['uid']) || empty($logoutInfo['cid'])) {
2270
            return false;
2271
        }
2272
2273
        $sessionLifetime = api_get_configuration_value('session_lifetime');
2274
        /*
2275
         * When $_configuration['session_lifetime'] is larger than ~100 hours
2276
         * (in order to let users take exercises with no problems)
2277
         * the function Tracking::get_time_spent_on_the_course() returns larger values (200h) due the condition:
2278
         * login_course_date > now() - INTERVAL $session_lifetime SECOND
2279
         */
2280
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
2281
            $sessionLifetime = 3600; // 1 hour
2282
        }
2283
        if (!empty($logoutInfo) && !empty($logoutInfo['cid'])) {
2284
            $sessionId = 0;
2285
            if (!empty($logoutInfo['sid'])) {
2286
                $sessionId = (int) $logoutInfo['sid'];
2287
            }
2288
2289
            if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2290
                return false;
2291
            }
2292
2293
            $tableCourseAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2294
            $userId = (int) $logoutInfo['uid'];
2295
            $courseId = (int) $logoutInfo['cid'];
2296
2297
            $currentDate = api_get_utc_datetime();
2298
            // UTC time
2299
            $diff = time() - $sessionLifetime;
2300
            $time = api_get_utc_datetime($diff);
2301
            $sql = "SELECT course_access_id, logout_course_date
2302
                    FROM $tableCourseAccess
2303
                    WHERE
2304
                        user_id = $userId AND
2305
                        c_id = $courseId  AND
2306
                        session_id = $sessionId AND
2307
                        login_course_date > '$time'
2308
                    ORDER BY login_course_date DESC
2309
                    LIMIT 1";
2310
            $result = Database::query($sql);
2311
            $insert = false;
2312
            if (Database::num_rows($result) > 0) {
2313
                $row = Database::fetch_array($result, 'ASSOC');
2314
                $courseAccessId = $row['course_access_id'];
2315
                $sql = "UPDATE $tableCourseAccess SET
2316
                                logout_course_date = '$currentDate',
2317
                                counter = counter + 1
2318
                            WHERE course_access_id = $courseAccessId";
2319
                Database::query($sql);
2320
            } else {
2321
                $insert = true;
2322
            }
2323
2324
            if ($insert) {
2325
                $ip = Database::escape_string(api_get_real_ip());
2326
                $sql = "INSERT INTO $tableCourseAccess (c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2327
                        VALUES ($courseId, '$ip', $userId, '$currentDate', '$currentDate', 1, $sessionId)";
2328
                Database::query($sql);
2329
            }
2330
2331
            return true;
2332
        }
2333
    }
2334
2335
    /**
2336
     * Register a "fake" time spent on the platform, for example to match the
2337
     * estimated time he took to author an assignment/work, see configuration
2338
     * setting considered_working_time.
2339
     * This assumes there is already some connection of the student to the
2340
     * course, otherwise he wouldn't be able to upload an assignment.
2341
     * This works by creating a new record, copy of the current one, then
2342
     * updating the current one to be just the considered_working_time and
2343
     * end at the same second as the user connected to the course.
2344
     *
2345
     * @param int    $courseId    The course in which to add the time
2346
     * @param int    $userId      The user for whom to add the time
2347
     * @param int    $sessionId   The session in which to add the time (if any)
2348
     * @param string $virtualTime The amount of time to be added,
2349
     *                            in a hh:mm:ss format. If int, we consider it is expressed in hours.
2350
     * @param int    $workId      Student publication id result
2351
     *
2352
     * @return true on successful insertion, false otherwise
2353
     */
2354
    public static function eventAddVirtualCourseTime(
2355
        $courseId,
2356
        $userId,
2357
        $sessionId,
2358
        $virtualTime,
2359
        $workId
2360
    ) {
2361
        if (empty($virtualTime)) {
2362
            return false;
2363
        }
2364
2365
        $courseId = (int) $courseId;
2366
        $userId = (int) $userId;
2367
        $sessionId = (int) $sessionId;
2368
2369
        $logoutDate = api_get_utc_datetime();
2370
        $loginDate = ChamiloApi::addOrSubTimeToDateTime(
2371
            $virtualTime,
2372
            $logoutDate,
2373
            false
2374
        );
2375
2376
        $ip = api_get_real_ip();
2377
        $params = [
2378
            'login_course_date' => $loginDate,
2379
            'logout_course_date' => $logoutDate,
2380
            'session_id' => $sessionId,
2381
            'user_id' => $userId,
2382
            'counter' => 0,
2383
            'c_id' => $courseId,
2384
            'user_ip' => $ip,
2385
        ];
2386
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2387
        Database::insert($courseTrackingTable, $params);
2388
2389
        // Time should also be added to the track_e_login table so as to
2390
        // affect total time on the platform
2391
        $params = [
2392
            'login_user_id' => $userId,
2393
            'login_date' => $loginDate,
2394
            'user_ip' => $ip,
2395
            'logout_date' => $logoutDate,
2396
        ];
2397
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2398
        Database::insert($platformTrackingTable, $params);
2399
2400
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2401
            $workId = (int) $workId;
2402
            $uniqueId = time();
2403
            $logInfo = [
2404
                'c_id' => $courseId,
2405
                'session_id' => $sessionId,
2406
                'tool' => TOOL_STUDENTPUBLICATION,
2407
                'date_reg' => $loginDate,
2408
                'action' => 'add_work_start_'.$workId,
2409
                'action_details' => $virtualTime,
2410
                'user_id' => $userId,
2411
                'current_id' => $uniqueId,
2412
            ];
2413
            self::registerLog($logInfo);
2414
2415
            $logInfo = [
2416
                'c_id' => $courseId,
2417
                'session_id' => $sessionId,
2418
                'tool' => TOOL_STUDENTPUBLICATION,
2419
                'date_reg' => $logoutDate,
2420
                'action' => 'add_work_end_'.$workId,
2421
                'action_details' => $virtualTime,
2422
                'user_id' => $userId,
2423
                'current_id' => $uniqueId,
2424
            ];
2425
            self::registerLog($logInfo);
2426
        }
2427
2428
        return true;
2429
    }
2430
2431
    /**
2432
     * Removes a "fake" time spent on the platform, for example to match the
2433
     * estimated time he took to author an assignment/work, see configuration
2434
     * setting considered_working_time.
2435
     * This method should be called when something that generated a fake
2436
     * time record is removed. Given the database link is weak (no real
2437
     * relationship kept between the deleted item and this record), this
2438
     * method just looks for the latest record that has the same time as the
2439
     * item's fake time, is in the past and in this course+session. If such a
2440
     * record cannot be found, it doesn't do anything.
2441
     * The IP address is not considered a useful filter here.
2442
     *
2443
     * @param int    $courseId    The course in which to add the time
2444
     * @param int    $userId      The user for whom to add the time
2445
     * @param int    $sessionId   The session in which to add the time (if any)
2446
     * @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.
2447
     *
2448
     * @return true on successful removal, false otherwise
2449
     */
2450
    public static function eventRemoveVirtualCourseTime(
2451
        $courseId,
2452
        $userId,
2453
        $sessionId,
2454
        $virtualTime,
2455
        $workId
2456
    ) {
2457
        if (empty($virtualTime)) {
2458
            return false;
2459
        }
2460
2461
        $courseId = (int) $courseId;
2462
        $userId = (int) $userId;
2463
        $sessionId = (int) $sessionId;
2464
        $originalVirtualTime = Database::escape_string($virtualTime);
2465
        $workId = (int) $workId;
2466
2467
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2468
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2469
2470
        // Change $virtualTime format from hh:mm:ss to hhmmss which is the
2471
        // format returned by SQL for a subtraction of two datetime values
2472
        // @todo make sure this is portable between DBMSes
2473
        if (preg_match('/:/', $virtualTime)) {
2474
            [$h, $m, $s] = preg_split('/:/', $virtualTime);
2475
            $virtualTime = $h * 3600 + $m * 60 + $s;
2476
        } else {
2477
            $virtualTime *= 3600;
2478
        }
2479
2480
        // Get the current latest course connection register. We need that
2481
        // record to re-use the data and create a new record.
2482
        $sql = "SELECT course_access_id
2483
                FROM $courseTrackingTable
2484
                WHERE
2485
                    user_id = $userId AND
2486
                    c_id = $courseId  AND
2487
                    session_id  = $sessionId AND
2488
                    counter = 0 AND
2489
                    (UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) = '$virtualTime'
2490
                ORDER BY login_course_date DESC LIMIT 0,1";
2491
        $result = Database::query($sql);
2492
2493
        // Ignore if we didn't find any course connection record in the last
2494
        // hour. In this case it wouldn't be right to add a "fake" time record.
2495
        if (Database::num_rows($result) > 0) {
2496
            // Found the latest connection
2497
            $row = Database::fetch_row($result);
2498
            $courseAccessId = $row[0];
2499
            $sql = "DELETE FROM $courseTrackingTable
2500
                    WHERE course_access_id = $courseAccessId";
2501
            Database::query($sql);
2502
        }
2503
        $sql = "SELECT login_id
2504
                FROM $platformTrackingTable
2505
                WHERE
2506
                    login_user_id = $userId AND
2507
                    (UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date)) = '$virtualTime'
2508
                ORDER BY login_date DESC LIMIT 0,1";
2509
        $result = Database::query($sql);
2510
        if (Database::num_rows($result) > 0) {
2511
            // Found the latest connection
2512
            $row = Database::fetch_row($result);
2513
            $loginAccessId = $row[0];
2514
            $sql = "DELETE FROM $platformTrackingTable
2515
                    WHERE login_id = $loginAccessId";
2516
            Database::query($sql);
2517
        }
2518
2519
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2520
            $sql = "SELECT id FROM track_e_access_complete
2521
                    WHERE
2522
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2523
                        c_id = $courseId AND
2524
                        session_id = $sessionId AND
2525
                        user_id = $userId AND
2526
                        action_details = '$originalVirtualTime' AND
2527
                        action = 'add_work_start_$workId' ";
2528
            $result = Database::query($sql);
2529
            $result = Database::fetch_array($result);
2530
            if ($result) {
2531
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2532
                Database::query($sql);
2533
            }
2534
2535
            $sql = "SELECT id FROM track_e_access_complete
2536
                    WHERE
2537
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2538
                        c_id = $courseId AND
2539
                        session_id = $sessionId AND
2540
                        user_id = $userId AND
2541
                        action_details = '$originalVirtualTime' AND
2542
                        action = 'add_work_end_$workId' ";
2543
            $result = Database::query($sql);
2544
            $result = Database::fetch_array($result);
2545
            if ($result) {
2546
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2547
                Database::query($sql);
2548
            }
2549
        }
2550
2551
        return false;
2552
    }
2553
2554
    /**
2555
     * For the sake of cohesion, this function is a switch.
2556
     * It's called by EventsDispatcher and fires the right function
2557
     * with the right require_once.
2558
     *
2559
     * @deprecated
2560
     *
2561
     * @param string $event_name
2562
     * @param array  $params
2563
     */
2564
    public static function event_send_mail($event_name, $params)
2565
    {
2566
        EventsMail::send_mail($event_name, $params);
2567
    }
2568
2569
    /**
2570
     * Filter EventEmailTemplate Filters see the main/inc/conf/events.conf.dist.php.
2571
     */
2572
2573
    /**
2574
     * Basic template event message filter (to be used by other filters as default).
2575
     *
2576
     * @deprecated
2577
     *
2578
     * @param array $values (passing by reference)     *
2579
     *
2580
     * @return bool True if everything is OK, false otherwise
2581
     */
2582
    public function event_send_mail_filter_func(&$values)
2583
    {
2584
        return true;
2585
    }
2586
2587
    /**
2588
     * user_registration - send_mail filter.
2589
     *
2590
     * @deprecated
2591
     *
2592
     * @param array $values (passing by reference)
2593
     *
2594
     * @return bool True if everything is OK, false otherwise
2595
     */
2596
    public function user_registration_event_send_mail_filter_func(&$values)
2597
    {
2598
        $res = self::event_send_mail_filter_func($values);
2599
        // proper logic for this filter
2600
        return $res;
2601
    }
2602
2603
    /**
2604
     * portal_homepage_edited - send_mail filter.
2605
     *
2606
     * @deprecated
2607
     *
2608
     * @param array $values (passing by reference)
2609
     *
2610
     * @return bool True if everything is OK, false otherwise
2611
     */
2612
    public function portal_homepage_edited_event_send_mail_filter_func(&$values)
2613
    {
2614
        $res = self::event_send_mail_filter_func($values);
2615
        // proper logic for this filter
2616
        return $res;
2617
    }
2618
2619
    /**
2620
     * Register the logout of the course (usually when logging out of the platform)
2621
     * from the track_e_access_complete table.
2622
     *
2623
     * @param array $logInfo Information stored by local.inc.php
2624
     *
2625
     * @return bool
2626
     */
2627
    public static function registerLog($logInfo)
2628
    {
2629
        $sessionId = api_get_session_id();
2630
        $courseId = api_get_course_int_id();
2631
2632
        if (isset($logInfo['c_id']) && !empty($logInfo['c_id'])) {
2633
            $courseId = $logInfo['c_id'];
2634
        }
2635
2636
        if (isset($logInfo['session_id']) && !empty($logInfo['session_id'])) {
2637
            $sessionId = $logInfo['session_id'];
2638
        }
2639
2640
        if (!Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2641
            return false;
2642
        }
2643
2644
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2645
            return false;
2646
        }
2647
2648
        $loginAs = (int) Session::read('login_as') === true;
2649
2650
        $logInfo['user_id'] = isset($logInfo['user_id']) ? $logInfo['user_id'] : api_get_user_id();
2651
        $logInfo['date_reg'] = isset($logInfo['date_reg']) ? $logInfo['date_reg'] : api_get_utc_datetime();
2652
        $logInfo['tool'] = !empty($logInfo['tool']) ? $logInfo['tool'] : '';
2653
        $logInfo['tool_id'] = !empty($logInfo['tool_id']) ? (int) $logInfo['tool_id'] : 0;
2654
        $logInfo['tool_id_detail'] = !empty($logInfo['tool_id_detail']) ? (int) $logInfo['tool_id_detail'] : 0;
2655
        $logInfo['action'] = !empty($logInfo['action']) ? $logInfo['action'] : '';
2656
        $logInfo['action_details'] = !empty($logInfo['action_details']) ? $logInfo['action_details'] : '';
2657
        $logInfo['ip_user'] = api_get_real_ip();
2658
        $logInfo['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
2659
        $logInfo['session_id'] = $sessionId;
2660
        $logInfo['c_id'] = $courseId;
2661
        $logInfo['ch_sid'] = session_id();
2662
        $logInfo['login_as'] = $loginAs;
2663
        $logInfo['info'] = !empty($logInfo['info']) ? $logInfo['info'] : '';
2664
        $logInfo['url'] = $_SERVER['REQUEST_URI'];
2665
        $logInfo['current_id'] = isset($logInfo['current_id']) ? $logInfo['current_id'] : Session::read('last_id', 0);
2666
2667
        $id = Database::insert('track_e_access_complete', $logInfo);
2668
        if ($id && empty($logInfo['current_id'])) {
2669
            Session::write('last_id', $id);
2670
        }
2671
2672
        return true;
2673
    }
2674
2675
    public static function getAttemptQuestionDuration($exeId, $questionId)
2676
    {
2677
        // Check current attempt.
2678
        $questionAttempt = self::getQuestionAttemptByExeIdAndQuestion($exeId, $questionId);
2679
        $alreadySpent = 0;
2680
        if (!empty($questionAttempt) && $questionAttempt['seconds_spent']) {
2681
            $alreadySpent = $questionAttempt['seconds_spent'];
2682
        }
2683
        $now = time();
2684
        $questionStart = Session::read('question_start', []);
2685
        if (!empty($questionStart) &&
2686
            isset($questionStart[$questionId]) && !empty($questionStart[$questionId])
2687
        ) {
2688
            $time = $questionStart[$questionId];
2689
        } else {
2690
            $diff = 0;
2691
            if (!empty($alreadySpent)) {
2692
                $diff = $alreadySpent;
2693
            }
2694
            $time = $questionStart[$questionId] = $now - $diff;
2695
            Session::write('question_start', $questionStart);
2696
        }
2697
2698
        return $now - $time;
2699
    }
2700
2701
    public static function logSubscribedUserInCourse(int $subscribedId, int $courseId)
2702
    {
2703
        $dateTime = api_get_utc_datetime();
2704
        $registrantId = api_get_user_id();
2705
2706
        self::addEvent(
2707
            LOG_SUBSCRIBE_USER_TO_COURSE,
2708
            LOG_COURSE_CODE,
2709
            api_get_course_entity($courseId)->getCode(),
2710
            $dateTime,
2711
            $registrantId,
2712
            $courseId
2713
        );
2714
2715
        self::addEvent(
2716
            LOG_SUBSCRIBE_USER_TO_COURSE,
2717
            LOG_USER_OBJECT,
2718
            api_get_user_info($subscribedId),
2719
            $dateTime,
2720
            $registrantId,
2721
            $courseId
2722
        );
2723
    }
2724
2725
    public static function logUserSubscribedInCourseSession(int $subscribedId, int $courseId, int $sessionId)
2726
    {
2727
        $dateTime = api_get_utc_datetime();
2728
        $registrantId = api_get_user_id();
2729
2730
        self::addEvent(
2731
            LOG_SESSION_ADD_USER_COURSE,
2732
            LOG_USER_ID,
2733
            $subscribedId,
2734
            $dateTime,
2735
            $registrantId,
2736
            $courseId,
2737
            $sessionId
2738
        );
2739
        self::addEvent(
2740
            LOG_SUBSCRIBE_USER_TO_COURSE,
2741
            LOG_COURSE_CODE,
2742
            api_get_course_entity($courseId)->getCode(),
2743
            $dateTime,
2744
            $registrantId,
2745
            $courseId,
2746
            $sessionId
2747
        );
2748
        self::addEvent(
2749
            LOG_SUBSCRIBE_USER_TO_COURSE,
2750
            LOG_USER_OBJECT,
2751
            api_get_user_info($subscribedId),
2752
            $dateTime,
2753
            $registrantId,
2754
            $courseId,
2755
            $sessionId
2756
        );
2757
    }
2758
}
2759