Passed
Push — 1.11.x ( 1353b8...defa11 )
by Julito
10:30
created

Event::get_attempt_count()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 22
c 0
b 0
f 0
nc 2
nop 5
dl 0
loc 36
rs 9.568
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 (null === $answer) {
597
            $answer = '';
598
        }
599
600
        if (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 (int) $attempt['count'];
1219
        }
1220
1221
        return 0;
1222
    }
1223
1224
    /**
1225
     * @param int   $user_id
1226
     * @param int   $lp_id
1227
     * @param array $course
1228
     * @param int   $session_id
1229
     * @param bool  $disconnectExerciseResultsFromLp (Replace orig_lp_* variables to null)
1230
     *
1231
     * @return bool
1232
     */
1233
    public static function delete_student_lp_events(
1234
        $user_id,
1235
        $lp_id,
1236
        $course,
1237
        $session_id,
1238
        $disconnectExerciseResultsFromLp = false
1239
    ) {
1240
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
1241
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1242
        $lpInteraction = Database::get_course_table(TABLE_LP_IV_INTERACTION);
1243
        $lpObjective = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
1244
1245
        if (empty($course) || empty($user_id)) {
1246
            return false;
1247
        }
1248
1249
        $course_id = $course['real_id'];
1250
        $user_id = (int) $user_id;
1251
        $lp_id = (int) $lp_id;
1252
        $session_id = (int) $session_id;
1253
1254
        if (empty($course_id)) {
1255
            $course_id = api_get_course_int_id();
1256
        }
1257
1258
        $track_e_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1259
        $track_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1260
        $recording_table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1261
1262
        // Make sure we have the exact lp_view_id
1263
        $sql = "SELECT id FROM $lp_view_table
1264
                WHERE
1265
                    c_id = $course_id AND
1266
                    user_id = $user_id AND
1267
                    lp_id = $lp_id AND
1268
                    session_id = $session_id";
1269
        $result = Database::query($sql);
1270
1271
        if (Database::num_rows($result)) {
1272
            $view = Database::fetch_array($result, 'ASSOC');
1273
            $lp_view_id = $view['id'];
1274
1275
            $sql = "DELETE FROM $lp_item_view_table
1276
                    WHERE c_id = $course_id AND lp_view_id = $lp_view_id";
1277
            Database::query($sql);
1278
1279
            $sql = "DELETE FROM $lpInteraction
1280
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1281
            Database::query($sql);
1282
1283
            $sql = "DELETE FROM $lpObjective
1284
                    WHERE c_id = $course_id AND lp_iv_id = $lp_view_id";
1285
            Database::query($sql);
1286
        }
1287
1288
        if (api_get_configuration_value('lp_minimum_time')) {
1289
            $sql = "DELETE FROM track_e_access_complete
1290
                    WHERE
1291
                        tool = 'learnpath' AND
1292
                        c_id = $course_id AND
1293
                        tool_id = $lp_id AND
1294
                        user_id = $user_id AND
1295
                        session_id = $session_id
1296
                    ";
1297
            Database::query($sql);
1298
        }
1299
1300
        $sql = "SELECT exe_id FROM $track_e_exercises
1301
                WHERE
1302
                    exe_user_id = $user_id AND
1303
                    session_id = $session_id AND
1304
                    c_id = $course_id AND
1305
                    orig_lp_id = $lp_id";
1306
        $result = Database::query($sql);
1307
        $exeList = [];
1308
        while ($row = Database::fetch_array($result, 'ASSOC')) {
1309
            $exeList[] = $row['exe_id'];
1310
        }
1311
1312
        if (!empty($exeList) && count($exeList) > 0) {
1313
            $exeListString = implode(',', $exeList);
1314
            if ($disconnectExerciseResultsFromLp) {
1315
                $sql = "UPDATE $track_e_exercises
1316
                        SET orig_lp_id = null,
1317
                            orig_lp_item_id = null,
1318
                            orig_lp_item_view_id = null
1319
                        WHERE exe_id IN ($exeListString)";
1320
                Database::query($sql);
1321
            } else {
1322
                $sql = "DELETE FROM $track_e_exercises
1323
                        WHERE exe_id IN ($exeListString)";
1324
                Database::query($sql);
1325
1326
                $sql = "DELETE FROM $track_attempts
1327
                        WHERE exe_id IN ($exeListString)";
1328
                Database::query($sql);
1329
1330
                $sql = "DELETE FROM $recording_table
1331
                        WHERE exe_id IN ($exeListString)";
1332
                Database::query($sql);
1333
            }
1334
        }
1335
1336
        $sql = "DELETE FROM $lp_view_table
1337
                WHERE
1338
                    c_id = $course_id AND
1339
                    user_id = $user_id AND
1340
                    lp_id= $lp_id AND
1341
                    session_id = $session_id
1342
            ";
1343
        Database::query($sql);
1344
1345
        self::addEvent(
1346
            LOG_LP_ATTEMPT_DELETE,
1347
            LOG_LP_ID,
1348
            $lp_id,
1349
            null,
1350
            null,
1351
            $course_id,
1352
            $session_id
1353
        );
1354
1355
        return true;
1356
    }
1357
1358
    /**
1359
     * Delete all exercise attempts (included in LP or not).
1360
     *
1361
     * @param int user id
1362
     * @param int exercise id
1363
     * @param int $course_id
1364
     * @param int session id
1365
     */
1366
    public static function delete_all_incomplete_attempts(
1367
        $user_id,
1368
        $exercise_id,
1369
        $course_id,
1370
        $session_id = 0
1371
    ) {
1372
        $user_id = (int) $user_id;
1373
        $exercise_id = (int) $exercise_id;
1374
        $course_id = (int) $course_id;
1375
        $session_id = (int) $session_id;
1376
1377
        if (!empty($user_id) && !empty($exercise_id) && !empty($course_id)) {
1378
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1379
            $sql = "DELETE FROM $table
1380
                    WHERE
1381
                        exe_user_id = $user_id AND
1382
                        exe_exo_id = $exercise_id AND
1383
                        c_id = $course_id AND
1384
                        session_id = $session_id AND
1385
                        status = 'incomplete' ";
1386
            Database::query($sql);
1387
            self::addEvent(
1388
                LOG_EXERCISE_RESULT_DELETE,
1389
                LOG_EXERCISE_AND_USER_ID,
1390
                $exercise_id.'-'.$user_id,
1391
                null,
1392
                null,
1393
                $course_id,
1394
                $session_id
1395
            );
1396
        }
1397
    }
