Passed
Push — master ( 84dd35...ea9938 )
by Julito
18:15
created

Event::get_event_users()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 7
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 11
rs 10
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
     * @author Sebastien Piraux <[email protected]> old code
22
     * @author Julio Montoya
23
     *
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) && $visibility != SESSION_AVAILABLE) {
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']) && (int) $value['value'] == 1) {
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
        $score,
490
        $answer,
491
        $question_id,
492
        $exe_id,
493
        $position,
494
        $exercise_id = 0,
495
        $updateResults = false,
496
        $questionDuration = 0,
497
        $fileName = null,
498
        $user_id = null,
499
        $course_id = null,
500
        $session_id = null,
501
        $learnpath_id = null,
502
        $learnpath_item_id = null
503
    ) {
504
        global $debug;
505
        $questionDuration = (int) $questionDuration;
506
        $question_id = (int) $question_id;
507
        $exe_id = (int) $exe_id;
508
        $position = (int) $position;
509
        $course_id = (int) $course_id;
510
        $now = api_get_utc_datetime();
511
        $recording = api_get_configuration_value('quiz_answer_extra_recording');
512
513
        // check user_id or get from context
514
        if (empty($user_id)) {
515
            $user_id = api_get_user_id();
516
            // anonymous
517
            if (empty($user_id)) {
518
                $user_id = api_get_anonymous_id();
519
            }
520
        }
521
        // check course_id or get from context
522
        if (empty($course_id)) {
523
            $course_id = api_get_course_int_id();
524
        }
525
        // check session_id or get from context
526
        $session_id = (int) $session_id;
527
        if (empty($session_id)) {
528
            $session_id = api_get_session_id();
529
        }
530
        // check learnpath_id or get from context
531
        if (empty($learnpath_id)) {
532
            global $learnpath_id;
533
        }
534
        // check learnpath_item_id or get from context
535
        if (empty($learnpath_item_id)) {
536
            global $learnpath_item_id;
537
        }
538
539
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
540
541
        if ($debug) {
542
            error_log('----- entering saveQuestionAttempt() function ------');
543
            error_log("answer: $answer");
544
            error_log("score: $score");
545
            error_log("question_id : $question_id");
546
            error_log("position: $position");
547
        }
548
549
        //Validation in case of fraud with active control time
550
        if (!ExerciseLib::exercise_time_control_is_valid($exercise_id, $learnpath_id, $learnpath_item_id)) {
551
            if ($debug) {
552
                error_log("exercise_time_control_is_valid is false");
553
            }
554
            $score = 0;
555
            $answer = 0;
556
        }
557
558
        if (empty($question_id) || empty($exe_id) || empty($user_id)) {
559
            return false;
560
        }
561
562
        if (null === $answer) {
563
            $answer = '';
564
        }
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 ($updateResults == false) {
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($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 int    $exeId
678
     * @param int    $questionId Question ID
679
     * @param int    $answerId   Answer ID
680
     * @param int    $correct
681
     * @param string $coords     Coordinates of this point (e.g. 123;324)
682
     * @param bool   $updateResults
683
     * @param int    $exerciseId
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
        $exeId,
691
        $questionId,
692
        $answerId,
693
        $correct,
694
        $coords,
695
        $updateResults = false,
696
        $exerciseId = 0
697
    ) {
698
        $debug = false;
699
        global $safe_lp_id, $safe_lp_item_id;
700
701
        if (false == $updateResults) {
702
            // Validation in case of fraud with activated control time
703
            if (!ExerciseLib::exercise_time_control_is_valid($exerciseId, $safe_lp_id, $safe_lp_item_id)) {
704
                if ($debug) {
705
                    error_log('Attempt is fraud');
706
                }
707
                $correct = 0;
708
            }
709
        }
710
711
        if (empty($exeId)) {
712
            if ($debug) {
713
                error_log('exe id is empty');
714
            }
715
716
            return false;
717
        }
718
719
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
720
        if ($updateResults) {
721
            if ($debug) {
722
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
723
            }
724
            $params = [
725
                'hotspot_correct' => $correct,
726
                'hotspot_coordinate' => $coords,
727
            ];
728
            Database::update(
729
                $table,
730
                $params,
731
                [
732
                    'hotspot_user_id = ? AND hotspot_exe_id = ? AND hotspot_question_id = ? AND hotspot_answer_id = ? ' => [
733
                        api_get_user_id(),
734
                        $exeId,
735
                        $questionId,
736
                        $answerId,
737
                    ],
738
                ]
739
            );
740
        } else {
741
            if ($debug) {
742
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
743
            }
744
745
            return Database::insert(
746
                $table,
747
                [
748
                    'hotspot_user_id' => api_get_user_id(),
749
                    'c_id' => api_get_course_int_id(),
750
                    'hotspot_exe_id' => $exeId,
751
                    'hotspot_question_id' => $questionId,
752
                    'hotspot_answer_id' => $answerId,
753
                    'hotspot_correct' => $correct,
754
                    'hotspot_coordinate' => $coords,
755
                ]
756
            );
757
        }
758
    }
759
760
    /**
761
     * Records information for common (or admin) events (in the track_e_default table).
762
     *
763
     * @author Yannick Warnier <[email protected]>
764
     *
765
     * @param string $event_type       Type of event
766
     * @param string $event_value_type Type of value
767
     * @param mixed  $event_value      Value (string, or array in the case of user info)
768
     * @param string $datetime         Datetime (UTC) (defaults to null)
769
     * @param int    $user_id          User ID (defaults to null)
770
     * @param int    $course_id        Course ID (defaults to null)
771
     * @param int    $sessionId        Session ID
772
     *
773
     * @return bool
774
     * @assert ('','','') === false
775
     */
776
    public static function addEvent(
777
        $event_type,
778
        $event_value_type,
779
        $event_value,
780
        $datetime = null,
781
        $user_id = null,
782
        $course_id = null,
783
        $sessionId = 0
784
    ) {
785
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
786
787
        if (empty($event_type)) {
788
            return false;
789
        }
790
        $event_type = Database::escape_string($event_type);
791
        $event_value_type = Database::escape_string($event_value_type);
792
        if (!empty($course_id)) {
793
            $course_id = (int) $course_id;
794
        } else {
795
            $course_id = api_get_course_int_id();
796
        }
797
        if (!empty($sessionId)) {
798
            $sessionId = (int) $sessionId;
799
        } else {
800
            $sessionId = api_get_session_id();
801
        }
802
803
        //Clean the user_info
804
        if ($event_value_type == LOG_USER_OBJECT) {
805
            if (is_array($event_value)) {
806
                unset($event_value['complete_name']);
807
                unset($event_value['complete_name_with_username']);
808
                unset($event_value['firstName']);
809
                unset($event_value['lastName']);
810
                unset($event_value['avatar_small']);
811
                unset($event_value['avatar']);
812
                unset($event_value['mail']);
813
                unset($event_value['password']);
814
                unset($event_value['last_login']);
815
                unset($event_value['picture_uri']);
816
                $event_value = serialize($event_value);
817
            }
818
        }
819
        // If event is an array then the $event_value_type should finish with
820
        // the suffix _array for example LOG_WORK_DATA = work_data_array
821
        if (is_array($event_value)) {
822
            $event_value = serialize($event_value);
823
        }
824
825
        $event_value = Database::escape_string($event_value);
826
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
827
828
        if (!isset($datetime)) {
829
            $datetime = api_get_utc_datetime();
830
        }
831
832
        $datetime = Database::escape_string($datetime);
833
834
        if (!isset($user_id)) {
835
            $user_id = api_get_user_id();
836
        }
837
838
        $params = [
839
            'default_user_id' => $user_id,
840
            'c_id' => $course_id,
841
            'default_date' => $datetime,
842
            'default_event_type' => $event_type,
843
            'default_value_type' => $event_value_type,
844
            'default_value' => $event_value,
845
            'session_id' => $sessionId,
846
        ];
847
        Database::insert($table, $params);
848
849
        return true;
850
    }
851
852
    /**
853
     * Gets the last attempt of an exercise based in the exe_id.
854
     *
855
     * @param int $exeId
856
     *
857
     * @return mixed
858
     */
859
    public static function getLastAttemptDateOfExercise($exeId)
860
    {
861
        $exeId = (int) $exeId;
862
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
863
        $sql = "SELECT max(tms) as last_attempt_date
864
                FROM $track_attempts
865
                WHERE exe_id = $exeId";
866
        $rs_last_attempt = Database::query($sql);
867
        $row_last_attempt = Database::fetch_array($rs_last_attempt);
868
        $date = $row_last_attempt['last_attempt_date']; //Get the date of last attempt
869
870
        return $date;
871
    }
872
873
    /**
874
     * Gets the last attempt of an exercise based in the exe_id.
875
     *
876
     * @param int $exeId
877
     *
878
     * @return mixed
879
     */
880
    public static function getLatestQuestionIdFromAttempt($exeId)
881
    {
882
        $exeId = (int) $exeId;
883
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
884
        $sql = "SELECT question_id FROM $track_attempts
885
                WHERE exe_id = $exeId
886
                ORDER BY tms DESC
887
                LIMIT 1";
888
        $result = Database::query($sql);
889
        if (Database::num_rows($result)) {
890
            $row = Database::fetch_array($result);
891
892
            return $row['question_id'];
893
        }
894
895
        return false;
896
    }
897
898
    /**
899
     * Gets how many attempts exists by user, exercise, learning path.
900
     *
901
     * @param int user id
902
     * @param int exercise id
903
     * @param int lp id
904
     * @param int lp item id
905
     * @param int lp item view id
906
     *
907
     * @return int
908
     */
909
    public static function get_attempt_count(
910
        $user_id,
911
        $exerciseId,
912
        $lp_id,
913
        $lp_item_id,
914
        $lp_item_view_id
915
    ) {
916
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
917
        $user_id = (int) $user_id;
918
        $exerciseId = (int) $exerciseId;
919
        $lp_id = (int) $lp_id;
920
        $lp_item_id = (int) $lp_item_id;
921
        $lp_item_view_id = (int) $lp_item_view_id;
922
        $courseId = api_get_course_int_id();
923
        $sessionId = api_get_session_id();
924
925
        $sql = "SELECT count(*) as count
926
                FROM $table
927
                WHERE
928
                    exe_exo_id = $exerciseId AND
929
                    exe_user_id = $user_id AND
930
                    status != 'incomplete' AND
931
                    orig_lp_id = $lp_id AND
932
                    orig_lp_item_id = $lp_item_id AND
933
                    orig_lp_item_view_id = $lp_item_view_id AND
934
                    c_id = $courseId AND
935
                    session_id = $sessionId";
936
937
        $result = Database::query($sql);
938
        if (Database::num_rows($result) > 0) {
939
            $attempt = Database::fetch_array($result, 'ASSOC');
940
941
            return (int) $attempt['count'];
942
        }
943
944
        return 0;
945
    }
946
947
    public static function getAttemptPosition(
948
        $exeId,
949
        $user_id,
950
        $exerciseId,
951
        $lp_id,
952
        $lp_item_id,
953
        $lp_item_view_id
954
    ) {
955
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
956
        $user_id = (int) $user_id;
957
        $exerciseId = (int) $exerciseId;
958
        $lp_id = (int) $lp_id;
959
        $lp_item_id = (int) $lp_item_id;
960
        $lp_item_view_id = (int) $lp_item_view_id;
961
        $courseId = api_get_course_int_id();
962
        $sessionId = api_get_session_id();
963
964
        $sql = "SELECT exe_id
965
                FROM $table
966
                WHERE
967
                    exe_exo_id = $exerciseId AND
968
                    exe_user_id = $user_id AND
969
                    status = '' AND
970
                    orig_lp_id = $lp_id AND
971
                    orig_lp_item_id = $lp_item_id AND
972
                    orig_lp_item_view_id = $lp_item_view_id AND
973
                    c_id = $courseId AND
974
                    session_id = $sessionId
975
                ORDER by exe_id
976
                ";
977
978
        $result = Database::query($sql);
979
        if (Database::num_rows($result) > 0) {
980
            $position = 1;
981
            while ($row = Database::fetch_array($result, 'ASSOC')) {
982
                if ($row['exe_id'] === $exeId) {
983
                    break;
984
                }
985
                $position++;
986
            }
987
988
            return $position;
989
        }
990
991
        return 0;
992
    }
993
994
    /**
995
     * @param $user_id
996
     * @param $exerciseId
997
     * @param $lp_id
998
     * @param $lp_item_id
999
     *
1000
     * @return int
1001
     */
1002
    public static function get_attempt_count_not_finished(
1003
        $user_id,
1004
        $exerciseId,
1005
        $lp_id,
1006
        $lp_item_id
1007
    ) {
1008
        $stat_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1009
        $user_id = (int) $user_id;
1010
        $exerciseId = (int) $exerciseId;
1011
        $lp_id = (int) $lp_id;
1012
        $lp_item_id = (int) $lp_item_id;
1013
        //$lp_item_view_id = (int) $lp_item_view_id;
1014
        $courseId = api_get_course_int_id();
1015
        $sessionId = api_get_session_id();
1016
1017
        $sql = "SELECT count(*) as count
1018
                FROM $stat_table
1019
                WHERE
1020
                    exe_exo_id 			= $exerciseId AND
1021
                    exe_user_id 		= $user_id AND
1022
                    status 				!= 'incomplete' AND
1023
                    orig_lp_id 			= $lp_id AND
1024
                    orig_lp_item_id 	= $lp_item_id AND
1025
                    c_id = $courseId AND
1026
                    session_id = $sessionId";
1027
1028
        $query = Database::query($sql);
1029
        if (Database::num_rows($query) > 0) {
1030
            $attempt = Database::fetch_array($query, 'ASSOC');
1031
1032
            return (int) $attempt['count'];
1033
        }
1034
1035
        return 0;
1036
    }
1037
1038
    /**
1039
     * @param int   $user_id
1040
     * @param int   $lp_id
1041
     * @param array $course
1042
     * @param int   $session_id
1043
     * @param bool  $disconnectExerciseResultsFromLp (Replace orig_lp_* variables to null)
1044
     *
1045
     * @return bool
1046
     */
1047
    public static function delete_student_lp_events(
1048
        $user_id,
1049
        $lp_id,
1050
        $course,
1051
        $session_id,
1052
        $disconnectExerciseResultsFromLp = false
1053
    ) {
1054
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
1055
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1056
        $lpInteraction = Database::get_course_table(TABLE_LP_IV_INTERACTION);
1057
        $lpObjective = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
1058
1059
        if (empty($course) || empty($user_id)) {
1060
            return false;
1061
        }
1062
1063
        $course_id = $course['real_id'];
1064
        $user_id = (int) $user_id;
1065
        $lp_id = (int) $lp_id;
1066
        $session_id = (int) $session_id;
1067
1068
        if (empty($course_id)) {
1069
            $course_id = api_get_course_int_id();
1070
        }
1071
1072
        $track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1073
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1074
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1075
1076
        // Make sure we have the exact lp_view_id
1077
        $sql = "SELECT id FROM $lp_view_table
1078
                WHERE
1079
                    c_id = $course_id AND
1080
                    user_id = $user_id AND
1081
                    lp_id = $lp_id AND
1082
                    session_id = $session_id";
1083
        $result = Database::query($sql);
1084
1085
        if (Database::num_rows($result)) {
1086
            $view = Database::fetch_array($result, 'ASSOC');
1087
            $lp_view_id = $view['id'];
1088
1089
            $sql = "DELETE FROM $lp_item_view_table
1090
                    WHERE c_id = $course_id AND lp_view_id = $lp_view_id";
1091
            Database::query($sql);
1092
1093
            $sql = "DELETE FROM $lpInteraction
1094
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1095
            Database::query($sql);
1096
1097
            $sql = "DELETE FROM $lpObjective
1098
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1099
            Database::query($sql);
1100
        }
1101
1102
        if (api_get_configuration_value('lp_minimum_time')) {
1103
            $sql = "DELETE FROM track_e_access_complete
1104
                    WHERE
1105
                        tool = 'learnpath' AND
1106
                        c_id = $course_id AND
1107
                        tool_id = $lp_id AND
1108
                        user_id = $user_id AND
1109
                        session_id = $session_id
1110
                    ";
1111
            Database::query($sql);
1112
        }
1113
1114
        $sql = "SELECT exe_id FROM $track_e_exercises
1115
                WHERE
1116
                    exe_user_id = $user_id AND
1117
                    session_id = $session_id AND
1118
                    c_id = $course_id AND
1119
                    orig_lp_id = $lp_id";
1120
        $result = Database::query($sql);
1121
        $exeList = [];
1122
        while ($row = Database::fetch_array($result, 'ASSOC')) {
1123
            $exeList[] = $row['exe_id'];
1124
        }
1125
1126
        if (!empty($exeList) && count($exeList) > 0) {
1127
            $exeListString = implode(',', $exeList);
1128
            if ($disconnectExerciseResultsFromLp) {
1129
                $sql = "UPDATE $track_e_exercises
1130
                        SET orig_lp_id = null,
1131
                            orig_lp_item_id = null,
1132
                            orig_lp_item_view_id = null
1133
                        WHERE exe_id IN ($exeListString)";
1134
                Database::query($sql);
1135
            } else {
1136
                $sql = "DELETE FROM $track_e_exercises
1137
                    WHERE exe_id IN ($exeListString)";
1138
                Database::query($sql);
1139
1140
                $sql = "DELETE FROM $track_attempts
1141
                    WHERE exe_id IN ($exeListString)";
1142
                Database::query($sql);
1143
1144
                $sql = "DELETE FROM $recording_table
1145
                    WHERE exe_id IN ($exeListString)";
1146
                Database::query($sql);
1147
            }
1148
        }
1149
1150
        $sql = "DELETE FROM $lp_view_table
1151
                WHERE
1152
                    c_id = $course_id AND
1153
                    user_id = $user_id AND
1154
                    lp_id= $lp_id AND
1155
                    session_id = $session_id
1156
            ";
1157
        Database::query($sql);
1158
1159
        self::addEvent(
1160
            LOG_LP_ATTEMPT_DELETE,
1161
            LOG_LP_ID,
1162
            $lp_id,
1163
            null,
1164
            null,
1165
            $course_id,
1166
            $session_id
1167
        );
1168
1169
        return true;
1170
    }
1171
1172
    /**
1173
     * Delete all exercise attempts (included in LP or not).
1174
     *
1175
     * @param int user id
1176
     * @param int exercise id
1177
     * @param int $course_id
1178
     * @param int session id
1179
     */
1180
    public static function delete_all_incomplete_attempts(
1181
        $user_id,
1182
        $exercise_id,
1183
        $course_id,
1184
        $session_id = 0
1185
    ) {
1186
        $user_id = (int) $user_id;
1187
        $exercise_id = (int) $exercise_id;
1188
        $course_id = (int) $course_id;
1189
        $session_id = (int) $session_id;
1190
1191
        if (!empty($user_id) && !empty($exercise_id) && !empty($course_id)) {
1192
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1193
            $sql = "DELETE FROM $table
1194
                    WHERE
1195
                        exe_user_id = $user_id AND
1196
                        exe_exo_id = $exercise_id AND
1197
                        c_id = $course_id AND
1198
                        session_id = $session_id AND
1199
                        status = 'incomplete' ";
1200
            Database::query($sql);
1201
            self::addEvent(
1202
                LOG_EXERCISE_RESULT_DELETE,
1203
                LOG_EXERCISE_AND_USER_ID,
1204
                $exercise_id.'-'.$user_id,
1205
                null,
1206
                null,
1207
                $course_id,
1208
                $session_id
1209
            );
1210
        }
1211
    }
1212
1213
    /**
1214
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1215
     *
1216
     * @param int $exercise_id
1217
     * @param int $courseId
1218
     * @param int $session_id
1219
     *
1220
     * @return array with the results
1221
     */
1222
    public static function get_all_exercise_results(
1223
        $exercise_id,
1224
        $courseId,
1225
        $session_id = 0,
1226
        $load_question_list = true,
1227
        $user_id = null
1228
    ) {
1229
        $TABLETRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1230
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1231
        $courseId = (int) $courseId;
1232
        $exercise_id = (int) $exercise_id;
1233
        $session_id = (int) $session_id;
1234
1235
        $user_condition = null;
1236
        if (!empty($user_id)) {
1237
            $user_id = (int) $user_id;
1238
            $user_condition = "AND exe_user_id = $user_id ";
1239
        }
1240
        $sql = "SELECT * FROM $TABLETRACK_EXERCICES
1241
                WHERE
1242
                    status = ''  AND
1243
                    c_id = $courseId AND
1244
                    exe_exo_id = $exercise_id AND
1245
                    session_id = $session_id  AND
1246
                    orig_lp_id =0 AND
1247
                    orig_lp_item_id = 0
1248
                    $user_condition
1249
                ORDER BY exe_id";
1250
        $res = Database::query($sql);
1251
        $list = [];
1252
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1253
            $list[$row['exe_id']] = $row;
1254
            if ($load_question_list) {
1255
                $sql = "SELECT * FROM $TBL_TRACK_ATTEMPT
1256
                        WHERE exe_id = {$row['exe_id']}";
1257
                $res_question = Database::query($sql);
1258
                while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1259
                    $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1260
                }
1261
            }
1262
        }
1263
1264
        return $list;
1265
    }
