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

Event::updateEventExercise()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 73
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
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