1398
1399
    /**
1400
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1401
     *
1402
     * @param int $exercise_id
1403
     * @param int $courseId
1404
     * @param int $session_id
1405
     *
1406
     * @return array with the results
1407
     */
1408
    public static function get_all_exercise_results(
1409
        $exercise_id,
1410
        $courseId,
1411
        $session_id = 0,
1412
        $load_question_list = true,
1413
        $user_id = null
1414
    ) {
1415
        $TABLETRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1416
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1417
        $courseId = (int) $courseId;
1418
        $exercise_id = (int) $exercise_id;
1419
        $session_id = (int) $session_id;
1420
1421
        $user_condition = null;
1422
        if (!empty($user_id)) {
1423
            $user_id = (int) $user_id;
1424
            $user_condition = "AND exe_user_id = $user_id ";
1425
        }
1426
        $sql = "SELECT * FROM $TABLETRACK_EXERCICES
1427
                WHERE
1428
                    status = ''  AND
1429
                    c_id = $courseId AND
1430
                    exe_exo_id = $exercise_id AND
1431
                    session_id = $session_id  AND
1432
                    orig_lp_id =0 AND
1433
                    orig_lp_item_id = 0
1434
                    $user_condition
1435
                ORDER BY exe_id";
1436
        $res = Database::query($sql);
1437
        $list = [];
1438
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1439
            $list[$row['exe_id']] = $row;
1440
            if ($load_question_list) {
1441
                $sql = "SELECT * FROM $TBL_TRACK_ATTEMPT
1442
                        WHERE exe_id = {$row['exe_id']}";
1443
                $res_question = Database::query($sql);
1444
                while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1445
                    $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1446
                }
1447
            }
1448
        }
1449
1450
        return $list;
1451
    }
1452
1453
    /**
1454
     * Gets all exercise results (NO Exercises in LPs ) from a given exercise id, course, session.
1455
     *
1456
     * @param int  $courseId
1457
     * @param int  $session_id
1458
     * @param bool $get_count
1459
     *
1460
     * @return array with the results
1461
     */
1462
    public static function get_all_exercise_results_by_course(
1463
        $courseId,
1464
        $session_id = 0,
1465
        $get_count = true
1466
    ) {
1467
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1468
        $courseId = (int) $courseId;
1469
        $session_id = (int) $session_id;
1470
1471
        $select = '*';
1472
        if ($get_count) {
1473
            $select = 'count(*) as count';
1474
        }
1475
        $sql = "SELECT $select FROM $table_track_exercises
1476
                WHERE   status = ''  AND
1477
                        c_id = $courseId AND
1478
                        session_id = $session_id  AND
1479
                        orig_lp_id = 0 AND
1480
                        orig_lp_item_id = 0
1481
                ORDER BY exe_id";
1482
        $res = Database::query($sql);
1483
        if ($get_count) {
1484
            $row = Database::fetch_array($res, 'ASSOC');
1485
1486
            return $row['count'];
1487
        } else {
1488
            $list = [];
1489
            while ($row = Database::fetch_array($res, 'ASSOC')) {
1490
                $list[$row['exe_id']] = $row;
1491
            }
1492
1493
            return $list;
1494
        }
1495
    }
1496
1497
    /**
1498
     * Gets all exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1499
     *
1500
     * @param int $user_id
1501
     * @param int $courseId
1502
     * @param int $session_id
1503
     *
1504
     * @return array with the results
1505
     */
1506
    public static function get_all_exercise_results_by_user(
1507
        $user_id,
1508
        $courseId,
1509
        $session_id = 0
1510
    ) {
1511
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1512
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1513
        $courseId = (int) $courseId;
1514
        $session_id = (int) $session_id;
1515
        $user_id = (int) $user_id;
1516
1517
        $sql = "SELECT * FROM $table_track_exercises
1518
                WHERE
1519
                    status = '' AND
1520
                    exe_user_id = $user_id AND
1521
                    c_id = $courseId AND
1522
                    session_id = $session_id AND
1523
                    orig_lp_id = 0 AND
1524
                    orig_lp_item_id = 0
1525
                ORDER by exe_id";
1526
1527
        $res = Database::query($sql);
1528
        $list = [];
1529
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1530
            $list[$row['exe_id']] = $row;
1531
            $sql = "SELECT * FROM $table_track_attempt
1532
                    WHERE exe_id = {$row['exe_id']}";
1533
            $res_question = Database::query($sql);
1534
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1535
                $list[$row['exe_id']]['question_list'][$row_q['question_id']] = $row_q;
1536
            }
1537
        }
1538
1539
        return $list;
1540
    }
1541
1542
    /**
1543
     * Gets exercise results (NO Exercises in LPs) from a given exercise id, course, session.
1544
     *
1545
     * @param int    $exe_id attempt id
1546
     * @param string $status
1547
     *
1548
     * @return array with the results
1549
     */
1550
    public static function get_exercise_results_by_attempt($exe_id, $status = null)
1551
    {
1552
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1553
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1554
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1555
        $exe_id = (int) $exe_id;
1556
1557
        $status = Database::escape_string($status);
1558
1559
        $sql = "SELECT * FROM $table_track_exercises
1560
                WHERE status = '$status' AND exe_id = $exe_id";
1561
1562
        $res = Database::query($sql);
1563
        $list = [];
1564
        if (Database::num_rows($res)) {
1565
            $row = Database::fetch_array($res, 'ASSOC');
1566
1567
            //Checking if this attempt was revised by a teacher
1568
            $sql_revised = "SELECT exe_id FROM $table_track_attempt_recording
1569
                            WHERE author != '' AND exe_id = $exe_id
1570
                            LIMIT 1";
1571
            $res_revised = Database::query($sql_revised);
1572
            $row['attempt_revised'] = 0;
1573
            if (Database::num_rows($res_revised) > 0) {
1574
                $row['attempt_revised'] = 1;
1575
            }
1576
            $list[$exe_id] = $row;
1577
            $sql = "SELECT * FROM $table_track_attempt
1578
                    WHERE exe_id = $exe_id
1579
                    ORDER BY tms ASC";
1580
            $res_question = Database::query($sql);
1581
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1582
                $list[$exe_id]['question_list'][$row_q['question_id']] = $row_q;
1583
            }
1584
        }
1585
1586
        return $list;
1587
    }