1266
1267
    /**
1268
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1269
     *
1270
     * @param int  $courseId
1271
     * @param int  $session_id
1272
     * @param bool $get_count
1273
     *
1274
     * @return array with the results
1275
     */
1276
    public static function get_all_exercise_results_by_course(
1277
        $courseId,
1278
        $session_id = 0,
1279
        $get_count = true
1280
    ) {
1281
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1282
        $courseId = (int) $courseId;
1283
        $session_id = (int) $session_id;
1284
1285
        $select = '*';
1286
        if ($get_count) {
1287
            $select = 'count(*) as count';
1288
        }
1289
        $sql = "SELECT $select FROM $table_track_exercises
1290
                WHERE   status = ''  AND
1291
                        c_id = $courseId AND
1292
                        session_id = $session_id  AND
1293
                        orig_lp_id = 0 AND
1294
                        orig_lp_item_id = 0
1295
                ORDER BY exe_id";
1296
        $res = Database::query($sql);
1297
        if ($get_count) {
1298
            $row = Database::fetch_array($res, 'ASSOC');
1299
1300
            return $row['count'];
1301
        } else {
1302
            $list = [];
1303
            while ($row = Database::fetch_array($res, 'ASSOC')) {
1304
                $list[$row['exe_id']] = $row;
1305
            }
1306
1307
            return $list;
1308
        }
1309
    }
