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

Event::saveExerciseAttemptHotspot()   B

Complexity

Conditions 9
Paths 24

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 37
nc 24
nop 10
dl 0
loc 68
rs 7.7724
c 0
b 0
f 0

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