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

Event::updateEventExercise()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 82
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 45
c 0
b 0
f 0
nc 17
nop 13
dl 0
loc 82
rs 8.2666

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