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

Event::updateEventExercise()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 74
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* See license terms in /license.txt */
3
4
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
5
use Chamilo\CoreBundle\Entity\TrackEAttemptRecording;
6
use ChamiloSession as Session;
7
8
/**
9
 * Class Event
10
 * Functions of this library are used to record informations when some kind
11
 * of event occur. Each event has his own types of informations then each event
12
 * use its own function.
13
 */
14
class Event
15
{
16
    /**
17
     * @param int $userId
18
     *
19
     * @return bool
20
     * @desc   Record information for login event when an user identifies himself with username & password
21
     * @author Sebastien Piraux <[email protected]> old code
22
     * @author Julio Montoya
23
     *
24
     */
25
    public static function eventLogin($userId)
26
    {
27
        $userInfo = api_get_user_info($userId);
28
        $userId = (int) $userId;
29
30
        if (empty($userInfo)) {
31
            return false;
32
        }
33
34
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
35
        $reallyNow = api_get_utc_datetime();
36
        $userIp = Database::escape_string(api_get_real_ip());
37
38
        $sql = "INSERT INTO $table (login_user_id, user_ip, login_date, logout_date) VALUES
39
                    ($userId,
40
                    '$userIp',
41
                    '$reallyNow',
42
                    '$reallyNow'
43
                )";
44
        Database::query($sql);
45
46
        $status = 'student';
47
        if (SESSIONADMIN == $userInfo['status']) {
48
            $status = 'sessionadmin';
49
        }
50
        if (COURSEMANAGER == $userInfo['status']) {
51
            $status = 'teacher';
52
        }
53
        if (DRH == $userInfo['status']) {
54
            $status = 'DRH';
55
        }
56
57
        // Auto subscribe
58
        $autoSubscribe = api_get_setting($status.'_autosubscribe');
59
        if ($autoSubscribe) {
60
            $autoSubscribe = explode('|', $autoSubscribe);
61
            foreach ($autoSubscribe as $code) {
62
                if (CourseManager::course_exists($code)) {
63
                    CourseManager::subscribeUser($userId, $code);
64
                }
65
            }
66
        }
67
68
        return true;
69
    }
70
71
    /**
72
     * @param int $sessionId
73
     *
74
     * @return bool
75
     */
76
    public static function isSessionLogNeedToBeSave($sessionId)
77
    {
78
        if (!empty($sessionId)) {
79
            $visibility = api_get_session_visibility($sessionId);
80
            if (!empty($visibility) && $visibility != SESSION_AVAILABLE) {
81
                $extraFieldValue = new ExtraFieldValue('session');
82
                $value = $extraFieldValue->get_values_by_handler_and_field_variable(
83
                    $sessionId,
84
                    'disable_log_after_session_ends'
85
                );
86
                if (!empty($value) && isset($value['value']) && (int) $value['value'] == 1) {
87
                    return false;
88
                }
89
            }
90
        }
91
92
        return true;
93
    }
94
95
    /**
96
     * @author Sebastien Piraux <[email protected]>
97
     * @desc   Record information for access event for courses
98
     *
99
     * @return bool
100
     */
101
    public static function accessCourse()
102
    {
103
        if (Session::read('login_as')) {
104
            return false;
105
        }
106
107
        $TABLETRACK_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
108
        // For "what's new" notification
109
        $TABLETRACK_LASTACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
110
111
        $sessionId = api_get_session_id();
112
        $now = api_get_utc_datetime();
113
        $courseId = api_get_course_int_id();
114
        $userId = api_get_user_id();
115
        $ip = Database::escape_string(api_get_real_ip());
116
117
        if (false === self::isSessionLogNeedToBeSave($sessionId)) {
118
            return false;
119
        }
120
121
        if ($userId) {
122
            $userId = $userId;
123
        } else {
124
            $userId = '0'; // no one
125
        }
126
        $sql = "INSERT INTO $TABLETRACK_ACCESS  (user_ip, access_user_id, c_id, access_date, access_session_id)
127
                VALUES ('$ip', $userId, $courseId, '$now', $sessionId)";
128
129
        Database::query($sql);
130
131
        // added for "what's new" notification
132
        $sql = "UPDATE $TABLETRACK_LASTACCESS  SET access_date = '$now'
133
                WHERE
134
                  access_user_id = $userId AND
135
                  c_id = $courseId AND
136
                  access_tool IS NULL AND
137
                  access_session_id = $sessionId";
138
        $result = Database::query($sql);
139
140
        if (0 == Database::affected_rows($result)) {
141
            $sql = "INSERT INTO $TABLETRACK_LASTACCESS (access_user_id, c_id, access_date, access_session_id)
142
                    VALUES ($userId, $courseId, '$now', $sessionId)";
143
            Database::query($sql);
144
        }
145
146
        return true;
147
    }
148
149
    /**
150
     * @param string $tool name of the tool
151
     *
152
     * @author Sebastien Piraux <[email protected]>
153
     * @desc   Record information for access event for tools
154
     *
155
     *  $tool can take this values :
156
     *  Links, Calendar, Document, Announcements,
157
     *  Group, Video, Works, Users, Exercises, Course Desc
158
     *  ...
159
     *  Values can be added if new modules are created (15char max)
160
     *  I encourage to use $nameTool as $tool when calling this function
161
     *
162
     * Functionality for "what's new" notification is added by Toon Van Hoecke
163
     *
164
     * @return bool
165
     */
166
    public static function event_access_tool($tool)
167
    {
168
        if (Session::read('login_as')) {
169
            return false;
170
        }
171
172
        $tool = Database::escape_string($tool);
173
174
        if (empty($tool)) {
175
            return false;
176
        }
177
178
        $courseInfo = api_get_course_info();
179
        $sessionId = api_get_session_id();
180
        $reallyNow = api_get_utc_datetime();
181
        $userId = api_get_user_id();
182
183
        if (empty($courseInfo)) {
184
            return false;
185
        }
186
187
        if (false === self::isSessionLogNeedToBeSave($sessionId)) {
188
            return false;
189
        }
190
191
        $courseId = $courseInfo['real_id'];
192
193
        $tableAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
194
        //for "what's new" notification
195
        $tableLastAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
196
197
        // record information
198
        // only if user comes from the course $_cid
199
        //if( eregi($_configuration['root_web'].$_cid,$_SERVER['HTTP_REFERER'] ) )
200
        //$pos = strpos($_SERVER['HTTP_REFERER'],$_configuration['root_web'].$_cid);
201
        $coursePath = isset($courseInfo['path']) ? $courseInfo['path'] : null;
202
203
        $pos = isset($_SERVER['HTTP_REFERER']) ? strpos(
204
            strtolower($_SERVER['HTTP_REFERER']),
205
            strtolower(api_get_path(WEB_COURSE_PATH).$coursePath)
206
        ) : false;
207
        // added for "what's new" notification
208
        $pos2 = isset($_SERVER['HTTP_REFERER']) ? strpos(
209
            strtolower($_SERVER['HTTP_REFERER']),
210
            strtolower(api_get_path(WEB_PATH)."index")
211
        ) : false;
212
213
        // end "what's new" notification
214
        if (false !== $pos || false !== $pos2) {
215
            $params = [
216
                'access_user_id' => $userId,
217
                'c_id' => $courseId,
218
                'access_tool' => $tool,
219
                'access_date' => $reallyNow,
220
                'access_session_id' => $sessionId,
221
                'user_ip' => Database::escape_string(api_get_real_ip()),
222
            ];
223
            Database::insert($tableAccess, $params);
224
        }
225
226
        // "what's new" notification
227
        $sql = "UPDATE $tableLastAccess
228
                SET access_date = '$reallyNow'
229
                WHERE
230
                    access_user_id = $userId AND
231
                    c_id = $courseId AND
232
                    access_tool = '$tool' AND
233
                    access_session_id = $sessionId";
234
        $result = Database::query($sql);
235
236
        if (0 == Database::affected_rows($result)) {
237
            $params = [
238
                'access_user_id' => $userId,
239
                'c_id' => $courseId,
240
                'access_tool' => $tool,
241
                'access_date' => $reallyNow,
242
                'access_session_id' => $sessionId,
243
            ];
244
            Database::insert($tableLastAccess, $params);
245
        }
246
247
        return true;
248
    }
249
250
    /**
251
     * Record information for download event (when an user click to d/l a
252
     * document) it will be used in a redirection page.
253
     *
254
     * @param string $documentUrl
255
     *
256
     * @return int
257
     *
258
     * @author Sebastien Piraux <[email protected]>
259
     * @author Evie Embrechts (bug fixed: The user id is put in single quotes)
260
     */
261
    public static function event_download($documentUrl)
262
    {
263
        if (Session::read('login_as')) {
264
            return false;
265
        }
266
267
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
268
        $documentUrl = Database::escape_string($documentUrl);
269
270
        $reallyNow = api_get_utc_datetime();
271
        $userId = api_get_user_id();
272
        $courseId = api_get_course_int_id();
273
        $sessionId = api_get_session_id();
274
275
        return Database::insert(
276
            $table,
277
            [
278
                'down_user_id' => $userId,
279
                'c_id' => $courseId,
280
                'down_doc_path' => $documentUrl,
281
                'down_date' => $reallyNow,
282
                'down_session_id' => $sessionId,
283
            ]
284
        );
285
    }
286
287
    /**
288
     * @param int $documentId of document (id in mainDb.document table)
289
     *
290
     * @author Sebastien Piraux <[email protected]>
291
     * @desc   Record information for upload event
292
     *         used in the works tool to record informations when
293
     *         an user upload 1 work
294
     *
295
     * @return int
296
     */
297
    public static function event_upload($documentId)
298
    {
299
        if (Session::read('login_as')) {
300
            return false;
301
        }
302
303
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_UPLOADS);
304
        $courseId = api_get_course_int_id();
305
        $reallyNow = api_get_utc_datetime();
306
        $userId = api_get_user_id();
307
        $documentId = (int) $documentId;
308
        $sessionId = api_get_session_id();
309
310
        $sql = "INSERT INTO $table
311
                ( upload_user_id,
312
                  c_id,
313
                  upload_work_id,
314
                  upload_date,
315
                  upload_session_id
316
                )
317
                VALUES (
318
                 $userId,
319
                 $courseId,
320
                 $documentId,
321
                 '$reallyNow',
322
                 $sessionId
323
                )";
324
        Database::query($sql);
325
326
        return 1;
327
    }