1310
1311
    /**
1312
     * Gets all exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1313
     *
1314
     * @param int $user_id
1315
     * @param int $courseId
1316
     * @param int $session_id
1317
     *
1318
     * @return array with the results
1319
     */
1320
    public static function get_all_exercise_results_by_user(
1321
        $user_id,
1322
        $courseId,
1323
        $session_id = 0
1324
    ) {
1325
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1326
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1327
        $courseId = (int) $courseId;
1328
        $session_id = (int) $session_id;
1329
        $user_id = (int) $user_id;
1330
1331
        $sql = "SELECT * FROM $table_track_exercises
1332
                WHERE
1333
                    status = '' AND
1334
                    exe_user_id = $user_id AND
1335
                    c_id = $courseId AND
1336
                    session_id = $session_id AND
1337
                    orig_lp_id = 0 AND
1338
                    orig_lp_item_id = 0
1339
                ORDER by exe_id";
1340
1341
        $res = Database::query($sql);
1342
        $list = [];
1343
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1344
            $list[$row['exe_id']] = $row;
1345
            $sql = "SELECT * FROM $table_track_attempt
1346
                    WHERE exe_id = {$row['exe_id']}";
1347
            $res_question = Database::query($sql);
1348
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1349
                $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1350
            }
1351
        }
