Passed
Push — master ( 5db41b...b5271b )
by Julito
10:05
created

Event::updateEventExercise()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 74
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

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