328
329
    /**
330
     * Record information for link event (when an user click on an added link)
331
     * it will be used in a redirection page.
332
     *
333
     * @param int $linkId (id in c_link table)
334
     *
335
     * @return int
336
     *
337
     * @author Sebastien Piraux <[email protected]>
338
     */
339
    public static function event_link($linkId)
340
    {
341
        if (Session::read('login_as')) {
342
            return false;
343
        }
344
345
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
346
        $reallyNow = api_get_utc_datetime();
347
        $userId = api_get_user_id();
348
        $courseId = api_get_course_int_id();
349
        $linkId = (int) $linkId;
350
        $sessionId = api_get_session_id();
351
        $sql = "INSERT INTO ".$table."
352
                    ( links_user_id,
353
                     c_id,
354
                     links_link_id,
355
                     links_date,
356
                     links_session_id
357
                    ) VALUES (
358
                     $userId,
359
                     $courseId,
360
                     $linkId,
361
                     '$reallyNow',
362
                     $sessionId
363
                    )";
364
        Database::query($sql);
365
366
        return 1;
367
    }
368
369
    /**
370
     * Update the TRACK_E_EXERCICES exercises.
371
     * Record result of user when an exercise was done.
372
     *
373
     * @param int    $exeId
374
     * @param int    $exoId
375
     * @param mixed  $score
376
     * @param int    $weighting
377
     * @param int    $sessionId
378
     * @param int    $learnpathId
379
     * @param int    $learnpathItemId
380
     * @param int    $learnpathItemViewId
381
     * @param int    $duration
382
     * @param array  $questionsList
383
     * @param string $status
384
     * @param array  $remindList
385
     * @param null   $endDate
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $endDate is correct as it would always require null to be passed?
Loading history...
386
     *
387
     * @return bool
388
     *
389
     * @author Sebastien Piraux <[email protected]>
390
     * @author Julio Montoya Armas <[email protected]> Reworked 2010
391
     */
