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