1588
1589
    /**
1590
     * Gets exercise results (NO Exercises in LPs) from a given user, exercise id, course, session, lp_id, lp_item_id.
1591
     *
1592
     * @param   int     user id
1593
     * @param   int     exercise id
1594
     * @param   int     course id
1595
     * @param   int     session id
1596
     * @param   int     lp id
1597
     * @param   int     lp item id
1598
     * @param   string order asc or desc
1599
     *
1600
     * @return array with the results
1601
     */
1602
    public static function getExerciseResultsByUser(
1603
        $user_id,
1604
        $exercise_id,
1605
        $courseId,
1606
        $session_id = 0,
1607
        $lp_id = 0,
1608
        $lp_item_id = 0,
1609
        $order = null
1610
    ) {
1611
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1612
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1613
        $table_track_attempt_recording = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1614
        $courseId = (int) $courseId;
1615
        $exercise_id = (int) $exercise_id;
1616
        $session_id = (int) $session_id;
1617
        $user_id = (int) $user_id;
1618
        $lp_id = (int) $lp_id;
1619
        $lp_item_id = (int) $lp_item_id;
1620
1621
        if (!in_array(strtolower($order), ['asc', 'desc'])) {
1622
            $order = 'asc';
1623
        }
1624
1625
        $sql = "SELECT * FROM $table_track_exercises
1626
                WHERE
1627
                    status 			= '' AND
1628
                    exe_user_id 	= $user_id AND
1629
                    c_id 	        = $courseId AND
1630
                    exe_exo_id 		= $exercise_id AND
1631
                    session_id 		= $session_id AND
1632
                    orig_lp_id 		= $lp_id AND
1633
                    orig_lp_item_id = $lp_item_id
1634
                ORDER by exe_id $order ";
1635
1636
        $res = Database::query($sql);
1637
        $list = [];
1638
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1639
            // Checking if this attempt was revised by a teacher
1640
            $exeId = $row['exe_id'];
1641
            $sql = "SELECT exe_id FROM $table_track_attempt_recording
1642
                    WHERE author != '' AND exe_id = $exeId
1643
                    LIMIT 1";
1644
            $res_revised = Database::query($sql);
1645
            $row['attempt_revised'] = 0;
1646
            if (Database::num_rows($res_revised) > 0) {
1647
                $row['attempt_revised'] = 1;
1648
            }
1649
            $row['total_percentage'] = ($row['exe_result'] / $row['exe_weighting']) * 100;
1650
1651
            $list[$row['exe_id']] = $row;
1652
            $sql = "SELECT * FROM $table_track_attempt
1653
                    WHERE exe_id = $exeId";
1654
            $res_question = Database::query($sql);
1655
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1656
                $list[$row['exe_id']]['question_list'][$row_q['question_id']][] = $row_q;
1657
            }
1658
        }
1659
1660
        return $list;
1661
    }
1662
1663
    /**
1664
     * Count exercise attempts (NO Exercises in LPs ) from a given exercise id, course, session.
1665
     *
1666
     * @param int $user_id
1667
     * @param int $exercise_id
1668
     * @param int $courseId
1669
     * @param int $session_id
1670
     *
1671
     * @return array with the results
1672
     */
1673
    public static function count_exercise_attempts_by_user(
1674
        $user_id,
1675
        $exercise_id,
1676
        $courseId,
1677
        $session_id = 0
1678
    ) {
1679
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1680
        $courseId = (int) $courseId;
1681
        $exercise_id = (int) $exercise_id;
1682
        $session_id = (int) $session_id;
1683
        $user_id = (int) $user_id;
1684
1685
        $sql = "SELECT count(*) as count
1686
                FROM $table
1687
                WHERE status = ''  AND
1688
                    exe_user_id = $user_id AND
1689
                    c_id = $courseId AND
1690
                    exe_exo_id = $exercise_id AND
1691
                    session_id = $session_id AND
1692
                    orig_lp_id =0 AND
1693
                    orig_lp_item_id = 0
1694
                ORDER BY exe_id";
1695
        $res = Database::query($sql);
1696
        $result = 0;
1697
        if (Database::num_rows($res) > 0) {
1698
            $row = Database::fetch_array($res, 'ASSOC');
1699
            $result = $row['count'];
1700
        }
1701
1702
        return $result;
1703
    }
1704
1705
    /**
1706
     * Gets all exercise BEST results attempts (NO Exercises in LPs)
1707
     * from a given exercise id, course, session per user.
1708
     *
1709
     * @param int $exercise_id
1710
     * @param int $courseId
1711
     * @param int $session_id
1712
     * @param int $userId
1713
     *
1714
     * @return array with the results
1715
     *
1716
     * @todo rename this function
1717
     */
1718
    public static function get_best_exercise_results_by_user(
1719
        $exercise_id,
1720
        $courseId,
1721
        $session_id = 0,
1722
        $userId = 0
1723
    ) {
1724
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1725
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1726
        $courseId = (int) $courseId;
1727
        $exercise_id = (int) $exercise_id;
1728
        $session_id = (int) $session_id;
1729
1730
        $sql = "SELECT * FROM $table_track_exercises
1731
                WHERE
1732
                    status = '' AND
1733
                    c_id = $courseId AND
1734
                    exe_exo_id = $exercise_id AND
1735
                    session_id = $session_id AND
1736
                    orig_lp_id = 0 AND
1737
                    orig_lp_item_id = 0";
1738
1739
        if (!empty($userId)) {
1740
            $userId = (int) $userId;
1741
            $sql .= " AND exe_user_id = $userId ";
1742
        }
1743
        $sql .= ' ORDER BY exe_id';
1744
1745
        $res = Database::query($sql);
1746
        $list = [];
1747
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1748
            $list[$row['exe_id']] = $row;
1749
            $exeId = $row['exe_id'];
1750
            $sql = "SELECT * FROM $table_track_attempt
1751
                    WHERE exe_id = $exeId";
1752
            $res_question = Database::query($sql);
1753
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1754
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1755
            }
1756
        }
1757
1758
        // Getting the best results of every student
1759
        $best_score_return = [];
1760
        foreach ($list as $student_result) {
1761
            $user_id = $student_result['exe_user_id'];
1762
            $current_best_score[$user_id] = $student_result['exe_result'];
1763
            if (!isset($best_score_return[$user_id]['exe_result'])) {
1764
                $best_score_return[$user_id] = $student_result;
1765
            }
1766
1767
            if ($current_best_score[$user_id] > $best_score_return[$user_id]['exe_result']) {
1768
                $best_score_return[$user_id] = $student_result;
1769
            }
1770
        }
1771
1772
        return $best_score_return;
1773
    }