1352
1353
        return $list;
1354
    }
1355
1356
    /**
1357
     * Gets exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1358
     *
1359
     * @param int    $exe_id attempt id
1360
     * @param string $status
1361
     *
1362
     * @return array with the results
1363
     */
1364
    public static function get_exercise_results_by_attempt($exe_id, $status = null)
1365
    {
1366
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1367
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1368
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1369
        $exe_id = (int) $exe_id;
1370
1371
        $status = Database::escape_string($status);
1372
1373
        $sql = "SELECT * FROM $table_track_exercises
1374
                WHERE status = '$status' AND exe_id = $exe_id";
1375
1376
        $res = Database::query($sql);
1377
        $list = [];
1378
        if (Database::num_rows($res)) {
1379
            $row = Database::fetch_array($res, 'ASSOC');
1380
1381
            //Checking if this attempt was revised by a teacher
1382
            $sql_revised = "SELECT exe_id FROM $table_track_attempt_recording
1383
                            WHERE author != '' AND exe_id = $exe_id
1384
                            LIMIT 1";
1385
            $res_revised = Database::query($sql_revised);
1386
            $row['attempt_revised'] = 0;
1387
            if (Database::num_rows($res_revised) > 0) {
1388
                $row['attempt_revised'] = 1;
1389
            }
1390
            $list[$exe_id] = $row;
1391
            $sql = "SELECT * FROM $table_track_attempt
1392
                    WHERE exe_id = $exe_id
1393
                    ORDER BY tms ASC";
1394
            $res_question = Database::query($sql);
1395
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1396
                $list[$exe_id]['question_list'][$row_q['question_id']] = $row_q;
1397
            }
1398
        }
1399
1400
        return $list;
1401
    }
1402
1403
    /**
1404
     * Gets exercise results (NO Exercises in LPs) from a given user, exercise id, course, session, lp_id, lp_item_id.
1405
     *
1406
     * @param int     user id
1407
     * @param int     exercise id
1408
     * @param int     course id
1409
     * @param int     session id
1410
     * @param int     lp id
1411
     * @param int     lp item id
1412
     * @param string order asc or desc
1413
     *
1414
     * @return array with the results
1415
     */
1416
    public static function getExerciseResultsByUser(
1417
        $user_id,
1418
        $exercise_id,
1419
        $courseId,
1420
        $session_id = 0,
1421
        $lp_id = 0,
1422
        $lp_item_id = 0,
1423
        $order = null
1424
    ) {
1425
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1426
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1427
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1428
        $courseId = (int) $courseId;
1429
        $exercise_id = (int) $exercise_id;
1430
        $session_id = (int) $session_id;
1431
        $user_id = (int) $user_id;
1432
        $lp_id = (int) $lp_id;
1433
        $lp_item_id = (int) $lp_item_id;
1434
1435
        if (!in_array(strtolower($order), ['asc', 'desc'])) {
1436
            $order = 'asc';
1437
        }
1438
1439
        $sql = "SELECT * FROM $table_track_exercises
1440
                WHERE
1441
                    status 			= '' AND
1442
                    exe_user_id 	= $user_id AND
1443
                    c_id 	        = $courseId AND
1444
                    exe_exo_id 		= $exercise_id AND
1445
                    session_id 		= $session_id AND
1446
                    orig_lp_id 		= $lp_id AND
1447
                    orig_lp_item_id = $lp_item_id
1448
                ORDER by exe_id $order ";
1449
1450
        $res = Database::query($sql);
1451
        $list = [];
1452
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1453
            // Checking if this attempt was revised by a teacher
1454
            $exeId = $row['exe_id'];
1455
            $sql = "SELECT exe_id FROM $table_track_attempt_recording
1456
                    WHERE author != '' AND exe_id = $exeId
1457
                    LIMIT 1";
1458
            $res_revised = Database::query($sql);
1459
            $row['attempt_revised'] = 0;
1460
            if (Database::num_rows($res_revised) > 0) {
1461
                $row['attempt_revised'] = 1;
1462
            }
1463
            $row['total_percentage'] = ($row['score'] / $row['max_score']) * 100;
1464
            $list[$row['exe_id']] = $row;
1465
            $sql = "SELECT * FROM $table_track_attempt
1466
                    WHERE exe_id = $exeId";
1467
            $res_question = Database::query($sql);
1468
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1469
                $list[$row['exe_id']]['question_list'][$row_q['question_id']][] = $row_q;
1470
            }
1471
        }
1472
1473
        return $list;
1474
    }
1475
1476
    /**
1477
     * Count exercise attempts (NO Exercises in LPs ) from a given exercise id, course, session.
1478
     *
1479
     * @param int $user_id
1480
     * @param int $exercise_id
1481
     * @param int $courseId
1482
     * @param int $session_id
1483
     *
1484
     * @return array with the results
1485
     */
1486
    public static function count_exercise_attempts_by_user(
1487
        $user_id,
1488
        $exercise_id,
1489
        $courseId,
1490
        $session_id = 0
1491
    ) {
1492
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1493
        $courseId = (int) $courseId;
1494
        $exercise_id = (int) $exercise_id;
1495
        $session_id = (int) $session_id;
1496
        $user_id = (int) $user_id;
1497
1498
        $sql = "SELECT count(*) as count
1499
                FROM $table
1500
                WHERE status = ''  AND
1501
                    exe_user_id = $user_id AND
1502
                    c_id = $courseId AND
1503
                    exe_exo_id = $exercise_id AND
1504
                    session_id = $session_id AND
1505
                    orig_lp_id =0 AND
1506
                    orig_lp_item_id = 0
1507
                ORDER BY exe_id";
1508
        $res = Database::query($sql);
1509
        $result = 0;
1510
        if (Database::num_rows($res) > 0) {
1511
            $row = Database::fetch_array($res, 'ASSOC');
1512
            $result = $row['count'];
1513
        }
1514
1515
        return $result;
1516
    }
1517
1518
    /**
1519
     * Gets all exercise BEST results attempts (NO Exercises in LPs)
1520
     * from a given exercise id, course, session per user.
1521
     *
1522
     * @param int $exercise_id
1523
     * @param int $courseId
1524
     * @param int $session_id
1525
     * @param int $userId
1526
     *
1527
     * @return array with the results
1528
     *
1529
     * @todo rename this function
1530
     */
1531
    public static function get_best_exercise_results_by_user(
1532
        $exercise_id,
1533
        $courseId,
1534
        $session_id = 0,
1535
        $userId = 0
1536
    ) {
1537
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1538
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1539
        $courseId = (int) $courseId;
1540
        $exercise_id = (int) $exercise_id;
1541
        $session_id = (int) $session_id;
1542
1543
        $sql = "SELECT * FROM $table_track_exercises
1544
                WHERE
1545
                    status = '' AND
1546
                    c_id = $courseId AND
1547
                    exe_exo_id = $exercise_id AND
1548
                    session_id = $session_id AND
1549
                    orig_lp_id = 0 AND
1550
                    orig_lp_item_id = 0";
1551
1552
        if (!empty($userId)) {
1553
            $userId = (int) $userId;
1554
            $sql .= " AND exe_user_id = $userId ";
1555
        }
1556
        $sql .= ' ORDER BY exe_id';
1557
1558
        $res = Database::query($sql);
1559
        $list = [];
1560
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1561
            $list[$row['exe_id']] = $row;
1562
            $exeId = $row['exe_id'];
1563
            $sql = "SELECT * FROM $table_track_attempt
1564
                    WHERE exe_id = $exeId";
1565
            $res_question = Database::query($sql);
1566
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1567
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1568
            }
