Completed
Push — master ( 88715c...d7c14e )
by Julito
09:34
created

Event::saveQuestionAttempt()   F

Complexity

Conditions 26
Paths > 20000

Size

Total Lines 181
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 108
c 0
b 0
f 0
nc 115488
nop 13
dl 0
loc 181
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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