Passed
Push — master ( 75cb6d...4670ec )
by Julito
11:34
created

Event::eventRemoveVirtualCourseTime()   B

Complexity

Conditions 8
Paths 41

Size

Total Lines 103
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

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