1569
        }
1570
1571
        // Getting the best results of every student
1572
        $best_score_return = [];
1573
        foreach ($list as $student_result) {
1574
            $user_id = $student_result['exe_user_id'];
1575
            $current_best_score[$user_id] = $student_result['score'];
1576
            if (!isset($best_score_return[$user_id]['score'])) {
1577
                $best_score_return[$user_id] = $student_result;
1578
            }
1579
1580
            if ($current_best_score[$user_id] > $best_score_return[$user_id]['score']) {
1581
                $best_score_return[$user_id] = $student_result;
1582
            }
1583
        }
1584
1585
        return $best_score_return;
1586
    }
1587
1588
    /**
1589
     * Get the last best result from all attempts in exercises per user (out of learning paths).
1590
     *
1591
     * @param int  $user_id
1592
     * @param int  $exercise_id
1593
     * @param int  $courseId
1594
     * @param int  $session_id
1595
     * @param bool $skipLpResults
1596
     *
1597
     * @return array
1598
     */
1599
    public static function get_best_attempt_exercise_results_per_user(
1600
        $user_id,
1601
        $exercise_id,
1602
        $courseId,
1603
        $session_id = 0,
1604
        $skipLpResults = true
1605
    ) {
1606
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1607
        $courseId = (int) $courseId;
1608
        $exercise_id = (int) $exercise_id;
1609
        $session_id = (int) $session_id;
1610
        $user_id = (int) $user_id;
1611
1612
        $sql = "SELECT * FROM $table
1613
                WHERE
1614
                    status = ''  AND
1615
                    c_id = $courseId AND
1616
                    exe_exo_id = $exercise_id AND
1617
                    session_id = $session_id  AND
1618
                    exe_user_id = $user_id
1619
                ";
1620
1621
        if ($skipLpResults) {
1622
            $sql .= ' AND
1623
                    orig_lp_id = 0 AND
1624
                orig_lp_item_id = 0 ';
1625
        }
1626
1627
        $sql .= ' ORDER BY exe_id ';
1628
1629
        $res = Database::query($sql);
1630
        $list = [];
1631
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1632
            $list[$row['exe_id']] = $row;
1633
        }
1634
        //Getting the best results of every student
1635
        $best_score_return = [];
1636
        $best_score_return['score'] = 0;
1637
1638
        foreach ($list as $result) {
1639
            $current_best_score = $result;
1640
            if ($current_best_score['score'] > $best_score_return['score']) {
1641
                $best_score_return = $result;
1642
            }
1643
        }
1644
        if (!isset($best_score_return['max_score'])) {
1645
            $best_score_return = [];
1646
        }
1647
1648
        return $best_score_return;
1649
    }
1650
1651
    /**
1652
     * @param int $exercise_id
1653
     * @param int $courseId
1654
     * @param int $session_id
1655
     *
1656
     * @return mixed
1657
     */
1658
    public static function count_exercise_result_not_validated(
1659
        $exercise_id,
1660
        $courseId,
1661
        $session_id = 0
1662
    ) {
1663
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1664
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1665
        $courseId = (int) $courseId;
1666
        $session_id = (int) $session_id;
1667
        $exercise_id = (int) $exercise_id;
1668
1669
        $sql = "SELECT count(e.exe_id) as count
1670
                FROM $table_track_exercises e
1671
                LEFT JOIN $table_track_attempt a
1672
                ON e.exe_id = a.exe_id
1673
                WHERE
1674
                    exe_exo_id = $exercise_id AND
1675
                    c_id = $courseId AND
1676
                    e.session_id = $session_id  AND
1677
                    orig_lp_id = 0 AND
1678
                    marks IS NULL AND
1679
                    status = '' AND
1680
                    orig_lp_item_id = 0
1681
                ORDER BY e.exe_id";
1682
        $res = Database::query($sql);
1683
        $row = Database::fetch_array($res, 'ASSOC');
1684
1685
        return $row['count'];
1686
    }
1687
1688
    /**
1689
     * Gets all exercise events from a Learning Path within a Course    nd Session.
1690
     *
1691
     * @param int $exercise_id
1692
     * @param int $courseId
1693
     * @param int $session_id
1694
     *
1695
     * @return array
1696
     */
1697
    public static function get_all_exercise_event_from_lp(
1698
        $exercise_id,
1699
        $courseId,
1700
        $session_id = 0
1701
    ) {
1702
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1703
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1704
        $courseId = (int) $courseId;
1705
        $exercise_id = (int) $exercise_id;
1706
        $session_id = (int) $session_id;
1707
1708
        $sql = "SELECT * FROM $table_track_exercises
1709
                WHERE
1710
                    status = '' AND
1711
                    c_id = $courseId AND
1712
                    exe_exo_id = $exercise_id AND
1713
                    session_id = $session_id AND
1714
                    orig_lp_id !=0 AND
1715
                    orig_lp_item_id != 0";
1716
1717
        $res = Database::query($sql);
1718
        $list = [];
1719
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1720
            $exeId = $row['exe_id'];
1721
            $list[$exeId] = $row;
1722
            $sql = "SELECT * FROM $table_track_attempt
1723
                    WHERE exe_id = $exeId";
1724
            $res_question = Database::query($sql);
1725
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1726
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1727
            }
1728
        }
1729
1730
        return $list;
1731
    }
1732
1733
    /**
1734
     * Get a list of all the exercises in a given learning path.
1735
     *
1736
     * @param int $lp_id
1737
     * @param int $course_id This parameter is probably deprecated as lp_id now is a global iid
1738
     *
1739
     * @return array
1740
     */
1741
    public static function get_all_exercises_from_lp($lp_id, $course_id)
1742
    {
1743
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
1744
        $course_id = (int) $course_id;
1745
        $lp_id = (int) $lp_id;
1746
        $sql = "SELECT * FROM $lp_item_table
1747
                WHERE
1748
                    c_id = $course_id AND
1749
                    lp_id = $lp_id AND
1750
                    item_type = 'quiz'
1751
                ORDER BY parent_item_id, display_order";
1752
        $res = Database::query($sql);
1753
1754
        $list = [];
1755
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1756
            $list[] = $row;
1757
        }
1758
1759
        return $list;
1760
    }
1761
1762
    /**
1763
     * This function gets the comments of an exercise.
1764
     *
1765
     * @param int $exe_id
1766
     * @param int $question_id
1767
     *
1768
     * @return string the comment
1769
     */
1770
    public static function get_comments($exe_id, $question_id)
1771
    {
1772
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1773
        $exe_id = (int) $exe_id;
1774
        $question_id = (int) $question_id;
1775
        $sql = "SELECT teacher_comment
1776
                FROM $table
1777
                WHERE
1778
                    exe_id = $exe_id AND
1779
                    question_id = $question_id
1780
                ORDER by question_id";
1781
        $sqlres = Database::query($sql);
1782
        $comm = strval(Database::result($sqlres, 0, 'teacher_comment'));
1783
        $comm = trim($comm);
1784
1785
        return $comm;
1786
    }
1787
1788
    /**
1789
     * Get all the track_e_attempt records for a given
1790
     * track_e_exercises.exe_id (pk).
1791
     *
1792
     * @param int $exeId The exe_id from an exercise attempt record
1793
     *
1794
     * @return array The complete records from track_e_attempt that match the given exe_id
1795
     */