1774
1775
    /**
1776
     * Get the last best result from all attempts in exercises per user (out of learning paths).
1777
     *
1778
     * @param int  $user_id
1779
     * @param int  $exercise_id
1780
     * @param int  $courseId
1781
     * @param int  $session_id
1782
     * @param bool $skipLpResults
1783
     *
1784
     * @return array
1785
     */
1786
    public static function get_best_attempt_exercise_results_per_user(
1787
        $user_id,
1788
        $exercise_id,
1789
        $courseId,
1790
        $session_id = 0,
1791
        $skipLpResults = true
1792
    ) {
1793
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1794
        $courseId = (int) $courseId;
1795
        $exercise_id = (int) $exercise_id;
1796
        $session_id = (int) $session_id;
1797
        $user_id = (int) $user_id;
1798
1799
        $sql = "SELECT * FROM $table
1800
                WHERE
1801
                    status = ''  AND
1802
                    c_id = $courseId AND
1803
                    exe_exo_id = $exercise_id AND
1804
                    session_id = $session_id  AND
1805
                    exe_user_id = $user_id
1806
                ";
1807
1808
        if ($skipLpResults) {
1809
            $sql .= ' AND
1810
                orig_lp_id = 0 AND
1811
                orig_lp_item_id = 0 ';
1812
        }
1813
1814
        $sql .= ' ORDER BY exe_id ';
1815
1816
        $res = Database::query($sql);
1817
        $list = [];
1818
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1819
            $list[$row['exe_id']] = $row;
1820
        }
1821
        // Getting the best results of every student.
1822
        $best_score_return = [];
1823
        $best_score_return['exe_result'] = 0;
1824
1825
        foreach ($list as $result) {
1826
            $current_best_score = $result;
1827
            if ($current_best_score['exe_result'] > $best_score_return['exe_result']) {
1828
                $best_score_return = $result;
1829
            }
1830
        }
1831
        if (!isset($best_score_return['exe_weighting'])) {
1832
            $best_score_return = [];
1833
        }
1834
1835
        return $best_score_return;
1836
    }
1837
1838
    /**
1839
     * @param int $exercise_id
1840
     * @param int $courseId
1841
     * @param int $session_id
1842
     *
1843
     * @return mixed
1844
     */
1845
    public static function count_exercise_result_not_validated(
1846
        $exercise_id,
1847
        $courseId,
1848
        $session_id = 0
1849
    ) {
1850
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1851
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
1852
        $courseId = (int) $courseId;
1853
        $session_id = (int) $session_id;
1854
        $exercise_id = (int) $exercise_id;
1855
1856
        $sql = "SELECT count(e.exe_id) as count
1857
                FROM $table_track_exercises e
1858
                LEFT JOIN $table_track_attempt a
1859
                ON e.exe_id = a.exe_id
1860
                WHERE
1861
                    exe_exo_id = $exercise_id AND
1862
                    c_id = $courseId AND
1863
                    e.session_id = $session_id  AND
1864
                    orig_lp_id = 0 AND
1865
                    marks IS NULL AND
1866
                    status = '' AND
1867
                    orig_lp_item_id = 0
1868
                ORDER BY e.exe_id";
1869
        $res = Database::query($sql);
1870
        $row = Database::fetch_array($res, 'ASSOC');
1871
1872
        return $row['count'];
1873
    }
1874
1875
    /**
1876
     * Gets all exercise events from a Learning Path within a Course    nd Session.
1877
     *
1878
     * @param int $exercise_id
1879
     * @param int $courseId
1880
     * @param int $session_id
1881
     *
1882
     * @return array
1883
     */
1884
    public static function get_all_exercise_event_from_lp(
1885
        $exercise_id,
1886
        $courseId,
1887
        $session_id = 0
1888
    ) {
1889
        $table_track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
1890
        $table_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1891
        $courseId = (int) $courseId;
1892
        $exercise_id = (int) $exercise_id;
1893
        $session_id = (int) $session_id;
1894
1895
        $sql = "SELECT * FROM $table_track_exercises
1896
                WHERE
1897
                    status = '' AND
1898
                    c_id = $courseId AND
1899
                    exe_exo_id = $exercise_id AND
1900
                    session_id = $session_id AND
1901
                    orig_lp_id !=0 AND
1902
                    orig_lp_item_id != 0";
1903
1904
        $res = Database::query($sql);
1905
        $list = [];
1906
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1907
            $exeId = $row['exe_id'];
1908
            $list[$exeId] = $row;
1909
            $sql = "SELECT * FROM $table_track_attempt
1910
                    WHERE exe_id = $exeId";
1911
            $res_question = Database::query($sql);
1912
            while ($row_q = Database::fetch_array($res_question, 'ASSOC')) {
1913
                $list[$exeId]['question_list'][$row_q['question_id']] = $row_q;
1914
            }
1915
        }
1916
1917
        return $list;
1918
    }
1919
1920
    /**
1921
     * Get a list of all the exercises in a given learning path.
1922
     *
1923
     * @param int $lp_id
1924
     * @param int $course_id This parameter is probably deprecated as lp_id now is a global iid
1925
     *
1926
     * @return array
1927
     */
1928
    public static function get_all_exercises_from_lp($lp_id, $course_id)
1929
    {
1930
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
1931
        $course_id = (int) $course_id;
1932
        $lp_id = (int) $lp_id;
1933
        $sql = "SELECT * FROM $lp_item_table
1934
                WHERE
1935
                    c_id = $course_id AND
1936
                    lp_id = $lp_id AND
1937
                    item_type = 'quiz'
1938
                ORDER BY parent_item_id, display_order";
1939
        $res = Database::query($sql);
1940
1941
        $list = [];
1942
        while ($row = Database::fetch_array($res, 'ASSOC')) {
1943
            $list[] = $row;
1944
        }
1945
1946
        return $list;
1947
    }
1948
1949
    /**
1950
     * This function gets the comments of an exercise.
1951
     *
1952
     * @param int $exe_id
1953
     * @param int $question_id
1954
     *
1955
     * @return string the comment
1956
     */
1957
    public static function get_comments($exe_id, $question_id)
1958
    {
1959
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1960
        $exe_id = (int) $exe_id;
1961
        $question_id = (int) $question_id;
1962
        $sql = "SELECT teacher_comment
1963
                FROM $table
1964
                WHERE
1965
                    exe_id = $exe_id AND
1966
                    question_id = $question_id
1967
                ORDER by question_id";
1968
        $sqlres = Database::query($sql);
1969
        $comm = strval(Database::result($sqlres, 0, 'teacher_comment'));
1970
        $comm = trim($comm);
1971
1972
        return $comm;
1973
    }
