Passed
Push — 1.11.x ( 87bdef...017ad9 )
by Julito
11:41
created

Event::getQuestionAttemptByExeIdAndQuestion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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