Passed
Push — master ( e80e7a...4357b6 )
by Julito
11:11 queued 02:04
created

Event::getQuestionAttemptByExeIdAndQuestion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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