1796
    public static function getAllExerciseEventByExeId($exeId)
1797
    {
1798
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1799
        $exeId = (int) $exeId;
1800
1801
        $sql = "SELECT * FROM $table
1802
                WHERE exe_id = $exeId
1803
                ORDER BY position";
1804
        $res_question = Database::query($sql);
1805
        $list = [];
1806
        if (Database::num_rows($res_question)) {
1807
            while ($row = Database::fetch_array($res_question, 'ASSOC')) {
1808
                $list[$row['question_id']][] = $row;
1809
            }
1810
        }
1811
1812
        return $list;
1813
    }
1814
1815
    public static function getQuestionAttemptByExeIdAndQuestion($exeId, $questionId)
1816
    {
1817
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1818
        $exeId = (int) $exeId;
1819
        $questionId = (int) $questionId;
1820
1821
        $sql = "SELECT * FROM $table
1822
                WHERE
1823
                    exe_id = $exeId AND
1824
                    question_id = $questionId
1825
                ORDER BY position";
1826
        $result = Database::query($sql);
1827
        $attempt = [];
1828
        if (Database::num_rows($result)) {
1829
            $attempt = Database::fetch_array($result, 'ASSOC');
1830
        }
1831
1832
        return $attempt;
1833
    }
1834
    /**
1835
     * Delete one record from the track_e_attempt table (recorded quiz answer)
1836
     * and register the deletion event (LOG_QUESTION_RESULT_DELETE) in
1837
     * track_e_default.
1838
     *
1839
     * @param int $exeId       The track_e_exercises.exe_id (primary key)
1840
     * @param int $user_id     The user who answered (already contained in exe_id)
1841
     * @param int $courseId    The course in which it happened (already contained in exe_id)
1842
     * @param int $session_id  The session in which it happened (already contained in exe_id)
1843
     * @param int $question_id The c_quiz_question.iid
1844
     */
1845
    public static function delete_attempt(
1846
        $exeId,
1847
        $user_id,
1848
        $courseId,
1849
        $session_id,
1850
        $question_id
1851
    ) {
1852
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1853
1854
        $exeId = (int) $exeId;
1855
        $user_id = (int) $user_id;
1856
        $courseId = (int) $courseId;
1857
        $session_id = (int) $session_id;
1858
        $question_id = (int) $question_id;
1859
1860
        $sql = "DELETE FROM $table
1861
                WHERE
1862
                    exe_id = $exeId AND
1863
                    user_id = $user_id AND
1864
                    c_id = $courseId AND
1865
                    session_id = $session_id AND
1866
                    question_id = $question_id ";
1867
        Database::query($sql);
1868
1869
        self::addEvent(
1870
            LOG_QUESTION_RESULT_DELETE,
1871
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
1872
            $exeId.'-'.$question_id,
1873
            null,
1874
            null,
1875
            $courseId,
1876
            $session_id
1877
        );
1878
    }
1879
1880
    /**
1881
     * Delete one record from the track_e_hotspot table based on a given
1882
     * track_e_exercises.exe_id.
1883
     *
1884
     * @param     $exeId
1885
     * @param     $user_id
1886
     * @param int $courseId
1887
     * @param     $question_id
1888
     * @param int $sessionId
1889
     */
1890
    public static function delete_attempt_hotspot(
1891
        $exeId,
1892
        $user_id,
1893
        $courseId,
1894
        $question_id,
1895
        $sessionId = null
1896
    ) {
1897
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
1898
1899
        $exeId = (int) $exeId;
1900
        $user_id = (int) $user_id;
1901
        $courseId = (int) $courseId;
1902
        $question_id = (int) $question_id;
1903
        if (!isset($sessionId)) {
1904
            $sessionId = api_get_session_id();
1905
        }
1906
1907
        $sql = "DELETE FROM $table
1908
                WHERE
1909
                    hotspot_exe_id = $exeId AND
1910
                    hotspot_user_id = $user_id AND
1911
                    c_id = $courseId AND
1912
                    hotspot_question_id = $question_id ";
1913
        Database::query($sql);
1914
        self::addEvent(
1915
            LOG_QUESTION_RESULT_DELETE,
1916
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
1917
            $exeId.'-'.$question_id,
1918
            null,
1919
            null,
1920
            $courseId,
1921
            $sessionId
1922
        );
1923
    }
1924
1925
    /**
1926
     * Registers in track_e_course_access when user logs in for the first time to a course.
1927
     *
1928
     * @param int $courseId  ID of the course
1929
     * @param int $user_id   ID of the user
1930
     * @param int $sessionId ID of the session (if any)
1931
     *
1932
     * @return bool
1933
     */
1934
    public static function eventCourseLogin($courseId, $user_id, $sessionId)
1935
    {
1936
        if (Session::read('login_as')) {
1937
            return false;
1938
        }
1939
1940
        $sessionId = (int) $sessionId;
1941
        if (false === self::isSessionLogNeedToBeSave($sessionId)) {
1942
            return false;
1943
        }
1944
1945
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
1946
        $loginDate = $logoutDate = api_get_utc_datetime();
1947
1948
        // $counter represents the number of time this record has been refreshed
1949
        $counter = 1;
1950
        $courseId = (int) $courseId;
1951
        $user_id = (int) $user_id;
1952
        $ip = Database::escape_string(api_get_real_ip());
1953
1954
        $sql = "INSERT INTO $table(c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
1955
                VALUES($courseId, '$ip', $user_id, '$loginDate', '$logoutDate', $counter, $sessionId)";
1956
        $courseAccessId = Database::query($sql);
1957
1958
        if ($courseAccessId) {
1959
            // Course catalog stats modifications see #4191
1960
            CourseManager::update_course_ranking(
1961
                null,
1962
                null,
1963
                null,
1964
                null,
1965
                true,
1966
                false
1967
            );
1968
1969
            return true;
1970
        }
1971
    }
1972
1973
    /**
1974
     * Updates the user - course - session every X minutes
1975
     * In order to avoid.
1976
     *
1977
     * @param int $courseId
1978
     * @param int $userId
1979
     * @param int $sessionId
1980
     * @param int $minutes
1981
     *
1982
     * @return bool
1983
     */
1984
    public static function eventCourseLoginUpdate(
1985
        $courseId,
1986
        $userId,
1987
        $sessionId,
1988
        $minutes = 5
1989
    ) {
1990
        if (Session::read('login_as')) {
1991
            return false;
1992
        }
1993
1994
        if (empty($courseId) || empty($userId)) {
1995
            return false;
1996
        }
1997
1998
        $sessionId = (int) $sessionId;
1999
2000
        if (false === self::isSessionLogNeedToBeSave($sessionId)) {
2001
            return false;
2002
        }
2003
2004
        $courseId = (int) $courseId;
2005
        $userId = (int) $userId;
2006
2007
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2008
        $sql = "SELECT course_access_id, logout_course_date
2009
                FROM $table
2010
                WHERE
2011
                    c_id = $courseId AND
2012
                    session_id = $sessionId AND
2013
                    user_id = $userId
2014
                ORDER BY login_course_date DESC
2015
                LIMIT 1";
2016
2017
        $result = Database::query($sql);
2018
2019
        // Save every 5 minutes by default
2020
        $seconds = $minutes * 60;
2021
        $maxSeconds = 3600; // Only update if max diff is one hour
2022
        if (Database::num_rows($result)) {
2023
            $row = Database::fetch_array($result);
2024
            $id = $row['course_access_id'];
2025
            $logout = $row['logout_course_date'];
2026
            $now = time();
2027
            $logout = api_strtotime($logout, 'UTC');
2028
            if ($now - $logout > $seconds &&
2029
                $now - $logout < $maxSeconds
2030
            ) {
2031
                $now = api_get_utc_datetime();
2032
                $sql = "UPDATE $table SET
2033
                            logout_course_date = '$now',
2034
                            counter = counter + 1
2035
                        WHERE course_access_id = $id";
2036
                Database::query($sql);
2037
            }
2038
2039
            return true;
2040
        }
2041
2042
        return false;
2043
    }
