Completed
Push — master ( 1fcdba...966b12 )
by Julito
12:06
created

Event::eventRemoveVirtualCourseTime()   B

Complexity

Conditions 8
Paths 41

Size

Total Lines 102
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 64
nc 41
nop 5
dl 0
loc 102
rs 7.541
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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