392
    public static function updateEventExercise(
393
        $exeId,
394
        $exoId,
395
        $score,
396
        $weighting,
397
        $sessionId,
398
        $learnpathId = 0,
399
        $learnpathItemId = 0,
400
        $learnpathItemViewId = 0,
401
        $duration = 0,
402
        $questionsList = [],
403
        $status = '',
404
        $remindList = [],
405
        $endDate = null
406
    ) {
407
        if (empty($exeId)) {
408
            return false;
409
        }
410
411
        if (!isset($status) || empty($status)) {
412
            $status = '';
413
        } else {
414
            $status = Database::escape_string($status);
415
        }
416
417
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
418
419
        if (!empty($questionsList)) {
420
            $questionsList = array_map('intval', $questionsList);
421
        }
422
423
        if (!empty($remindList)) {
424
            $remindList = array_map('intval', $remindList);
425
            $remindList = array_filter($remindList);
426
            $remindList = implode(",", $remindList);
427
        } else {
428
            $remindList = '';
429
        }
430
431
        if (empty($endDate)) {
432
            $endDate = api_get_utc_datetime();
433
        }
434
        $exoId = (int) $exoId;
435
        $sessionId = (int) $sessionId;
436
        $learnpathId = (int) $learnpathId;
437
        $learnpathItemId = (int) $learnpathItemId;
438
        $learnpathItemViewId = (int) $learnpathItemViewId;
439
        $duration = (int) $duration;
440
        $exeId = (int) $exeId;
441
        $score = Database::escape_string($score);
442
        $weighting = Database::escape_string($weighting);
443
        $questions = implode(',', $questionsList);
444
        $userIp = Database::escape_string(api_get_real_ip());
445
446
        $sql = "UPDATE $table SET
447
               exe_exo_id = $exoId,
448
               score = '$score',
449
               max_score = '$weighting',
450
               session_id = $sessionId,
451
               orig_lp_id = $learnpathId,
452
               orig_lp_item_id = $learnpathItemId,
453
               orig_lp_item_view_id = $learnpathItemViewId,
454
               exe_duration = $duration,
455
               exe_date = '$endDate',
456
               status = '$status',
457
               questions_to_check = '$remindList',
458
               data_tracking = '$questions',
459
               user_ip = '$userIp'
460
             WHERE exe_id = $exeId";
461
        Database::query($sql);
462
463
        //Deleting control time session track
464
        //ExerciseLib::exercise_time_control_delete($exo_id);
465
        return true;
466
    }