2044
2045
    /**
2046
     * Register the logout of the course (usually when logging out of the platform)
2047
     * from the track_e_course_access table.
2048
     *
2049
     * @param array $logoutInfo Information stored by local.inc.php
2050
     *                          before new context ['uid'=> x, 'cid'=>y, 'sid'=>z]
2051
     *
2052
     * @return bool
2053
     */
2054
    public static function courseLogout($logoutInfo)
2055
    {
2056
        if (Session::read('login_as')) {
2057
            return false;
2058
        }
2059
2060
        if (empty($logoutInfo['uid']) || empty($logoutInfo['cid'])) {
2061
            return false;
2062
        }
2063
2064
        $sessionLifetime = api_get_configuration_value('session_lifetime');
2065
        /*
2066
         * When $_configuration['session_lifetime'] is larger than ~100 hours
2067
         * (in order to let users take exercises with no problems)
2068
         * the function Tracking::get_time_spent_on_the_course() returns larger values (200h) due the condition:
2069
         * login_course_date > now() - INTERVAL $session_lifetime SECOND
2070
         */
2071
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
2072
            $sessionLifetime = 3600; // 1 hour
2073
        }
2074
        if (!empty($logoutInfo) && !empty($logoutInfo['cid'])) {
2075
            $sessionId = 0;
2076
            if (!empty($logoutInfo['sid'])) {
2077
                $sessionId = (int) $logoutInfo['sid'];
2078
            }
2079
2080
            if (false === self::isSessionLogNeedToBeSave($sessionId)) {
2081
                return false;
2082
            }
2083
2084
            $tableCourseAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2085
            $userId = (int) $logoutInfo['uid'];
2086
            $courseId = (int) $logoutInfo['cid'];
2087
2088
            $currentDate = api_get_utc_datetime();
2089
            // UTC time
2090
            $diff = time() - $sessionLifetime;
2091
            $time = api_get_utc_datetime($diff);
2092
            $sql = "SELECT course_access_id, logout_course_date
2093
                    FROM $tableCourseAccess
2094
                    WHERE
2095
                        user_id = $userId AND
2096
                        c_id = $courseId  AND
2097
                        session_id = $sessionId AND
2098
                        login_course_date > '$time'
2099
                    ORDER BY login_course_date DESC
2100
                    LIMIT 1";
2101
            $result = Database::query($sql);
2102
            $insert = false;
2103
            if (Database::num_rows($result) > 0) {
2104
                $row = Database::fetch_array($result, 'ASSOC');
2105
                $courseAccessId = $row['course_access_id'];
2106
                $sql = "UPDATE $tableCourseAccess SET
2107
                                logout_course_date = '$currentDate',
2108
                                counter = counter + 1
2109
                            WHERE course_access_id = $courseAccessId";
2110
                Database::query($sql);
2111
            } else {
2112
                $insert = true;
2113
            }
2114
2115
            if ($insert) {
2116
                $ip = Database::escape_string(api_get_real_ip());
2117
                $sql = "INSERT INTO $tableCourseAccess (c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2118
                        VALUES ($courseId, '$ip', $userId, '$currentDate', '$currentDate', 1, $sessionId)";
2119
                Database::query($sql);
2120
            }
2121
2122
            return true;
2123
        }
2124
    }
2125
2126
    /**
2127
     * Register a "fake" time spent on the platform, for example to match the
2128
     * estimated time he took to author an assignment/work, see configuration
2129
     * setting considered_working_time.
2130
     * This assumes there is already some connection of the student to the
2131
     * course, otherwise he wouldn't be able to upload an assignment.
2132
     * This works by creating a new record, copy of the current one, then
2133
     * updating the current one to be just the considered_working_time and
2134
     * end at the same second as the user connected to the course.
2135
     *
2136
     * @param int    $courseId    The course in which to add the time
2137
     * @param int    $userId      The user for whom to add the time
2138
     * @param int    $sessionId   The session in which to add the time (if any)
2139
     * @param string $virtualTime The amount of time to be added,
2140
     *                            in a hh:mm:ss format. If int, we consider it is expressed in hours.
2141
     * @param int    $workId      Student publication id result
2142
     *
2143
     * @return true on successful insertion, false otherwise
2144
     */
2145
    public static function eventAddVirtualCourseTime(
2146
        $courseId,
2147
        $userId,
2148
        $sessionId,
2149
        $virtualTime,
2150
        $workId
2151
    ) {
2152
        if (empty($virtualTime)) {
2153
            return false;
2154
        }
2155
2156
        $courseId = (int) $courseId;
2157
        $userId = (int) $userId;
2158
        $sessionId = (int) $sessionId;
2159
2160
        $logoutDate = api_get_utc_datetime();
2161
        $loginDate = ChamiloApi::addOrSubTimeToDateTime(
2162
            $virtualTime,
2163
            $logoutDate,
2164
            false
2165
        );
2166
2167
        $ip = api_get_real_ip();
2168
        $params = [
2169
            'login_course_date' => $loginDate,
2170
            'logout_course_date' => $logoutDate,
2171
            'session_id' => $sessionId,
2172
            'user_id' => $userId,
2173
            'counter' => 0,
2174
            'c_id' => $courseId,
2175
            'user_ip' => $ip,
2176
        ];
2177
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2178
        Database::insert($courseTrackingTable, $params);
2179
2180
        // Time should also be added to the track_e_login table so as to
2181
        // affect total time on the platform
2182
        $params = [
2183
            'login_user_id' => $userId,
2184
            'login_date' => $loginDate,
2185
            'user_ip' => $ip,
2186
            'logout_date' => $logoutDate,
2187
        ];
2188
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2189
        Database::insert($platformTrackingTable, $params);
2190
2191
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2192
            $workId = (int) $workId;
2193
            $uniqueId = time();
2194
            $logInfo = [
2195
                'c_id' => $courseId,
2196
                'session_id' => $sessionId,
2197
                'tool' => TOOL_STUDENTPUBLICATION,
2198
                'date_reg' => $loginDate,
2199
                'action' => 'add_work_start_'.$workId,
2200
                'action_details' => $virtualTime,
2201
                'user_id' => $userId,
2202
                'current_id' => $uniqueId,
2203
            ];
2204
            self::registerLog($logInfo);
2205
2206
            $logInfo = [
2207
                'c_id' => $courseId,
2208
                'session_id' => $sessionId,
2209
                'tool' => TOOL_STUDENTPUBLICATION,
2210
                'date_reg' => $logoutDate,
2211
                'action' => 'add_work_end_'.$workId,
2212
                'action_details' => $virtualTime,
2213
                'user_id' => $userId,
2214
                'current_id' => $uniqueId,
2215
            ];
2216
            self::registerLog($logInfo);
2217
        }
2218
2219
        return true;
2220
    }
2221
2222
    /**
2223
     * Removes a "fake" time spent on the platform, for example to match the
2224
     * estimated time he took to author an assignment/work, see configuration
2225
     * setting considered_working_time.
2226
     * This method should be called when something that generated a fake
2227
     * time record is removed. Given the database link is weak (no real
2228
     * relationship kept between the deleted item and this record), this
2229
     * method just looks for the latest record that has the same time as the
2230
     * item's fake time, is in the past and in this course+session. If such a
2231
     * record cannot be found, it doesn't do anything.
2232
     * The IP address is not considered a useful filter here.
2233
     *
2234
     * @param int    $courseId    The course in which to add the time
2235
     * @param int    $userId      The user for whom to add the time
2236
     * @param int    $sessionId   The session in which to add the time (if any)
2237
     * @param string $virtualTime The amount of time to be added, in a hh:mm:ss format. If int, we consider it is
2238
     *                            expressed in hours.
2239
     *
2240
     * @return true on successful removal, false otherwise
2241
     */