1974
1975
    /**
1976
     * Get all the track_e_attempt records for a given
1977
     * track_e_exercises.exe_id (pk).
1978
     *
1979
     * @param int $exeId The exe_id from an exercise attempt record
1980
     *
1981
     * @return array The complete records from track_e_attempt that match the given exe_id
1982
     */
1983
    public static function getAllExerciseEventByExeId($exeId)
1984
    {
1985
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
1986
        $exeId = (int) $exeId;
1987
1988
        $sql = "SELECT * FROM $table
1989
                WHERE exe_id = $exeId
1990
                ORDER BY position";
1991
        $res_question = Database::query($sql);
1992
        $list = [];
1993
        if (Database::num_rows($res_question)) {
1994
            while ($row = Database::fetch_array($res_question, 'ASSOC')) {
1995
                $list[$row['question_id']][] = $row;
1996
            }
1997
        }
1998
1999
        return $list;
2000
    }
2001
2002
    public static function getQuestionAttemptByExeIdAndQuestion($exeId, $questionId)
2003
    {
2004
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2005
        $exeId = (int) $exeId;
2006
        $questionId = (int) $questionId;
2007
2008
        $sql = "SELECT * FROM $table
2009
                WHERE
2010
                    exe_id = $exeId AND
2011
                    question_id = $questionId
2012
                ORDER BY position";
2013
        $result = Database::query($sql);
2014
        $attempt = [];
2015
        if (Database::num_rows($result)) {
2016
            $attempt = Database::fetch_array($result, 'ASSOC');
2017
        }
2018
2019
        return $attempt;
2020
    }
2021
2022
    /**
2023
     * Delete one record from the track_e_attempt table (recorded quiz answer)
2024
     * and register the deletion event (LOG_QUESTION_RESULT_DELETE) in
2025
     * track_e_default.
2026
     *
2027
     * @param int $exeId       The track_e_exercises.exe_id (primary key)
2028
     * @param int $user_id     The user who answered (already contained in exe_id)
2029
     * @param int $courseId    The course in which it happened (already contained in exe_id)
2030
     * @param int $session_id  The session in which it happened (already contained in exe_id)
2031
     * @param int $question_id The c_quiz_question.iid
2032
     */
2033
    public static function delete_attempt(
2034
        $exeId,
2035
        $user_id,
2036
        $courseId,
2037
        $session_id,
2038
        $question_id
2039
    ) {
2040
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2041
2042
        $exeId = (int) $exeId;
2043
        $user_id = (int) $user_id;
2044
        $courseId = (int) $courseId;
2045
        $session_id = (int) $session_id;
2046
        $question_id = (int) $question_id;
2047
2048
        $sql = "DELETE FROM $table
2049
                WHERE
2050
                    exe_id = $exeId AND
2051
                    user_id = $user_id AND
2052
                    c_id = $courseId AND
2053
                    session_id = $session_id AND
2054
                    question_id = $question_id ";
2055
        Database::query($sql);
2056
2057
        self::addEvent(
2058
            LOG_QUESTION_RESULT_DELETE,
2059
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2060
            $exeId.'-'.$question_id,
2061
            null,
2062
            null,
2063
            $courseId,
2064
            $session_id
2065
        );
2066
    }
2067
2068
    /**
2069
     * Delete one record from the track_e_hotspot table based on a given
2070
     * track_e_exercises.exe_id.
2071
     *
2072
     * @param $exeId
2073
     * @param $user_id
2074
     * @param int $courseId
2075
     * @param $question_id
2076
     * @param int $sessionId
2077
     */
2078
    public static function delete_attempt_hotspot(
2079
        $exeId,
2080
        $user_id,
2081
        $courseId,
2082
        $question_id,
2083
        $sessionId = null
2084
    ) {
2085
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_HOTSPOT);
2086
2087
        $exeId = (int) $exeId;
2088
        $user_id = (int) $user_id;
2089
        $courseId = (int) $courseId;
2090
        $question_id = (int) $question_id;
2091
        if (!isset($sessionId)) {
2092
            $sessionId = api_get_session_id();
2093
        }
2094
2095
        $sql = "DELETE FROM $table
2096
                WHERE
2097
                    hotspot_exe_id = $exeId AND
2098
                    hotspot_user_id = $user_id AND
2099
                    c_id = $courseId AND
2100
                    hotspot_question_id = $question_id ";
2101
        Database::query($sql);
2102
        self::addEvent(
2103
            LOG_QUESTION_RESULT_DELETE,
2104
            LOG_EXERCISE_ATTEMPT_QUESTION_ID,
2105
            $exeId.'-'.$question_id,
2106
            null,
2107
            null,
2108
            $courseId,
2109
            $sessionId
2110
        );
2111
    }
2112
2113
    /**
2114
     * Registers in track_e_course_access when user logs in for the first time to a course.
2115
     *
2116
     * @param int $courseId  ID of the course
2117
     * @param int $user_id   ID of the user
2118
     * @param int $sessionId ID of the session (if any)
2119
     *
2120
     * @return bool
2121
     */
2122
    public static function eventCourseLogin($courseId, $user_id, $sessionId)
2123
    {
2124
        if (Session::read('login_as')) {
2125
            return false;
2126
        }
2127
2128
        $sessionId = (int) $sessionId;
2129
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2130
            return false;
2131
        }
2132
2133
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2134
        $loginDate = $logoutDate = api_get_utc_datetime();
2135
2136
        // $counter represents the number of time this record has been refreshed
2137
        $counter = 1;
2138
        $courseId = (int) $courseId;
2139
        $user_id = (int) $user_id;
2140
        $ip = Database::escape_string(api_get_real_ip());
2141
2142
        $sql = "INSERT INTO $table(c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2143
                VALUES($courseId, '$ip', $user_id, '$loginDate', '$logoutDate', $counter, $sessionId)";
2144
        $courseAccessId = Database::query($sql);
2145
2146
        if ($courseAccessId) {
2147
            // Course catalog stats modifications see #4191
2148
            CourseManager::update_course_ranking(
2149
                null,
2150
                null,
2151
                null,
2152
                null,
2153
                true,
2154
                false
2155
            );
2156
2157
            return true;
2158
        }
2159
    }
2160
2161
    /**
2162
     * Updates the user - course - session every X minutes
2163
     * In order to avoid.
2164
     *
2165
     * @param int $courseId
2166
     * @param int $userId
2167
     * @param int $sessionId
2168
     * @param int $minutes
2169
     *
2170
     * @return bool
2171
     */