467
468
    /**
469
     * Record an event for this attempt at answering an exercise.
470
     *
471
     * @param float  $score             Score achieved
472
     * @param string $answer            Answer given
473
     * @param int    $question_id
474
     * @param int    $exe_id            Exercise attempt ID a.k.a exe_id (from track_e_exercise)
475
     * @param int    $position
476
     * @param int    $exercise_id       From c_quiz
477
     * @param bool   $updateResults
478
     * @param int    $duration          Time spent in seconds
479
     * @param string $fileName          Filename (for audio answers - using nanogong)
480
     * @param int    $user_id           The user who's going to get this score.
481
     * @param int    $course_id         Default value of null means "get from context".
482
     * @param int    $session_id        Default value of null means "get from context".
483
     * @param int    $learnpath_id      (from c_lp table). Default value of null means "get from context".
484
     * @param int    $learnpath_item_id (from the c_lp_item table). Default value of null means "get from context".
485
     *
486
     * @return bool Result of the insert query
487
     */
488
    public static function saveQuestionAttempt(
489
        $score,
490
        $answer,
491
        $question_id,
492
        $exe_id,
493
        $position,
494
        $exercise_id = 0,
495
        $updateResults = false,
496
        $questionDuration = 0,
497
        $fileName = null,
498
        $user_id = null,
499
        $course_id = null,
500
        $session_id = null,
501
        $learnpath_id = null,
502
        $learnpath_item_id = null
503
    ) {
504
        global $debug;
505
        $questionDuration = (int) $questionDuration;
506
        $question_id = (int) $question_id;
507
        $exe_id = (int) $exe_id;
508
        $position = (int) $position;
509
        $course_id = (int) $course_id;
510
        $now = api_get_utc_datetime();
511
        $recording = api_get_configuration_value('quiz_answer_extra_recording');
512
513
        // check user_id or get from context
514
        if (empty($user_id)) {
515
            $user_id = api_get_user_id();
516
            // anonymous
517
            if (empty($user_id)) {
518
                $user_id = api_get_anonymous_id();
519
            }
520
        }
521
        // check course_id or get from context
522
        if (empty($course_id)) {
523
            $course_id = api_get_course_int_id();
524
        }
525
        // check session_id or get from context
526
        $session_id = (int) $session_id;
527
        if (empty($session_id)) {
528
            $session_id = api_get_session_id();
529
        }
530
        // check learnpath_id or get from context
531
        if (empty($learnpath_id)) {
532
            global $learnpath_id;
533
        }
534
        // check learnpath_item_id or get from context
535
        if (empty($learnpath_item_id)) {
536
            global $learnpath_item_id;
537
        }
538
539
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
540
541
        if ($debug) {
542
            error_log('----- entering saveQuestionAttempt() function ------');
543
            error_log("answer: $answer");
544
            error_log("score: $score");
545
            error_log("question_id : $question_id");
546
            error_log("position: $position");
547
        }
548
549
        //Validation in case of fraud with active control time
550
        if (!ExerciseLib::exercise_time_control_is_valid($exercise_id, $learnpath_id, $learnpath_item_id)) {
551
            if ($debug) {
552
                error_log("exercise_time_control_is_valid is false");
553
            }
554
            $score = 0;
555
            $answer = 0;
556
        }
557
558
        if (empty($question_id) || empty($exe_id) || empty($user_id)) {
559
            return false;
560
        }
561
562
        if (null === $answer) {
563
            $answer = '';
564
        }
565
566
        if (null === $score) {
567
            $score = 0;
568
        }
569
570
        $attempt = [
571
            'user_id' => $user_id,
572
            'question_id' => $question_id,
573
            'answer' => $answer,
574
            'marks' => $score,
575
            'c_id' => $course_id,
576
            'session_id' => $session_id,
577
            'position' => $position,
578
            'tms' => $now,
579
            'filename' => !empty($fileName) ? basename($fileName) : $fileName,
580
            'teacher_comment' => '',
581
            'seconds_spent' => $questionDuration,
582
        ];
583
584
        // Check if attempt exists.
585
        $sql = "SELECT exe_id FROM $TBL_TRACK_ATTEMPT
586
                WHERE
587
                    c_id = $course_id AND
588
                    session_id = $session_id AND
589
                    exe_id = $exe_id AND
590
                    user_id = $user_id AND
591
                    question_id = $question_id AND
592
                    position = $position";
593
        $result = Database::query($sql);
594
        $attemptData = [];
595
        if (Database::num_rows($result)) {
596
            $attemptData = Database::fetch_array($result, 'ASSOC');
597
            if ($updateResults == false) {
598
                //The attempt already exist do not update use  update_event_exercise() instead
599
                return false;
600
            }
601
        } else {
602
            $attempt['exe_id'] = $exe_id;
603
        }
604
605
        if ($debug) {
606
            error_log("updateResults : $updateResults");
607
            error_log('Saving question attempt:');
608
            error_log($sql);
609
        }
610
611
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
612
        $em = Database::getManager();
613
        if (false == $updateResults) {
614
            $attempt_id = Database::insert($TBL_TRACK_ATTEMPT, $attempt);
615
616
            if ($attempt_id) {
617
                $recording = new TrackEAttemptRecording();
618
                $recording
619
                    ->setExeId($attempt_id)
620
                    ->setQuestionId($question_id)
621
                    ->setAnswer($answer)
622
                    ->setMarks($score)
623
                    ->setAuthor('')
624
                    ->setSessionId($session_id)
625
                ;
626
                $em->persist($recording);
627
                $em->flush();
628
            }
629
        } else {
630
            if (api_get_configuration_value('allow_time_per_question')) {
631
                $attempt['seconds_spent'] = $questionDuration + (int) $attemptData['seconds_spent'];
632
            }
633
            Database::update(
634
                $TBL_TRACK_ATTEMPT,
635
                $attempt,
636
                [
637
                    'exe_id = ? AND question_id = ? AND user_id = ? ' => [
638
                        $exe_id,
639
                        $question_id,
640
                        $user_id,
641
                    ],
642
                ]
643
            );
644
645
            if ($recording) {
646
                $attempt_recording = [
647
                    'exe_id' => $exe_id,
648
                    'question_id' => $question_id,
649
                    'answer' => $answer,
650
                    'marks' => $score,
651
                    'insert_date' => $now,
652
                    'author' => '',
653
                    'session_id' => $session_id,
654
                ];
655
656
                Database::update(
657
                    $recording_table,
658
                    $attempt_recording,
659
                    [
660
                        'exe_id = ? AND question_id = ? AND session_id = ? ' => [
661
                            $exe_id,
662
                            $question_id,
663
                            $session_id,
664
                        ],
665
                    ]
666
                );
667
            }
668
            $attempt_id = $exe_id;
669
        }
670
671
        return $attempt_id;
672
    }