2242
    public static function eventRemoveVirtualCourseTime(
2243
        $courseId,
2244
        $userId,
2245
        $sessionId = 0,
2246
        $virtualTime,
2247
        $workId
2248
    ) {
2249
        if (empty($virtualTime)) {
2250
            return false;
2251
        }
2252
2253
        $originalVirtualTime = $virtualTime;
2254
2255
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2256
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2257
        $courseId = (int) $courseId;
2258
        $userId = (int) $userId;
2259
        $sessionId = (int) $sessionId;
2260
        // Change $virtualTime format from hh:mm:ss to hhmmss which is the
2261
        // format returned by SQL for a subtraction of two datetime values
2262
        // @todo make sure this is portable between DBMSes
2263
        // @todo make sure this is portable between DBMSes
2264
        if (preg_match('/:/', $virtualTime)) {
2265
            [$h, $m, $s] = preg_split('/:/', $virtualTime);
2266
            $virtualTime = $h * 3600 + $m * 60 + $s;
2267
        } else {
2268
            $virtualTime *= 3600;
2269
        }
2270
2271
        // Get the current latest course connection register. We need that
2272
        // record to re-use the data and create a new record.
2273
        $sql = "SELECT course_access_id
2274
                FROM $courseTrackingTable
2275
                WHERE
2276
                    user_id = $userId AND
2277
                    c_id = $courseId  AND
2278
                    session_id  = $sessionId AND
2279
                    counter = 0 AND
2280
                    (UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) = '$virtualTime'
2281
                ORDER BY login_course_date DESC LIMIT 0,1";
2282
        $result = Database::query($sql);
2283
2284
        // Ignore if we didn't find any course connection record in the last
2285
        // hour. In this case it wouldn't be right to add a "fake" time record.
2286
        if (Database::num_rows($result) > 0) {
2287
            // Found the latest connection
2288
            $row = Database::fetch_row($result);
2289
            $courseAccessId = $row[0];
2290
            $sql = "DELETE FROM $courseTrackingTable
2291
                    WHERE course_access_id = $courseAccessId";
2292
            Database::query($sql);
2293
        }
2294
        $sql = "SELECT login_id
2295
                FROM $platformTrackingTable
2296
                WHERE
2297
                    login_user_id = $userId AND
2298
                    (UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date)) = '$virtualTime'
2299
                ORDER BY login_date DESC LIMIT 0,1";
2300
        $result = Database::query($sql);
2301
        if (Database::num_rows($result) > 0) {
2302
            $row = Database::fetch_row($result);
2303
            $loginAccessId = $row[0];
2304
            $sql = "DELETE FROM $platformTrackingTable
2305
                    WHERE login_id = $loginAccessId";
2306
            Database::query($sql);
2307
        }
2308
2309
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2310
            $workId = (int) $workId;
2311
            $sql = "SELECT id FROM track_e_access_complete
2312
                    WHERE
2313
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2314
                        c_id = $courseId AND
2315
                        session_id = $sessionId AND
2316
                        user_id = $userId AND
2317
                        action_details = '$originalVirtualTime' AND
2318
                        action = 'add_work_start_$workId' ";
2319
            $result = Database::query($sql);
2320
            $result = Database::fetch_array($result);
2321
            if ($result) {
2322
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2323
                Database::query($sql);
2324
            }
2325
2326
            $sql = "SELECT id FROM track_e_access_complete
2327
                    WHERE
2328
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2329
                        c_id = $courseId AND
2330
                        session_id = $sessionId AND
2331
                        user_id = $userId AND
2332
                        action_details = '$originalVirtualTime' AND
2333
                        action = 'add_work_end_$workId' ";
2334
            $result = Database::query($sql);
2335
            $result = Database::fetch_array($result);
2336
            if ($result) {
2337
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2338
                Database::query($sql);
2339
            }
2340
        }
2341
2342
        return false;
2343
    }
2344
2345
    /**
2346
     * Register the logout of the course (usually when logging out of the platform)
2347
     * from the track_e_access_complete table.
2348
     *
2349
     * @param array $logInfo Information stored by local.inc.php
2350
     *
2351
     * @return bool
2352
     */
2353
    public static function registerLog($logInfo)
2354
    {
2355
        $sessionId = api_get_session_id();
2356
        $courseId = api_get_course_int_id();
2357
2358
        if (isset($logInfo['c_id']) && !empty($logInfo['c_id'])) {
2359
            $courseId = $logInfo['c_id'];
2360
        }
2361
2362
        if (isset($logInfo['session_id']) && !empty($logInfo['session_id'])) {
2363
            $sessionId = $logInfo['session_id'];
2364
        }
2365
2366
        if (!Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2367
            return false;
2368
        }
2369
2370
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2371
            return false;
2372
        }
2373
2374
        $loginAs = (int) Session::read('login_as') === true;
2375
2376
        $logInfo['user_id'] = isset($logInfo['user_id']) ? $logInfo['user_id'] : api_get_user_id();
2377
        $logInfo['date_reg'] = isset($logInfo['date_reg']) ? $logInfo['date_reg'] : api_get_utc_datetime();
2378
        $logInfo['tool'] = !empty($logInfo['tool']) ? $logInfo['tool'] : '';
2379
        $logInfo['tool_id'] = !empty($logInfo['tool_id']) ? (int) $logInfo['tool_id'] : 0;
2380
        $logInfo['tool_id_detail'] = !empty($logInfo['tool_id_detail']) ? (int) $logInfo['tool_id_detail'] : 0;
2381
        $logInfo['action'] = !empty($logInfo['action']) ? $logInfo['action'] : '';
2382
        $logInfo['action_details'] = !empty($logInfo['action_details']) ? $logInfo['action_details'] : '';
2383
        $logInfo['ip_user'] = api_get_real_ip();
2384
        $logInfo['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
2385
        $logInfo['session_id'] = $sessionId;
2386
        $logInfo['c_id'] = $courseId;
2387
        $logInfo['ch_sid'] = session_id();
2388
        $logInfo['login_as'] = $loginAs;
2389
        $logInfo['info'] = !empty($logInfo['info']) ? $logInfo['info'] : '';
2390
        $logInfo['url'] = $_SERVER['REQUEST_URI'];
2391
        $logInfo['current_id'] = isset($logInfo['current_id']) ? $logInfo['current_id'] : Session::read('last_id', 0);
2392
2393
        $id = Database::insert('track_e_access_complete', $logInfo);
2394
        if ($id && empty($logInfo['current_id'])) {
2395
            Session::write('last_id', $id);
2396
        }
2397
2398
        return true;
2399
    }
2400
2401
    public static function getAttemptQuestionDuration($exeId, $questionId)
2402
    {
2403
        // Check current attempt.
2404
        $questionAttempt = self::getQuestionAttemptByExeIdAndQuestion($exeId, $questionId);
2405
        $alreadySpent = 0;
2406
        if (!empty($questionAttempt) && $questionAttempt['seconds_spent']) {
2407
            $alreadySpent = $questionAttempt['seconds_spent'];
2408
        }
2409
        $now = time();
2410
        $questionStart = Session::read('question_start', []);
2411
        if (!empty($questionStart) &&
2412
            isset($questionStart[$questionId]) && !empty($questionStart[$questionId])
2413
        ) {
2414
            $time = $questionStart[$questionId];
2415
        } else {
2416
            $diff = 0;
2417
            if (!empty($alreadySpent)) {
2418
                $diff = $alreadySpent;
2419
            }
2420
            $time = $questionStart[$questionId] = $now - $diff;
2421
            Session::write('question_start', $questionStart);
2422
        }
2423
2424
        return $now - $time;
2425
    }
2426
}
2427