2172
    public static function eventCourseLoginUpdate(
2173
        $courseId,
2174
        $userId,
2175
        $sessionId,
2176
        $minutes = 5
2177
    ) {
2178
        if (Session::read('login_as')) {
2179
            return false;
2180
        }
2181
2182
        if (empty($courseId) || empty($userId)) {
2183
            return false;
2184
        }
2185
2186
        $sessionId = (int) $sessionId;
2187
2188
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2189
            return false;
2190
        }
2191
2192
        $courseId = (int) $courseId;
2193
        $userId = (int) $userId;
2194
2195
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2196
        $sql = "SELECT course_access_id, logout_course_date
2197
                FROM $table
2198
                WHERE
2199
                    c_id = $courseId AND
2200
                    session_id = $sessionId AND
2201
                    user_id = $userId
2202
                ORDER BY login_course_date DESC
2203
                LIMIT 1";
2204
2205
        $result = Database::query($sql);
2206
2207
        // Save every 5 minutes by default
2208
        $seconds = $minutes * 60;
2209
        $maxSeconds = 3600; // Only update if max diff is one hour
2210
        if (Database::num_rows($result)) {
2211
            $row = Database::fetch_array($result);
2212
            $id = $row['course_access_id'];
2213
            $logout = $row['logout_course_date'];
2214
            $now = time();
2215
            $logout = api_strtotime($logout, 'UTC');
2216
            if ($now - $logout > $seconds &&
2217
                $now - $logout < $maxSeconds
2218
            ) {
2219
                $now = api_get_utc_datetime();
2220
                $sql = "UPDATE $table SET
2221
                            logout_course_date = '$now',
2222
                            counter = counter + 1
2223
                        WHERE course_access_id = $id";
2224
                Database::query($sql);
2225
            }
2226
2227
            return true;
2228
        }
2229
2230
        return false;
2231
    }
2232
2233
    /**
2234
     * Register the logout of the course (usually when logging out of the platform)
2235
     * from the track_e_course_access table.
2236
     *
2237
     * @param array $logoutInfo Information stored by local.inc.php
2238
     *                          before new context ['uid'=> x, 'cid'=>y, 'sid'=>z]
2239
     *
2240
     * @return bool
2241
     */
2242
    public static function courseLogout($logoutInfo)
2243
    {
2244
        if (Session::read('login_as')) {
2245
            return false;
2246
        }
2247
2248
        if (empty($logoutInfo['uid']) || empty($logoutInfo['cid'])) {
2249
            return false;
2250
        }
2251
2252
        $sessionLifetime = api_get_configuration_value('session_lifetime');
2253
        /*
2254
         * When $_configuration['session_lifetime'] is larger than ~100 hours
2255
         * (in order to let users take exercises with no problems)
2256
         * the function Tracking::get_time_spent_on_the_course() returns larger values (200h) due the condition:
2257
         * login_course_date > now() - INTERVAL $session_lifetime SECOND
2258
         */
2259
        if (empty($sessionLifetime) || $sessionLifetime > 86400) {
2260
            $sessionLifetime = 3600; // 1 hour
2261
        }
2262
        if (!empty($logoutInfo) && !empty($logoutInfo['cid'])) {
2263
            $sessionId = 0;
2264
            if (!empty($logoutInfo['sid'])) {
2265
                $sessionId = (int) $logoutInfo['sid'];
2266
            }
2267
2268
            if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2269
                return false;
2270
            }
2271
2272
            $tableCourseAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2273
            $userId = (int) $logoutInfo['uid'];
2274
            $courseId = (int) $logoutInfo['cid'];
2275
2276
            $currentDate = api_get_utc_datetime();
2277
            // UTC time
2278
            $diff = time() - $sessionLifetime;
2279
            $time = api_get_utc_datetime($diff);
2280
            $sql = "SELECT course_access_id, logout_course_date
2281
                    FROM $tableCourseAccess
2282
                    WHERE
2283
                        user_id = $userId AND
2284
                        c_id = $courseId  AND
2285
                        session_id = $sessionId AND
2286
                        login_course_date > '$time'
2287
                    ORDER BY login_course_date DESC
2288
                    LIMIT 1";
2289
            $result = Database::query($sql);
2290
            $insert = false;
2291
            if (Database::num_rows($result) > 0) {
2292
                $row = Database::fetch_array($result, 'ASSOC');
2293
                $courseAccessId = $row['course_access_id'];
2294
                $sql = "UPDATE $tableCourseAccess SET
2295
                                logout_course_date = '$currentDate',
2296
                                counter = counter + 1
2297
                            WHERE course_access_id = $courseAccessId";
2298
                Database::query($sql);
2299
            } else {
2300
                $insert = true;
2301
            }
2302
2303
            if ($insert) {
2304
                $ip = Database::escape_string(api_get_real_ip());
2305
                $sql = "INSERT INTO $tableCourseAccess (c_id, user_ip, user_id, login_course_date, logout_course_date, counter, session_id)
2306
                        VALUES ($courseId, '$ip', $userId, '$currentDate', '$currentDate', 1, $sessionId)";
2307
                Database::query($sql);
2308
            }
2309
2310
            return true;
2311
        }
2312
    }
2313
2314
    /**
2315
     * Register a "fake" time spent on the platform, for example to match the
2316
     * estimated time he took to author an assignment/work, see configuration
2317
     * setting considered_working_time.
2318
     * This assumes there is already some connection of the student to the
2319
     * course, otherwise he wouldn't be able to upload an assignment.
2320
     * This works by creating a new record, copy of the current one, then
2321
     * updating the current one to be just the considered_working_time and
2322
     * end at the same second as the user connected to the course.
2323
     *
2324
     * @param int    $courseId    The course in which to add the time
2325
     * @param int    $userId      The user for whom to add the time
2326
     * @param int    $sessionId   The session in which to add the time (if any)
2327
     * @param string $virtualTime The amount of time to be added,
2328
     *                            in a hh:mm:ss format. If int, we consider it is expressed in hours.
2329
     * @param int    $workId      Student publication id result
2330
     *
2331
     * @return true on successful insertion, false otherwise
2332
     */