673
674
    /**
675
     * Record an hotspot spot for this attempt at answering an hotspot question.
676
     *
677
     * @param int    $exeId
678
     * @param int    $questionId Question ID
679
     * @param int    $answerId   Answer ID
680
     * @param int    $correct
681
     * @param string $coords     Coordinates of this point (e.g. 123;324)
682
     * @param bool   $updateResults
683
     * @param int    $exerciseId
684
     *
685
     * @return bool Result of the insert query
686
     *
687
     * @uses \Course code and user_id from global scope $_cid and $_user
688
     */
689
    public static function saveExerciseAttemptHotspot(
690
        $exeId,
691
        $questionId,
692
        $answerId,
693
        $correct,
694
        $coords,
695
        $updateResults = false,
696
        $exerciseId = 0
697
    ) {
698
        $debug = false;
699
        global $safe_lp_id, $safe_lp_item_id;
700
701
        if (false == $updateResults) {
702
            // Validation in case of fraud with activated control time
703
            if (!ExerciseLib::exercise_time_control_is_valid($exerciseId, $safe_lp_id, $safe_lp_item_id)) {
704
                if ($debug) {
705
                    error_log('Attempt is fraud');
706
                }
707
                $correct = 0;
708
            }
709
        }
710
711
        if (empty($exeId)) {
712
            if ($debug) {
713
                error_log('exe id is empty');
714
            }
715
716
            return false;
717
        }
718
719
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
720
        if ($updateResults) {
721
            if ($debug) {
722
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
723
            }
724
            $params = [
725
                'hotspot_correct' => $correct,
726
                'hotspot_coordinate' => $coords,
727
            ];
728
            Database::update(
729
                $table,
730
                $params,
731
                [
732
                    'hotspot_user_id = ? AND hotspot_exe_id = ? AND hotspot_question_id = ? AND hotspot_answer_id = ? ' => [
733
                        api_get_user_id(),
734
                        $exeId,
735
                        $questionId,
736
                        $answerId,
737
                    ],
738
                ]
739
            );
740
        } else {
741
            if ($debug) {
742
                error_log("Insert hotspot results: exeId: $exeId correct: $correct");
743
            }
744
745
            return Database::insert(
746
                $table,
747
                [
748
                    'hotspot_user_id' => api_get_user_id(),
749
                    'c_id' => api_get_course_int_id(),
750
                    'hotspot_exe_id' => $exeId,
751
                    'hotspot_question_id' => $questionId,
752
                    'hotspot_answer_id' => $answerId,
753
                    'hotspot_correct' => $correct,
754
                    'hotspot_coordinate' => $coords,
755
                ]
756
            );
757
        }
758
    }
