Passed
Push — 1.11.x ( ca559e...8dfc93 )
by Julito
13:14
created

Event::delete_all_incomplete_attempts()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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