2333
    public static function eventAddVirtualCourseTime(
2334
        $courseId,
2335
        $userId,
2336
        $sessionId,
2337
        $virtualTime,
2338
        $workId
2339
    ) {
2340
        if (empty($virtualTime)) {
2341
            return false;
2342
        }
2343
2344
        $courseId = (int) $courseId;
2345
        $userId = (int) $userId;
2346
        $sessionId = (int) $sessionId;
2347
2348
        $logoutDate = api_get_utc_datetime();
2349
        $loginDate = ChamiloApi::addOrSubTimeToDateTime(
2350
            $virtualTime,
2351
            $logoutDate,
2352
            false
2353
        );
2354
2355
        $ip = api_get_real_ip();
2356
        $params = [
2357
            'login_course_date' => $loginDate,
2358
            'logout_course_date' => $logoutDate,
2359
            'session_id' => $sessionId,
2360
            'user_id' => $userId,
2361
            'counter' => 0,
2362
            'c_id' => $courseId,
2363
            'user_ip' => $ip,
2364
        ];
2365
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2366
        Database::insert($courseTrackingTable, $params);
2367
2368
        // Time should also be added to the track_e_login table so as to
2369
        // affect total time on the platform
2370
        $params = [
2371
            'login_user_id' => $userId,
2372
            'login_date' => $loginDate,
2373
            'user_ip' => $ip,
2374
            'logout_date' => $logoutDate,
2375
        ];
2376
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2377
        Database::insert($platformTrackingTable, $params);
2378
2379
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2380
            $workId = (int) $workId;
2381
            $uniqueId = time();
2382
            $logInfo = [
2383
                'c_id' => $courseId,
2384
                'session_id' => $sessionId,
2385
                'tool' => TOOL_STUDENTPUBLICATION,
2386
                'date_reg' => $loginDate,
2387
                'action' => 'add_work_start_'.$workId,
2388
                'action_details' => $virtualTime,
2389
                'user_id' => $userId,
2390
                'current_id' => $uniqueId,
2391
            ];
2392
            self::registerLog($logInfo);
2393
2394
            $logInfo = [
2395
                'c_id' => $courseId,
2396
                'session_id' => $sessionId,
2397
                'tool' => TOOL_STUDENTPUBLICATION,
2398
                'date_reg' => $logoutDate,
2399
                'action' => 'add_work_end_'.$workId,
2400
                'action_details' => $virtualTime,
2401
                'user_id' => $userId,
2402
                'current_id' => $uniqueId,
2403
            ];
2404
            self::registerLog($logInfo);
2405
        }
2406
2407
        return true;
2408
    }
2409
2410
    /**
2411
     * Removes a "fake" time spent on the platform, for example to match the
2412
     * estimated time he took to author an assignment/work, see configuration
2413
     * setting considered_working_time.
2414
     * This method should be called when something that generated a fake
2415
     * time record is removed. Given the database link is weak (no real
2416
     * relationship kept between the deleted item and this record), this
2417
     * method just looks for the latest record that has the same time as the
2418
     * item's fake time, is in the past and in this course+session. If such a
2419
     * record cannot be found, it doesn't do anything.
2420
     * The IP address is not considered a useful filter here.
2421
     *
2422
     * @param int    $courseId    The course in which to add the time
2423
     * @param int    $userId      The user for whom to add the time
2424
     * @param int    $sessionId   The session in which to add the time (if any)
2425
     * @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.
2426
     *
2427
     * @return true on successful removal, false otherwise
2428
     */
2429
    public static function eventRemoveVirtualCourseTime(
2430
        $courseId,
2431
        $userId,
2432
        $sessionId = 0,
2433
        $virtualTime,
2434
        $workId
2435
    ) {
2436
        if (empty($virtualTime)) {
2437
            return false;
2438
        }
2439
2440
        $originalVirtualTime = $virtualTime;
2441
2442
        $courseTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2443
        $platformTrackingTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
2444
        $courseId = (int) $courseId;
2445
        $userId = (int) $userId;
2446
        $sessionId = (int) $sessionId;
2447
2448
        // Change $virtualTime format from hh:mm:ss to hhmmss which is the
2449
        // format returned by SQL for a subtraction of two datetime values
2450
        // @todo make sure this is portable between DBMSes
2451
        if (preg_match('/:/', $virtualTime)) {
2452
            list($h, $m, $s) = preg_split('/:/', $virtualTime);
2453
            $virtualTime = $h * 3600 + $m * 60 + $s;
2454
        } else {
2455
            $virtualTime *= 3600;
2456
        }
2457
2458
        // Get the current latest course connection register. We need that
2459
        // record to re-use the data and create a new record.
2460
        $sql = "SELECT course_access_id
2461
                FROM $courseTrackingTable
2462
                WHERE
2463
                    user_id = $userId AND
2464
                    c_id = $courseId  AND
2465
                    session_id  = $sessionId AND
2466
                    counter = 0 AND
2467
                    (UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) = '$virtualTime'
2468
                ORDER BY login_course_date DESC LIMIT 0,1";
2469
        $result = Database::query($sql);
2470
2471
        // Ignore if we didn't find any course connection record in the last
2472
        // hour. In this case it wouldn't be right to add a "fake" time record.
2473
        if (Database::num_rows($result) > 0) {
2474
            // Found the latest connection
2475
            $row = Database::fetch_row($result);
2476
            $courseAccessId = $row[0];
2477
            $sql = "DELETE FROM $courseTrackingTable
2478
                    WHERE course_access_id = $courseAccessId";
2479
            Database::query($sql);
2480
        }
2481
        $sql = "SELECT login_id
2482
                FROM $platformTrackingTable
2483
                WHERE
2484
                    login_user_id = $userId AND
2485
                    (UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date)) = '$virtualTime'
2486
                ORDER BY login_date DESC LIMIT 0,1";
2487
        $result = Database::query($sql);
2488
        if (Database::num_rows($result) > 0) {
2489
            // Found the latest connection
2490
            $row = Database::fetch_row($result);
2491
            $loginAccessId = $row[0];
2492
            $sql = "DELETE FROM $platformTrackingTable
2493
                    WHERE login_id = $loginAccessId";
2494
            Database::query($sql);
2495
        }
2496
2497
        if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2498
            $workId = (int) $workId;
2499
            $sql = "SELECT id FROM track_e_access_complete
2500
                    WHERE
2501
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2502
                        c_id = $courseId AND
2503
                        session_id = $sessionId AND
2504
                        user_id = $userId AND
2505
                        action_details = '$originalVirtualTime' AND
2506
                        action = 'add_work_start_$workId' ";
2507
            $result = Database::query($sql);
2508
            $result = Database::fetch_array($result);
2509
            if ($result) {
2510
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2511
                Database::query($sql);
2512
            }
2513
2514
            $sql = "SELECT id FROM track_e_access_complete