759
760
    /**
761
     * Records information for common (or admin) events (in the track_e_default table).
762
     *
763
     * @author Yannick Warnier <[email protected]>
764
     *
765
     * @param string $event_type       Type of event
766
     * @param string $event_value_type Type of value
767
     * @param mixed  $event_value      Value (string, or array in the case of user info)
768
     * @param string $datetime         Datetime (UTC) (defaults to null)
769
     * @param int    $user_id          User ID (defaults to null)
770
     * @param int    $course_id        Course ID (defaults to null)
771
     * @param int    $sessionId        Session ID
772
     *
773
     * @return bool
774
     * @assert ('','','') === false
775
     */
776
    public static function addEvent(
777
        $event_type,
778
        $event_value_type,
779
        $event_value,
780
        $datetime = null,
781
        $user_id = null,
782
        $course_id = null,
783
        $sessionId = 0
784
    ) {
785
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
786
787
        if (empty($event_type)) {
788
            return false;
789
        }
790
        $event_type = Database::escape_string($event_type);
791
        $event_value_type = Database::escape_string($event_value_type);
792
        if (!empty($course_id)) {
793
            $course_id = (int) $course_id;
794
        } else {
795
            $course_id = api_get_course_int_id();
796
        }
797
        if (!empty($sessionId)) {
798
            $sessionId = (int) $sessionId;
799
        } else {
800
            $sessionId = api_get_session_id();
801
        }
802
803
        //Clean the user_info
804
        if ($event_value_type == LOG_USER_OBJECT) {
805
            if (is_array($event_value)) {
806
                unset($event_value['complete_name']);
807
                unset($event_value['complete_name_with_username']);
808
                unset($event_value['firstName']);
809
                unset($event_value['lastName']);
810
                unset($event_value['avatar_small']);
811
                unset($event_value['avatar']);
812
                unset($event_value['mail']);
813
                unset($event_value['password']);
814
                unset($event_value['last_login']);
815
                unset($event_value['picture_uri']);
816
                $event_value = serialize($event_value);
817
            }
818
        }
819
        // If event is an array then the $event_value_type should finish with
820
        // the suffix _array for example LOG_WORK_DATA = work_data_array
821
        if (is_array($event_value)) {
822
            $event_value = serialize($event_value);
823
        }
824
825
        $event_value = Database::escape_string($event_value);
826
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
827
828
        if (!isset($datetime)) {
829
            $datetime = api_get_utc_datetime();
830
        }
831
832
        $datetime = Database::escape_string($datetime);
833
834
        if (!isset($user_id)) {
835
            $user_id = api_get_user_id();
836
        }
837
838
        $params = [
839
            'default_user_id' => $user_id,
840
            'c_id' => $course_id,
841
            'default_date' => $datetime,
842
            'default_event_type' => $event_type,
843
            'default_value_type' => $event_value_type,
844
            'default_value' => $event_value,
845
            'session_id' => $sessionId,
846
        ];
847
        Database::insert($table, $params);
848
849
        return true;
850
    }
851
852
    /**
853
     * 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