2515
                    WHERE
2516
                        tool = '".TOOL_STUDENTPUBLICATION."' AND
2517
                        c_id = $courseId AND
2518
                        session_id = $sessionId AND
2519
                        user_id = $userId AND
2520
                        action_details = '$originalVirtualTime' AND
2521
                        action = 'add_work_end_$workId' ";
2522
            $result = Database::query($sql);
2523
            $result = Database::fetch_array($result);
2524
            if ($result) {
2525
                $sql = 'DELETE FROM track_e_access_complete WHERE id = '.$result['id'];
2526
                Database::query($sql);
2527
            }
2528
        }
2529
2530
        return false;
2531
    }
2532
2533
    /**
2534
     * For the sake of cohesion, this function is a switch.
2535
     * It's called by EventsDispatcher and fires the right function
2536
     * with the right require_once.
2537
     *
2538
     * @deprecated
2539
     *
2540
     * @param string $event_name
2541
     * @param array  $params
2542
     */
2543
    public static function event_send_mail($event_name, $params)
2544
    {
2545
        EventsMail::send_mail($event_name, $params);
2546
    }
2547
2548
    /**
2549
     * Filter EventEmailTemplate Filters see the main/inc/conf/events.conf.dist.php.
2550
     */
2551
2552
    /**
2553
     * Basic template event message filter (to be used by other filters as default).
2554
     *
2555
     * @deprecated
2556
     *
2557
     * @param array $values (passing by reference)     *
2558
     *
2559
     * @return bool True if everything is OK, false otherwise
2560
     */
2561
    public function event_send_mail_filter_func(&$values)
2562
    {
2563
        return true;
2564
    }
2565
2566
    /**
2567
     * user_registration - send_mail filter.
2568
     *
2569
     * @deprecated
2570
     *
2571
     * @param array $values (passing by reference)
2572
     *
2573
     * @return bool True if everything is OK, false otherwise
2574
     */
2575
    public function user_registration_event_send_mail_filter_func(&$values)
2576
    {
2577
        $res = self::event_send_mail_filter_func($values);
2578
        // proper logic for this filter
2579
        return $res;
2580
    }
2581
2582
    /**
2583
     * portal_homepage_edited - send_mail filter.
2584
     *
2585
     * @deprecated
2586
     *
2587
     * @param array $values (passing by reference)
2588
     *
2589
     * @return bool True if everything is OK, false otherwise
2590
     */
2591
    public function portal_homepage_edited_event_send_mail_filter_func(&$values)
2592
    {
2593
        $res = self::event_send_mail_filter_func($values);
2594
        // proper logic for this filter
2595
        return $res;
2596
    }
2597
2598
    /**
2599
     * Register the logout of the course (usually when logging out of the platform)
2600
     * from the track_e_access_complete table.
2601
     *
2602
     * @param array $logInfo Information stored by local.inc.php
2603
     *
2604
     * @return bool
2605
     */
2606
    public static function registerLog($logInfo)
2607
    {
2608
        $sessionId = api_get_session_id();
2609
        $courseId = api_get_course_int_id();
2610
2611
        if (isset($logInfo['c_id']) && !empty($logInfo['c_id'])) {
2612
            $courseId = $logInfo['c_id'];
2613
        }
2614
2615
        if (isset($logInfo['session_id']) && !empty($logInfo['session_id'])) {
2616
            $sessionId = $logInfo['session_id'];
2617
        }
2618
2619
        if (!Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2620
            return false;
2621
        }
2622
2623
        if (self::isSessionLogNeedToBeSave($sessionId) === false) {
2624
            return false;
2625
        }
2626
2627
        $loginAs = (int) Session::read('login_as') === true;
2628
2629
        $logInfo['user_id'] = isset($logInfo['user_id']) ? $logInfo['user_id'] : api_get_user_id();
2630
        $logInfo['date_reg'] = isset($logInfo['date_reg']) ? $logInfo['date_reg'] : api_get_utc_datetime();
2631
        $logInfo['tool'] = !empty($logInfo['tool']) ? $logInfo['tool'] : '';
2632
        $logInfo['tool_id'] = !empty($logInfo['tool_id']) ? (int) $logInfo['tool_id'] : 0;
2633
        $logInfo['tool_id_detail'] = !empty($logInfo['tool_id_detail']) ? (int) $logInfo['tool_id_detail'] : 0;
2634
        $logInfo['action'] = !empty($logInfo['action']) ? $logInfo['action'] : '';
2635
        $logInfo['action_details'] = !empty($logInfo['action_details']) ? $logInfo['action_details'] : '';
2636
        $logInfo['ip_user'] = api_get_real_ip();
2637
        $logInfo['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
2638
        $logInfo['session_id'] = $sessionId;
2639
        $logInfo['c_id'] = $courseId;
2640
        $logInfo['ch_sid'] = session_id();
2641
        $logInfo['login_as'] = $loginAs;
2642
        $logInfo['info'] = !empty($logInfo['info']) ? $logInfo['info'] : '';
2643
        $logInfo['url'] = $_SERVER['REQUEST_URI'];
2644
        $logInfo['current_id'] = isset($logInfo['current_id']) ? $logInfo['current_id'] : Session::read('last_id', 0);
2645
2646
        $id = Database::insert('track_e_access_complete', $logInfo);
2647
        if ($id && empty($logInfo['current_id'])) {
2648
            Session::write('last_id', $id);
2649
        }
2650
2651
        return true;
2652
    }
2653
2654
    public static function getAttemptQuestionDuration($exeId, $questionId)
2655
    {
2656
        // Check current attempt.
2657
        $questionAttempt = self::getQuestionAttemptByExeIdAndQuestion($exeId, $questionId);
2658
        $alreadySpent = 0;
2659
        if (!empty($questionAttempt) && $questionAttempt['seconds_spent']) {
2660
            $alreadySpent = $questionAttempt['seconds_spent'];
2661
        }
2662
        $now = time();
2663
        $questionStart = Session::read('question_start', []);
2664
        if (!empty($questionStart) &&
2665
            isset($questionStart[$questionId]) && !empty($questionStart[$questionId])
2666
        ) {
2667
            $time = $questionStart[$questionId];
2668
        } else {
2669
            $diff = 0;
2670
            if (!empty($alreadySpent)) {
2671
                $diff = $alreadySpent;
2672
            }
2673
            $time = $questionStart[$questionId] = $now - $diff;
2674
            Session::write('question_start', $questionStart);
2675
        }
2676
2677
        return $now - $time;
2678
    }
2679
}
2680