Tracking::get_avg_student_exercise_score()   F
last analyzed

Complexity

Conditions 22
Paths 7705

Size

Total Lines 135
Code Lines 81

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 81
c 0
b 0
f 0
nc 7705
nop 6
dl 0
loc 135
rs 0

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Entity\Course;
6
use Chamilo\CoreBundle\Entity\Session as SessionEntity;
7
use Chamilo\CoreBundle\Entity\TrackEAttemptQualify;
8
use Chamilo\CoreBundle\Entity\TrackEDownloads;
9
use Chamilo\CoreBundle\Entity\User;
10
use Chamilo\CoreBundle\Enums\ActionIcon;
11
use Chamilo\CoreBundle\Enums\StateIcon;
12
use Chamilo\CoreBundle\Framework\Container;
13
use Chamilo\CourseBundle\Entity\CLp;
14
use Chamilo\CourseBundle\Entity\CQuiz;
15
use Chamilo\CourseBundle\Entity\CStudentPublication;
16
use ChamiloSession as Session;
17
use CpChart\Cache as pCache;
18
use CpChart\Data as pData;
19
use CpChart\Image as pImage;
20
use ExtraField as ExtraFieldModel;
21
22
/**
23
 *  Class Tracking.
24
 *
25
 *  @author  Julio Montoya <[email protected]>
26
 */
27
class Tracking
28
{
29
    /**
30
     * Get group reporting.
31
     *
32
     * @param int    $course_id
33
     * @param int    $sessionId
34
     * @param int    $group_id
35
     * @param string $type
36
     * @param int    $start
37
     * @param int    $limit
38
     * @param int    $sidx
39
     * @param string $sord
40
     * @param array  $where_condition
41
     *
42
     * @return array|null
43
     */
44
    public static function get_group_reporting(
45
        $courseId,
46
        $sessionId = 0,
47
        $group_id = 0,
48
        $type = 'all',
49
        $start = 0,
50
        $limit = 1000,
51
        $sidx = 1,
52
        $sord = 'desc',
53
        $where_condition = []
54
    ) {
55
        $courseId = (int) $courseId;
56
        $sessionId = (int) $sessionId;
57
58
        if (empty($courseId)) {
59
            return null;
60
        }
61
        $course = api_get_course_entity($courseId);
62
63
        $session = api_get_session_entity($sessionId);
64
        if ('count' === $type) {
65
            return GroupManager::get_group_list(null, $course, null, $sessionId, true);
66
        }
67
68
        $groupList = GroupManager::get_group_list(null, $course, null, $sessionId, false, null, true);
69
        $parsedResult = [];
70
        if (!empty($groupList)) {
71
            foreach ($groupList as $group) {
72
                $users = GroupManager::get_users($group->getIid(), true, null, null, false, $courseId);
73
                $time = 0;
74
                $avg_student_score = 0;
75
                $avg_student_progress = 0;
76
                $work = 0;
77
                $messages = 0;
78
                foreach ($users as $user_data) {
79
                    $user = api_get_user_entity($user_data['user_id']);
80
                    $time += self::get_time_spent_on_the_course(
81
                        $user_data['user_id'],
82
                        $courseId,
83
                        $sessionId
84
                    );
85
                    $average = self::get_avg_student_score(
86
                        $user_data['user_id'],
87
                        $course,
88
                        [],
89
                        $session
90
                    );
91
                    if (is_numeric($average)) {
92
                        $avg_student_score += $average;
93
                    }
94
                    $avg_student_progress += self::get_avg_student_progress(
95
                        $user_data['user_id'],
96
                        $course,
97
                        [],
98
                        $session
99
                    );
100
                    $work += Container::getStudentPublicationRepository()->countUserPublications(
101
                        $user,
102
                        $course,
103
                        $session
104
                    );
105
                    $messages += Container::getForumPostRepository()->countUserForumPosts($user, $course, $session);
106
                }
107
108
                $countUsers = count($users);
109
                $averageProgress = empty($countUsers) ? 0 : round($avg_student_progress / $countUsers, 2);
110
                $averageScore = empty($countUsers) ? 0 : round($avg_student_score / $countUsers, 2);
111
112
                $groupItem = [
113
                    'id' => $group->getIid(),
114
                    'title' => $group->getTitle(),
115
                    'time' => api_time_to_hms($time),
116
                    'progress' => $averageProgress,
117
                    'score' => $averageScore,
118
                    'works' => $work,
119
                    'messages' => $messages,
120
                ];
121
                $parsedResult[] = $groupItem;
122
            }
123
        }
124
125
        return $parsedResult;
126
    }
127
128
    /**
129
     * @param int    $session_id
130
     * @param string $origin
131
     * @param bool   $export_csv
132
     * @param int    $lp_id
133
     * @param int    $lp_item_id
134
     * @param int    $extendId
135
     * @param int    $extendAttemptId
136
     * @param string $extendedAttempt
137
     * @param string $extendedAll
138
     * @param string $type            classic or simple
139
     * @param bool   $allowExtend     Optional. Allow or not extend te results
140
     *
141
     * @return string
142
     */
143
    public static function getLpStats(
144
        int $user_id,
145
        Course $course,
146
        ?SessionEntity $session,
147
        $origin,
148
        $export_csv,
149
        $lp_id,
150
        $lp_item_id = null,
151
        $extendId = null,
152
        $extendAttemptId = null,
153
        $extendedAttempt = null,
154
        $extendedAll = null,
155
        $type = 'classic',
156
        $allowExtend = true
157
    ) {
158
        if (empty($lp_id)) {
159
            return '';
160
        }
161
162
        $hideTime = ('true' === api_get_setting('lp.hide_lp_time'));
163
        $lp_id = (int) $lp_id;
164
165
        $lp_item_id = (int) $lp_item_id;
166
        $user_id = (int) $user_id;
167
        $sessionId = $session ? $session->getId() : 0;
168
        $origin = Security::remove_XSS($origin);
169
        $lp = Container::getLpRepository()->find($lp_id);
170
        $list = learnpath::get_flat_ordered_items_list($lp);
171
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
172
        $courseId = $course->getId();
173
        $session_condition = api_get_session_condition($sessionId);
174
175
        // Extend all button
176
        $output = '';
177
        $extra = '<script>
178
        $(function() {
179
            $( "#dialog:ui-dialog" ).dialog( "destroy" );
180
            $( "#dialog-confirm" ).dialog({
181
                autoOpen: false,
182
                show: "blind",
183
                resizable: false,
184
                height:300,
185
                modal: true
186
            });
187
188
            $(".export").click(function() {
189
                var targetUrl = $(this).attr("href");
190
                $( "#dialog-confirm" ).dialog({
191
                    width:400,
192
                    height:300,
193
                    buttons: {
194
                        "'.addslashes(get_lang('Download')).'": function() {
195
                            var option = $("input[name=add_logo]:checked").val();
196
                            location.href = targetUrl+"&add_logo="+option;
197
                            $(this).dialog("close");
198
                        }
199
                    }
200
                });
201
                $("#dialog-confirm").dialog("open");
202
203
                return false;
204
            });
205
        });
206
        </script>';
207
208
        $extra .= '<div id="dialog-confirm" title="'.get_lang('Please confirm your choice').'">';
209
        if ('tracking' === $origin) {
210
            $form = new FormValidator('report', 'post', null, null, ['class' => 'form-vertical']);
211
            $form->addCheckBox('add_logo', '', get_lang('Add right logo'), ['id' => 'export_format_csv_label']);
212
            $extra .= $form->returnForm();
213
        }
214
        $extra .= '</div>';
215
        $output .= $extra;
216
217
        $url_suffix = '&lp_id='.$lp_id;
218
        if ('tracking' === $origin) {
219
            $url_suffix = '&sid='.$sessionId.'&cid='.$courseId.'&student_id='.$user_id.'&lp_id='.$lp_id.'&origin='.$origin;
220
        }
221
222
        $extend_all = 0;
223
        if (!empty($extendedAll)) {
224
            $extend_all_link = Display::url(
225
                Display::getMdiIcon(
226
                    ActionIcon::VIEW_LESS,
227
                    'ch-tool-icon',
228
                    null,
229
                    ICON_SIZE_SMALL,
230
                    get_lang('Hide all attempts')
231
                ),
232
                api_get_self().'?'.api_get_cidreq().'&action=stats'.$url_suffix
233
            );
234
            $extend_all = 1;
235
        } else {
236
            $extend_all_link = Display::url(
237
                Display::getMdiIcon(
238
                    ActionIcon::VIEW_MORE,
239
                    'ch-tool-icon',
240
                    null,
241
                    ICON_SIZE_SMALL,
242
                    get_lang('Show all attempts')
243
                ),
244
                api_get_self().'?'.api_get_cidreq().'&action=stats&extend_all=1'.$url_suffix
245
            );
246
        }
247
248
        if ('tracking' !== $origin) {
249
            $output .= '<div class="section-status">';
250
            $output .= Display::page_header(get_lang('My progress'));
251
            $output .= '</div>';
252
        }
253
254
        $actionColumn = null;
255
        if ('classic' === $type) {
256
            $actionColumn = ' <th>'.get_lang('Detail').'</th>';
257
        }
258
259
        $timeHeader = '<th class="lp_time" colspan="2">'.get_lang('Time').'</th>';
260
        if ($hideTime) {
261
            $timeHeader = '';
262
        }
263
        $output .= '<div class="table-responsive">';
264
        $output .= '<table id="lp_tracking" class="table tracking">
265
            <thead>
266
            <tr class="table-header">
267
                <th width="16">'.(true === $allowExtend ? $extend_all_link : '&nbsp;').'</th>
268
                <th colspan="4">
269
                    '.get_lang('Learning object name').'
270
                </th>
271
                <th colspan="2">
272
                    '.get_lang('Status').'
273
                </th>
274
                <th colspan="2">
275
                    '.get_lang('Score').'
276
                </th>
277
                '.$timeHeader.'
278
                '.$actionColumn.'
279
                </tr>
280
            </thead>
281
            <tbody>
282
        ';
283
284
        // Going through the items using the $items[] array instead of the database order ensures
285
        // we get them in the same order as in the imsmanifest file, which is rather random when using
286
        // the database table.
287
        $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
288
        $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
289
        $TBL_LP_VIEW = Database::get_course_table(TABLE_LP_VIEW);
290
        $tbl_quiz_questions = Database::get_course_table(TABLE_QUIZ_QUESTION);
291
        $TBL_QUIZ = Database::get_course_table(TABLE_QUIZ_TEST);
292
        $tbl_stats_exercices = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
293
        $tbl_stats_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
294
295
        $sql = "SELECT max(view_count)
296
                FROM $TBL_LP_VIEW
297
                WHERE
298
                    c_id = $courseId AND
299
                    lp_id = $lp_id AND
300
                    user_id = $user_id
301
                    $session_condition";
302
        $res = Database::query($sql);
303
        $view = 0;
304
        if (Database::num_rows($res) > 0) {
305
            $myrow = Database::fetch_array($res);
306
            $view = (int) $myrow[0];
307
        }
308
309
        $counter = 0;
310
        $total_time = 0;
311
        $h = get_lang('h');
312
313
        if (!empty($export_csv)) {
314
            $csvHeaders = [
315
                get_lang('Learning object name'),
316
                get_lang('Status'),
317
                get_lang('Score'),
318
            ];
319
320
            if (false === $hideTime) {
321
                $csvHeaders[] = get_lang('Time');
322
            }
323
            $csv_content[] = $csvHeaders;
324
        }
325
326
        $result_disabled_ext_all = true;
327
        $chapterTypes = learnpath::getChapterTypes();
328
        $accessToPdfExport = api_is_allowed_to_edit(false, false, true);
329
330
        $minimumAvailable = self::minimumTimeAvailable($sessionId, $courseId);
331
        $timeCourse = [];
332
        if ($minimumAvailable) {
333
            $timeCourse = self::getCalculateTime($user_id, $courseId, $sessionId);
334
            Session::write('trackTimeCourse', $timeCourse);
335
        }
336
337
        // Show lp items
338
        if (is_array($list) && count($list) > 0) {
339
            foreach ($list as $my_item_id) {
340
                $extend_this = 0;
341
                $order = 'DESC';
342
                if ((!empty($extendId) && $extendId == $my_item_id) || $extend_all) {
343
                    $extend_this = 1;
344
                    $order = 'ASC';
345
                }
346
347
                // Prepare statement to go through each attempt.
348
                $viewCondition = null;
349
                if (!empty($view)) {
350
                    $viewCondition = " AND v.view_count = $view  ";
351
                }
352
353
                $sql = "SELECT
354
                    iv.status as mystatus,
355
                    v.view_count as mycount,
356
                    iv.score as myscore,
357
                    iv.total_time as mytime,
358
                    i.iid as myid,
359
                    i.lp_id as mylpid,
360
                    iv.lp_view_id as mylpviewid,
361
                    i.title as mytitle,
362
                    i.max_score as mymaxscore,
363
                    iv.max_score as myviewmaxscore,
364
                    i.item_type as item_type,
365
                    iv.view_count as iv_view_count,
366
                    iv.iid as iv_id,
367
                    path
368
                FROM $TBL_LP_ITEM as i
369
                INNER JOIN $TBL_LP_ITEM_VIEW as iv
370
                ON (i.iid = iv.lp_item_id)
371
                INNER JOIN $TBL_LP_VIEW as v
372
                ON (iv.lp_view_id = v.iid)
373
                WHERE
374
                    i.iid = $my_item_id AND
375
                    i.lp_id = $lp_id  AND
376
                    v.user_id = $user_id
377
                    $session_condition
378
                    $viewCondition
379
                ORDER BY iv.view_count $order ";
380
381
                $result = Database::query($sql);
382
                $num = Database::num_rows($result);
383
                $time_for_total = 0;
384
                $attemptResult = 0;
385
386
                if ($timeCourse) {
387
                    if (isset($timeCourse['learnpath_detailed']) &&
388
                        isset($timeCourse['learnpath_detailed'][$lp_id]) &&
389
                        isset($timeCourse['learnpath_detailed'][$lp_id][$my_item_id])
390
                    ) {
391
                        $attemptResult = $timeCourse['learnpath_detailed'][$lp_id][$my_item_id][$view];
392
                    }
393
                }
394
395
                // Extend all
396
                if (($extend_this || $extend_all) && $num > 0) {
397
                    $row = Database::fetch_array($result);
398
                    $result_disabled_ext_all = false;
399
                    if ('quiz' === $row['item_type']) {
400
                        // Check results_disabled in quiz table.
401
                        $my_path = Database::escape_string($row['path']);
402
                        $sql = "SELECT results_disabled
403
                                FROM $TBL_QUIZ
404
                                WHERE
405
                                    iid ='".$my_path."'";
406
                        $res_result_disabled = Database::query($sql);
407
                        $row_result_disabled = Database::fetch_row($res_result_disabled);
408
409
                        if (Database::num_rows($res_result_disabled) > 0 &&
410
                            1 === (int) $row_result_disabled[0]
411
                        ) {
412
                            $result_disabled_ext_all = true;
413
                        }
414
                    }
415
416
                    // If there are several attempts, and the link to extend has been clicked, show each attempt...
417
                    $oddclass = 'row_even';
418
                    if (0 === ($counter % 2)) {
419
                        $oddclass = 'row_odd';
420
                    }
421
                    $extend_link = '';
422
                    if (!empty($inter_num)) {
423
                        $extend_link = Display::url(
424
                            Display::getMdiIcon(
425
                                ActionIcon::VISIBLE,
426
                                'ch-tool-icon',
427
                                null,
428
                                ICON_SIZE_SMALL,
429
                                get_lang('Hide attempt view')
430
                            ),
431
                            api_get_self().'?action=stats&fold_id='.$my_item_id.$url_suffix
432
                        );
433
                    }
434
                    $title = $row['mytitle'];
435
436
                    if (empty($title)) {
437
                        $title = learnpath::rl_get_resource_name($courseInfo['code'], $lp_id, $row['myid']);
438
                    }
439
440
                    if (in_array($row['item_type'], $chapterTypes)) {
441
                        $title = "<h4> $title </h4>";
442
                    }
443
                    $lesson_status = $row['mystatus'];
444
                    $title = Security::remove_XSS($title);
445
                    $counter++;
446
447
                    $action = null;
448
                    if ('classic' === $type) {
449
                        $action = '<td></td>';
450
                    }
451
452
                    if (in_array($row['item_type'], $chapterTypes)) {
453
                        $output .= '<tr class="'.$oddclass.'">
454
                                <td>'.$extend_link.'</td>
455
                                <td colspan="4">
456
                                   '.$title.'
457
                                </td>
458
                                <td colspan="2">'.learnpathItem::humanize_status($lesson_status, true, $type).'</td>
459
                                <td colspan="2"></td>
460
                                <td colspan="2"></td>
461
                                '.$action.'
462
                            </tr>';
463
                        continue;
464
                    } else {
465
                        $output .= '<tr class="'.$oddclass.'">
466
                                <td>'.$extend_link.'</td>
467
                                <td colspan="4">'.$title.'</td>
468
                                <td colspan="2"></td>
469
                                <td colspan="2"></td>
470
                                <td colspan="2"></td>
471
                                '.$action.'
472
                            </tr>';
473
                    }
474
475
                    $attemptCount = 1;
476
                    do {
477
                        // Check if there are interactions below.
478
                        $extend_attempt_link = '';
479
                        $extend_this_attempt = 0;
480
481
                        if ($timeCourse) {
482
                            //$attemptResult = 0;
483
                            if (isset($timeCourse['learnpath_detailed']) &&
484
                                isset($timeCourse['learnpath_detailed'][$lp_id]) &&
485
                                isset($timeCourse['learnpath_detailed'][$lp_id][$my_item_id])
486
                            ) {
487
                                $attemptResult = $timeCourse['learnpath_detailed'][$lp_id][$my_item_id][$row['iv_view_count']];
488
                            }
489
                        }
490
                        if ((
491
                                learnpath::get_interactions_count_from_db($row['iv_id'], $courseId) > 0 ||
492
                                learnpath::get_objectives_count_from_db($row['iv_id'], $courseId) > 0
493
                            ) &&
494
                            !$extend_all
495
                        ) {
496
                            if ($extendAttemptId == $row['iv_id']) {
497
                                // The extend button for this attempt has been clicked.
498
                                $extend_this_attempt = 1;
499
                                $extend_attempt_link = Display::url(
500
                                    Display::getMdiIcon(
501
                                        ActionIcon::VISIBLE,
502
                                        'ch-tool-icon',
503
                                        null,
504
                                        ICON_SIZE_SMALL,
505
                                        get_lang('Hide attempt view')
506
                                    ),
507
                                    api_get_self().'?action=stats&extend_id='.$my_item_id.'&fold_attempt_id='.$row['iv_id'].$url_suffix
508
                                );
509
                                if ($accessToPdfExport) {
510
                                    $extend_attempt_link .= '&nbsp;'.
511
                                        Display::url(
512
                                            Display::getMdiIcon(
513
                                                ActionIcon::EXPORT_PDF,
514
                                                'ch-tool-icon',
515
                                                null,
516
                                                ICON_SIZE_SMALL,
517
                                                get_lang('Export to PDF')
518
                                            ),
519
                                            api_get_self().'?action=export_stats&extend_id='.$my_item_id.'&extend_attempt_id='.$row['iv_id'].$url_suffix,
520
                                            ['class' => 'export']
521
                                        );
522
                                }
523
                            } else { // Same case if fold_attempt_id is set, so not implemented explicitly.
524
                                // The "extend" button for this attempt has not been clicked.
525
                                $extend_attempt_link = Display::url(
526
                                    Display::getMdiIcon(
527
                                        ActionIcon::INVISIBLE,
528
                                        'ch-tool-icon',
529
                                        null,
530
                                        ICON_SIZE_SMALL,
531
                                        get_lang('Extend attempt view')
532
                                    ),
533
                                    api_get_self().'?action=stats&extend_id='.$my_item_id.'&extend_attempt_id='.$row['iv_id'].$url_suffix
534
                                );
535
                                if ($accessToPdfExport) {
536
                                    $extend_attempt_link .= '&nbsp;'.
537
                                        Display::url(
538
                                            Display::getMdiIcon(
539
                                                ActionIcon::EXPORT_PDF,
540
                                                'ch-tool-icon',
541
                                                null,
542
                                                ICON_SIZE_SMALL,
543
                                                get_lang('Export to PDF')
544
                                            ),
545
                                            api_get_self().'?action=export_stats&extend_id='.$my_item_id.'&extend_attempt_id='.$row['iv_id'].$url_suffix,
546
                                            ['class' => 'export']
547
                                        );
548
                                }
549
                            }
550
                        }
551
552
                        $oddclass = 'row_even';
553
                        if (0 == ($counter % 2)) {
554
                            $oddclass = 'row_odd';
555
                        }
556
557
                        $lesson_status = $row['mystatus'];
558
                        $score = $row['myscore'];
559
                        $time_for_total += $row['mytime'];
560
                        $attemptTime = $row['mytime'];
561
562
                        if ($minimumAvailable) {
563
                            $lp_time = $timeCourse[TOOL_LEARNPATH];
564
                            $lpTime = null;
565
                            if (isset($lp_time[$lp_id])) {
566
                                $lpTime = (int) $lp_time[$lp_id];
567
                            }
568
                            $time_for_total = $lpTime;
569
570
                            if ($timeCourse) {
571
                                $time_for_total = (int) $attemptResult;
572
                                $attemptTime = (int) $attemptResult;
573
                            }
574
                        }
575
576
                        $time = learnpathItem::getScormTimeFromParameter('js', $attemptTime);
577
578
                        if (0 == $score) {
579
                            $maxscore = $row['mymaxscore'];
580
                        } else {
581
                            if ('sco' === $row['item_type']) {
582
                                if (!empty($row['myviewmaxscore']) && $row['myviewmaxscore'] > 0) {
583
                                    $maxscore = $row['myviewmaxscore'];
584
                                } elseif ('' === $row['myviewmaxscore']) {
585
                                    $maxscore = 0;
586
                                } else {
587
                                    $maxscore = $row['mymaxscore'];
588
                                }
589
                            } else {
590
                                $maxscore = $row['mymaxscore'];
591
                            }
592
                        }
593
594
                        // Remove "NaN" if any (@todo: locate the source of these NaN)
595
                        $time = str_replace('NaN', '00'.$h.'00\'00"', $time);
596
597
                        if ('dir' !== $row['item_type']) {
598
                            if (!$is_allowed_to_edit && $result_disabled_ext_all) {
599
                                $view_score = Display::getMdiIcon(
600
                                    ActionIcon::INVISIBLE,
601
                                    'ch-tool-icon',
602
                                    null,
603
                                    ICON_SIZE_SMALL,
604
                                    get_lang('Results hidden by the exercise setting')
605
                                );
606
                            } else {
607
                                switch ($row['item_type']) {
608
                                    case 'sco':
609
                                        if (0 == $maxscore) {
610
                                            $view_score = $score;
611
                                        } else {
612
                                            $view_score = ExerciseLib::show_score(
613
                                                $score,
614
                                                $maxscore,
615
                                                false
616
                                            );
617
                                        }
618
                                        break;
619
                                    case 'document':
620
                                        $view_score = (0 == $score ? '/' : ExerciseLib::show_score($score, $maxscore, false));
621
                                        break;
622
                                    default:
623
                                        $view_score = ExerciseLib::show_score(
624
                                            $score,
625
                                            $maxscore,
626
                                            false
627
                                        );
628
                                        break;
629
                                }
630
                            }
631
632
                            $action = null;
633
                            if ('classic' === $type) {
634
                                $action = '<td></td>';
635
                            }
636
                            $timeRow = '<td class="lp_time" colspan="2">'.$time.'</td>';
637
                            if ($hideTime) {
638
                                $timeRow = '';
639
                            }
640
                            $output .= '<tr class="'.$oddclass.'">
641
                                    <td></td>
642
                                    <td style="width:70px;float:left;">'.$extend_attempt_link.'</td>
643
                                    <td colspan="3">'.get_lang('Attempt').' '.$attemptCount.'</td>
644
                                    <td colspan="2">'.learnpathItem::humanize_status($lesson_status, true, $type).'</td>
645
                                    <td colspan="2">'.$view_score.'</td>
646
                                    '.$timeRow.'
647
                                    '.$action.'
648
                                </tr>';
649
                            $attemptCount++;
650
                            if (!empty($export_csv)) {
651
                                $temp = [];
652
                                $temp[] = $title = Security::remove_XSS($title);
653
                                $temp[] = Security::remove_XSS(
654
                                    learnpathItem::humanize_status($lesson_status, false, $type)
655
                                );
656
657
                                if ('quiz' === $row['item_type']) {
658
                                    if (!$is_allowed_to_edit && $result_disabled_ext_all) {
659
                                        $temp[] = '/';
660
                                    } else {
661
                                        $temp[] = (0 == $score ? '0/'.$maxscore : (0 == $maxscore ? $score : $score.'/'.float_format($maxscore, 1)));
662
                                    }
663
                                } else {
664
                                    $temp[] = (0 == $score ? '/' : (0 == $maxscore ? $score : $score.'/'.float_format($maxscore, 1)));
665
                                }
666
667
                                if (false === $hideTime) {
668
                                    $temp[] = $time;
669
                                }
670
                                $csv_content[] = $temp;
671
                            }
672
                        }
673
674
                        $counter++;
675
                        $action = null;
676
                        if ('classic' === $type) {
677
                            $action = '<td></td>';
678
                        }
679
680
                        if ($extend_this_attempt || $extend_all) {
681
                            $list1 = learnpath::get_iv_interactions_array($row['iv_id'], $courseId);
682
                            foreach ($list1 as $id => $interaction) {
683
                                $oddclass = 'row_even';
684
                                if (0 == ($counter % 2)) {
685
                                    $oddclass = 'row_odd';
686
                                }
687
                                $timeRow = '<td class="lp_time">'.$interaction['time'].'</td>';
688
                                if ($hideTime) {
689
                                    $timeRow = '';
690
                                }
691
692
                                $output .= '<tr class="'.$oddclass.'">
693
                                        <td></td>
694
                                        <td></td>
695
                                        <td></td>
696
                                        <td>'.$interaction['order_id'].'</td>
697
                                        <td>'.$interaction['id'].'</td>';
698
699
                                $output .= '
700
                                        <td colspan="2">'.$interaction['type'].'</td>
701
                                        <td>'.$interaction['student_response_formatted'].'</td>
702
                                        <td>'.$interaction['result'].'</td>
703
                                        <td>'.$interaction['latency'].'</td>
704
                                        '.$timeRow.'
705
                                        '.$action.'
706
                                    </tr>';
707
                                $counter++;
708
                            }
709
                            $list2 = learnpath::get_iv_objectives_array($row['iv_id'], $courseId);
710
                            foreach ($list2 as $id => $interaction) {
711
                                $oddclass = 'row_even';
712
                                if (0 === ($counter % 2)) {
713
                                    $oddclass = 'row_odd';
714
                                }
715
                                $output .= '<tr class="'.$oddclass.'">
716
                                        <td></td>
717
                                        <td></td>
718
                                        <td></td>
719
                                        <td>'.$interaction['order_id'].'</td>
720
                                        <td colspan="2">'.$interaction['objective_id'].'</td>
721
                                        <td colspan="2">'.$interaction['status'].'</td>
722
                                        <td>'.$interaction['score_raw'].'</td>
723
                                        <td>'.$interaction['score_max'].'</td>
724
                                        <td>'.$interaction['score_min'].'</td>
725
                                        '.$action.'
726
                                     </tr>';
727
                                $counter++;
728
                            }
729
                        }
730
                    } while ($row = Database::fetch_array($result));
731
                } elseif ($num > 0) {
732
                    // Not extended.
733
                    $row = Database::fetch_assoc($result);
734
                    $my_id = $row['myid'];
735
                    $my_lp_id = $row['mylpid'];
736
                    $my_lp_view_id = $row['mylpviewid'];
737
                    $my_path = $row['path'];
738
                    $result_disabled_ext_all = false;
739
                    if ('quiz' === $row['item_type']) {
740
                        // Check results_disabled in quiz table.
741
                        $my_path = Database::escape_string($my_path);
742
                        $sql = "SELECT results_disabled
743
                                FROM $TBL_QUIZ
744
                                WHERE iid = '$my_path' ";
745
                        $res_result_disabled = Database::query($sql);
746
                        $row_result_disabled = Database::fetch_row($res_result_disabled);
747
748
                        if (Database::num_rows($res_result_disabled) > 0 &&
749
                            1 === (int) $row_result_disabled[0]
750
                        ) {
751
                            $result_disabled_ext_all = true;
752
                        }
753
                    }
754
755
                    // Check if there are interactions below
756
                    $extend_this_attempt = 0;
757
                    $inter_num = learnpath::get_interactions_count_from_db($row['iv_id'], $courseId);
758
                    $objec_num = learnpath::get_objectives_count_from_db($row['iv_id'], $courseId);
759
                    $extend_attempt_link = '';
760
                    if ($inter_num > 0 || $objec_num > 0) {
761
                        if (!empty($extendAttemptId) && $extendAttemptId == $row['iv_id']) {
762
                            // The extend button for this attempt has been clicked.
763
                            $extend_this_attempt = 1;
764
                            $extend_attempt_link = Display::url(
765
                                Display::getMdiIcon(
766
                                    ActionIcon::VISIBLE,
767
                                    'ch-tool-icon',
768
                                    null,
769
                                    ICON_SIZE_SMALL,
770
                                    get_lang('Hide attempt view')
771
                                ),
772
                                api_get_self().'?action=stats&extend_id='.$my_item_id.'&fold_attempt_id='.$row['iv_id'].$url_suffix
773
                            );
774
                        } else {
775
                            // Same case if fold_attempt_id is set, so not implemented explicitly.
776
                            // The "Extend" button for this attempt has not been clicked.
777
                            $extend_attempt_link = Display::url(
778
                                Display::getMdiIcon(
779
                                    ActionIcon::INVISIBLE,
780
                                    'ch-tool-icon',
781
                                    null,
782
                                    ICON_SIZE_SMALL,
783
                                    get_lang('Extend attempt view')
784
                                ),
785
                                api_get_self().'?action=stats&extend_id='.$my_item_id.'&extend_attempt_id='.$row['iv_id'].$url_suffix
786
                            );
787
                        }
788
                    }
789
790
                    $oddclass = 'row_even';
791
                    if (0 == ($counter % 2)) {
792
                        $oddclass = 'row_odd';
793
                    }
794
795
                    $extend_link = '';
796
                    if ($inter_num > 1) {
797
                        $extend_link = Display::url(
798
                            Display::getMdiIcon(
799
                                ActionIcon::INVISIBLE,
800
                                'ch-tool-icon',
801
                                null,
802
                                ICON_SIZE_SMALL,
803
                                get_lang('Extend attempt view')
804
                            ),
805
                            api_get_self().'?action=stats&extend_id='.$my_item_id.'&extend_attempt_id='.$row['iv_id'].$url_suffix
806
                        );
807
                    }
808
809
                    $lesson_status = $row['mystatus'];
810
                    $score = $row['myscore'];
811
                    $subtotal_time = $row['mytime'];
812
                    while ($tmp_row = Database::fetch_array($result)) {
813
                        $subtotal_time += $tmp_row['mytime'];
814
                    }
815
816
                    $title = $row['mytitle'];
817
                    $sessionCondition = api_get_session_condition($sessionId);
818
                    // Selecting the exe_id from stats attempts tables in order to look the max score value.
819
                    $sql = 'SELECT * FROM '.$tbl_stats_exercices.'
820
                            WHERE
821
                                exe_exo_id="'.$row['path'].'" AND
822
                                exe_user_id="'.$user_id.'" AND
823
                                orig_lp_id = "'.$lp_id.'" AND
824
                                orig_lp_item_id = "'.$row['myid'].'" AND
825
                                c_id = '.$courseId.' AND
826
                                status <> "incomplete"
827
                                '.$sessionCondition.'
828
                             ORDER BY exe_date DESC
829
                             LIMIT 1';
830
831
                    $resultLastAttempt = Database::query($sql);
832
                    $num = Database::num_rows($resultLastAttempt);
833
                    $id_last_attempt = null;
834
                    if ($num > 0) {
835
                        while ($rowLA = Database::fetch_array($resultLastAttempt)) {
836
                            $id_last_attempt = $rowLA['exe_id'];
837
                        }
838
                    }
839
840
                    switch ($row['item_type']) {
841
                        case 'sco':
842
                            if (!empty($row['myviewmaxscore']) && $row['myviewmaxscore'] > 0) {
843
                                $maxscore = $row['myviewmaxscore'];
844
                            } elseif ('' === $row['myviewmaxscore']) {
845
                                $maxscore = 0;
846
                            } else {
847
                                $maxscore = $row['mymaxscore'];
848
                            }
849
                            break;
850
                        case 'quiz':
851
                            // Get score and total time from last attempt of a exercise en lp.
852
                            $sql = "SELECT iid, score
853
                                    FROM $TBL_LP_ITEM_VIEW
854
                                    WHERE
855
                                        lp_item_id = '".(int) $my_id."' AND
856
                                        lp_view_id = '".(int) $my_lp_view_id."'
857
                                    ORDER BY view_count DESC
858
                                    LIMIT 1";
859
                            $res_score = Database::query($sql);
860
                            $row_score = Database::fetch_array($res_score);
861
862
                            $sql = "SELECT SUM(total_time) as total_time
863
                                    FROM $TBL_LP_ITEM_VIEW
864
                                    WHERE
865
                                        lp_item_id = '".(int) $my_id."' AND
866
                                        lp_view_id = '".(int) $my_lp_view_id."'";
867
                            $res_time = Database::query($sql);
868
                            $row_time = Database::fetch_array($res_time);
869
870
                            $score = 0;
871
                            $subtotal_time = 0;
872
                            if (Database::num_rows($res_score) > 0 &&
873
                                Database::num_rows($res_time) > 0
874
                            ) {
875
                                $score = (float) $row_score['score'];
876
                                $subtotal_time = (int) $row_time['total_time'];
877
                            }
878
                            // Selecting the max score from an attempt.
879
                            $sql = "SELECT SUM(t.ponderation) as maxscore
880
                                    FROM (
881
                                        SELECT DISTINCT
882
                                            question_id, marks, ponderation
883
                                        FROM $tbl_stats_attempts as at
884
                                        INNER JOIN $tbl_quiz_questions as q
885
                                        ON (q.iid = at.question_id)
886
                                        WHERE exe_id ='$id_last_attempt'
887
                                    ) as t";
888
889
                            $result = Database::query($sql);
890
                            $row_max_score = Database::fetch_array($result);
891
                            $maxscore = $row_max_score['maxscore'];
892
893
                            // Get duration time from track_e_exercises.exe_duration instead of lp_view_item.total_time
894
                            $sql = 'SELECT SUM(exe_duration) exe_duration
895
                                    FROM '.$tbl_stats_exercices.'
896
                                    WHERE
897
                                        exe_exo_id="'.$row['path'].'" AND
898
                                        exe_user_id="'.$user_id.'" AND
899
                                        orig_lp_id = "'.$lp_id.'" AND
900
                                        orig_lp_item_id = "'.$row['myid'].'" AND
901
                                        c_id = '.$courseId.' AND
902
                                        status <> "incomplete" AND
903
                                        session_id = '.$sessionId.'
904
                                     ORDER BY exe_date DESC ';
905
                            $sumScoreResult = Database::query($sql);
906
                            $durationRow = Database::fetch_assoc($sumScoreResult);
907
                            if (!empty($durationRow['exe_duration'])) {
908
                                $exeDuration = $durationRow['exe_duration'];
909
                                if ($exeDuration != $subtotal_time &&
910
                                    !empty($row_score['iid']) &&
911
                                    !empty($exeDuration)
912
                                ) {
913
                                    $subtotal_time = $exeDuration;
914
                                    // Update c_lp_item_view.total_time
915
                                    $sqlUpdate = "UPDATE $TBL_LP_ITEM_VIEW SET total_time = '$exeDuration'
916
                                                  WHERE iid = ".$row_score['iid'];
917
                                    Database::query($sqlUpdate);
918
                                }
919
                            }
920
                            break;
921
                        default:
922
                            $maxscore = $row['mymaxscore'];
923
                            break;
924
                    }
925
926
                    $time_for_total = $subtotal_time;
927
                    $time = learnpathItem::getScormTimeFromParameter('js', $subtotal_time);
928
                    if (empty($title)) {
929
                        $title = learnpath::rl_get_resource_name(
930
                            $courseInfo['code'],
931
                            $lp_id,
932
                            $row['myid']
933
                        );
934
                    }
935
936
                    $action = null;
937
                    if ('classic' === $type) {
938
                        $action = '<td></td>';
939
                    }
940
941
                    if (in_array($row['item_type'], $chapterTypes)) {
942
                        $title = Security::remove_XSS($title);
943
                        $output .= '<tr class="'.$oddclass.'">
944
                                <td>'.$extend_link.'</td>
945
                                <td colspan="10">
946
                                <h4>'.$title.'</h4>
947
                                </td>
948
                                '.$action.'
949
                            </tr>';
950
                    } else {
951
                        $correct_test_link = '-';
952
                        $showRowspan = false;
953
                        if ('quiz' === $row['item_type']) {
954
                            $my_url_suffix = '&cid='.$courseId.'&student_id='.$user_id.'&lp_id='.intval($row['mylpid']).'&origin='.$origin;
955
                            $sql = 'SELECT * FROM '.$tbl_stats_exercices.'
956
                                     WHERE
957
                                        exe_exo_id="'.$row['path'].'" AND
958
                                        exe_user_id="'.$user_id.'" AND
959
                                        orig_lp_id = "'.$lp_id.'" AND
960
                                        orig_lp_item_id = "'.$row['myid'].'" AND
961
                                        c_id = '.$courseId.' AND
962
                                        status <> "incomplete" AND
963
                                        session_id = '.$sessionId.'
964
                                     ORDER BY exe_date DESC ';
965
966
                            $resultLastAttempt = Database::query($sql);
967
                            $num = Database::num_rows($resultLastAttempt);
968
                            $showRowspan = false;
969
                            if ($num > 0) {
970
                                $linkId = 'link_'.$my_id;
971
                                if (1 == $extendedAttempt &&
972
                                    $lp_id == $my_lp_id &&
973
                                    $lp_item_id == $my_id
974
                                ) {
975
                                    $showRowspan = true;
976
                                    $correct_test_link = Display::url(
977
                                        Display::getMdiIcon(
978
                                            ActionIcon::VIEW_LESS,
979
                                            'ch-tool-icon',
980
                                            null,
981
                                            ICON_SIZE_SMALL,
982
                                            get_lang('Hide all attempts')
983
                                        ),
984
                                        api_get_self().'?action=stats'.$my_url_suffix.'&sid='.$sessionId.'&lp_item_id='.$my_id.'#'.$linkId,
985
                                        ['id' => $linkId]
986
                                    );
987
                                } else {
988
                                    $correct_test_link = Display::url(
989
                                        Display::getMdiIcon(
990
                                            ActionIcon::VIEW_MORE,
991
                                            'ch-tool-icon',
992
                                            null,
993
                                            ICON_SIZE_SMALL,
994
                                            get_lang(
995
                                                'Show all attemptsByExercise'
996
                                            )
997
                                        ),
998
                                        api_get_self().'?action=stats&extend_attempt=1'.$my_url_suffix.'&sid='.$sessionId.'&lp_item_id='.$my_id.'#'.$linkId,
999
                                        ['id' => $linkId]
1000
                                    );
1001
                                }
1002
                            }
1003
                        }
1004
1005
                        $title = Security::remove_XSS($title);
1006
                        $action = null;
1007
                        if ('classic' === $type) {
1008
                            $action = '<td '.($showRowspan ? 'rowspan="2"' : '').'>'.$correct_test_link.'</td>';
1009
                        }
1010
1011
                        if ($lp_id == $my_lp_id && false) {
1012
                            $output .= '<tr class ='.$oddclass.'>
1013
                                    <td>'.$extend_link.'</td>
1014
                                    <td colspan="4">'.$title.'</td>
1015
                                    <td colspan="2">&nbsp;</td>
1016
                                    <td colspan="2">&nbsp;</td>
1017
                                    <td colspan="2">&nbsp;</td>
1018
                                    '.$action.'
1019
                                </tr>';
1020
                            $output .= '</tr>';
1021
                        } else {
1022
                            if ($lp_id == $my_lp_id && $lp_item_id == $my_id) {
1023
                                $output .= "<tr class='$oddclass'>";
1024
                            } else {
1025
                                $output .= "<tr class='$oddclass'>";
1026
                            }
1027
1028
                            $scoreItem = null;
1029
                            if ('quiz' === $row['item_type']) {
1030
                                if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1031
                                    $scoreItem .= Display::getMdiIcon(
1032
                                        ActionIcon::INVISIBLE,
1033
                                        'ch-tool-icon',
1034
                                        null,
1035
                                        ICON_SIZE_SMALL,
1036
                                        get_lang('Results hidden by the exercise setting')
1037
                                    );
1038
                                } else {
1039
                                    $scoreItem .= ExerciseLib::show_score($score, $maxscore, false);
1040
                                }
1041
                            } else {
1042
                                $scoreItem .= 0 == $score ? '/' : (0 == $maxscore ? $score : $score.'/'.$maxscore);
1043
                            }
1044
1045
                            $timeRow = '<td class="lp_time" colspan="2">'.$time.'</td>';
1046
                            if ($hideTime) {
1047
                                $timeRow = '';
1048
                            }
1049
1050
                            $output .= '
1051
                                <td>'.$extend_link.'</td>
1052
                                <td colspan="4">'.$title.'</td>
1053
                                <td colspan="2">'.learnpathitem::humanize_status($lesson_status).'</td>
1054
                                <td colspan="2">'.$scoreItem.'</td>
1055
                                '.$timeRow.'
1056
                                '.$action.'
1057
                             ';
1058
                            $output .= '</tr>';
1059
                        }
1060
1061
                        if (!empty($export_csv)) {
1062
                            $temp = [];
1063
                            $temp[] = api_html_entity_decode($title, ENT_QUOTES);
1064
                            $temp[] = api_html_entity_decode($lesson_status, ENT_QUOTES);
1065
                            if ('quiz' === $row['item_type']) {
1066
                                if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1067
                                    $temp[] = '/';
1068
                                } else {
1069
                                    $temp[] = (0 == $score ? '0/'.$maxscore : (0 == $maxscore ? $score : $score.'/'.float_format($maxscore, 1)));
1070
                                }
1071
                            } else {
1072
                                $temp[] = (0 == $score ? '/' : (0 == $maxscore ? $score : $score.'/'.float_format($maxscore, 1)));
1073
                            }
1074
1075
                            if (false === $hideTime) {
1076
                                $temp[] = $time;
1077
                            }
1078
                            $csv_content[] = $temp;
1079
                        }
1080
                    }
1081
1082
                    $counter++;
1083
                    $action = null;
1084
                    if ('classic' === $type) {
1085
                        $action = '<td></td>';
1086
                    }
1087
1088
                    if ($extend_this_attempt || $extend_all) {
1089
                        $list1 = learnpath::get_iv_interactions_array($row['iv_id'], $courseId);
1090
                        foreach ($list1 as $id => $interaction) {
1091
                            $oddclass = 'row_even';
1092
                            if (0 == ($counter % 2)) {
1093
                                $oddclass = 'row_odd';
1094
                            }
1095
                            $timeRow = '<td class="lp_time">'.$interaction['time'].'</td>';
1096
                            if ($hideTime) {
1097
                                $timeRow = '';
1098
                            }
1099
1100
                            $output .= '<tr class="'.$oddclass.'">
1101
                                    <td></td>
1102
                                    <td></td>
1103
                                    <td></td>
1104
                                    <td>'.$interaction['order_id'].'</td>
1105
                                    <td>'.$interaction['id'].'</td>
1106
                                    <td colspan="2">'.$interaction['type'].'</td>
1107
                                    <td>'.urldecode($interaction['student_response']).'</td>
1108
                                    <td>'.$interaction['result'].'</td>
1109
                                    <td>'.$interaction['latency'].'</td>
1110
                                    '.$timeRow.'
1111
                                    '.$action.'
1112
                               </tr>';
1113
                            $counter++;
1114
                        }
1115
1116
                        $list2 = learnpath::get_iv_objectives_array($row['iv_id'], $courseId);
1117
                        foreach ($list2 as $id => $interaction) {
1118
                            $oddclass = 'row_even';
1119
                            if (0 == ($counter % 2)) {
1120
                                $oddclass = 'row_odd';
1121
                            }
1122
                            $output .= '<tr class="'.$oddclass.'">
1123
                                    <td></td>
1124
                                    <td></td>
1125
                                    <td></td>
1126
                                    <td>'.$interaction['order_id'].'</td>
1127
                                    <td colspan="2">'.$interaction['objective_id'].'</td>
1128
                                    <td colspan="2">'.$interaction['status'].'</td>
1129
                                    <td>'.$interaction['score_raw'].'</td>
1130
                                    <td>'.$interaction['score_max'].'</td>
1131
                                    <td>'.$interaction['score_min'].'</td>
1132
                                    '.$action.'
1133
                               </tr>';
1134
                            $counter++;
1135
                        }
1136
                    }
1137
1138
                    // Attempts listing by exercise.
1139
                    if ($lp_id == $my_lp_id && $lp_item_id == $my_id && $extendedAttempt) {
1140
                        // Get attempts of a exercise.
1141
                        if (!empty($lp_id) &&
1142
                            !empty($lp_item_id) &&
1143
                            'quiz' === $row['item_type']
1144
                        ) {
1145
                            $sql = "SELECT path FROM $TBL_LP_ITEM
1146
                                    WHERE
1147
                                        iid = '$lp_item_id' AND
1148
                                        lp_id = '$lp_id'";
1149
                            $res_path = Database::query($sql);
1150
                            $row_path = Database::fetch_array($res_path);
1151
1152
                            if (Database::num_rows($res_path) > 0) {
1153
                                $sql = 'SELECT * FROM '.$tbl_stats_exercices.'
1154
                                        WHERE
1155
                                            exe_exo_id="'.(int) $row_path['path'].'" AND
1156
                                            status <> "incomplete" AND
1157
                                            exe_user_id="'.$user_id.'" AND
1158
                                            orig_lp_id = "'.(int) $lp_id.'" AND
1159
                                            orig_lp_item_id = "'.(int) $lp_item_id.'" AND
1160
                                            c_id = '.$courseId.'  AND
1161
                                            session_id = '.$sessionId.'
1162
                                        ORDER BY exe_date';
1163
                                $res_attempts = Database::query($sql);
1164
                                $num_attempts = Database::num_rows($res_attempts);
1165
                                if ($num_attempts > 0) {
1166
                                    $n = 1;
1167
                                    while ($row_attempts = Database::fetch_array($res_attempts)) {
1168
                                        $my_score = $row_attempts['score'];
1169
                                        $my_maxscore = $row_attempts['max_score'];
1170
                                        $my_exe_id = $row_attempts['exe_id'];
1171
                                        $mktime_start_date = api_strtotime($row_attempts['start_date'], 'UTC');
1172
                                        $mktime_exe_date = api_strtotime($row_attempts['exe_date'], 'UTC');
1173
                                        $time_attemp = ' - ';
1174
                                        if ($mktime_start_date && $mktime_exe_date) {
1175
                                            $time_attemp = api_format_time($row_attempts['exe_duration'], 'js');
1176
                                        }
1177
                                        if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1178
                                            $view_score = Display::getMdiIcon(
1179
                                                ActionIcon::INVISIBLE,
1180
                                                'ch-tool-icon',
1181
                                                null,
1182
                                                ICON_SIZE_SMALL,
1183
                                                get_lang('Results hidden by the exercise setting')
1184
                                            );
1185
                                        } else {
1186
                                            // Show only float when need it
1187
                                            if (0 == $my_score) {
1188
                                                $view_score = ExerciseLib::show_score(
1189
                                                    0,
1190
                                                    $my_maxscore,
1191
                                                    false
1192
                                                );
1193
                                            } else {
1194
                                                if (0 == $my_maxscore) {
1195
                                                    $view_score = $my_score;
1196
                                                } else {
1197
                                                    $view_score = ExerciseLib::show_score(
1198
                                                        $my_score,
1199
                                                        $my_maxscore,
1200
                                                        false
1201
                                                    );
1202
                                                }
1203
                                            }
1204
                                        }
1205
                                        $my_lesson_status = $row_attempts['status'];
1206
                                        if ('' === $my_lesson_status) {
1207
                                            $my_lesson_status = learnpathitem::humanize_status('completed');
1208
                                        } elseif ('incomplete' === $my_lesson_status) {
1209
                                            $my_lesson_status = learnpathitem::humanize_status('incomplete');
1210
                                        }
1211
                                        $timeRow = '<td class="lp_time" colspan="2">'.$time_attemp.'</td>';
1212
                                        if ($hideTime) {
1213
                                            $timeRow = '';
1214
                                        }
1215
1216
                                        $output .= '<tr class="'.$oddclass.'" >
1217
                                        <td></td>
1218
                                        <td>'.$extend_attempt_link.'</td>
1219
                                        <td colspan="3">'.get_lang('Attempt').' '.$n.'</td>
1220
                                        <td colspan="2">'.$my_lesson_status.'</td>
1221
                                        <td colspan="2">'.$view_score.'</td>
1222
                                        '.$timeRow;
1223
1224
                                        if ('classic' === $action) {
1225
                                            if ('tracking' !== $origin) {
1226
                                                if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1227
                                                    $output .= '<td>
1228
                                                            <img
1229
                                                                src="'.Display::returnIconPath('quiz_na.gif').'"
1230
                                                                alt="'.get_lang('Show attempt').'"
1231
                                                                title="'.get_lang('Show attempt').'" />
1232
                                                            </td>';
1233
                                                } else {
1234
                                                    $output .= '<td>
1235
                                                            <a
1236
                                                                href="../exercise/exercise_show.php?origin='.$origin.'&id='.$my_exe_id.'&cid='.$courseId.'"
1237
                                                                target="_parent">
1238
                                                            <img
1239
                                                                src="'.Display::returnIconPath('quiz.png').'"
1240
                                                                alt="'.get_lang('Show attempt').'"
1241
                                                                title="'.get_lang('Show attempt').'" />
1242
                                                            </a></td>';
1243
                                                }
1244
                                            } else {
1245
                                                if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1246
                                                    $output .= '<td>
1247
                                                                <img
1248
                                                                    src="'.Display::returnIconPath('quiz_na.gif').'"
1249
                                                                    alt="'.get_lang('Show and grade attempt').'"
1250
                                                                    title="'.get_lang('Show and grade attempt').'" />
1251
                                                                </td>';
1252
                                                } else {
1253
                                                    $output .= '<td>
1254
                                                                    <a
1255
                                                                        href="../exercise/exercise_show.php?cid='.$courseId.'&origin=correct_exercise_in_lp&id='.$my_exe_id.'"
1256
                                                                        target="_parent">
1257
                                                                    <img
1258
                                                                        src="'.Display::returnIconPath('quiz.gif').'"
1259
                                                                        alt="'.get_lang('Show and grade attempt').'"
1260
                                                                        title="'.get_lang('Show and grade attempt').'">
1261
                                                                    </a>
1262
                                                                    </td>';
1263
                                                }
1264
                                            }
1265
                                        }
1266
                                        $output .= '</tr>';
1267
                                        $n++;
1268
                                    }
1269
                                }
1270
                                $output .= '<tr><td colspan="12">&nbsp;</td></tr>';
1271
                            }
1272
                        }
1273
                    }
1274
                }
1275
1276
                $total_time += $time_for_total;
1277
                // QUIZZ IN LP
1278
                $a_my_id = [];
1279
                if (!empty($my_lp_id)) {
1280
                    $a_my_id[] = $my_lp_id;
1281
                }
1282
            }
1283
        }
1284
1285
        // NOT Extend all "left green cross"
1286
        if (!empty($a_my_id)) {
1287
            if ($extendedAttempt) {
1288
                // "Right green cross" extended
1289
                $total_score = self::get_avg_student_score(
1290
                    $user_id,
1291
                    $course,
1292
                    $a_my_id,
1293
                    $session,
1294
                    false,
1295
                    false
1296
                );
1297
            } else {
1298
                // "Left green cross" extended
1299
                $total_score = self::get_avg_student_score(
1300
                    $user_id,
1301
                    $course,
1302
                    $a_my_id,
1303
                    $session,
1304
                    false,
1305
                    true
1306
                );
1307
            }
1308
        } else {
1309
            // Extend all "left green cross"
1310
            $total_score = self::get_avg_student_score(
1311
                $user_id,
1312
                $course,
1313
                [$lp_id],
1314
                $session,
1315
                false,
1316
                false
1317
            );
1318
        }
1319
1320
        $total_time = learnpathItem::getScormTimeFromParameter('js', $total_time);
1321
        $total_time = str_replace('NaN', '00'.$h.'00\'00"', $total_time);
1322
1323
        if (!$is_allowed_to_edit && $result_disabled_ext_all) {
1324
            $final_score = Display::getMdiIcon(
1325
                ActionIcon::INVISIBLE,
1326
                'ch-tool-icon',
1327
                null,
1328
                ICON_SIZE_SMALL,
1329
                get_lang('Results hidden by the exercise setting')
1330
            );
1331
            $finalScoreToCsv = get_lang('Results hidden by the exercise setting');
1332
        } else {
1333
            if (is_numeric($total_score)) {
1334
                $final_score = $total_score.'%';
1335
            } else {
1336
                $final_score = $total_score;
1337
            }
1338
            $finalScoreToCsv = $final_score;
1339
        }
1340
        $progress = learnpath::getProgress($lp_id, $user_id, $courseId, $sessionId);
1341
1342
        $oddclass = 'row_even';
1343
        if (0 == ($counter % 2)) {
1344
            $oddclass = 'row_odd';
1345
        }
1346
1347
        $action = null;
1348
        if ('classic' === $type) {
1349
            $action = '<td></td>';
1350
        }
1351
1352
        $timeTotal = '<td class="lp_time" colspan="2">'.$total_time.'</div>';
1353
        if ($hideTime) {
1354
            $timeTotal = '';
1355
        }
1356
1357
        $output .= '<tr class="'.$oddclass.'">
1358
                <td></td>
1359
                <td colspan="4">
1360
                    <i>'.get_lang('Total of completed learning objects').'</i>
1361
                </td>
1362
                <td colspan="2">'.$progress.'%</td>
1363
                <td colspan="2">'.$final_score.'</td>
1364
                '.$timeTotal.'
1365
                '.$action.'
1366
           </tr>';
1367
1368
        $output .= '
1369
                    </tbody>
1370
                </table>
1371
            </div>
1372
        ';
1373
1374
        if (!empty($export_csv)) {
1375
            $temp = [
1376
                '',
1377
                '',
1378
                '',
1379
                '',
1380
            ];
1381
            $csv_content[] = $temp;
1382
            $temp = [
1383
                get_lang('Total of completed learning objects'),
1384
                '',
1385
                $finalScoreToCsv,
1386
            ];
1387
1388
            if (false === $hideTime) {
1389
                $temp[] = $total_time;
1390
            }
1391
1392
            $csv_content[] = $temp;
1393
            ob_end_clean();
1394
            Export::arrayToCsv($csv_content, 'reporting_learning_path_details');
1395
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1396
        }
1397
1398
        return $output;
1399
    }
1400
1401
    /**
1402
     * @param int  $userId
1403
     * @param bool $getCount
1404
     *
1405
     * @return array
1406
     */
1407
    public static function getStats($userId, $getCount = false)
1408
    {
1409
        $courses = [];
1410
        $assignedCourses = [];
1411
        $drhCount = 0;
1412
        $teachersCount = 0;
1413
        $studentsCount = 0;
1414
        $studentBossCount = 0;
1415
        $courseCount = 0;
1416
        $sessionCount = 0;
1417
        $assignedCourseCount = 0;
1418
1419
        if (api_is_drh() && api_drh_can_access_all_session_content()) {
1420
            $studentList = SessionManager::getAllUsersFromCoursesFromAllSessionFromStatus(
1421
                'drh_all',
1422
                $userId,
1423
                false,
1424
                null,
1425
                null,
1426
                null,
1427
                null,
1428
                null,
1429
                null,
1430
                null,
1431
                [],
1432
                [],
1433
                STUDENT
1434
            );
1435
1436
            $students = [];
1437
            if (is_array($studentList)) {
1438
                foreach ($studentList as $studentData) {
1439
                    $students[] = $studentData['user_id'];
1440
                }
1441
            }
1442
1443
            $studentBossesList = SessionManager::getAllUsersFromCoursesFromAllSessionFromStatus(
1444
                'drh_all',
1445
                $userId,
1446
                $getCount,
1447
                null,
1448
                null,
1449
                null,
1450
                null,
1451
                null,
1452
                null,
1453
                null,
1454
                [],
1455
                [],
1456
                STUDENT_BOSS
1457
            );
1458
1459
            if ($getCount) {
1460
                $studentBossCount = $studentBossesList;
1461
            } else {
1462
                $studentBosses = [];
1463
                if (is_array($studentBossesList)) {
1464
                    foreach ($studentBossesList as $studentBossData) {
1465
                        $studentBosses[] = $studentBossData['user_id'];
1466
                    }
1467
                }
1468
            }
1469
1470
            $teacherList = SessionManager::getAllUsersFromCoursesFromAllSessionFromStatus(
1471
                'drh_all',
1472
                $userId,
1473
                $getCount,
1474
                null,
1475
                null,
1476
                null,
1477
                null,
1478
                null,
1479
                null,
1480
                null,
1481
                [],
1482
                [],
1483
                COURSEMANAGER
1484
            );
1485
1486
            if ($getCount) {
1487
                $teachersCount = $teacherList;
1488
            } else {
1489
                $teachers = [];
1490
                foreach ($teacherList as $teacherData) {
1491
                    $teachers[] = $teacherData['user_id'];
1492
                }
1493
            }
1494
1495
            $humanResources = SessionManager::getAllUsersFromCoursesFromAllSessionFromStatus(
1496
                'drh_all',
1497
                $userId,
1498
                $getCount,
1499
                null,
1500
                null,
1501
                null,
1502
                null,
1503
                null,
1504
                null,
1505
                null,
1506
                [],
1507
                [],
1508
                DRH
1509
            );
1510
1511
            if ($getCount) {
1512
                $drhCount = $humanResources;
1513
            } else {
1514
                $humanResourcesList = [];
1515
                if (is_array($humanResources)) {
1516
                    foreach ($humanResources as $item) {
1517
                        $humanResourcesList[] = $item['user_id'];
1518
                    }
1519
                }
1520
            }
1521
1522
            $platformCourses = SessionManager::getAllCoursesFollowedByUser(
1523
                $userId,
1524
                null,
1525
                null,
1526
                null,
1527
                null,
1528
                null,
1529
                $getCount
1530
            );
1531
1532
            if ($getCount) {
1533
                $courseCount = $platformCourses;
1534
            } else {
1535
                foreach ($platformCourses as $course) {
1536
                    $courses[$course['code']] = $course['code'];
1537
                }
1538
            }
1539
1540
            $sessions = SessionManager::get_sessions_followed_by_drh(
1541
                $userId,
1542
                null,
1543
                null,
1544
                false
1545
            );
1546
        } else {
1547
            $studentList = UserManager::getUsersFollowedByUser(
1548
                $userId,
1549
                STUDENT,
1550
                false,
1551
                false,
1552
                false,
1553
                null,
1554
                null,
1555
                null,
1556
                null,
1557
                null,
1558
                null,
1559
                COURSEMANAGER
1560
            );
1561
1562
            $students = [];
1563
            if (is_array($studentList)) {
1564
                foreach ($studentList as $studentData) {
1565
                    $students[] = $studentData['user_id'];
1566
                }
1567
            }
1568
1569
            $studentBossesList = UserManager::getUsersFollowedByUser(
1570
                $userId,
1571
                STUDENT_BOSS,
1572
                false,
1573
                false,
1574
                $getCount,
1575
                null,
1576
                null,
1577
                null,
1578
                null,
1579
                null,
1580
                null,
1581
                COURSEMANAGER
1582
            );
1583
1584
            if ($getCount) {
1585
                $studentBossCount = $studentBossesList;
1586
            } else {
1587
                $studentBosses = [];
1588
                if (is_array($studentBossesList)) {
1589
                    foreach ($studentBossesList as $studentBossData) {
1590
                        $studentBosses[] = $studentBossData['user_id'];
1591
                    }
1592
                }
1593
            }
1594
1595
            $teacherList = UserManager::getUsersFollowedByUser(
1596
                $userId,
1597
                COURSEMANAGER,
1598
                false,
1599
                false,
1600
                $getCount,
1601
                null,
1602
                null,
1603
                null,
1604
                null,
1605
                null,
1606
                null,
1607
                COURSEMANAGER
1608
            );
1609
1610
            if ($getCount) {
1611
                $teachersCount = $teacherList;
1612
            } else {
1613
                $teachers = [];
1614
                foreach ($teacherList as $teacherData) {
1615
                    $teachers[] = $teacherData['user_id'];
1616
                }
1617
            }
1618
1619
            $humanResources = UserManager::getUsersFollowedByUser(
1620
                $userId,
1621
                DRH,
1622
                false,
1623
                false,
1624
                $getCount,
1625
                null,
1626
                null,
1627
                null,
1628
                null,
1629
                null,
1630
                null,
1631
                COURSEMANAGER
1632
            );
1633
1634
            if ($getCount) {
1635
                $drhCount = $humanResources;
1636
            } else {
1637
                $humanResourcesList = [];
1638
                foreach ($humanResources as $item) {
1639
                    $humanResourcesList[] = $item['user_id'];
1640
                }
1641
            }
1642
1643
            $platformCourses = CourseManager::getCoursesFollowedByUser(
1644
                $userId,
1645
                COURSEMANAGER,
1646
                null,
1647
                null,
1648
                null,
1649
                null,
1650
                $getCount,
1651
                null,
1652
                null,
1653
                true
1654
            );
1655
1656
            if ($getCount) {
1657
                $assignedCourseCount = $platformCourses;
1658
            } else {
1659
                foreach ($platformCourses as $course) {
1660
                    $assignedCourses[$course['code']] = $course['code'];
1661
                }
1662
            }
1663
1664
            $platformCourses = CourseManager::getCoursesFollowedByUser(
1665
                $userId,
1666
                COURSEMANAGER,
1667
                null,
1668
                null,
1669
                null,
1670
                null,
1671
                $getCount
1672
            );
1673
1674
            if ($getCount) {
1675
                $courseCount = $platformCourses;
1676
            } else {
1677
                foreach ($platformCourses as $course) {
1678
                    $courses[$course['code']] = $course['code'];
1679
                }
1680
            }
1681
1682
            $sessions = SessionManager::getSessionsFollowedByUser(
1683
                $userId,
1684
                COURSEMANAGER,
1685
                null,
1686
                null,
1687
                false
1688
            );
1689
        }
1690
1691
        if ($getCount) {
1692
            return [
1693
                'drh' => $drhCount,
1694
                'teachers' => $teachersCount,
1695
                'student_count' => count($students),
1696
                'student_list' => $students,
1697
                'student_bosses' => $studentBossCount,
1698
                'courses' => $courseCount,
1699
                'session_count' => count($sessions),
1700
                'session_list' => $sessions,
1701
                'assigned_courses' => $assignedCourseCount,
1702
            ];
1703
        }
1704
1705
        return [
1706
            'drh' => $humanResourcesList,
1707
            'teachers' => $teachers,
1708
            'student_list' => $students,
1709
            'student_bosses' => $studentBosses,
1710
            'courses' => $courses,
1711
            'sessions' => $sessions,
1712
            'assigned_courses' => $assignedCourses,
1713
        ];
1714
    }
1715
1716
    /**
1717
     * Calculates the time spent on the platform by a user.
1718
     *
1719
     * @param int|array $userId
1720
     * @param string    $timeFilter       type of time filter: 'last_week' or 'custom'
1721
     * @param string    $start_date       start date date('Y-m-d H:i:s')
1722
     * @param string    $end_date         end date date('Y-m-d H:i:s')
1723
     * @param bool      $returnAllRecords
1724
     *
1725
     * @return int|array
1726
     */
1727
    public static function get_time_spent_on_the_platform(
1728
        $userId,
1729
        $timeFilter = 'last_7_days',
1730
        $start_date = null,
1731
        $end_date = null,
1732
        $returnAllRecords = false
1733
    ) {
1734
        $tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1735
        $condition_time = '';
1736
1737
        if (is_array($userId)) {
1738
            $userList = array_map('intval', $userId);
1739
            $userCondition = " login_user_id IN ('".implode("','", $userList)."')";
1740
        } else {
1741
            $userId = (int) $userId;
1742
            $userCondition = " login_user_id = $userId ";
1743
        }
1744
1745
        $url_condition = null;
1746
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1747
        $url_table = null;
1748
        $accessUrlUtil = Container::getAccessUrlUtil();
1749
        if ($accessUrlUtil->isMultiple()) {
1750
            $access_url_id = $accessUrlUtil->getCurrent()->getId();
1751
            $url_table = ", $tbl_url_rel_user as url_users";
1752
            $url_condition = " AND u.login_user_id = url_users.user_id AND access_url_id = $access_url_id";
1753
        }
1754
1755
        if (empty($timeFilter)) {
1756
            $timeFilter = 'last_week';
1757
        }
1758
1759
        $today = new DateTime('now', new DateTimeZone('UTC'));
1760
1761
        switch ($timeFilter) {
1762
            case 'last_7_days':
1763
                $newDate = new DateTime('-7 day', new DateTimeZone('UTC'));
1764
                $condition_time = " AND (login_date >= '{$newDate->format('Y-m-d H:i:s')}'";
1765
                $condition_time .= " AND logout_date <= '{$today->format('Y-m-d H:i:s')}') ";
1766
                break;
1767
            case 'last_30_days':
1768
                $newDate = new DateTime('-30 days', new DateTimeZone('UTC'));
1769
                $condition_time = " AND (login_date >= '{$newDate->format('Y-m-d H:i:s')}'";
1770
                $condition_time .= "AND logout_date <= '{$today->format('Y-m-d H:i:s')}') ";
1771
                break;
1772
            case 'wide':
1773
                if (!empty($start_date) && !empty($end_date)) {
1774
                    $start_date = Database::escape_string($start_date);
1775
                    $end_date = Database::escape_string($end_date);
1776
                    $condition_time = ' AND (
1777
                        (login_date >= "'.$start_date.'" AND login_date <= "'.$end_date.'") OR
1778
                        (logout_date >= "'.$start_date.'" AND logout_date <= "'.$end_date.'") OR
1779
                        (login_date <= "'.$start_date.'" AND logout_date >= "'.$end_date.'")
1780
                    ) ';
1781
                }
1782
                break;
1783
            case 'custom':
1784
                if (!empty($start_date) && !empty($end_date)) {
1785
                    $start_date = Database::escape_string($start_date);
1786
                    $end_date = Database::escape_string($end_date);
1787
                    $condition_time = ' AND (login_date >= "'.$start_date.'" AND logout_date <= "'.$end_date.'" ) ';
1788
                }
1789
                break;
1790
        }
1791
1792
        if ($returnAllRecords) {
1793
            $sql = "SELECT login_date, logout_date, TIMESTAMPDIFF(SECOND, login_date, logout_date) diff
1794
                    FROM $tbl_track_login u $url_table
1795
                    WHERE $userCondition $condition_time $url_condition
1796
                    ORDER BY login_date";
1797
            $rs = Database::query($sql);
1798
1799
            return Database::store_result($rs, 'ASSOC');
1800
        }
1801
1802
        $sql = "SELECT SUM(TIMESTAMPDIFF(SECOND, login_date, logout_date)) diff
1803
    	        FROM $tbl_track_login u $url_table
1804
                WHERE $userCondition $condition_time $url_condition";
1805
        $rs = Database::query($sql);
1806
        $row = Database::fetch_assoc($rs);
1807
        $diff = $row['diff'];
1808
1809
        if ($diff >= 0) {
1810
            return $diff;
1811
        }
1812
1813
        return -1;
1814
    }
1815
1816
    /**
1817
     * @param string $startDate
1818
     * @param string $endDate
1819
     *
1820
     * @return int
1821
     */
1822
    public static function getTotalTimeSpentOnThePlatform(
1823
        $startDate = '',
1824
        $endDate = ''
1825
    ) {
1826
        $tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1827
        $tbl_url_rel_user = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1828
1829
        $url_table = null;
1830
        $url_condition = null;
1831
        $accessUrlUtil = Container::getAccessUrlUtil();
1832
        if ($accessUrlUtil->isMultiple()) {
1833
            $access_url_id = $accessUrlUtil->getCurrent()->getId();
1834
            $url_table = ", ".$tbl_url_rel_user." as url_users";
1835
            $url_condition = " AND u.login_user_id = url_users.user_id AND access_url_id='$access_url_id'";
1836
        }
1837
1838
        if (!empty($startDate) && !empty($endDate)) {
1839
            $startDate = Database::escape_string($startDate);
1840
            $endDate = Database::escape_string($endDate);
1841
            $condition_time = ' (login_date >= "'.$startDate.'" AND logout_date <= "'.$endDate.'" ) ';
1842
        }
1843
        $sql = "SELECT SUM(TIMESTAMPDIFF(SECOND, login_date, logout_date)) diff
1844
    	        FROM $tbl_track_login u $url_table
1845
                WHERE $condition_time $url_condition";
1846
        $rs = Database::query($sql);
1847
        $row = Database::fetch_assoc($rs);
1848
        $diff = $row['diff'];
1849
1850
        if ($diff >= 0) {
1851
            return $diff;
1852
        }
1853
1854
        return -1;
1855
    }
1856
1857
    /**
1858
     * Checks if the "lp_minimum_time" feature is available for the course.
1859
     *
1860
     * @param int $sessionId
1861
     * @param int $courseId
1862
     *
1863
     * @return bool
1864
     */
1865
    public static function minimumTimeAvailable($sessionId, $courseId)
1866
    {
1867
        if ('true' !== api_get_setting('lp.lp_minimum_time')) {
1868
            return false;
1869
        }
1870
1871
        if (!empty($sessionId)) {
1872
            $extraFieldValue = new ExtraFieldValue('session');
1873
            $value = $extraFieldValue->get_values_by_handler_and_field_variable($sessionId, 'new_tracking_system');
1874
1875
            if ($value && isset($value['value']) && 1 == $value['value']) {
1876
                return true;
1877
            }
1878
        } else {
1879
            if ($courseId) {
1880
                $extraFieldValue = new ExtraFieldValue('course');
1881
                $value = $extraFieldValue->get_values_by_handler_and_field_variable($courseId, 'new_tracking_system');
1882
                if ($value && isset($value['value']) && 1 == $value['value']) {
1883
                    return true;
1884
                }
1885
            }
1886
        }
1887
1888
        return false;
1889
    }
1890
1891
    /**
1892
     * Calculates the time spent on the course.
1893
     *
1894
     * @param array|int $userId
1895
     * @param int       $courseId
1896
     * @param int       $sessionId
1897
     *
1898
     * @return int Time in seconds
1899
     */
1900
    public static function get_time_spent_on_the_course(
1901
        $userId,
1902
        $courseId,
1903
        $sessionId = 0
1904
    ) {
1905
        $courseId = (int) $courseId;
1906
1907
        if (empty($courseId) || empty($userId)) {
1908
            return 0;
1909
        }
1910
1911
        if (self::minimumTimeAvailable($sessionId, $courseId)) {
1912
            $courseTime = self::getCalculateTime($userId, $courseId, $sessionId);
1913
1914
            return isset($courseTime['total_time']) ? $courseTime['total_time'] : 0;
1915
        }
1916
1917
        $conditionUser = '';
1918
        $sessionId = (int) $sessionId;
1919
        if (is_array($userId)) {
1920
            $userId = array_map('intval', $userId);
1921
            $conditionUser = " AND user_id IN (".implode(',', $userId).") ";
1922
        } else {
1923
            if (!empty($userId)) {
1924
                $userId = (int) $userId;
1925
                $conditionUser = " AND user_id = $userId ";
1926
            }
1927
        }
1928
1929
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
1930
        $sql = "SELECT
1931
                SUM(UNIX_TIMESTAMP(logout_course_date) - UNIX_TIMESTAMP(login_course_date)) as nb_seconds
1932
                FROM $table
1933
                WHERE
1934
                    UNIX_TIMESTAMP(logout_course_date) > UNIX_TIMESTAMP(login_course_date) AND
1935
                    c_id = '$courseId' ";
1936
1937
        if (-1 != $sessionId) {
1938
            $sql .= "AND session_id = '$sessionId' ";
1939
        }
1940
1941
        $sql .= $conditionUser;
1942
1943
        $rs = Database::query($sql);
1944
        $row = Database::fetch_array($rs);
1945
1946
        return $row['nb_seconds'];
1947
    }
1948
1949
    /**
1950
     * Get first connection date for a student.
1951
     *
1952
     * @param int $student_id
1953
     *
1954
     * @return string|bool Date format long without day or false if there are no connections
1955
     */
1956
    public static function get_first_connection_date($student_id)
1957
    {
1958
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1959
        $sql = 'SELECT login_date
1960
                FROM '.$table.'
1961
                WHERE login_user_id = '.intval($student_id).'
1962
                ORDER BY login_date ASC
1963
                LIMIT 0,1';
1964
1965
        $rs = Database::query($sql);
1966
        if (Database::num_rows($rs) > 0) {
1967
            if ($first_login_date = Database::result($rs, 0, 0)) {
1968
                return api_convert_and_format_date(
1969
                    $first_login_date,
1970
                    DATE_FORMAT_SHORT
1971
                );
1972
            }
1973
        }
1974
1975
        return false;
1976
    }
1977
1978
    /**
1979
     * Get las connection date for a student.
1980
     *
1981
     * @param int  $student_id
1982
     * @param bool $warning_message  Show a warning message (optional)
1983
     * @param bool $return_timestamp True for returning results in timestamp (optional)
1984
     *
1985
     * @return string|int|bool Date format long without day, false if there are no connections or
1986
     *                         timestamp if parameter $return_timestamp is true
1987
     */
1988
    public static function get_last_connection_date(
1989
        $student_id,
1990
        $warning_message = false,
1991
        $return_timestamp = false
1992
    ) {
1993
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1994
        $sql = 'SELECT login_date
1995
                FROM '.$table.'
1996
                WHERE login_user_id = '.intval($student_id).'
1997
                ORDER BY login_date
1998
                DESC LIMIT 0,1';
1999
2000
        $rs = Database::query($sql);
2001
        if (Database::num_rows($rs) > 0) {
2002
            if ($last_login_date = Database::result($rs, 0, 0)) {
2003
                $last_login_date = api_get_local_time($last_login_date);
2004
                if ($return_timestamp) {
2005
                    return api_strtotime($last_login_date, 'UTC');
2006
                } else {
2007
                    if (!$warning_message) {
2008
                        return api_format_date($last_login_date, DATE_FORMAT_SHORT);
2009
                    } else {
2010
                        $timestamp = api_strtotime($last_login_date, 'UTC');
2011
                        $currentTimestamp = time();
2012
2013
                        //If the last connection is > than 7 days, the text is red
2014
                        //345600 = 7 days in seconds
2015
                        if ($currentTimestamp - $timestamp > 604800) {
2016
                            return '<em style="color: #F00;">'.api_format_date($last_login_date, DATE_FORMAT_SHORT).'</em>';
2017
                        } else {
2018
                            return api_format_date($last_login_date, DATE_FORMAT_SHORT);
2019
                        }
2020
                    }
2021
                }
2022
            }
2023
        }
2024
2025
        return false;
2026
    }
2027
2028
    /**
2029
     * Get first user's connection date on the course.
2030
     *
2031
     * @param int User id
2032
     * @param int $courseId
2033
     * @param int Session id (optional, default=0)
2034
     * @param bool $convert_date
2035
     *
2036
     * @return string|bool Date with format long without day or false if there is no date
2037
     */
2038
    public static function get_first_connection_date_on_the_course(
2039
        $student_id,
2040
        $courseId,
2041
        $sessionId = 0,
2042
        $convert_date = true
2043
    ) {
2044
        $student_id = (int) $student_id;
2045
        $courseId = (int) $courseId;
2046
        $sessionId = (int) $sessionId;
2047
2048
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2049
        $sql = 'SELECT login_course_date
2050
                FROM '.$table.'
2051
                WHERE
2052
                    user_id = '.$student_id.' AND
2053
                    c_id = '.$courseId.' AND
2054
                    session_id = '.$sessionId.'
2055
                ORDER BY login_course_date ASC
2056
                LIMIT 0,1';
2057
        $rs = Database::query($sql);
2058
        if (Database::num_rows($rs) > 0) {
2059
            if ($first_login_date = Database::result($rs, 0, 0)) {
2060
                if (empty($first_login_date)) {
2061
                    return false;
2062
                }
2063
2064
                if ($convert_date) {
2065
                    return api_convert_and_format_date(
2066
                        $first_login_date,
2067
                        DATE_FORMAT_SHORT
2068
                    );
2069
                }
2070
2071
                return $first_login_date;
2072
            }
2073
        }
2074
2075
        return false;
2076
    }
2077
2078
    /**
2079
     * Get last user's connection date on the course.
2080
     *
2081
     * @param     int         User id
2082
     * @param array $courseInfo real_id and code are used
2083
     * @param    int            Session id (optional, default=0)
2084
     * @param bool $convert_date
2085
     *
2086
     * @return string|bool Date with format long without day or false if there is no date
2087
     */
2088
    public static function get_last_connection_date_on_the_course(
2089
        $student_id,
2090
        $courseInfo,
2091
        $sessionId = 0,
2092
        $convert_date = true
2093
    ) {
2094
        // protect data
2095
        $student_id = (int) $student_id;
2096
        $sessionId = (int) $sessionId;
2097
2098
        if (empty($courseInfo) || empty($student_id)) {
2099
            return false;
2100
        }
2101
2102
        $courseId = (int) $courseInfo['real_id'];
2103
2104
        if (empty($courseId)) {
2105
            return false;
2106
        }
2107
2108
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2109
2110
        if (self::minimumTimeAvailable($sessionId, $courseId)) {
2111
            // Show the last date on which the user acceed the session when it was active
2112
            $where_condition = '';
2113
            $userInfo = api_get_user_info($student_id);
2114
            if (STUDENT == $userInfo['status'] && !empty($sessionId)) {
2115
                // fin de acceso a la sesión
2116
                $sessionInfo = SessionManager::fetch($sessionId);
2117
                $last_access = $sessionInfo['access_end_date'];
2118
                if (!empty($last_access)) {
2119
                    $where_condition = ' AND logout_course_date < "'.$last_access.'" ';
2120
                }
2121
            }
2122
            $sql = "SELECT logout_course_date
2123
                    FROM $table
2124
                    WHERE   user_id = $student_id AND
2125
                            c_id = $courseId AND
2126
                            session_id = $sessionId $where_condition
2127
                    ORDER BY logout_course_date DESC
2128
                    LIMIT 0,1";
2129
2130
            $rs = Database::query($sql);
2131
            if (Database::num_rows($rs) > 0) {
2132
                if ($last_login_date = Database::result($rs, 0, 0)) {
2133
                    if (empty($last_login_date)) {
2134
                        return false;
2135
                    }
2136
                    if ($convert_date) {
2137
                        return api_convert_and_format_date($last_login_date, DATE_FORMAT_SHORT);
2138
                    }
2139
2140
                    return $last_login_date;
2141
                }
2142
            }
2143
        } else {
2144
            $sql = "SELECT logout_course_date
2145
                    FROM $table
2146
                    WHERE   user_id = $student_id AND
2147
                            c_id = $courseId AND
2148
                            session_id = $sessionId
2149
                    ORDER BY logout_course_date DESC
2150
                    LIMIT 0,1";
2151
2152
            $rs = Database::query($sql);
2153
            if (Database::num_rows($rs) > 0) {
2154
                if ($last_login_date = Database::result($rs, 0, 0)) {
2155
                    if (empty($last_login_date)) {
2156
                        return false;
2157
                    }
2158
                    //see #5736
2159
                    $last_login_date_timestamp = api_strtotime($last_login_date);
2160
                    $now = time();
2161
                    //If the last connection is > than 7 days, the text is red
2162
                    //345600 = 7 days in seconds
2163
                    if ($now - $last_login_date_timestamp > 604800) {
2164
                        if ($convert_date) {
2165
                            $last_login_date = api_convert_and_format_date($last_login_date, DATE_FORMAT_SHORT);
2166
                            $icon = null;
2167
                            if (api_is_allowed_to_edit()) {
2168
                                $url = api_get_path(WEB_CODE_PATH).
2169
                                    'announcements/announcements.php?action=add&remind_inactive='.$student_id.'&cid='.$courseInfo['real_id'];
2170
                                $icon = '<a href="'.$url.'" title="'.get_lang('Remind inactive user').'">
2171
                                  '.Display::getMdiIcon(
2172
                                        StateIcon::WARNING,
2173
                                        'ch-tool-icon',
2174
                                        null,
2175
                                        ICON_SIZE_SMALL
2176
                                    ).'
2177
                                 </a>';
2178
                            }
2179
2180
                            return $icon.Display::label($last_login_date, 'warning');
2181
                        }
2182
2183
                        return $last_login_date;
2184
                    } else {
2185
                        if ($convert_date) {
2186
                            return api_convert_and_format_date($last_login_date, DATE_FORMAT_SHORT);
2187
                        }
2188
2189
                        return $last_login_date;
2190
                    }
2191
                }
2192
            }
2193
        }
2194
2195
        return false;
2196
    }
2197
2198
    public static function getLastConnectionInAnyCourse($studentId)
2199
    {
2200
        $studentId = (int) $studentId;
2201
2202
        if (empty($studentId)) {
2203
            return false;
2204
        }
2205
2206
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2207
        $sql = "SELECT logout_course_date
2208
                FROM $table
2209
                WHERE user_id = $studentId
2210
                ORDER BY logout_course_date DESC
2211
                LIMIT 1";
2212
        $result = Database::query($sql);
2213
        if (Database::num_rows($result)) {
2214
            $row = Database::fetch_array($result);
2215
2216
            return $row['logout_course_date'];
2217
        }
2218
2219
        return false;
2220
    }
2221
2222
    /**
2223
     * Get last course access by course/session.
2224
     */
2225
    public static function getLastConnectionDateByCourse($courseId, $sessionId = 0)
2226
    {
2227
        $courseId = (int) $courseId;
2228
        $sessionId = (int) $sessionId;
2229
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2230
2231
        $sql = "SELECT logout_course_date
2232
                FROM $table
2233
                WHERE
2234
                        c_id = $courseId AND
2235
                        session_id = $sessionId
2236
                ORDER BY logout_course_date DESC
2237
                LIMIT 0,1";
2238
2239
        $result = Database::query($sql);
2240
        if (Database::num_rows($result)) {
2241
            $row = Database::fetch_array($result);
2242
            if ($row) {
2243
                return $row['logout_course_date'];
2244
            }
2245
        }
2246
2247
        return '';
2248
    }
2249
2250
    /**
2251
     * Get count of the connections to the course during a specified period.
2252
     *
2253
     * @param int $courseId
2254
     * @param   int     Session id (optional)
2255
     * @param   int     Datetime from which to collect data (defaults to 0)
2256
     * @param   int     Datetime to which to collect data (defaults to now)
2257
     *
2258
     * @return int count connections
2259
     */
2260
    public static function get_course_connections_count(
2261
        $courseId,
2262
        $sessionId = 0,
2263
        $start = 0,
2264
        $stop = null
2265
    ) {
2266
        if ($start < 0) {
2267
            $start = 0;
2268
        }
2269
        if (!isset($stop) || $stop < 0) {
2270
            $stop = api_get_utc_datetime();
2271
        }
2272
2273
        // Given we're storing in cache, round the start and end times
2274
        // to the lower minute
2275
        $roundedStart = substr($start, 0, -2).'00';
2276
        $roundedStop = substr($stop, 0, -2).'00';
2277
        $roundedStart = Database::escape_string($roundedStart);
2278
        $roundedStop = Database::escape_string($roundedStop);
2279
        $month_filter = " AND login_course_date > '$roundedStart' AND login_course_date < '$roundedStop' ";
2280
        $courseId = (int) $courseId;
2281
        $sessionId = (int) $sessionId;
2282
        $count = 0;
2283
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
2284
        $sql = "SELECT count(*) as count_connections
2285
                FROM $table
2286
                WHERE
2287
                    c_id = $courseId AND
2288
                    session_id = $sessionId
2289
                    $month_filter";
2290
2291
        //This query can be very slow (several seconds on an indexed table
2292
        // with 14M rows). As such, we'll try to use APCu if it is
2293
        // available to store the resulting value for a few seconds
2294
        $cacheAvailable = api_get_configuration_value('apc');
2295
        if (true === $cacheAvailable) {
2296
            $apc = apcu_cache_info(true);
2297
            $apc_end = $apc['start_time'] + $apc['ttl'];
2298
            $apc_var = api_get_configuration_value('apc_prefix').'course_access_'.$courseId.'_'.$sessionId.'_'.strtotime($roundedStart).'_'.strtotime($roundedStop);
2299
            if (apcu_exists($apc_var) && (time() < $apc_end) &&
2300
                apcu_fetch($apc_var) > 0
2301
            ) {
2302
                $count = apcu_fetch($apc_var);
2303
            } else {
2304
                $rs = Database::query($sql);
2305
                if (Database::num_rows($rs) > 0) {
2306
                    $row = Database::fetch_object($rs);
2307
                    $count = $row->count_connections;
2308
                }
2309
                apcu_clear_cache();
2310
                apcu_store($apc_var, $count, 60);
2311
            }
2312
        } else {
2313
            $rs = Database::query($sql);
2314
            if (Database::num_rows($rs) > 0) {
2315
                $row = Database::fetch_object($rs);
2316
                $count = $row->count_connections;
2317
            }
2318
        }
2319
2320
        return $count;
2321
    }
2322
2323
    /**
2324
     * Get count courses per student.
2325
     *
2326
     * @param int  $user_id          Student id
2327
     * @param bool $include_sessions Include sessions (optional)
2328
     *
2329
     * @return int count courses
2330
     */
2331
    public static function count_course_per_student($user_id, $include_sessions = true)
2332
    {
2333
        $user_id = (int) $user_id;
2334
        $tbl_course_rel_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
2335
        $tbl_session_course_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
2336
2337
        $sql = 'SELECT DISTINCT c_id
2338
                FROM '.$tbl_course_rel_user.'
2339
                WHERE user_id = '.$user_id.' AND relation_type<>'.COURSE_RELATION_TYPE_RRHH;
2340
        $rs = Database::query($sql);
2341
        $nb_courses = Database::num_rows($rs);
2342
2343
        if ($include_sessions) {
2344
            $sql = 'SELECT DISTINCT c_id
2345
                    FROM '.$tbl_session_course_rel_user.'
2346
                    WHERE user_id = '.$user_id;
2347
            $rs = Database::query($sql);
2348
            $nb_courses += Database::num_rows($rs);
2349
        }
2350
2351
        return $nb_courses;
2352
    }
2353
2354
    /**
2355
     * Gets the score average from all tests in a course by student.
2356
     *
2357
     * @param $student_id
2358
     * @param $course_code
2359
     * @param int  $exercise_id
2360
     * @param null $sessionId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $sessionId is correct as it would always require null to be passed?
Loading history...
2361
     * @param int  $active_filter 2 for consider all tests
2362
     *                            1 for active <> -1
2363
     *                            0 for active <> 0
2364
     * @param int  $into_lp       1 for all exercises
2365
     *                            0 for without LP
2366
     * @param mixed id
2367
     * @param string code
2368
     * @param int id (optional), filtered by exercise
2369
     * @param int id (optional), if param $sessionId is null
2370
     *                                               it'll return results including sessions, 0 = session is not
2371
     *                                               filtered
2372
     *
2373
     * @return string value (number %) Which represents a round integer about the score average
2374
     */
2375
    public static function get_avg_student_exercise_score(
2376
        $student_id,
2377
        $course_code,
2378
        $exercise_id = 0,
2379
        $sessionId = null,
2380
        $active_filter = 1,
2381
        $into_lp = 0
2382
    ) {
2383
        $course_code = Database::escape_string($course_code);
2384
        $course_info = api_get_course_info($course_code);
2385
        if (!empty($course_info)) {
2386
            // table definition
2387
            $tbl_course_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
2388
            $tbl_stats_exercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2389
2390
            // Compose a filter based on optional exercise given
2391
            $condition_quiz = '';
2392
            if (!empty($exercise_id)) {
2393
                $exercise_id = (int) $exercise_id;
2394
                $condition_quiz = " AND iid = $exercise_id ";
2395
            }
2396
2397
            // Compose a filter based on optional session id given
2398
            $sessionCondition = '';
2399
            $session = null;
2400
            if ($sessionId !== null) {
2401
                $session = api_get_session_entity($sessionId);
2402
                $sessionCondition = api_get_session_condition($sessionId);
2403
            }
2404
2405
            $condition_active = '';
2406
            if (1 == $active_filter) {
2407
                $condition_active = 'AND active <> -1';
2408
            } elseif (0 == $active_filter) {
2409
                $condition_active = 'AND active <> 0';
2410
            }
2411
            $condition_into_lp = '';
2412
            $select_lp_id = '';
2413
            if (0 == $into_lp) {
2414
                $condition_into_lp = 'AND orig_lp_id = 0 AND orig_lp_item_id = 0';
2415
            } else {
2416
                $select_lp_id = ', orig_lp_id as lp_id ';
2417
            }
2418
2419
            $quizRepo = Container::getQuizRepository();
2420
            $course = api_get_course_entity($course_info['real_id']);
2421
            $qb = $quizRepo->getResourcesByCourse($course, $session, null, null, false);
2422
            $qb
2423
                ->select('count(resource)')
2424
                ->setMaxResults(1);
2425
            $count_quiz = $qb->getQuery()->getSingleScalarResult();
2426
2427
            /*$sql = "SELECT count(iid)
2428
    		        FROM $tbl_course_quiz
2429
    				WHERE c_id = {$course_info['real_id']} $condition_active $condition_quiz ";
2430
            $count_quiz = 0;
2431
            $countQuizResult = Database::query($sql);
2432
            if (!empty($countQuizResult)) {
2433
                $count_quiz = Database::fetch_row($countQuizResult);
2434
            }*/
2435
            if (!empty($count_quiz) && !empty($student_id)) {
2436
                if (is_array($student_id)) {
2437
                    $student_id = array_map('intval', $student_id);
2438
                    $condition_user = " AND exe_user_id IN (".implode(',', $student_id).") ";
2439
                } else {
2440
                    $student_id = (int) $student_id;
2441
                    $condition_user = " AND exe_user_id = '$student_id' ";
2442
                }
2443
2444
                if (empty($exercise_id)) {
2445
                    $sql = "SELECT iid FROM $tbl_course_quiz
2446
                            WHERE c_id = {$course_info['real_id']} $condition_active $condition_quiz";
2447
                    $result = Database::query($sql);
2448
                    $exercise_list = [];
2449
                    $exercise_id = null;
2450
                    if (!empty($result) && Database::num_rows($result)) {
2451
                        while ($row = Database::fetch_array($result)) {
2452
                            $exercise_list[] = $row['iid'];
2453
                        }
2454
                    }
2455
                    if (!empty($exercise_list)) {
2456
                        $exercise_id = implode("','", $exercise_list);
2457
                    }
2458
                }
2459
2460
                $sql = "SELECT
2461
                        SUM(score/max_score*100) as avg_score,
2462
                        COUNT(*) as num_attempts
2463
                        $select_lp_id
2464
                        FROM $tbl_stats_exercise
2465
                        WHERE
2466
                            exe_exo_id IN ('".$exercise_id."')
2467
                            $condition_user AND
2468
                            status = '' AND
2469
                            c_id = {$course_info['real_id']}
2470
                            $sessionCondition
2471
                            $condition_into_lp
2472
                        ORDER BY exe_date DESC";
2473
2474
                $res = Database::query($sql);
2475
                $row = Database::fetch_array($res);
2476
                $quiz_avg_score = null;
2477
2478
                if (!empty($row['avg_score'])) {
2479
                    $quiz_avg_score = round($row['avg_score'], 2);
2480
                }
2481
2482
                if (!empty($row['num_attempts'])) {
2483
                    $quiz_avg_score = round($quiz_avg_score / $row['num_attempts'], 2);
2484
                }
2485
                if (is_array($student_id)) {
2486
                    $quiz_avg_score = round($quiz_avg_score / count($student_id), 2);
2487
                }
2488
                if (0 == $into_lp) {
2489
                    return $quiz_avg_score;
2490
                } else {
2491
                    if (!empty($row['lp_id'])) {
2492
                        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2493
                        $sql = "SELECT title FROM $tbl_lp WHERE iid = ".(int) $row['lp_id'];
2494
                        $result = Database::query($sql);
2495
                        $row_lp = Database::fetch_row($result);
2496
                        $lp_name = null;
2497
                        if ($row_lp && isset($row_lp[0])) {
2498
                            $lp_name = $row_lp[0];
2499
                        }
2500
2501
                        return [$quiz_avg_score, $lp_name];
2502
                    }
2503
2504
                    return [$quiz_avg_score, null];
2505
                }
2506
            }
2507
        }
2508
2509
        return null;
2510
    }
2511
2512
    /**
2513
     * Get count student's exercise COMPLETED attempts.
2514
     *
2515
     * @param int $student_id
2516
     * @param int $courseId
2517
     * @param int $exercise_id
2518
     * @param int $lp_id
2519
     * @param int $lp_item_id
2520
     * @param int $sessionId
2521
     * @param int $find_all_lp 0 = just LP specified
2522
     *                         1 = LP specified or whitout LP,
2523
     *                         2 = all rows
2524
     *
2525
     * @internal param \Student $int id
2526
     * @internal param \Course $string code
2527
     * @internal param \Exercise $int id
2528
     * @internal param \Learning $int path id (optional),
2529
     * for showing attempts inside a learning path $lp_id and $lp_item_id params are required
2530
     * @internal param \Learning $int path item id (optional),
2531
     * for showing attempts inside a learning path $lp_id and $lp_item_id params are required
2532
     *
2533
     * @return int count of attempts
2534
     */
2535
    public static function count_student_exercise_attempts(
2536
        $student_id,
2537
        $courseId,
2538
        $exercise_id,
2539
        $lp_id = 0,
2540
        $lp_item_id = 0,
2541
        $sessionId = 0,
2542
        $find_all_lp = 0
2543
    ) {
2544
        $courseId = intval($courseId);
2545
        $student_id = intval($student_id);
2546
        $exercise_id = intval($exercise_id);
2547
        $sessionId = intval($sessionId);
2548
2549
        $lp_id = intval($lp_id);
2550
        $lp_item_id = intval($lp_item_id);
2551
        $tbl_stats_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2552
        $sessionCondition = api_get_session_condition($sessionId);
2553
        $sql = "SELECT COUNT(exe_id) as essais
2554
                FROM $tbl_stats_exercises
2555
                WHERE
2556
                    c_id = $courseId AND
2557
                    exe_exo_id = $exercise_id AND
2558
                    status = '' AND
2559
                    exe_user_id= $student_id
2560
                    $sessionCondition
2561
                    ";
2562
2563
        if (1 == $find_all_lp) {
2564
            $sql .= "AND (orig_lp_id = $lp_id OR orig_lp_id = 0)
2565
                AND (orig_lp_item_id = $lp_item_id OR orig_lp_item_id = 0)";
2566
        } elseif (0 == $find_all_lp) {
2567
            $sql .= "AND orig_lp_id = $lp_id
2568
                AND orig_lp_item_id = $lp_item_id";
2569
        }
2570
2571
        $rs = Database::query($sql);
2572
        $row = Database::fetch_row($rs);
2573
        $count_attempts = $row[0];
2574
2575
        return $count_attempts;
2576
    }
2577
2578
    /**
2579
     * Get count student's exercise progress.
2580
     *
2581
     * @param CQuiz[] $exerciseList
2582
     * @param int     $user_id
2583
     * @param int     $courseId
2584
     * @param int     $sessionId
2585
     *
2586
     * @return string
2587
     */
2588
    public static function get_exercise_student_progress(
2589
        $exerciseList,
2590
        $user_id,
2591
        $courseId,
2592
        $sessionId
2593
    ) {
2594
        $courseId = (int) $courseId;
2595
        $user_id = (int) $user_id;
2596
        $sessionId = (int) $sessionId;
2597
2598
        if (empty($exerciseList)) {
2599
            return '0%';
2600
        }
2601
        $tbl_stats_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2602
        $exerciseIdList = [];
2603
        foreach ($exerciseList as $exercise) {
2604
            $exerciseIdList[] = $exercise->getIid();
2605
        }
2606
        $exercise_list_imploded = implode("' ,'", $exerciseIdList);
2607
        $sessionCondition = api_get_session_condition($sessionId);
2608
        $sql = "SELECT COUNT(DISTINCT exe_exo_id)
2609
                FROM $tbl_stats_exercises
2610
                WHERE
2611
                    c_id = $courseId AND
2612
                    session_id  = $sessionId AND
2613
                    exe_user_id = $user_id AND
2614
                    status = '' AND
2615
                    exe_exo_id IN ('$exercise_list_imploded')
2616
                    $sessionCondition
2617
                    ";
2618
2619
        $rs = Database::query($sql);
2620
        $count = 0;
2621
        if ($rs) {
2622
            $row = Database::fetch_row($rs);
2623
            $count = (int) $row[0];
2624
        }
2625
        $count = (0 != $count) ? 100 * round($count / count($exerciseList), 2).'%' : '0%';
2626
2627
        return $count;
2628
    }
2629
2630
    /**
2631
     * @param CQuiz $exercise_list
2632
     * @param int   $user_id
2633
     * @param int   $courseId
2634
     * @param int   $sessionId
2635
     *
2636
     * @return string
2637
     */
2638
    public static function get_exercise_student_average_best_attempt(
2639
        $exercise_list,
2640
        $user_id,
2641
        $courseId,
2642
        $sessionId
2643
    ) {
2644
        $result = 0;
2645
        if (!empty($exercise_list) && (is_array($exercise_list) || $exercise_list instanceof \Countable)) {
2646
            foreach ($exercise_list as $exercise_data) {
2647
                $exercise_id = $exercise_data->getIid();
2648
                $best_attempt = Event::get_best_attempt_exercise_results_per_user(
2649
                    $user_id,
2650
                    $exercise_id,
2651
                    $courseId,
2652
                    $sessionId,
2653
                    false
2654
                );
2655
2656
                if (!empty($best_attempt) && !empty($best_attempt['max_score'])) {
2657
                    $result += $best_attempt['score'] / $best_attempt['max_score'];
2658
                }
2659
            }
2660
2661
            if (count($exercise_list) > 0) {
2662
                $result = $result / count($exercise_list);
2663
                $result = round($result, 2) * 100;
2664
            }
2665
        }
2666
2667
        return $result.'%';
2668
    }
2669
2670
    /**
2671
     * Returns the average student progress in the learning paths of the given
2672
     * course, it will take into account the progress that were not started.
2673
     *
2674
     * @param int|array     $studentId
2675
     * @param Course        $course          The course object
2676
     * @param array         $lpIdList        Limit average to listed lp ids
2677
     * @param SessionEntity $session         Session id (optional),
2678
     *                                       if parameter $sessionId is null(default) it'll return results including
2679
     *                                       sessions, 0 = session is not filtered
2680
     * @param bool          $returnArray     Will return an array of the type:
2681
     *                                       [sum_of_progresses, number] if it is set to true
2682
     * @param bool          $onlySeriousGame Optional. Limit average to lp on seriousgame mode
2683
     *
2684
     * @return float Average progress of the user in this course from 0 to 100
2685
     */
2686
    public static function get_avg_student_progress(
2687
        $studentId,
2688
        Course $course = null,
2689
        $lpIdList = [],
2690
        SessionEntity $session = null,
2691
        $returnArray = false,
2692
        $onlySeriousGame = false
2693
    ) {
2694
        // If there is at least one learning path and one student.
2695
        if (empty($studentId)) {
2696
            return false;
2697
        }
2698
        if (empty($course)) {
2699
            return false;
2700
        }
2701
2702
        $repo = Container::getLpRepository();
2703
        $qb = $repo->findAllByCourse($course, $session);
2704
        $lps = $qb->getQuery()->getResult();
2705
        $filteredLP = [];
2706
2707
        $sessionId = null !== $session ? $session->getId() : 0;
2708
2709
        /** @var CLp $lp */
2710
        foreach ($lps as $lp) {
2711
            $filteredLP[] = $lp->getIid();
2712
        }
2713
2714
        if (empty($filteredLP)) {
2715
            return false;
2716
        }
2717
2718
        $lpViewTable = Database::get_course_table(TABLE_LP_VIEW);
2719
        /*$lpConditions = [];
2720
        $lpConditions['c_id = ? '] = $courseInfo['real_id'];
2721
2722
        if ($sessionId > 0) {
2723
            $lpConditions['AND (session_id = ? OR session_id = 0 OR session_id IS NULL)'] = $sessionId;
2724
        } else {
2725
            $lpConditions['AND session_id = ?'] = $sessionId;
2726
        }
2727
2728
        if (is_array($lpIdList) && count($lpIdList) > 0) {
2729
            $placeHolders = [];
2730
            for ($i = 0; $i < count($lpIdList); $i++) {
2731
                $placeHolders[] = '?';
2732
            }
2733
            $lpConditions['AND iid IN('.implode(', ', $placeHolders).') '] = $lpIdList;
2734
        }
2735
2736
        if ($onlySeriousGame) {
2737
            $lpConditions['AND seriousgame_mode = ? '] = true;
2738
        }
2739
2740
        $resultLP = Database::select(
2741
            'iid',
2742
            $lPTable,
2743
            ['where' => $lpConditions]
2744
        );
2745
        $filteredLP = array_keys($resultLP);
2746
2747
        if (empty($filteredLP)) {
2748
            return false;
2749
        }*/
2750
2751
        $conditions = [
2752
            //" c_id = {$courseInfo['real_id']} ",
2753
            " lp_view.lp_id IN (".implode(', ', $filteredLP).") ",
2754
        ];
2755
2756
        $groupBy = 'GROUP BY lp_id';
2757
2758
        if (is_array($studentId)) {
2759
            $studentId = array_map('intval', $studentId);
2760
            $conditions[] = " lp_view.user_id IN (".implode(',', $studentId).")  ";
2761
        } else {
2762
            $studentId = (int) $studentId;
2763
            $conditions[] = " lp_view.user_id = '$studentId' ";
2764
2765
            if (empty($lpIdList)) {
2766
                $lpList = new LearnpathList(
2767
                    $studentId,
2768
                    ['real_id' => $course->getId()],
2769
                    $sessionId,
2770
                    null,
2771
                    false,
2772
                    null,
2773
                    true
2774
                );
2775
                $lpList = $lpList->get_flat_list();
2776
                if (!empty($lpList)) {
2777
                    /** @var $lp */
2778
                    foreach ($lpList as $lpId => $lp) {
2779
                        $lpIdList[] = $lp['lp_old_id'];
2780
                    }
2781
                }
2782
            }
2783
        }
2784
2785
        if (!empty($sessionId)) {
2786
            $conditions[] = " session_id = $sessionId ";
2787
        } else {
2788
            $conditions[] = ' (session_id = 0 OR session_id IS NULL) ';
2789
        }
2790
2791
        $conditionToString = implode('AND', $conditions);
2792
        $sql = "SELECT lp_id, view_count, progress
2793
                FROM $lpViewTable lp_view
2794
                WHERE
2795
                    $conditionToString
2796
                    $groupBy
2797
                ORDER BY view_count DESC";
2798
2799
        $result = Database::query($sql);
2800
2801
        $progress = [];
2802
        $viewCount = [];
2803
        while ($row = Database::fetch_assoc($result)) {
2804
            if (!isset($viewCount[$row['lp_id']])) {
2805
                $progress[$row['lp_id']] = $row['progress'];
2806
            }
2807
            $viewCount[$row['lp_id']] = $row['view_count'];
2808
        }
2809
2810
        // Fill with lp ids
2811
        $newProgress = [];
2812
        if (!empty($lpIdList)) {
2813
            foreach ($lpIdList as $lpId) {
2814
                if (isset($progress[$lpId])) {
2815
                    $newProgress[] = $progress[$lpId];
2816
                }
2817
            }
2818
            $total = count($lpIdList);
2819
        } else {
2820
            $newProgress = $progress;
2821
            $total = count($newProgress);
2822
        }
2823
2824
        $average = 0;
2825
        $sum = 0;
2826
        if (!empty($newProgress)) {
2827
            $sum = array_sum($newProgress);
2828
            $average = $sum / $total;
2829
        }
2830
2831
        if ($returnArray) {
2832
            return [
2833
                $sum,
2834
                $total,
2835
            ];
2836
        }
2837
2838
        return round($average, 1);
2839
    }
2840
2841
    /**
2842
     * This function gets:
2843
     * 1. The score average from all SCORM Test items in all LP in a course-> All the answers / All the max scores.
2844
     * 2. The score average from all Tests (quiz) in all LP in a course-> All the answers / All the max scores.
2845
     * 3. And finally it will return the average between 1. and 2.
2846
     *
2847
     * @param mixed         $student_id                      Array of user ids or an user id
2848
     * @param array         $lp_ids                          List of LP ids
2849
     * @param SessionEntity $session
2850
     *                                                       if param $sessionId is null(default) it'll return results
2851
     *                                                       including sessions, 0 = session is not filtered
2852
     * @param bool          $return_array                    Returns an array of the
2853
     *                                                       type [sum_score, num_score] if set to true
2854
     * @param bool          $get_only_latest_attempt_results get only the latest attempts or ALL attempts
2855
     * @param bool          $getOnlyBestAttempt
2856
     *
2857
     * @return string value (number %) Which represents a round integer explain in got in 3
2858
     *
2859
     * @todo improve performance, when loading 1500 users with 20 lps the script dies
2860
     * This function does not take the results of a Test out of a LP
2861
     */
2862
    public static function get_avg_student_score(
2863
        $student_id,
2864
        Course $course,
2865
        $lp_ids = [],
2866
        SessionEntity $session = null,
2867
        $return_array = false,
2868
        $get_only_latest_attempt_results = false,
2869
        $getOnlyBestAttempt = false
2870
    ) {
2871
        if (empty($student_id)) {
2872
            return null;
2873
        }
2874
2875
        $debug = false;
2876
        $tbl_stats_exercices = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
2877
        $tbl_stats_attempts = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
2878
2879
        // Get course tables names
2880
        $tbl_quiz_questions = Database::get_course_table(TABLE_QUIZ_QUESTION);
2881
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
2882
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
2883
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
2884
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2885
        $courseId = $course->getId();
2886
2887
        // Compose a filter based on optional learning paths list given
2888
        $condition_lp = '';
2889
        if (count($lp_ids) > 0) {
2890
            $condition_lp = " iid IN(".implode(',', $lp_ids).") ";
2891
        }
2892
2893
        // Compose a filter based on optional session id
2894
        $sessionId = null;
2895
        if (null !== $session) {
2896
            $sessionId = $session->getId();
2897
        }
2898
        $sessionCondition = api_get_session_condition($sessionId);
2899
2900
        $lp_list = $use_max_score = [];
2901
        if (empty($condition_lp)) {
2902
            $repo = Container::getLpRepository();
2903
            $qb = $repo->findAllByCourse($course, $session);
2904
            $lps = $qb->getQuery()->getResult();
2905
            /** @var CLp $lp */
2906
            foreach ($lps as $lp) {
2907
                $lpId = $lp->getIid();
2908
                $lp_list[] = $lpId;
2909
                $use_max_score[$lpId] = $lp->getUseMaxScore();
2910
            }
2911
        } else {
2912
            $sql = "SELECT DISTINCT(iid), use_max_score
2913
                    FROM $lp_table
2914
                    WHERE $condition_lp ";
2915
            $res_row_lp = Database::query($sql);
2916
            while ($row_lp = Database::fetch_array($res_row_lp)) {
2917
                $lp_list[] = $row_lp['iid'];
2918
                $use_max_score[$row_lp['iid']] = $row_lp['use_max_score'];
2919
            }
2920
        }
2921
2922
        if (empty($lp_list)) {
2923
            return null;
2924
        }
2925
2926
        // prepare filter on users
2927
        if (is_array($student_id)) {
2928
            array_walk($student_id, 'intval');
2929
            $condition_user1 = " AND user_id IN (".implode(',', $student_id).") ";
2930
        } else {
2931
            $condition_user1 = " AND user_id = $student_id ";
2932
        }
2933
2934
        // Getting latest LP result for a student
2935
        //@todo problem when a  course have more than 1500 users
2936
        $sql = "SELECT MAX(view_count) as vc, iid, progress, lp_id, user_id
2937
                FROM $lp_view_table
2938
                WHERE
2939
                    lp_id IN (".implode(',', $lp_list).")
2940
                    $condition_user1
2941
                    $sessionCondition
2942
                GROUP BY lp_id, user_id";
2943
        //AND        session_id = $sessionId
2944
2945
        $rs_last_lp_view_id = Database::query($sql);
2946
        $global_result = 0;
2947
        if (Database::num_rows($rs_last_lp_view_id) > 0) {
2948
            // Cycle through each line of the results (grouped by lp_id, user_id)
2949
            while ($row_lp_view = Database::fetch_array($rs_last_lp_view_id)) {
2950
                $count_items = 0;
2951
                $lpPartialTotal = 0;
2952
                $list = [];
2953
                $lp_view_id = $row_lp_view['iid'];
2954
                $lp_id = $row_lp_view['lp_id'];
2955
                $user_id = $row_lp_view['user_id'];
2956
2957
                if ($debug) {
2958
                    echo '<h2>LP id '.$lp_id.'</h2>';
2959
                    echo "get_only_latest_attempt_results: $get_only_latest_attempt_results <br />";
2960
                    echo "getOnlyBestAttempt: $getOnlyBestAttempt <br />";
2961
                }
2962
2963
                if ($get_only_latest_attempt_results || $getOnlyBestAttempt) {
2964
                    // Getting lp_items done by the user
2965
                    $sql = "SELECT DISTINCT lp_item_id
2966
                            FROM $lp_item_view_table
2967
                            WHERE
2968
                                lp_view_id = $lp_view_id
2969
                            ORDER BY lp_item_id";
2970
                    $res_lp_item = Database::query($sql);
2971
2972
                    while ($row_lp_item = Database::fetch_assoc($res_lp_item)) {
2973
                        $my_lp_item_id = $row_lp_item['lp_item_id'];
2974
                        $order = ' view_count DESC';
2975
                        if ($getOnlyBestAttempt) {
2976
                            $order = ' lp_iv.score DESC';
2977
                        }
2978
2979
                        // Getting the most recent attempt
2980
                        $sql = "SELECT
2981
                                    lp_iv.iid as lp_item_view_id,
2982
                                    lp_iv.score as score,
2983
                                    lp_i.max_score,
2984
                                    lp_iv.max_score as max_score_item_view,
2985
                                    lp_i.path,
2986
                                    lp_i.item_type,
2987
                                    lp_i.iid
2988
                                FROM $lp_item_view_table as lp_iv
2989
                                INNER JOIN $lp_item_table as lp_i
2990
                                ON (
2991
                                    lp_i.iid = lp_iv.lp_item_id
2992
                                )
2993
                                WHERE
2994
                                    lp_item_id = $my_lp_item_id AND
2995
                                    lp_view_id = $lp_view_id AND
2996
                                    (lp_i.item_type='sco' OR lp_i.item_type='".TOOL_QUIZ."')
2997
                                ORDER BY $order
2998
                                LIMIT 1";
2999
3000
                        $res_lp_item_result = Database::query($sql);
3001
                        while ($row_max_score = Database::fetch_assoc($res_lp_item_result)) {
3002
                            $list[] = $row_max_score;
3003
                        }
3004
                    }
3005
                } else {
3006
                    // For the currently analysed view, get the score and
3007
                    // max_score of each item if it is a sco or a TOOL_QUIZ
3008
                    $sql = "SELECT
3009
                                lp_iv.iid as lp_item_view_id,
3010
                                lp_iv.score as score,
3011
                                lp_i.max_score,
3012
                                lp_iv.max_score as max_score_item_view,
3013
                                lp_i.path,
3014
                                lp_i.item_type,
3015
                                lp_i.iid
3016
                              FROM $lp_item_view_table as lp_iv
3017
                              INNER JOIN $lp_item_table as lp_i
3018
                              ON lp_i.iid = lp_iv.lp_item_id
3019
                              WHERE
3020
                                lp_view_id = $lp_view_id AND
3021
                                (lp_i.item_type='sco' OR lp_i.item_type='".TOOL_QUIZ."')
3022
                            ";
3023
                    $res_max_score = Database::query($sql);
3024
                    while ($row_max_score = Database::fetch_assoc($res_max_score)) {
3025
                        $list[] = $row_max_score;
3026
                    }
3027
                }
3028
3029
                // Go through each scorable element of this view
3030
                $score_of_scorm_calculate = 0;
3031
                foreach ($list as $row_max_score) {
3032
                    // Came from the original lp_item
3033
                    $max_score = $row_max_score['max_score'];
3034
                    // Came from the lp_item_view
3035
                    $max_score_item_view = $row_max_score['max_score_item_view'];
3036
                    $score = $row_max_score['score'];
3037
                    if ($debug) {
3038
                        echo '<h3>Item Type: '.$row_max_score['item_type'].'</h3>';
3039
                    }
3040
3041
                    if ('sco' === $row_max_score['item_type']) {
3042
                        /* Check if it is sco (easier to get max_score)
3043
                           when there's no max score, we assume 100 as the max score,
3044
                           as the SCORM 1.2 says that the value should always be between 0 and 100.
3045
                        */
3046
                        if (0 == $max_score || is_null($max_score) || '' == $max_score) {
3047
                            // Chamilo style
3048
                            if ($use_max_score[$lp_id]) {
3049
                                $max_score = 100;
3050
                            } else {
3051
                                // Overwrites max score = 100 to use the one that came in the lp_item_view see BT#1613
3052
                                $max_score = $max_score_item_view;
3053
                            }
3054
                        }
3055
                        // Avoid division by zero errors
3056
                        if (!empty($max_score)) {
3057
                            $lpPartialTotal += $score / $max_score;
3058
                        }
3059
                        if ($debug) {
3060
                            var_dump("lpPartialTotal: $lpPartialTotal");
0 ignored issues
show
Security Debugging Code introduced by
var_dump('lpPartialTotal: '.$lpPartialTotal) looks like debug code. Are you sure you do not want to remove it?
Loading history...
3061
                            var_dump("score: $score");
3062
                            var_dump("max_score: $max_score");
3063
                        }
3064
                    } else {
3065
                        // Case of a TOOL_QUIZ element
3066
                        $item_id = $row_max_score['iid'];
3067
                        $item_path = $row_max_score['path'];
3068
                        $lp_item_view_id = (int) $row_max_score['lp_item_view_id'];
3069
3070
                        if (empty($lp_item_view_id)) {
3071
                            $lpItemCondition = ' (orig_lp_item_view_id = 0 OR orig_lp_item_view_id IS NULL) ';
3072
                        } else {
3073
                            $lpItemCondition = " orig_lp_item_view_id = $lp_item_view_id ";
3074
                        }
3075
3076
                        // Get last attempt to this exercise through
3077
                        // the current lp for the current user
3078
                        $order = 'exe_date DESC';
3079
                        if ($getOnlyBestAttempt) {
3080
                            $order = 'score DESC';
3081
                        }
3082
                        $sql = "SELECT exe_id, score
3083
                                FROM $tbl_stats_exercices
3084
                                WHERE
3085
                                    exe_exo_id = '$item_path' AND
3086
                                    exe_user_id = $user_id AND
3087
                                    orig_lp_item_id = $item_id AND
3088
                                    $lpItemCondition AND
3089
                                    c_id = $courseId AND
3090
                                    status = ''
3091
                                    $sessionCondition
3092
                                ORDER BY $order
3093
                                LIMIT 1";
3094
3095
                        $result_last_attempt = Database::query($sql);
3096
                        $num = Database::num_rows($result_last_attempt);
3097
                        if ($num > 0) {
3098
                            $attemptResult = Database::fetch_assoc($result_last_attempt);
3099
                            $id_last_attempt = $attemptResult['exe_id'];
3100
                            // We overwrite the score with the best one not the one saved in the LP (latest)
3101
                            if ($getOnlyBestAttempt && false == $get_only_latest_attempt_results) {
3102
                                if ($debug) {
3103
                                    echo "Following score comes from the track_exercise table not in the LP because the score is the best<br />";
3104
                                }
3105
                                $score = $attemptResult['score'];
3106
                            }
3107
3108
                            if ($debug) {
3109
                                echo "Attempt id: $id_last_attempt with score $score<br />";
3110
                            }
3111
                            // Within the last attempt number tracking, get the sum of
3112
                            // the max_scores of all questions that it was
3113
                            // made of (we need to make this call dynamic because of random questions selection)
3114
                            $sql = "SELECT SUM(t.ponderation) as maxscore FROM
3115
                                        (
3116
                                            SELECT DISTINCT
3117
                                                question_id,
3118
                                                marks,
3119
                                                ponderation
3120
                                            FROM $tbl_stats_attempts AS at
3121
                                            INNER JOIN $tbl_quiz_questions AS q
3122
                                            ON (q.iid = at.question_id)
3123
                                            WHERE
3124
                                                exe_id ='$id_last_attempt'
3125
                                        )
3126
                                        AS t";
3127
3128
                            $res_max_score_bis = Database::query($sql);
3129
                            $row_max_score_bis = Database::fetch_array($res_max_score_bis);
3130
3131
                            if (!empty($row_max_score_bis['maxscore'])) {
3132
                                $max_score = $row_max_score_bis['maxscore'];
3133
                            }
3134
                            if (!empty($max_score) && floatval($max_score) > 0) {
3135
                                $lpPartialTotal += $score / $max_score;
3136
                            }
3137
                            if ($debug) {
3138
                                var_dump("score: $score");
3139
                                var_dump("max_score: $max_score");
3140
                                var_dump("lpPartialTotal: $lpPartialTotal");
3141
                            }
3142
                        }
3143
                    }
3144
3145
                    if (in_array($row_max_score['item_type'], ['quiz', 'sco'])) {
3146
                        // Normal way
3147
                        if ($use_max_score[$lp_id]) {
3148
                            $count_items++;
3149
                        } else {
3150
                            if ('' != $max_score) {
3151
                                $count_items++;
3152
                            }
3153
                        }
3154
                        if ($debug) {
3155
                            echo '$count_items: '.$count_items;
3156
                        }
3157
                    }
3158
                }
3159
3160
                $score_of_scorm_calculate += $count_items ? (($lpPartialTotal / $count_items) * 100) : 0;
3161
                $global_result += $score_of_scorm_calculate;
3162
3163
                if ($debug) {
3164
                    var_dump("count_items: $count_items");
3165
                    var_dump("score_of_scorm_calculate: $score_of_scorm_calculate");
3166
                    var_dump("global_result: $global_result");
3167
                }
3168
            }
3169
        }
3170
3171
        $lp_with_quiz = 0;
3172
        foreach ($lp_list as $lp_id) {
3173
            // Check if LP have a score we assume that all SCO have an score
3174
            $sql = "SELECT count(iid) as count
3175
                    FROM $lp_item_table
3176
                    WHERE
3177
                        (item_type = 'quiz' OR item_type = 'sco') AND
3178
                        lp_id = ".$lp_id;
3179
            $result_have_quiz = Database::query($sql);
3180
            if (Database::num_rows($result_have_quiz) > 0) {
3181
                $row = Database::fetch_assoc($result_have_quiz);
3182
                if (is_numeric($row['count']) && 0 != $row['count']) {
3183
                    $lp_with_quiz++;
3184
                }
3185
            }
3186
        }
3187
3188
        if ($debug) {
3189
            echo '<h3>$lp_with_quiz '.$lp_with_quiz.' </h3>';
3190
            echo '<h3>Final return</h3>';
3191
        }
3192
3193
        if (0 != $lp_with_quiz) {
3194
            if (!$return_array) {
3195
                $score_of_scorm_calculate = round(($global_result / $lp_with_quiz), 2);
3196
                if ($debug) {
3197
                    var_dump($score_of_scorm_calculate);
3198
                }
3199
                if (empty($lp_ids)) {
3200
                    if ($debug) {
3201
                        echo '<h2>All lps fix: '.$score_of_scorm_calculate.'</h2>';
3202
                    }
3203
                }
3204
3205
                return $score_of_scorm_calculate;
3206
            }
3207
3208
            if ($debug) {
3209
                var_dump($global_result, $lp_with_quiz);
3210
            }
3211
3212
            return [$global_result, $lp_with_quiz];
3213
        }
3214
3215
        return '-';
3216
    }
3217
3218
    /**
3219
     * This function gets:
3220
     * 1. The score average from all SCORM Test items in all LP in a course-> All the answers / All the max scores.
3221
     * 2. The score average from all Tests (quiz) in all LP in a course-> All the answers / All the max scores.
3222
     * 3. And finally it will return the average between 1. and 2.
3223
     * This function does not take the results of a Test out of a LP.
3224
     *
3225
     * @param int|array $student_id  Array of user ids or an user id
3226
     * @param string    $course_code Course code
3227
     * @param array     $lp_ids      List of LP ids
3228
     * @param int       $sessionId   Session id (optional), if param $sessionId is 0(default)
3229
     *                               it'll return results including sessions, 0 = session is not filtered
3230
     *
3231
     * @return string value (number %) Which represents a round integer explain in got in 3
3232
     */
3233
    public static function getAverageStudentScore(
3234
        $student_id,
3235
        $course_code = '',
3236
        $lp_ids = [],
3237
        $sessionId = 0
3238
    ) {
3239
        if (empty($student_id)) {
3240
            return 0;
3241
        }
3242
3243
        $conditions = [];
3244
        if (!empty($course_code)) {
3245
            $course = api_get_course_info($course_code);
3246
            $courseId = $course['real_id'];
3247
            //$conditions[] = " lp.c_id = $courseId";
3248
        }
3249
3250
        // Get course tables names
3251
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3252
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3253
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3254
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3255
3256
        // Compose a filter based on optional learning paths list given
3257
        if (!empty($lp_ids) && count($lp_ids) > 0) {
3258
            $conditions[] = ' lp.iid IN ('.implode(',', $lp_ids).') ';
3259
        }
3260
3261
        // Compose a filter based on optional session id
3262
        $sessionId = (int) $sessionId;
3263
        if (!empty($sessionId)) {
3264
            $conditions[] = " lp_view.session_id = $sessionId ";
3265
        }
3266
3267
        if (is_array($student_id)) {
3268
            array_walk($student_id, 'intval');
3269
            $conditions[] = " lp_view.user_id IN (".implode(',', $student_id).") ";
3270
        } else {
3271
            $student_id = (int) $student_id;
3272
            $conditions[] = " lp_view.user_id = $student_id ";
3273
        }
3274
3275
        $conditionsToString = implode(' AND ', $conditions);
3276
        $sql = "SELECT
3277
                    SUM(lp_iv.score) sum_score,
3278
                    SUM(lp_i.max_score) sum_max_score
3279
                FROM $lp_table as lp
3280
                INNER JOIN $lp_item_table as lp_i
3281
                ON lp.iid = lp_i.lp_id
3282
                INNER JOIN $lp_view_table as lp_view
3283
                ON lp_view.lp_id = lp_i.lp_id
3284
                INNER JOIN $lp_item_view_table as lp_iv
3285
                ON
3286
                    lp_i.iid = lp_iv.lp_item_id AND
3287
                    lp_iv.lp_view_id = lp_view.iid
3288
                WHERE (lp_i.item_type='sco' OR lp_i.item_type='".TOOL_QUIZ."') AND
3289
                $conditionsToString
3290
        ";
3291
        $result = Database::query($sql);
3292
        $row = Database::fetch_assoc($result);
3293
3294
        if (empty($row['sum_max_score'])) {
3295
            return 0;
3296
        }
3297
3298
        return ($row['sum_score'] / $row['sum_max_score']) * 100;
3299
    }
3300
3301
    /**
3302
     * This function gets time spent in learning path for a student inside a course.
3303
     *
3304
     * @param int|array $student_id Student id(s)
3305
     * @param Course    $course     Course code
3306
     * @param array     $lp_ids     Limit average to listed lp ids
3307
     * @param int       $sessionId  Session id (optional), if param $sessionId is null(default)
3308
     *                              it'll return results including sessions, 0 = session is not filtered
3309
     *
3310
     * @return int Total time in seconds
3311
     */
3312
    public static function get_time_spent_in_lp(
3313
        $student_id,
3314
        Course $course,
3315
        $lp_ids = [],
3316
        $sessionId = 0
3317
    ) {
3318
        $student_id = (int) $student_id;
3319
        $sessionId = (int) $sessionId;
3320
        $total_time = 0;
3321
3322
        if (!empty($course)) {
3323
            $lpTable = Database::get_course_table(TABLE_LP_MAIN);
3324
            $lpItemTable = Database::get_course_table(TABLE_LP_ITEM);
3325
            $lpViewTable = Database::get_course_table(TABLE_LP_VIEW);
3326
            $lpItemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3327
            $trackExercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
3328
            $courseId = $course->getId();
3329
3330
            if (!empty($lp_ids) && is_array($lp_ids)) {
3331
                if (is_array(reset($lp_ids))) {
3332
                    $lp_ids = array_filter(array_map(static function ($x) {
3333
                        if (isset($x['iid'])) {
3334
                            return (int) $x['iid'];
3335
                        }
3336
                        if (isset($x['id'])) {
3337
                            return (int) $x['id'];
3338
                        }
3339
                        return null;
3340
                    }, $lp_ids), static fn($v) => !is_null($v));
3341
                } else {
3342
                    $lp_ids = array_map('intval', $lp_ids);
3343
                }
3344
            }
3345
3346
            // Compose a filter based on optional learning paths list given
3347
            $condition_lp = '';
3348
            if (!empty($lp_ids)) {
3349
                $condition_lp = " AND iid IN(".implode(',', $lp_ids).") ";
3350
            }
3351
3352
            // Check the real number of LPs corresponding to the filter in the
3353
            // database (and if no list was given, get them all)
3354
            $sql = "SELECT DISTINCT(iid) FROM $lpTable
3355
                WHERE 1=1 $condition_lp";
3356
            $result = Database::query($sql);
3357
            $session_condition = api_get_session_condition($sessionId);
3358
3359
            // calculates time
3360
            if (Database::num_rows($result) > 0) {
3361
                while ($row = Database::fetch_array($result)) {
3362
                    $lp_id = (int) $row['iid'];
3363
                    $lp = Container::getLpRepository()->find($lp_id);
3364
                    // Start Exercise in LP total_time
3365
                    // Get duration time from track_e_exercises.exe_duration instead of lp_view_item.total_time
3366
                    $list = learnpath::get_flat_ordered_items_list($lp, 0);
3367
                    foreach ($list as $item) {
3368
                        $itemId = is_array($item) ? (int) ($item['iid'] ?? $item['id'] ?? 0) : (int) $item;
3369
                        if ($itemId <= 0) {
3370
                            continue;
3371
                        }
3372
3373
                        $sql = "SELECT max(view_count)
3374
                            FROM $lpViewTable
3375
                            WHERE
3376
                                c_id = $courseId AND
3377
                                lp_id = $lp_id AND
3378
                                user_id = $student_id
3379
                                $session_condition";
3380
                        $res = Database::query($sql);
3381
                        $view = '';
3382
                        if (Database::num_rows($res) > 0) {
3383
                            $myrow = Database::fetch_array($res);
3384
                            $view = $myrow[0];
3385
                        }
3386
                        $viewCondition = '';
3387
                        if (!empty($view)) {
3388
                            $viewCondition = " AND v.view_count = $view  ";
3389
                        }
3390
                        $sql = "SELECT
3391
                                iv.iid,
3392
                                iv.total_time as mytime,
3393
                                i.iid as myid,
3394
                                iv.view_count as iv_view_count,
3395
                                path
3396
                            FROM $lpItemTable as i
3397
                            INNER JOIN $lpItemViewTable as iv
3398
                                ON (i.iid = iv.lp_item_id)
3399
                            INNER JOIN $lpViewTable as v
3400
                                ON (iv.lp_view_id = v.iid)
3401
                            WHERE
3402
                                v.c_id = $courseId AND
3403
                                i.iid = $itemId AND
3404
                                i.lp_id = $lp_id  AND
3405
                                v.user_id = $student_id AND
3406
                                item_type = 'quiz' AND
3407
                                path <> '' AND
3408
                                v.session_id = $sessionId
3409
                                $viewCondition
3410
                            ORDER BY iv.view_count DESC ";
3411
3412
                        $resultRow = Database::query($sql);
3413
                        if (Database::num_rows($resultRow)) {
3414
                            $row = Database::fetch_array($resultRow);
3415
                            $totalTimeInLpItemView = $row['mytime'];
3416
                            $lpItemViewId = $row['iid'];
3417
                            $sessionCondition = api_get_session_condition($sessionId);
3418
                            $sql = 'SELECT SUM(exe_duration) exe_duration
3419
                                FROM '.$trackExercises.'
3420
                                WHERE
3421
                                    exe_exo_id="'.Database::escape_string($row['path']).'" AND
3422
                                    exe_user_id="'.$student_id.'" AND
3423
                                    orig_lp_id = "'.$lp_id.'" AND
3424
                                    orig_lp_item_id = "'.$row['myid'].'" AND
3425
                                    c_id = '.$courseId.' AND
3426
                                    status <> "incomplete"
3427
                                    '.$sessionCondition.'
3428
                                 ORDER BY exe_date DESC ';
3429
3430
                            $sumScoreResult = Database::query($sql);
3431
                            $durationRow = Database::fetch_assoc($sumScoreResult);
3432
                            if (!empty($durationRow['exe_duration'])) {
3433
                                $exeDuration = $durationRow['exe_duration'];
3434
                                if (
3435
                                    $exeDuration != $totalTimeInLpItemView &&
3436
                                    !empty($lpItemViewId) &&
3437
                                    !empty($exeDuration)
3438
                                ) {
3439
                                    // Update c_lp_item_view.total_time
3440
                                    $sqlUpdate = "UPDATE $lpItemViewTable
3441
                                              SET total_time = '".Database::escape_string($exeDuration)."'
3442
                                              WHERE iid = ".$lpItemViewId;
3443
                                    Database::query($sqlUpdate);
3444
                                }
3445
                            }
3446
                        }
3447
                    }
3448
3449
                    // End total_time fix
3450
3451
                    // Calculate total time
3452
                    $sql = "SELECT SUM(total_time)
3453
                        FROM $lpItemViewTable AS item_view
3454
                        INNER JOIN $lpViewTable AS view
3455
                            ON (item_view.lp_view_id = view.iid)
3456
                        WHERE
3457
                            view.c_id = $courseId AND
3458
                            view.lp_id = $lp_id AND
3459
                            view.user_id = $student_id AND
3460
                            session_id = $sessionId";
3461
3462
                    $rs = Database::query($sql);
3463
                    if (Database::num_rows($rs) > 0) {
3464
                        $total_time += (int) Database::result($rs, 0, 0);
3465
                    }
3466
                }
3467
            }
3468
        }
3469
3470
        return $total_time;
3471
    }
3472
3473
    /**
3474
     * This function gets last connection time to one learning path.
3475
     *
3476
     * @param int|array $student_id  Student id(s)
3477
     * @param string    $course_code Course code
3478
     * @param int       $lp_id       Learning path id
3479
     * @param int       $sessionId
3480
     *
3481
     * @return int last connection timestamp
3482
     */
3483
    public static function get_last_connection_time_in_lp(
3484
        $student_id,
3485
        $course_code,
3486
        $lp_id,
3487
        $sessionId = 0
3488
    ) {
3489
        $course = api_get_course_info($course_code);
3490
        if (empty($course)) {
3491
            return 0;
3492
        }
3493
3494
        $courseId = $course['real_id'];
3495
        $student_id = (int) $student_id;
3496
        $lp_id = (int) $lp_id;
3497
        $sessionId = (int) $sessionId;
3498
        $lastTime = 0;
3499
3500
        if (self::minimumTimeAvailable($sessionId, $courseId)) {
3501
            $sql = "SELECT MAX(date_reg) max
3502
                    FROM track_e_access_complete
3503
                    WHERE
3504
                        user_id = $student_id AND
3505
                        c_id = $courseId AND
3506
                        session_id = $sessionId AND
3507
                        tool = 'learnpath' AND
3508
                        tool_id = $lp_id AND
3509
                        action = 'view' AND
3510
                        login_as = 0
3511
                    ORDER BY date_reg ASC
3512
                    LIMIT 1";
3513
            $rs = Database::query($sql);
3514
3515
            $lastConnection = 0;
3516
            if (Database::num_rows($rs) > 0) {
3517
                $value = Database::fetch_array($rs);
3518
                if (isset($value['max']) && !empty($value['max'])) {
3519
                    $lastConnection = api_strtotime($value['max'], 'UTC');
3520
                }
3521
            }
3522
3523
            if (!empty($lastConnection)) {
3524
                return $lastConnection;
3525
            }
3526
        }
3527
        if (!empty($course)) {
3528
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3529
            $t_lpv = Database::get_course_table(TABLE_LP_VIEW);
3530
            $t_lpiv = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3531
3532
            // Check the real number of LPs corresponding to the filter in the
3533
            // database (and if no list was given, get them all)
3534
            $sql = "SELECT iid FROM $lp_table
3535
                    WHERE iid = $lp_id ";
3536
            $row = Database::query($sql);
3537
            $count = Database::num_rows($row);
3538
3539
            // calculates last connection time
3540
            if ($count > 0) {
3541
                $sql = 'SELECT MAX(start_time)
3542
                        FROM '.$t_lpiv.' AS item_view
3543
                        INNER JOIN '.$t_lpv.' AS view
3544
                        ON (item_view.lp_view_id = view.iid)
3545
                        WHERE
3546
                            status != "not attempted" AND
3547
                            view.c_id = '.$courseId.' AND
3548
                            view.lp_id = '.$lp_id.' AND
3549
                            view.user_id = '.$student_id.' AND
3550
                            view.session_id = '.$sessionId;
3551
                $rs = Database::query($sql);
3552
                if (Database::num_rows($rs) > 0) {
3553
                    $lastTime = Database::result($rs, 0, 0);
3554
                }
3555
            }
3556
        }
3557
3558
        return $lastTime;
3559
    }
3560
3561
    public static function getFirstConnectionTimeInLp(
3562
        $student_id,
3563
        $course_code,
3564
        $lp_id,
3565
        $sessionId = 0
3566
    ) {
3567
        $course = api_get_course_info($course_code);
3568
        $student_id = (int) $student_id;
3569
        $lp_id = (int) $lp_id;
3570
        $sessionId = (int) $sessionId;
3571
        $time = 0;
3572
3573
        if (!empty($course)) {
3574
            $courseId = $course['real_id'];
3575
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3576
            $t_lpv = Database::get_course_table(TABLE_LP_VIEW);
3577
            $t_lpiv = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3578
3579
            // Check the real number of LPs corresponding to the filter in the
3580
            // database (and if no list was given, get them all)
3581
            $sql = "SELECT iid FROM $lp_table
3582
                    WHERE iid = $lp_id ";
3583
            $row = Database::query($sql);
3584
            $count = Database::num_rows($row);
3585
3586
            // calculates first connection time
3587
            if ($count > 0) {
3588
                $sql = 'SELECT MIN(start_time)
3589
                        FROM '.$t_lpiv.' AS item_view
3590
                        INNER JOIN '.$t_lpv.' AS view
3591
                        ON (item_view.lp_view_id = view.iid)
3592
                        WHERE
3593
                            status != "not attempted" AND
3594
                            view.c_id = '.$courseId.' AND
3595
                            view.lp_id = '.$lp_id.' AND
3596
                            view.user_id = '.$student_id.' AND
3597
                            view.session_id = '.$sessionId;
3598
                $rs = Database::query($sql);
3599
                if (Database::num_rows($rs) > 0) {
3600
                    $time = Database::result($rs, 0, 0);
3601
                }
3602
            }
3603
        }
3604
3605
        return $time;
3606
    }
3607
3608
    /**
3609
     * gets the list of students followed by coach.
3610
     *
3611
     * @param int $coach_id Coach id
3612
     *
3613
     * @return array List of students
3614
     */
3615
    public static function get_student_followed_by_coach($coach_id)
3616
    {
3617
        $coach_id = (int) $coach_id;
3618
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3619
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3620
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3621
        $tbl_session_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3622
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3623
3624
        $accessUrlUtil = Container::getAccessUrlUtil();
3625
        $access_url_id = -1;
3626
        if ($accessUrlUtil->isMultiple()) {
3627
            $access_url_id = $accessUrlUtil->getCurrent()->getId();
3628
        }
3629
        $students = [];
3630
        // At first, courses where $coach_id is coach of the course //
3631
        $sql = 'SELECT session_id, c_id
3632
                FROM '.$tbl_session_course_user.'
3633
                WHERE user_id='.$coach_id.' AND status = '.SessionEntity::COURSE_COACH;
3634
3635
        if (-1 != $access_url_id) {
3636
            $sql = 'SELECT scu.session_id, scu.c_id
3637
                    FROM '.$tbl_session_course_user.' scu
3638
                    INNER JOIN '.$tbl_session_rel_access_url.'  sru
3639
                    ON (scu.session_id=sru.session_id)
3640
                    WHERE
3641
                        scu.user_id='.$coach_id.' AND
3642
                        scu.status = '.SessionEntity::COURSE_COACH.' AND
3643
                        sru.access_url_id = '.$access_url_id;
3644
        }
3645
3646
        $result = Database::query($sql);
3647
3648
        while ($a_courses = Database::fetch_array($result)) {
3649
            $courseId = $a_courses['c_id'];
3650
            $sessionId = $a_courses['session_id'];
3651
3652
            $sql = "SELECT DISTINCT srcru.user_id
3653
                    FROM $tbl_session_course_user AS srcru
3654
                    INNER JOIN $tbl_session_user sru
3655
                    ON (srcru.user_id = sru.user_id AND srcru.session_id = sru.session_id)
3656
                    WHERE
3657
                        sru.relation_type = ".SessionEntity::STUDENT." AND
3658
                        srcru.c_id = '$courseId' AND
3659
                        srcru.session_id = '$sessionId'";
3660
3661
            $rs = Database::query($sql);
3662
            while ($row = Database::fetch_array($rs)) {
3663
                $students[$row['user_id']] = $row['user_id'];
3664
            }
3665
        }
3666
3667
        // Then, courses where $coach_id is coach of the session
3668
        $sql = "SELECT srcru.user_id
3669
            FROM $tbl_session_course_user srcru
3670
            INNER JOIN $tbl_session_course src
3671
            ON (srcru.c_id = src.c_id AND srcru.session_id = src.session_id)
3672
            INNER JOIN $tbl_session s
3673
            ON srcru.session_id = s.id AND src.session_id = s.id
3674
            INNER JOIN $tbl_session_user sru on s.id = sru.session_id
3675
            WHERE
3676
               srcru.status = ".SessionEntity::STUDENT." AND
3677
               sru.relation_type = ".SessionEntity::GENERAL_COACH." AND
3678
               sru.user_id = $coach_id";
3679
3680
        if (-1 != $access_url_id) {
3681
            $sql = "SELECT srcru.user_id
3682
                    FROM $tbl_session_course_user srcru
3683
                    INNER JOIN $tbl_session_course src
3684
                    ON (srcru.c_id = src.c_id AND srcru.session_id = src.session_id)
3685
                    INNER JOIN $tbl_session s
3686
                    ON srcru.session_id = s.id AND src.session_id = s.id
3687
                    INNER JOIN $tbl_session_user sru
3688
                    ON s.id = sru.session_id
3689
                    INNER JOIN $tbl_session_rel_access_url aurs
3690
                    ON s.id = aurs.session_id
3691
                    WHERE
3692
                        srcru.status = ".SessionEntity::STUDENT." AND
3693
                        sru.relation_type = ".SessionEntity::GENERAL_COACH." AND
3694
                        sru.user_id = $coach_id AND
3695
                        aurs.access_url_id = $access_url_id";
3696
        }
3697
3698
        $result = Database::query($sql);
3699
        while ($row = Database::fetch_array($result)) {
3700
            $students[$row['user_id']] = $row['user_id'];
3701
        }
3702
3703
        return $students;
3704
    }
3705
3706
    /**
3707
     * Check if a coach is allowed to follow a student.
3708
     *
3709
     * @param    int        Coach id
3710
     * @param    int        Student id
3711
     *
3712
     * @return bool
3713
     */
3714
    public static function is_allowed_to_coach_student($coach_id, $student_id)
3715
    {
3716
        $coach_id = intval($coach_id);
3717
        $student_id = intval($student_id);
3718
3719
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3720
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3721
        $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3722
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3723
3724
        // At first, courses where $coach_id is coach of the course
3725
        $sql = 'SELECT 1 FROM '.$tbl_session_course_user.'
3726
                WHERE user_id='.$coach_id.' AND status = '.SessionEntity::COURSE_COACH;
3727
        $result = Database::query($sql);
3728
        if (Database::num_rows($result) > 0) {
3729
            return true;
3730
        }
3731
3732
        // Then, courses where $coach_id is coach of the session
3733
        $sql = "SELECT srcru.user_id
3734
            FROM $tbl_session_course_user srcru
3735
            INNER JOIN $tbl_session_course src
3736
            ON (srcru.c_id = src.c_id AND srcru.session_id = src.session_id)
3737
            INNER JOIN $tbl_session s
3738
            ON srcru.session_id = s.id AND src.session_id = s.id
3739
            INNER JOIN $tblSessionRelUser sru
3740
            ON s.id = sru.session_id
3741
            WHERE
3742
                (srcru.status = ".SessionEntity::STUDENT." AND srcru.user_id = $student_id) AND
3743
                (sru.relation_type = ".SessionEntity::GENERAL_COACH." AND sru.user_id = $coach_id)";
3744
        $result = Database::query($sql);
3745
        if (Database::num_rows($result) > 0) {
3746
            return true;
3747
        }
3748
3749
        return false;
3750
    }
3751
3752
    /**
3753
     * Get courses followed by coach.
3754
     *
3755
     * @param     int        Coach id
3756
     * @param    int        Session id (optional)
3757
     *
3758
     * @return array Courses list
3759
     */
3760
    public static function get_courses_followed_by_coach($coach_id, $sessionId = 0)
3761
    {
3762
        $coach_id = intval($coach_id);
3763
        if (!empty($sessionId)) {
3764
            $sessionId = intval($sessionId);
3765
        }
3766
3767
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3768
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
3769
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3770
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
3771
        $tbl_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3772
        $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3773
3774
        // At first, courses where $coach_id is coach of the course.
3775
        $sql = 'SELECT DISTINCT c.code
3776
                FROM '.$tbl_session_course_user.' sc
3777
                INNER JOIN '.$tbl_course.' c
3778
                ON (c.id = sc.c_id)
3779
                WHERE sc.user_id = '.$coach_id.' AND sc.status = '.SessionEntity::COURSE_COACH;
3780
3781
        $accessUrlUtil = Container::getAccessUrlUtil();
3782
        if ($accessUrlUtil->isMultiple()) {
3783
            $access_url_id = $accessUrlUtil->getCurrent()->getId();
3784
            if (-1 != $access_url_id) {
3785
                $sql = 'SELECT DISTINCT c.code
3786
                        FROM '.$tbl_session_course_user.' scu
3787
                        INNER JOIN '.$tbl_course.' c
3788
                        ON (c.code = scu.c_id)
3789
                        INNER JOIN '.$tbl_course_rel_access_url.' cru
3790
                        ON (c.id = cru.c_id)
3791
                        WHERE
3792
                            scu.user_id='.$coach_id.' AND
3793
                            scu.status = '.SessionEntity::COURSE_COACH.' AND
3794
                            cru.access_url_id = '.$access_url_id;
3795
            }
3796
        }
3797
3798
        if (!empty($sessionId)) {
3799
            $sql .= ' AND session_id='.$sessionId;
3800
        }
3801
3802
        $courseList = [];
3803
        $result = Database::query($sql);
3804
        while ($row = Database::fetch_array($result)) {
3805
            $courseList[$row['code']] = $row['code'];
3806
        }
3807
3808
        // Then, courses where $coach_id is coach of the session
3809
        $sql = "SELECT DISTINCT course.code
3810
                FROM $tbl_session_course as session_course
3811
                INNER JOIN $tbl_session as session
3812
                    ON (session.id = session_course.session_id)
3813
                INNER JOIN $tblSessionRelUser session_user
3814
                    ON (session.id = session_user.session_id
3815
                    AND session_user.user_id = $coach_id
3816
                    AND session_user.relation_type = ".SessionEntity::GENERAL_COACH.")
3817
                INNER JOIN $tbl_course as course
3818
                    ON course.id = session_course.c_id";
3819
3820
        if ($accessUrlUtil->isMultiple()) {
3821
            $tbl_course_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
3822
            $access_url_id = $accessUrlUtil->getCurrent()->getId();
3823
            if (-1 != $access_url_id) {
3824
                $sql = "SELECT DISTINCT c.code
3825
                    FROM $tbl_session_course as session_course
3826
                    INNER JOIN $tbl_course c
3827
                    ON (c.id = session_course.c_id)
3828
                    INNER JOIN $tbl_session as session
3829
                    ON session.id = session_course.session_id
3830
                    INNER JOIN $tblSessionRelUser session_user
3831
                        ON (session.id = session_user.session_id
3832
                        AND session_user.user_id = $coach_id
3833
                        AND session_user.relation_type = ".SessionEntity::GENERAL_COACH.")
3834
                    INNER JOIN $tbl_course as course
3835
                        ON course.id = session_course.c_id
3836
                     INNER JOIN $tbl_course_rel_access_url course_rel_url
3837
                    ON (course_rel_url.c_id = c.id)";
3838
            }
3839
        }
3840
3841
        if (!empty($sessionId)) {
3842
            $sql .= ' WHERE session_course.session_id='.$sessionId;
3843
            if ($accessUrlUtil->isMultiple()) {
3844
                $sql .= ' AND access_url_id = '.$access_url_id;
3845
            }
3846
        } else {
3847
            if ($accessUrlUtil->isMultiple()) {
3848
                $sql .= ' WHERE access_url_id = '.$access_url_id;
3849
            }
3850
        }
3851
3852
        $result = Database::query($sql);
3853
        while ($row = Database::fetch_array($result)) {
3854
            $courseList[$row['code']] = $row['code'];
3855
        }
3856
3857
        return $courseList;
3858
    }
3859
3860
    /**
3861
     * Get sessions coached by user.
3862
     *
3863
     * @param int    $coach_id
3864
     * @param int    $start
3865
     * @param int    $limit
3866
     * @param bool   $getCount
3867
     * @param string $keyword
3868
     * @param string $description
3869
     * @param string $orderByName
3870
     * @param string $orderByDirection
3871
     * @param array  $options
3872
     *
3873
     * @return mixed
3874
     */
3875
    public static function get_sessions_coached_by_user(
3876
        $coach_id,
3877
        $start = 0,
3878
        $limit = 0,
3879
        $getCount = false,
3880
        $keyword = '',
3881
        $description = '',
3882
        $orderByName = '',
3883
        $orderByDirection = '',
3884
        $options = []
3885
    ) {
3886
        // table definition
3887
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
3888
        $tblSessionRelUser = Database::get_main_table(TABLE_MAIN_SESSION_USER);
3889
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
3890
        $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
3891
3892
        $coach_id = (int) $coach_id;
3893
3894
        $select = ' SELECT * FROM ';
3895
        if ($getCount) {
3896
            $select = ' SELECT count(DISTINCT id) as count FROM ';
3897
        }
3898
3899
        $limitCondition = null;
3900
        if (!empty($start) && !empty($limit)) {
3901
            $limitCondition = " LIMIT ".intval($start).", ".intval($limit);
3902
        }
3903
3904
        $keywordCondition = null;
3905
        if (!empty($keyword)) {
3906
            $keyword = Database::escape_string($keyword);
3907
            $keywordCondition = " AND (title LIKE '%$keyword%' ) ";
3908
3909
            if (!empty($description)) {
3910
                $description = Database::escape_string($description);
3911
                $keywordCondition = " AND (title LIKE '%$keyword%' OR description LIKE '%$description%' ) ";
3912
            }
3913
        }
3914
3915
        $extraFieldModel = new ExtraFieldModel('session');
3916
        $conditions = $extraFieldModel->parseConditions($options);
3917
        $sqlInjectJoins = $conditions['inject_joins'];
3918
        $extraFieldsConditions = $conditions['where'];
3919
        $sqlInjectWhere = $conditions['inject_where'];
3920
        $injectExtraFields = $conditions['inject_extra_fields'];
3921
3922
        $access_url_id = api_get_current_access_url_id();
3923
3924
        $orderBy = '';
3925
        if (!empty($orderByName)) {
3926
            if (in_array($orderByName, ['title', 'access_start_date'])) {
3927
                $orderByDirection = in_array(strtolower($orderByDirection), ['asc', 'desc']) ? $orderByDirection : 'asc';
3928
                $orderByName = Database::escape_string($orderByName);
3929
                $orderBy .= " ORDER BY `$orderByName` $orderByDirection";
3930
            }
3931
        }
3932
3933
        $sql = "
3934
            $select
3935
            (
3936
                SELECT DISTINCT
3937
                    s.id,
3938
                    title,
3939
                    $injectExtraFields
3940
                    s.access_start_date,
3941
                    s.access_end_date
3942
                FROM $tbl_session s
3943
                INNER JOIN $tbl_session_rel_access_url session_rel_url
3944
                ON (s.id = session_rel_url.session_id)
3945
                $sqlInjectJoins
3946
                INNER JOIN $tblSessionRelUser sru ON s.id = sru.session_id
3947
                WHERE
3948
                    (sru.user_id = $coach_id AND sru.relation_type = ".SessionEntity::GENERAL_COACH.") AND
3949
                    access_url_id = $access_url_id
3950
                    $keywordCondition
3951
                    $extraFieldsConditions
3952
                    $sqlInjectWhere
3953
            UNION
3954
                SELECT DISTINCT
3955
                    s.id,
3956
                    s.title,
3957
                    $injectExtraFields
3958
                    s.access_start_date,
3959
                    s.access_end_date
3960
                FROM $tbl_session as s
3961
                INNER JOIN $tbl_session_course_user as session_course_user
3962
                ON
3963
                    s.id = session_course_user.session_id AND
3964
                    session_course_user.user_id = $coach_id AND
3965
                    session_course_user.status = ".SessionEntity::COURSE_COACH."
3966
                INNER JOIN $tbl_session_rel_access_url session_rel_url
3967
                ON (s.id = session_rel_url.session_id)
3968
                $sqlInjectJoins
3969
                WHERE
3970
                    access_url_id = $access_url_id
3971
                    $keywordCondition
3972
                    $extraFieldsConditions
3973
                    $sqlInjectWhere
3974
            ) as sessions $limitCondition $orderBy
3975
            ";
3976
3977
        $rs = Database::query($sql);
3978
        if ($getCount) {
3979
            $row = Database::fetch_array($rs);
3980
3981
            return $row['count'];
3982
        }
3983
3984
        $sessions = [];
3985
        while ($row = Database::fetch_array($rs)) {
3986
            if ('0000-00-00 00:00:00' === $row['access_start_date']) {
3987
                $row['access_start_date'] = null;
3988
            }
3989
3990
            $sessions[$row['id']] = $row;
3991
        }
3992
3993
        if (!empty($sessions)) {
3994
            foreach ($sessions as &$session) {
3995
                if (empty($session['access_start_date'])) {
3996
                    $session['status'] = get_lang('active');
3997
                } else {
3998
                    $time_start = api_strtotime($session['access_start_date'], 'UTC');
3999
                    $time_end = api_strtotime($session['access_end_date'], 'UTC');
4000
                    if ($time_start < time() && time() < $time_end) {
4001
                        $session['status'] = get_lang('active');
4002
                    } else {
4003
                        if (time() < $time_start) {
4004
                            $session['status'] = get_lang('Not yet begun');
4005
                        } else {
4006
                            if (time() > $time_end) {
4007
                                $session['status'] = get_lang('Past');
4008
                            }
4009
                        }
4010
                    }
4011
                }
4012
            }
4013
        }
4014
4015
        return $sessions;
4016
    }
4017
4018
    /**
4019
     * Get courses list from a session.
4020
     *
4021
     * @param    int        Session id
4022
     *
4023
     * @return array Courses list
4024
     */
4025
    public static function get_courses_list_from_session($sessionId)
4026
    {
4027
        $sessionId = (int) $sessionId;
4028
4029
        // table definition
4030
        $tbl_session_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
4031
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4032
4033
        $sql = "SELECT DISTINCT code, c_id
4034
                FROM $tbl_session_course sc
4035
                INNER JOIN $courseTable c
4036
                ON sc.c_id = c.id
4037
                WHERE session_id= $sessionId";
4038
4039
        $result = Database::query($sql);
4040
4041
        $courses = [];
4042
        while ($row = Database::fetch_array($result)) {
4043
            $courses[$row['code']] = $row;
4044
        }
4045
4046
        return $courses;
4047
    }
4048
4049
    /**
4050
     * Count the number of documents that an user has uploaded to a course.
4051
     *
4052
     * @param    int|array   Student id(s)
4053
     * @param    string      Course code
4054
     * @param    int         Session id (optional),
4055
     * if param $sessionId is null(default)
4056
     * return count of assignments including sessions, 0 = session is not filtered
4057
     *
4058
     * @return int Number of documents
4059
     */
4060
    public static function count_student_uploaded_documents(
4061
        $student_id,
4062
        $course_code,
4063
        $sessionId = null
4064
    ) {
4065
        $a_course = api_get_course_info($course_code);
4066
        $repo = Container::getDocumentRepository();
4067
4068
        $user = api_get_user_entity($student_id);
4069
        $course = api_get_course_entity($a_course['real_id']);
4070
        $session = api_get_session_entity($sessionId);
4071
        //$group = api_get_group_entity(api_get_group_id());
4072
4073
        $qb = $repo->getResourcesByCourseLinkedToUser($user, $course, $session);
4074
4075
        $qb->select('count(resource)');
4076
        $count = $qb->getQuery()->getSingleScalarResult();
4077
4078
        return $count;
4079
    }
4080
4081
    /**
4082
     * This function counts the number of post by course.
4083
     *
4084
     * @param string $courseId
4085
     * @param int    $sessionId (optional), if is null(default) it'll return results including sessions,
4086
     *                          0 = session is not filtered
4087
     * @param int    $groupId
4088
     *
4089
     * @return int The number of post by course
4090
     */
4091
    public static function count_number_of_posts_by_course($courseId, $sessionId = null, $groupId = 0)
4092
    {
4093
        $repo = Container::getForumPostRepository();
4094
        $course = api_get_course_entity($courseId);
4095
        $session = api_get_session_entity($sessionId);
4096
        $group = api_get_group_entity($groupId);
4097
        $qb = $repo->getResourcesByCourse($course, $session, $group);
4098
4099
        $qb->select('count(resource)');
4100
        $count = $qb->getQuery()->getSingleScalarResult();
4101
4102
        return $count;
4103
    }
4104
4105
    /**
4106
     * This function counts the number of threads by course.
4107
     *
4108
     * @param int Course id
4109
     * @param int Session id (optional),
4110
     * if param $sessionId is null(default) it'll return results including
4111
     * sessions, 0 = session is not filtered
4112
     * @param int $groupId
4113
     *
4114
     * @return int The number of threads by course
4115
     */
4116
    public static function count_number_of_threads_by_course(
4117
        $courseId,
4118
        $sessionId = null,
4119
        $groupId = 0
4120
    ) {
4121
        $repo = Container::getForumThreadRepository();
4122
        $course = api_get_course_entity($courseId);
4123
        $session = api_get_session_entity($sessionId);
4124
        $group = api_get_group_entity($groupId);
4125
        $qb = $repo->getResourcesByCourse($course, $session, $group);
4126
4127
        $qb->select('count(resource)');
4128
        $count = $qb->getQuery()->getSingleScalarResult();
4129
4130
        return $count;
4131
    }
4132
4133
    /**
4134
     * This function counts the number of forums by course.
4135
     *
4136
     * @param int     Course id
4137
     * @param int     Session id (optional),
4138
     * if param $sessionId is null(default) it'll return results
4139
     * including sessions, 0 = session is not filtered
4140
     * @param int $groupId
4141
     *
4142
     * @return int The number of forums by course
4143
     */
4144
    public static function count_number_of_forums_by_course(
4145
        $courseId,
4146
        $sessionId = null,
4147
        $groupId = 0
4148
    ) {
4149
        $repo = Container::getForumRepository();
4150
        $course = api_get_course_entity($courseId);
4151
        $session = api_get_session_entity($sessionId);
4152
        $group = api_get_group_entity($groupId);
4153
4154
        $qb = $repo->getResourcesByCourse($course, $session, $group);
4155
        $qb->select('count(resource)');
4156
        $count = $qb->getQuery()->getSingleScalarResult();
4157
4158
        return $count;
4159
    }
4160
4161
    /**
4162
     * This function counts the chat last connections by course in x days.
4163
     *
4164
     * @param      string     Course code
4165
     * @param      int     Last x days
4166
     * @param    int        Session id (optional)
4167
     *
4168
     * @return int Chat last connections by course in x days
4169
     */
4170
    public static function chat_connections_during_last_x_days_by_course(
4171
        $course_code,
4172
        $last_days,
4173
        $session_id = 0
4174
    ) {
4175
        $course_info = api_get_course_info($course_code);
4176
        if (empty($course_info)) {
4177
            return null;
4178
        }
4179
        $courseId = $course_info['real_id'];
4180
4181
        // Protect data
4182
        $last_days = (int) $last_days;
4183
        $session_id = (int) $session_id;
4184
4185
        $tbl_stats_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
4186
        $now = api_get_utc_datetime();
4187
4188
        $sql = "SELECT count(*) FROM $tbl_stats_access
4189
                WHERE
4190
                    DATE_SUB('$now',INTERVAL $last_days DAY) <= access_date AND
4191
                    c_id = '$courseId' AND
4192
                    access_tool='".TOOL_CHAT."' AND
4193
                    session_id = '$session_id' ";
4194
        $result = Database::query($sql);
4195
        if (Database::num_rows($result)) {
4196
            $row = Database::fetch_row($result);
4197
            $count = $row[0];
4198
4199
            return $count;
4200
        }
4201
4202
        return 0;
4203
    }
4204
4205
    /**
4206
     * This function gets the last student's connection in chat.
4207
     *
4208
     * @param      int     Student id
4209
     * @param      string     Course code
4210
     * @param    int        Session id (optional)
4211
     *
4212
     * @return string datetime formatted without day (e.g: February 23, 2010 10:20:50 )
4213
     */
4214
    public static function chat_last_connection(
4215
        $student_id,
4216
        $courseId,
4217
        $session_id = 0
4218
    ) {
4219
        $student_id = (int) $student_id;
4220
        $courseId = (int) $courseId;
4221
        $session_id = (int) $session_id;
4222
        $date_time = '';
4223
4224
        // table definition
4225
        $tbl_stats_access = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
4226
        $sql = "SELECT access_date
4227
                FROM $tbl_stats_access
4228
                WHERE
4229
                     access_tool='".TOOL_CHAT."' AND
4230
                     access_user_id='$student_id' AND
4231
                     c_id = $courseId AND
4232
                     session_id = '$session_id'
4233
                ORDER BY access_date DESC limit 1";
4234
        $rs = Database::query($sql);
4235
        if (Database::num_rows($rs) > 0) {
4236
            $row = Database::fetch_array($rs);
4237
            $date_time = api_convert_and_format_date(
4238
                $row['access_date'],
4239
                null,
4240
                date_default_timezone_get()
4241
            );
4242
        }
4243
4244
        return $date_time;
4245
    }
4246
4247
    /**
4248
     * Get count student's visited links.
4249
     *
4250
     * @param int $student_id Student id
4251
     * @param int $courseId
4252
     * @param int $session_id Session id (optional)
4253
     *
4254
     * @return int count of visited links
4255
     */
4256
    public static function count_student_visited_links($student_id, $courseId, $session_id = 0)
4257
    {
4258
        $student_id = (int) $student_id;
4259
        $courseId = (int) $courseId;
4260
        $session_id = (int) $session_id;
4261
4262
        // table definition
4263
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
4264
4265
        $sql = 'SELECT 1
4266
                FROM '.$table.'
4267
                WHERE
4268
                    links_user_id= '.$student_id.' AND
4269
                    c_id = "'.$courseId.'" AND
4270
                    session_id = '.$session_id.' ';
4271
4272
        $rs = Database::query($sql);
4273
4274
        return Database::num_rows($rs);
4275
    }
4276
4277
    public static function countStudentDownloadedDocuments(int $studentId, int $courseId, int $sessionId = 0): int
4278
    {
4279
        $em = Database::getManager();
4280
        $qb = $em->createQueryBuilder();
4281
4282
        $qb->select('COUNT(td.downId)')
4283
            ->from(TrackEDownloads::class, 'td')
4284
            ->leftJoin('td.resourceLink', 'rl')
4285
            ->where('td.downUserId = :studentId')
4286
            ->andWhere('rl.course = :courseId')
4287
            ->setParameter('studentId', $studentId)
4288
            ->setParameter('courseId', $courseId);
4289
4290
        if ($sessionId > 0) {
4291
            $qb->andWhere('rl.session = :sessionId')
4292
                ->setParameter('sessionId', $sessionId);
4293
        }
4294
4295
        $query = $qb->getQuery();
4296
4297
        return (int) $query->getSingleScalarResult();
4298
    }
4299
4300
    /**
4301
     * Get course list inside a session from a student.
4302
     *
4303
     * @param int $user_id   Student id
4304
     * @param int $sessionId Session id (optional)
4305
     *
4306
     * @return array Courses list
4307
     */
4308
    public static function get_course_list_in_session_from_student($user_id, $sessionId = 0)
4309
    {
4310
        $user_id = (int) $user_id;
4311
        $sessionId = (int) $sessionId;
4312
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4313
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
4314
4315
        $sql = "SELECT c.code
4316
                FROM $tbl_session_course_user sc
4317
                INNER JOIN $courseTable c
4318
                WHERE
4319
                    user_id= $user_id  AND
4320
                    session_id = $sessionId";
4321
        $result = Database::query($sql);
4322
        $courses = [];
4323
        while ($row = Database::fetch_array($result)) {
4324
            $courses[$row['code']] = $row['code'];
4325
        }
4326
4327
        return $courses;
4328
    }
4329
4330
    /**
4331
     * Get inactive students in course.
4332
     *
4333
     * @param int        $courseId
4334
     * @param string|int $since      Since login course date (optional, default = 'never')
4335
     * @param int        $session_id (optional)
4336
     *
4337
     * @return array Inactive users
4338
     */
4339
    public static function getInactiveStudentsInCourse(
4340
        $courseId,
4341
        $since = 'never',
4342
        $session_id = 0
4343
    ) {
4344
        $tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
4345
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4346
        $table_course_rel_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4347
        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
4348
        $now = api_get_utc_datetime();
4349
        $courseId = (int) $courseId;
4350
        $session_id = (int) $session_id;
4351
4352
        if (empty($courseId)) {
4353
            return false;
4354
        }
4355
4356
        if ('never' === $since) {
4357
            if (empty($session_id)) {
4358
                $sql = 'SELECT course_user.user_id
4359
                        FROM '.$table_course_rel_user.' course_user
4360
                        LEFT JOIN '.$tbl_track_login.' stats_login
4361
                        ON course_user.user_id = stats_login.user_id AND
4362
                        relation_type<>'.COURSE_RELATION_TYPE_RRHH.'
4363
                        INNER JOIN '.$tableCourse.' c
4364
                        ON (c.id = course_user.c_id)
4365
                        WHERE
4366
                            course_user.c_id = '.$courseId.' AND
4367
                            stats_login.login_course_date IS NULL
4368
                        GROUP BY course_user.user_id';
4369
            } else {
4370
                $sql = 'SELECT session_course_user.user_id
4371
                        FROM '.$tbl_session_course_user.' session_course_user
4372
                        LEFT JOIN '.$tbl_track_login.' stats_login
4373
                        ON session_course_user.user_id = stats_login.user_id
4374
                        INNER JOIN '.$tableCourse.' c
4375
                        ON (c.id = session_course_user.c_id)
4376
                        WHERE
4377
                            session_course_user.c_id = '.$courseId.' AND
4378
                            stats_login.login_course_date IS NULL
4379
                        GROUP BY session_course_user.user_id';
4380
            }
4381
        } else {
4382
            $since = (int) $since;
4383
            if (empty($session_id)) {
4384
                $inner = 'INNER JOIN '.$table_course_rel_user.' course_user
4385
                          ON course_user.user_id = stats_login.user_id AND course_user.c_id = c.id ';
4386
            } else {
4387
                $inner = 'INNER JOIN '.$tbl_session_course_user.' session_course_user
4388
                          ON
4389
                            c.id = session_course_user.c_id AND
4390
                            session_course_user.session_id = '.$session_id.' AND
4391
                            session_course_user.user_id = stats_login.user_id ';
4392
            }
4393
4394
            $sql = 'SELECT
4395
                    stats_login.user_id,
4396
                    MAX(login_course_date) max_date
4397
                FROM '.$tbl_track_login.' stats_login
4398
                INNER JOIN '.$tableCourse.' c
4399
                ON (c.id = stats_login.c_id)
4400
                '.$inner.'
4401
                WHERE c.id = '.$courseId.'
4402
                GROUP BY stats_login.user_id
4403
                HAVING DATE_SUB("'.$now.'", INTERVAL '.$since.' DAY) > max_date ';
4404
        }
4405
4406
        $rs = Database::query($sql);
4407
4408
        $allow = Container::getPluginHelper()->isPluginEnabled('PauseTraining');
4409
        $allowPauseFormation = 'true' === api_get_plugin_setting('PauseTraining', 'allow_users_to_edit_pause_formation');
4410
4411
        $extraFieldValue = new ExtraFieldValue('user');
4412
        $users = [];
4413
        while ($user = Database::fetch_array($rs)) {
4414
            $userId = $user['user_id'];
4415
4416
            if ($allow && $allowPauseFormation) {
4417
                $pause = $extraFieldValue->get_values_by_handler_and_field_variable($userId, 'pause_formation');
4418
                if (!empty($pause) && isset($pause['value']) && 1 == $pause['value']) {
4419
                    // Skip user because he paused his formation.
4420
                    continue;
4421
                }
4422
            }
4423
4424
            $users[] = $userId;
4425
        }
4426
4427
        return $users;
4428
    }
4429
4430
    /**
4431
     * get count clicks about tools most used by course.
4432
     *
4433
     * @param int $courseId
4434
     * @param    int        Session id (optional),
4435
     * if param $session_id is null(default) it'll return results
4436
     * including sessions, 0 = session is not filtered
4437
     *
4438
     * @return array tools data
4439
     */
4440
    public static function get_tools_most_used_by_course($courseId, $session_id = null)
4441
    {
4442
        $courseId = (int) $courseId;
4443
        $data = [];
4444
        $TABLETRACK_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
4445
        $condition_session = '';
4446
        if (isset($session_id)) {
4447
            $session_id = (int) $session_id;
4448
            $condition_session = ' AND session_id = '.$session_id;
4449
        }
4450
        $sql = "SELECT
4451
                    access_tool,
4452
                    COUNT(DISTINCT access_user_id),
4453
                    count(access_tool) as count_access_tool
4454
                FROM $TABLETRACK_ACCESS
4455
                WHERE
4456
                    access_tool IS NOT NULL AND
4457
                    access_tool != '' AND
4458
                    c_id = '$courseId'
4459
                    $condition_session
4460
                GROUP BY access_tool
4461
                ORDER BY count_access_tool DESC
4462
                LIMIT 0, 3";
4463
        $rs = Database::query($sql);
4464
        if (Database::num_rows($rs) > 0) {
4465
            while ($row = Database::fetch_array($rs)) {
4466
                $data[] = $row;
4467
            }
4468
        }
4469
4470
        return $data;
4471
    }
4472
4473
    /**
4474
     * get documents most downloaded by course.
4475
     *
4476
     * @param      string     Course code
4477
     * @param    int        Session id (optional),
4478
     * if param $session_id is null(default) it'll return results including
4479
     * sessions, 0 = session is not filtered
4480
     * @param    int        Limit (optional, default = 0, 0 = without limit)
4481
     *
4482
     * @return array documents downloaded
4483
     */
4484
    public static function get_documents_most_downloaded_by_course(
4485
        $course_code,
4486
        $session_id = 0,
4487
        $limit = 0
4488
    ) {
4489
        $courseId = api_get_course_int_id($course_code);
4490
        $data = [];
4491
4492
        $TABLETRACK_DOWNLOADS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DOWNLOADS);
4493
        $tableResourceLink = Database::get_main_table('resource_link');
4494
        $tableResourceNode = Database::get_main_table('resource_node');
4495
        $condition_session = '';
4496
        $session_id = intval($session_id);
4497
        if (!empty($session_id)) {
4498
            $condition_session = ' AND l.session_id = '.$session_id;
4499
        }
4500
        $sql = "SELECT t.resource_link_id as lid,
4501
                       n.path as npath,
4502
                       n.title as ntitle,
4503
                       n.uuid as uuid,
4504
                       n.id as nid,
4505
                    COUNT(t.down_id) as count_down
4506
                FROM $TABLETRACK_DOWNLOADS t
4507
                    INNER JOIN $tableResourceLink l
4508
                    ON t.resource_link_id = l.id
4509
                    INNER JOIN $tableResourceNode n
4510
                    ON l.resource_node_id = n.id
4511
                WHERE l.c_id = $courseId
4512
                    $condition_session
4513
                GROUP BY nid
4514
                ORDER BY count_down DESC
4515
                LIMIT 0,  $limit";
4516
        $rs = Database::query($sql);
4517
4518
        if (Database::num_rows($rs) > 0) {
4519
            while ($row = Database::fetch_array($rs)) {
4520
                $data[] = $row;
4521
            }
4522
        }
4523
4524
        return $data;
4525
    }
4526
4527
    /**
4528
     * get links most visited by course.
4529
     *
4530
     * @param      string     Course code
4531
     * @param    int        Session id (optional),
4532
     * if param $session_id is null(default) it'll
4533
     * return results including sessions, 0 = session is not filtered
4534
     *
4535
     * @return array links most visited
4536
     */
4537
    public static function get_links_most_visited_by_course($course_code, $session_id = null)
4538
    {
4539
        $course_code = Database::escape_string($course_code);
4540
        $course_info = api_get_course_info($course_code);
4541
4542
        // Safety check: if course info is not available, return empty result.
4543
        if (empty($course_info) || empty($course_info['real_id'])) {
4544
            return [];
4545
        }
4546
4547
        $courseId = (int) $course_info['real_id'];
4548
        $data = [];
4549
        $TABLETRACK_LINKS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LINKS);
4550
        $TABLECOURSE_LINKS = Database::get_course_table(TABLE_LINK);
4551
4552
        $condition_session = '';
4553
        if (isset($session_id)) {
4554
            $session_id = intval($session_id);
4555
            $condition_session = ' AND sl.session_id = '.$session_id;
4556
        }
4557
4558
        $sql = "SELECT cl.title, cl.url,count(DISTINCT sl.links_user_id), count(cl.title) as count_visits
4559
                FROM $TABLETRACK_LINKS AS sl, $TABLECOURSE_LINKS AS cl
4560
                WHERE
4561
                    sl.links_link_id = cl.iid AND
4562
                    sl.c_id = $courseId
4563
                    $condition_session
4564
                GROUP BY cl.title, cl.url
4565
                ORDER BY count_visits DESC
4566
                LIMIT 0, 3";
4567
        $rs = Database::query($sql);
4568
        if (Database::num_rows($rs) > 0) {
4569
            while ($row = Database::fetch_array($rs)) {
4570
                $data[] = $row;
4571
            }
4572
        }
4573
4574
        return $data;
4575
    }
4576
4577
    /**
4578
     * Shows the user progress (when clicking in the Progress tab).
4579
     *
4580
     * @param int    $user_id
4581
     * @param int    $session_id
4582
     * @param string $extra_params
4583
     * @param bool   $show_courses
4584
     * @param bool   $showAllSessions
4585
     * @param bool   $returnArray
4586
     *
4587
     * @return string|array
4588
     * @throws \Doctrine\DBAL\Exception
4589
     * @throws Exception
4590
     */
4591
    public static function show_user_progress(
4592
        $user_id,
4593
        $session_id = 0,
4594
        $extra_params = '',
4595
        $show_courses = true,
4596
        $showAllSessions = true,
4597
        $returnArray = false
4598
    ) {
4599
        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
4600
        $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
4601
        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
4602
        $tbl_access_rel_course = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
4603
        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
4604
        $tbl_access_rel_session = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
4605
4606
        // Default tracking columns for session course list
4607
        $trackingColumns = [
4608
            'course_session' => [
4609
                'course_title' => true,
4610
                'published_exercises' => true,
4611
                'new_exercises' => true,
4612
                'my_average' => true,
4613
                'average_exercise_result' => true,
4614
                'time_spent' => true,
4615
                'lp_progress' => true,
4616
                'score' => true,
4617
                'best_score' => true,
4618
                'last_connection' => true,
4619
                'details' => true,
4620
            ],
4621
        ];
4622
4623
        // Optional config from platform setting (must be an array)
4624
        $trackingColumnsConfig = api_get_setting('session.tracking_columns', true);
4625
        if (is_array($trackingColumnsConfig) && !empty($trackingColumnsConfig)) {
4626
            $trackingColumns = $trackingColumnsConfig;
4627
        }
4628
4629
        // Safe shortcut for course_session columns
4630
        $courseSessionColumns = [];
4631
        if (isset($trackingColumns['course_session']) && is_array($trackingColumns['course_session'])) {
4632
            $courseSessionColumns = $trackingColumns['course_session'];
4633
        }
4634
4635
        $user_id = (int) $user_id;
4636
        $session_id = (int) $session_id;
4637
        $urlId = -1;
4638
4639
        $accessUrlUtil = Container::getAccessUrlUtil();
4640
        if ($accessUrlUtil->isMultiple()) {
4641
            $urlId = $accessUrlUtil->getCurrent()->getId();
4642
            $sql = "SELECT c.id, c.code, title
4643
                FROM $tbl_course_user cu
4644
                INNER JOIN $tbl_course c
4645
                ON (cu.c_id = c.id)
4646
                INNER JOIN $tbl_access_rel_course a
4647
                ON (a.c_id = c.id)
4648
                WHERE
4649
                    cu.user_id = $user_id AND
4650
                    relation_type<> ".COURSE_RELATION_TYPE_RRHH." AND
4651
                    access_url_id = $urlId
4652
                ORDER BY title";
4653
        } else {
4654
            $sql = "SELECT c.id, c.code, title
4655
                FROM $tbl_course_user cu
4656
                INNER JOIN $tbl_course c
4657
                ON (cu.c_id = c.id)
4658
                WHERE
4659
                    cu.user_id = $user_id AND
4660
                    relation_type <> ".COURSE_RELATION_TYPE_RRHH."
4661
                ORDER BY title";
4662
        }
4663
4664
        $rs = Database::query($sql);
4665
        $courses = $course_in_session = $temp_course_in_session = [];
4666
        $courseIdList = [];
4667
        while ($row = Database::fetch_assoc($rs)) {
4668
            $courses[$row['id']] = $row['title'];
4669
            $courseIdList[] = $row['id'];
4670
        }
4671
4672
        $orderBy = ' ORDER BY title ';
4673
        $extraInnerJoin = null;
4674
4675
        if (SessionManager::orderCourseIsEnabled() && !empty($session_id)) {
4676
            $orderBy = ' ORDER BY s.id, src.position ';
4677
            $tableSessionRelCourse = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
4678
            $extraInnerJoin = " INNER JOIN $tableSessionRelCourse src
4679
                            ON (cu.c_id = src.c_id AND src.session_id = $session_id) ";
4680
        }
4681
4682
        $sessionCondition = '';
4683
        if (!empty($session_id)) {
4684
            $sessionCondition = " AND s.id = $session_id";
4685
        }
4686
4687
        // Get the list of sessions where the user is subscribed as student
4688
        if ($accessUrlUtil->isMultiple()) {
4689
            $sql = "SELECT DISTINCT c.code, s.id as session_id, s.title
4690
                FROM $tbl_session_course_user cu
4691
                INNER JOIN $tbl_access_rel_session a
4692
                ON (a.session_id = cu.session_id)
4693
                INNER JOIN $tbl_session s
4694
                ON (s.id = a.session_id)
4695
                INNER JOIN $tbl_course c
4696
                ON (c.id = cu.c_id)
4697
                $extraInnerJoin
4698
                WHERE
4699
                    cu.user_id = $user_id AND
4700
                    access_url_id = $urlId
4701
                    $sessionCondition
4702
                $orderBy ";
4703
        } else {
4704
            $sql = "SELECT DISTINCT c.code, s.id as session_id, s.title
4705
                FROM $tbl_session_course_user cu
4706
                INNER JOIN $tbl_session s
4707
                ON (s.id = cu.session_id)
4708
                INNER JOIN $tbl_course c
4709
                ON (c.id = cu.c_id)
4710
                $extraInnerJoin
4711
                WHERE
4712
                    cu.user_id = $user_id
4713
                    $sessionCondition
4714
                $orderBy ";
4715
        }
4716
4717
        $rs = Database::query($sql);
4718
        $simple_session_array = [];
4719
        while ($row = Database::fetch_assoc($rs)) {
4720
            $course_info = api_get_course_info($row['code']);
4721
            $temp_course_in_session[$row['session_id']]['course_list'][$course_info['real_id']] = $course_info;
4722
            $temp_course_in_session[$row['session_id']]['title'] = $row['title'];
4723
            $simple_session_array[$row['session_id']] = $row['title'];
4724
        }
4725
4726
        foreach ($simple_session_array as $my_session_id => $session_title) {
4727
            $course_list = $temp_course_in_session[$my_session_id]['course_list'];
4728
            $my_course_data = [];
4729
            foreach ($course_list as $courseId => $course_data) {
4730
                $my_course_data[$courseId] = $course_data['title'];
4731
            }
4732
4733
            if (empty($session_id)) {
4734
                $my_course_data = utf8_sort($my_course_data);
4735
            }
4736
4737
            $final_course_data = [];
4738
            foreach ($my_course_data as $course_id => $value) {
4739
                if (isset($course_list[$course_id])) {
4740
                    $final_course_data[$course_id] = $course_list[$course_id];
4741
                }
4742
            }
4743
            $course_in_session[$my_session_id]['course_list'] = $final_course_data;
4744
            $course_in_session[$my_session_id]['title'] = $session_title;
4745
        }
4746
4747
        if ($returnArray) {
4748
            $course_in_session[0] = $courseIdList;
4749
4750
            return $course_in_session;
4751
        }
4752
4753
        $html = '';
4754
4755
        // ---------------------------------------------------------------------
4756
        // Course list ("My courses")
4757
        // ---------------------------------------------------------------------
4758
        if ($show_courses) {
4759
            if (!empty($courses)) {
4760
                $html .= Display::page_subheader(
4761
                    Display::getMdiIcon(
4762
                        'book-open-page-variant',
4763
                        'ch-tool-icon',
4764
                        null,
4765
                        ICON_SIZE_SMALL,
4766
                        get_lang('My courses')
4767
                    ).' '.get_lang('My courses')
4768
                );
4769
4770
                $columns = [
4771
                    'course_title' => get_lang('Course'),
4772
                    'time_spent' => get_lang('Time spent in the course'),
4773
                    'progress' => get_lang('Progress'),
4774
                    'best_score_in_lp' => get_lang('Best score in learning path'),
4775
                    'best_score_not_in_lp' => get_lang('Best score not in learning path'),
4776
                    'latest_login' => get_lang('Latest login'),
4777
                    'details' => get_lang('Details'),
4778
                ];
4779
4780
                // Optional per-column visibility config for "My courses"
4781
                $availableColumns = [];
4782
                if (isset($trackingColumns['my_progress_courses']) && is_array($trackingColumns['my_progress_courses'])) {
4783
                    $availableColumns = $trackingColumns['my_progress_courses'];
4784
                }
4785
4786
                $html .= '<div class="table-responsive">';
4787
                $html .= '<table class="table table-striped table-hover">';
4788
                $html .= '<thead><tr>';
4789
                foreach ($columns as $columnKey => $name) {
4790
                    if (!empty($availableColumns)) {
4791
                        if (isset($availableColumns[$columnKey]) && false == $availableColumns[$columnKey]) {
4792
                            continue;
4793
                        }
4794
                    }
4795
                    $html .= Display::tag('th', $name);
4796
                }
4797
                $html .= '</tr></thead><tbody>';
4798
4799
                foreach ($courses as $courseId => $course_title) {
4800
                    $course = api_get_course_entity($courseId);
4801
                    if (null === $course) {
4802
                        continue;
4803
                    }
4804
4805
                    $courseCode = $course->getCode();
4806
4807
                    $total_time_login = self::get_time_spent_on_the_course(
4808
                        $user_id,
4809
                        $courseId
4810
                    );
4811
                    $time = api_time_to_hms($total_time_login);
4812
                    $progress = self::get_avg_student_progress(
4813
                        $user_id,
4814
                        $course
4815
                    );
4816
                    $bestScore = self::get_avg_student_score(
4817
                        $user_id,
4818
                        $course,
4819
                        [],
4820
                        null,
4821
                        false,
4822
                        false,
4823
                        true
4824
                    );
4825
4826
                    $qb = Container::getQuizRepository()->findAllByCourse($course, null, null, 1, false);
4827
                    /** @var CQuiz[] $exercises */
4828
                    $exercises = $qb->getQuery()->getResult();
4829
4830
                    $bestScoreAverageNotInLP = 0;
4831
                    if (!empty($exercises)) {
4832
                        foreach ($exercises as $exerciseData) {
4833
                            $results = Event::get_best_exercise_results_by_user(
4834
                                $exerciseData->getIid(),
4835
                                $courseId,
4836
                                0,
4837
                                $user_id
4838
                            );
4839
                            $best = 0;
4840
                            if (!empty($results)) {
4841
                                foreach ($results as $result) {
4842
                                    if (!empty($result['max_score'])) {
4843
                                        $score = $result['score'] / $result['max_score'];
4844
                                        if ($score > $best) {
4845
                                            $best = $score;
4846
                                        }
4847
                                    }
4848
                                }
4849
                            }
4850
                            $bestScoreAverageNotInLP += $best;
4851
                        }
4852
                        $bestScoreAverageNotInLP = round($bestScoreAverageNotInLP / count($exercises) * 100, 2);
4853
                    }
4854
4855
                    $last_connection = self::get_last_connection_date_on_the_course(
4856
                        $user_id,
4857
                        ['real_id' => $courseId]
4858
                    );
4859
4860
                    if (is_null($progress) || empty($progress)) {
4861
                        $progress = '0%';
4862
                    } else {
4863
                        $progress = $progress.'%';
4864
                    }
4865
4866
                    if (isset($_GET['course']) &&
4867
                        $courseCode == $_GET['course'] &&
4868
                        empty($_GET['session_id'])
4869
                    ) {
4870
                        $html .= '<tr class="row_odd" style="background-color:#FBF09D">';
4871
                    } else {
4872
                        $html .= '<tr class="row_even">';
4873
                    }
4874
4875
                    $url = api_get_course_url($courseId, $session_id);
4876
                    $course_url = Display::url($course_title, $url, ['target' => SESSION_LINK_TARGET]);
4877
4878
                    if (empty($bestScore)) {
4879
                        $bestScoreResult = '-';
4880
                    } else {
4881
                        $bestScoreResult = $bestScore.'%';
4882
                    }
4883
                    if (empty($bestScoreAverageNotInLP)) {
4884
                        $bestScoreNotInLP = '-';
4885
                    } else {
4886
                        $bestScoreNotInLP = $bestScoreAverageNotInLP.'%';
4887
                    }
4888
4889
                    $detailsLink = '';
4890
                    if (isset($_GET['course']) &&
4891
                        $courseCode == $_GET['course'] &&
4892
                        empty($_GET['session_id'])
4893
                    ) {
4894
                        $detailsLink .= '<a href="#course_session_header">';
4895
                        $detailsLink .= Display::getMdiIcon(
4896
                            'fast-forward-outline',
4897
                            'ch-tool-icon',
4898
                            null,
4899
                            ICON_SIZE_SMALL,
4900
                            get_lang('Details')
4901
                        );
4902
                        $detailsLink .= '</a>';
4903
                    } else {
4904
                        $detailsLink .= '<a href="'.api_get_self().'?course='.$courseCode.$extra_params.'#course_session_header">';
4905
                        $detailsLink .= Display::getMdiIcon(
4906
                            'fast-forward-outline',
4907
                            'ch-tool-icon',
4908
                            null,
4909
                            ICON_SIZE_SMALL,
4910
                            get_lang('Details')
4911
                        );
4912
                        $detailsLink .= '</a>';
4913
                    }
4914
4915
                    $result = [
4916
                        'course_title' => $course_url,
4917
                        'time_spent' => $time,
4918
                        'progress' => $progress,
4919
                        'best_score_in_lp' => $bestScoreResult,
4920
                        'best_score_not_in_lp' => $bestScoreNotInLP,
4921
                        'latest_login' => $last_connection,
4922
                        'details' => $detailsLink,
4923
                    ];
4924
4925
                    foreach ($result as $columnKey => $data) {
4926
                        if (!empty($availableColumns)) {
4927
                            if (isset($availableColumns[$columnKey]) && false == $availableColumns[$columnKey]) {
4928
                                continue;
4929
                            }
4930
                        }
4931
                        $html .= '<td>'.$data.'</td>';
4932
                    }
4933
4934
                    $html .= '</tr>';
4935
                }
4936
                $html .= '</tbody></table>';
4937
                $html .= '</div>';
4938
            }
4939
        }
4940
4941
        // ---------------------------------------------------------------------
4942
        // Session list
4943
        // ---------------------------------------------------------------------
4944
        if (!empty($course_in_session)) {
4945
            $main_session_graph = '';
4946
            // Load graphics only when calling to a specific session
4947
            $all_exercise_graph_name_list = [];
4948
            $my_results = [];
4949
            $all_exercise_graph_list = [];
4950
            $all_exercise_start_time = [];
4951
4952
            foreach ($course_in_session as $my_session_id => $session_data) {
4953
                $course_list = $session_data['course_list'];
4954
                $user_count = count(SessionManager::get_users_by_session($my_session_id));
4955
                $exercise_graph_name_list = [];
4956
                $exercise_graph_list = [];
4957
4958
                foreach ($course_list as $course_data) {
4959
                    $course = api_get_course_entity($course_data['real_id']);
4960
                    if (null === $course) {
4961
                        continue;
4962
                    }
4963
4964
                    $courseId = $course->getId();
4965
4966
                    $qb = Container::getQuizRepository()->findAllByCourse($course, null, null, 1, false);
4967
                    /** @var CQuiz[] $exercises */
4968
                    $exercises = $qb->getQuery()->getResult();
4969
                    $countExercises = count($exercises);
4970
4971
                    foreach ($exercises as $exercise_data) {
4972
                        $disabled = $exercise_data->getResultsDisabled();
4973
                        $exerciseId = $exercise_data->getIid();
4974
                        if (0 == $disabled || 2 == $disabled) {
4975
                            $best_average = (int)
4976
                            ExerciseLib::get_best_average_score_by_exercise(
4977
                                $exerciseId,
4978
                                $courseId,
4979
                                $my_session_id,
4980
                                $user_count
4981
                            )
4982
                            ;
4983
4984
                            $exercise_graph_list[] = $best_average;
4985
                            $all_exercise_graph_list[] = $best_average;
4986
4987
                            $user_result_data = ExerciseLib::get_best_attempt_by_user(
4988
                                api_get_user_id(),
4989
                                $exerciseId,
4990
                                $courseId,
4991
                                $my_session_id
4992
                            );
4993
4994
                            $score = 0;
4995
                            if (!empty($user_result_data['max_score']) && 0 != intval($user_result_data['max_score'])) {
4996
                                $score = intval($user_result_data['score'] / $user_result_data['max_score'] * 100);
4997
                            }
4998
4999
                            $start = $exercise_data->getStartTime() ? $exercise_data->getStartTime()->getTimestamp() : null;
5000
                            $time = null !== $start ? $start : 0;
5001
                            $all_exercise_start_time[] = $time;
5002
                            $my_results[] = $score;
5003
                            $exerciseTitle = $exercise_data->getTitle();
5004
                            if ($countExercises <= 10) {
5005
                                $title = cut($course_data['title'], 30)." \n ".cut($exerciseTitle, 30);
5006
                                $exercise_graph_name_list[] = $title;
5007
                                $all_exercise_graph_name_list[] = $title;
5008
                            } else {
5009
                                // If there are more than 10 results, space becomes limited, so only show exercise title
5010
                                $title = cut($exerciseTitle, 30);
5011
                                $exercise_graph_name_list[] = $title;
5012
                                $all_exercise_graph_name_list[] = $title;
5013
                            }
5014
                        }
5015
                    }
5016
                }
5017
            }
5018
5019
            // Complete graph
5020
            if (!empty($my_results) && !empty($all_exercise_graph_list)) {
5021
                asort($all_exercise_start_time);
5022
5023
                // Fix exams order
5024
                $final_all_exercise_graph_name_list = [];
5025
                $my_results_final = [];
5026
                $final_all_exercise_graph_list = [];
5027
5028
                foreach ($all_exercise_start_time as $key => $time) {
5029
                    $label_time = '';
5030
                    if (!empty($time)) {
5031
                        $label_time = date('d-m-y', $time);
5032
                    }
5033
                    $final_all_exercise_graph_name_list[] = $all_exercise_graph_name_list[$key].' '.$label_time;
5034
                    $my_results_final[] = $my_results[$key];
5035
                    $final_all_exercise_graph_list[] = $all_exercise_graph_list[$key];
5036
                }
5037
5038
                $main_session_graph = self::generate_session_exercise_graph(
5039
                    $final_all_exercise_graph_name_list,
5040
                    $my_results_final,
5041
                    $final_all_exercise_graph_list
5042
                );
5043
            }
5044
5045
            $sessionIcon = Display::getMdiIcon(
5046
                'google-classroom',
5047
                'ch-tool-icon',
5048
                null,
5049
                ICON_SIZE_SMALL,
5050
                get_lang('Course sessions')
5051
            );
5052
5053
            $anchor = Display::url('', '', ['name' => 'course_session_header']);
5054
            $html .= $anchor.Display::page_subheader(
5055
                    $sessionIcon.' '.get_lang('Course sessions')
5056
                );
5057
5058
            $html .= '<div class="table-responsive">';
5059
            $html .= '<table class="table table-striped table-hover">';
5060
            $html .= '<thead>';
5061
            $html .= '<tr>
5062
              '.Display::tag('th', get_lang('Session'), ['width' => '300px']).'
5063
              '.Display::tag('th', get_lang('Tests available'), ['width' => '300px']).'
5064
              '.Display::tag('th', get_lang('New tests')).'
5065
              '.Display::tag('th', get_lang('Average test result')).'
5066
              '.Display::tag('th', get_lang('Details')).'
5067
              </tr>';
5068
            $html .= '</thead>';
5069
            $html .= '<tbody>';
5070
5071
            // Session entity reused for statistics
5072
            $session = api_get_session_entity($my_session_id);
5073
5074
            foreach ($course_in_session as $my_session_id => $session_data) {
5075
                $course_list = $session_data['course_list'];
5076
                $session_title = $session_data['title'];
5077
5078
                if (false == $showAllSessions) {
5079
                    if (!empty($session_id) && $session_id != $my_session_id) {
5080
                        continue;
5081
                    }
5082
                }
5083
5084
                $all_exercises = 0;
5085
                $all_unanswered_exercises_by_user = 0;
5086
                $all_average = 0;
5087
                $stats_array = [];
5088
5089
                foreach ($course_list as $course_data) {
5090
                    $courseId = $course_data['real_id'];
5091
                    $course = api_get_course_entity($courseId);
5092
                    if (null === $course) {
5093
                        continue;
5094
                    }
5095
5096
                    $sessionEntity = api_get_session_entity($my_session_id);
5097
5098
                    $qb = Container::getQuizRepository()->findAllByCourse($course, $sessionEntity, null, 2);
5099
                    /** @var CQuiz[] $exercises */
5100
                    $exercises = $qb->getQuery()->getResult();
5101
                    $count_exercises = count($exercises);
5102
5103
                    // Count of user results
5104
                    $done_exercises = null;
5105
                    $answered_exercises = 0;
5106
                    if (!empty($exercises)) {
5107
                        foreach ($exercises as $exercise_item) {
5108
                            $attempts = Event::count_exercise_attempts_by_user(
5109
                                api_get_user_id(),
5110
                                $exercise_item->getIid(),
5111
                                $courseId,
5112
                                $my_session_id
5113
                            );
5114
                            if ($attempts > 1) {
5115
                                $answered_exercises++;
5116
                            }
5117
                        }
5118
                    }
5119
5120
                    // Average
5121
                    $average = ExerciseLib::get_average_score_by_course(
5122
                        $courseId,
5123
                        $my_session_id
5124
                    );
5125
                    $all_exercises += $count_exercises;
5126
                    $all_unanswered_exercises_by_user += $count_exercises - $answered_exercises;
5127
                    $all_average += $average;
5128
                }
5129
5130
                if (!empty($course_list)) {
5131
                    $all_average = $all_average / count($course_list);
5132
                }
5133
5134
                if (isset($_GET['session_id']) && $my_session_id == (int) $_GET['session_id']) {
5135
                    $html .= '<tr style="background-color:#FBF09D">';
5136
                } else {
5137
                    $html .= '<tr>';
5138
                }
5139
5140
                $url = api_get_path(WEB_CODE_PATH)."session/index.php?session_id={$my_session_id}";
5141
5142
                $html .= Display::tag('td', Display::url($session_title, $url, ['target' => SESSION_LINK_TARGET]));
5143
                $html .= Display::tag('td', $all_exercises);
5144
                $html .= Display::tag('td', $all_unanswered_exercises_by_user);
5145
                $html .= Display::tag('td', ExerciseLib::convert_to_percentage($all_average));
5146
5147
                $icon = Display::url(
5148
                    Display::getMdiIcon(
5149
                        'fast-forward-outline',
5150
                        'ch-tool-icon',
5151
                        null,
5152
                        ICON_SIZE_SMALL,
5153
                        get_lang('Details')
5154
                    ),
5155
                    api_get_self().'?session_id='.$my_session_id.'#course_session_list'
5156
                );
5157
5158
                $html .= Display::tag('td', $icon);
5159
                $html .= '</tr>';
5160
            }
5161
            $html .= '</tbody>';
5162
            $html .= '</table></div><br />';
5163
            $html .= Display::div(
5164
                $main_session_graph,
5165
                [
5166
                    'id' => 'session_graph',
5167
                    'class' => 'chart-session',
5168
                    'style' => 'position:relative; text-align: center;',
5169
                ]
5170
            );
5171
5172
            // -----------------------------------------------------------------
5173
            // Checking selected session: course list inside a session
5174
            // -----------------------------------------------------------------
5175
            if (isset($_GET['session_id'])) {
5176
                $session_id_from_get = (int) $_GET['session_id'];
5177
                if (isset($course_in_session[$session_id_from_get])) {
5178
                    $session_data = $course_in_session[$session_id_from_get];
5179
                    $course_list = $session_data['course_list'];
5180
5181
                    $html .= '<a name= "course_session_list"></a>';
5182
                    $html .= Display::tag('h3', $session_data['title'].' - '.get_lang('Course list'));
5183
5184
                    $html .= '<div class="table-responsive">';
5185
                    $html .= '<table class="table table-hover table-striped">';
5186
5187
                    $columnHeaders = [
5188
                        'course_title' => [
5189
                            get_lang('Course'),
5190
                            ['width' => '300px'],
5191
                        ],
5192
                        'published_exercises' => [
5193
                            get_lang('Tests available'),
5194
                        ],
5195
                        'new_exercises' => [
5196
                            get_lang('New tests'),
5197
                        ],
5198
                        'my_average' => [
5199
                            get_lang('My average'),
5200
                        ],
5201
                        'average_exercise_result' => [
5202
                            get_lang('Average test result'),
5203
                        ],
5204
                        'time_spent' => [
5205
                            get_lang('Time spent in the course'),
5206
                        ],
5207
                        'lp_progress' => [
5208
                            get_lang('Learning path progress'),
5209
                        ],
5210
                        'score' => [
5211
                            get_lang('Score').
5212
                            Display::getMdiIcon(
5213
                                ActionIcon::INFORMATION,
5214
                                'ch-tool-icon',
5215
                                null,
5216
                                ICON_SIZE_SMALL,
5217
                                get_lang('Average of tests in Learning Paths')
5218
                            ),
5219
                        ],
5220
                        'best_score' => [
5221
                            get_lang('Best score'),
5222
                        ],
5223
                        'last_connection' => [
5224
                            get_lang('Latest login'),
5225
                        ],
5226
                        'details' => [
5227
                            get_lang('Details'),
5228
                        ],
5229
                    ];
5230
5231
                    $html .= '<thead><tr>';
5232
                    foreach ($columnHeaders as $key => $columnSetting) {
5233
                        // Apply visibility config for course_session columns
5234
                        if (!empty($courseSessionColumns)) {
5235
                            if (!array_key_exists($key, $courseSessionColumns)
5236
                                || !$courseSessionColumns[$key]
5237
                            ) {
5238
                                continue;
5239
                            }
5240
                        }
5241
5242
                        $settings = isset($columnSetting[1]) ? $columnSetting[1] : [];
5243
                        $html .= Display::tag(
5244
                            'th',
5245
                            $columnSetting[0],
5246
                            $settings
5247
                        );
5248
                    }
5249
5250
                    $html .= '</tr>
5251
                    </thead>
5252
                    <tbody>';
5253
5254
                    foreach ($course_list as $course_data) {
5255
                        $course_code = $course_data['code'];
5256
                        $course_title = $course_data['title'];
5257
                        $courseId = $course_data['real_id'];
5258
                        $course = api_get_course_entity($courseId);
5259
                        if (null === $course) {
5260
                            continue;
5261
                        }
5262
5263
                        $sessionEntity = api_get_session_entity($session_id_from_get);
5264
5265
                        $qb = Container::getQuizRepository()->findAllByCourse($course, $sessionEntity, null, 2);
5266
                        /** @var CQuiz[] $exercises */
5267
                        $exercises = $qb->getQuery()->getResult();
5268
5269
                        $count_exercises = 0;
5270
                        if (!empty($exercises)) {
5271
                            $count_exercises = count($exercises);
5272
                        }
5273
5274
                        $answered_exercises = 0;
5275
                        foreach ($exercises as $exercise_item) {
5276
                            $attempts = Event::count_exercise_attempts_by_user(
5277
                                api_get_user_id(),
5278
                                $exercise_item->getIid(),
5279
                                $courseId,
5280
                                $session_id_from_get
5281
                            );
5282
                            if ($attempts > 1) {
5283
                                $answered_exercises++;
5284
                            }
5285
                        }
5286
5287
                        $unanswered_exercises = $count_exercises - $answered_exercises;
5288
5289
                        // Average
5290
                        $average = ExerciseLib::get_average_score_by_course(
5291
                            $courseId,
5292
                            $session_id_from_get
5293
                        );
5294
                        $my_average = ExerciseLib::get_average_score_by_course_by_user(
5295
                            api_get_user_id(),
5296
                            $courseId,
5297
                            $session_id_from_get
5298
                        );
5299
5300
                        $bestScore = self::get_avg_student_score(
5301
                            $user_id,
5302
                            $course,
5303
                            [],
5304
                            $sessionEntity,
5305
                            false,
5306
                            false,
5307
                            true
5308
                        );
5309
5310
                        $stats_array[$course_code] = [
5311
                            'exercises' => $count_exercises,
5312
                            'unanswered_exercises_by_user' => $unanswered_exercises,
5313
                            'done_exercises' => $done_exercises,
5314
                            'average' => $average,
5315
                            'my_average' => $my_average,
5316
                            'best_score' => $bestScore,
5317
                        ];
5318
5319
                        $last_connection = self::get_last_connection_date_on_the_course(
5320
                            $user_id,
5321
                            $course_data,
5322
                            $session_id_from_get
5323
                        );
5324
5325
                        $progress = self::get_avg_student_progress(
5326
                            $user_id,
5327
                            $course,
5328
                            [],
5329
                            $sessionEntity
5330
                        );
5331
5332
                        $total_time_login = self::get_time_spent_on_the_course(
5333
                            $user_id,
5334
                            $courseId,
5335
                            $session_id_from_get
5336
                        );
5337
                        $time = api_time_to_hms($total_time_login);
5338
5339
                        $percentage_score = self::get_avg_student_score(
5340
                            $user_id,
5341
                            $course,
5342
                            [],
5343
                            $sessionEntity
5344
                        );
5345
                        $courseCodeFromGet = isset($_GET['course']) ? $_GET['course'] : null;
5346
5347
                        if ($course_code == $courseCodeFromGet && isset($_GET['session_id']) &&
5348
                            (int) $_GET['session_id'] == $session_id_from_get
5349
                        ) {
5350
                            $html .= '<tr class="row_odd" style="background-color:#FBF09D">';
5351
                        } else {
5352
                            $html .= '<tr class="row_even">';
5353
                        }
5354
5355
                        $url = api_get_course_url($courseId, $session_id_from_get);
5356
                        $course_url = Display::url(
5357
                            $course_title,
5358
                            $url,
5359
                            ['target' => SESSION_LINK_TARGET]
5360
                        );
5361
5362
                        if (is_numeric($progress)) {
5363
                            $progress = $progress.'%';
5364
                        } else {
5365
                            $progress = '0%';
5366
                        }
5367
                        if (is_numeric($percentage_score)) {
5368
                            $percentage_score = $percentage_score.'%';
5369
                        } else {
5370
                            $percentage_score = '0%';
5371
                        }
5372
5373
                        if (is_numeric($stats_array[$course_code]['best_score'])) {
5374
                            $bestScore = $stats_array[$course_code]['best_score'].'%';
5375
                        } else {
5376
                            $bestScore = '-';
5377
                        }
5378
5379
                        if (empty($last_connection) || is_bool($last_connection)) {
5380
                            $last_connection = '';
5381
                        }
5382
5383
                        if ($course_code == $courseCodeFromGet &&
5384
                            isset($_GET['session_id']) &&
5385
                            (int) $_GET['session_id'] == $session_id_from_get
5386
                        ) {
5387
                            $details = Display::url(
5388
                                Display::getMdiIcon(
5389
                                    'fast-forward-outline',
5390
                                    'ch-tool-icon',
5391
                                    null,
5392
                                    ICON_SIZE_SMALL,
5393
                                    get_lang('Details')
5394
                                ),
5395
                                '#course_session_data'
5396
                            );
5397
                        } else {
5398
                            $detailsUrl = api_get_self().
5399
                                '?course='.$course_code.'&session_id='.$session_id_from_get.$extra_params.'#course_session_data';
5400
                            $details = Display::url(
5401
                                Display::getMdiIcon(
5402
                                    'fast-forward-outline',
5403
                                    'ch-tool-icon',
5404
                                    null,
5405
                                    ICON_SIZE_SMALL,
5406
                                    get_lang('Details')
5407
                                ),
5408
                                $detailsUrl
5409
                            );
5410
                        }
5411
5412
                        $data = [
5413
                            'course_title' => $course_url,
5414
                            'published_exercises' => $stats_array[$course_code]['exercises'],
5415
                            'new_exercises' => $stats_array[$course_code]['unanswered_exercises_by_user'],
5416
                            'my_average' => ExerciseLib::convert_to_percentage($stats_array[$course_code]['my_average']),
5417
                            'average_exercise_result' => 0 == $stats_array[$course_code]['average']
5418
                                ? '-'
5419
                                : '('.ExerciseLib::convert_to_percentage($stats_array[$course_code]['average']).')',
5420
                            'time_spent' => $time,
5421
                            'lp_progress' => $progress,
5422
                            'score' => $percentage_score,
5423
                            'best_score' => $bestScore,
5424
                            'last_connection' => $last_connection,
5425
                            'details' => $details,
5426
                        ];
5427
5428
                        foreach ($data as $key => $value) {
5429
                            // Apply course_session visibility config if available
5430
                            if (!empty($courseSessionColumns)) {
5431
                                if (!array_key_exists($key, $courseSessionColumns)
5432
                                    || !$courseSessionColumns[$key]
5433
                                ) {
5434
                                    continue;
5435
                                }
5436
                            }
5437
5438
                            $html .= Display::tag('td', $value);
5439
                        }
5440
5441
                        $html .= '</tr>';
5442
                    }
5443
5444
                    $html .= '</tbody></table></div>';
5445
                }
5446
            }
5447
        }
5448
5449
        $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled');
5450
        if ($pluginCalendar) {
5451
            $course_in_session[0] = $courseIdList;
5452
            $plugin = LearningCalendarPlugin::create();
5453
            $html .= $plugin->getUserStatsPanel($user_id, $course_in_session);
5454
        }
5455
5456
        return $html;
5457
    }
5458
5459
5460
    /**
5461
     * Shows the user detail progress (when clicking in the details link).
5462
     *
5463
     * @param int  $userId
5464
     * @param int  $courseId
5465
     * @param int  $sessionId
5466
     * @param bool $showDiagram
5467
     *
5468
     * @return string html code
5469
     */
5470
    public static function show_course_detail($userId, $courseId, $sessionId = 0, $showDiagram = false)
5471
    {
5472
        $html = '';
5473
        $courseId = (int) $courseId;
5474
5475
        if (empty($courseId)) {
5476
            return '';
5477
        }
5478
5479
        $userId = (int) $userId;
5480
        $sessionId = (int) $sessionId;
5481
        $course = api_get_course_entity($courseId);
5482
        if (null === $course) {
5483
            return '';
5484
        }
5485
5486
        $courseCode = $course->getCode();
5487
5488
        $html .= '<a name="course_session_data"></a>';
5489
        $html .= Display::page_subheader($course->getTitle());
5490
5491
        if ($showDiagram && !empty($sessionId)) {
5492
            $visibility = api_get_session_visibility($sessionId);
5493
            if (SESSION_AVAILABLE === $visibility) {
5494
                $html .= Display::page_subheader2($course->getTitle());
5495
            }
5496
        }
5497
5498
        $html .= '<div class="table-responsive">';
5499
        $html .= '<table class="table table-striped table-hover">';
5500
5501
        // Course details (per exercise)
5502
        $html .= '
5503
        <thead>
5504
        <tr>
5505
        <th>'.get_lang('Tests').'</th>
5506
        <th>'.get_lang('Attempts').'</th>
5507
        <th>'.get_lang('Best attempt').'</th>
5508
        <th>'.get_lang('Ranking').'</th>
5509
        <th>'.get_lang('Best result in course').'</th>
5510
        <th>'.get_lang('Statistics').' '
5511
            .Display::getMdiIcon(
5512
                ActionIcon::INFORMATION,
5513
                'ch-tool-icon',
5514
                null,
5515
                ICON_SIZE_SMALL,
5516
                get_lang('In case of multiple attempts, only shows the best result of each learner')
5517
            ).
5518
            '</th>
5519
        </tr>
5520
        </thead>
5521
        <tbody>';
5522
5523
        $session = null;
5524
        if (empty($sessionId)) {
5525
            $user_list = CourseManager::get_user_list_from_course_code(
5526
                $courseCode,
5527
                $sessionId,
5528
                null,
5529
                null,
5530
                STUDENT
5531
            );
5532
        } else {
5533
            $session = api_get_session_entity($sessionId);
5534
            $user_list = CourseManager::get_user_list_from_course_code(
5535
                $courseCode,
5536
                $sessionId,
5537
                null,
5538
                null,
5539
                0
5540
            );
5541
        }
5542
5543
        $qb = Container::getQuizRepository()->findAllByCourse($course, $session, null, 2, false);
5544
        /** @var CQuiz[] $exercises */
5545
        $exercises = $qb->getQuery()->getResult();
5546
5547
        $to_graph_exercise_result = [];
5548
        if (!empty($exercises)) {
5549
            $weighting = $exe_id = 0;
5550
            foreach ($exercises as $exercise) {
5551
                $exerciseId = $exercise->getIid();
5552
5553
                // Load exercise instance for visibility check
5554
                $exercise_obj = new Exercise($courseId);
5555
                $exercise_obj->read($exerciseId);
5556
                $visible_return = $exercise_obj->is_visible();
5557
5558
                $score = $weighting = $attempts = 0;
5559
5560
                // Getting count of attempts by user
5561
                $attempts = Event::count_exercise_attempts_by_user(
5562
                    api_get_user_id(),
5563
                    $exerciseId,
5564
                    $courseId,
5565
                    $sessionId
5566
                );
5567
5568
                $html .= '<tr class="row_even">';
5569
                $url = api_get_path(WEB_CODE_PATH).
5570
                    "exercise/overview.php?cid={$courseId}&sid=$sessionId&exerciseId={$exerciseId}";
5571
5572
                if (true == $visible_return['value']) {
5573
                    $exerciseTitle = Display::url(
5574
                        $exercise->getTitle(),
5575
                        $url,
5576
                        ['target' => SESSION_LINK_TARGET]
5577
                    );
5578
                } elseif (-1 == $exercise->getActive()) {
5579
                    $exerciseTitle = sprintf(get_lang('%s (deleted)'), $exercise->getTitle());
5580
                } else {
5581
                    // Fallback: not visible but not deleted
5582
                    $exerciseTitle = $exercise->getTitle();
5583
                }
5584
5585
                $html .= Display::tag('td', $exerciseTitle);
5586
                $resultsDisabled = $exercise->getResultsDisabled();
5587
5588
                // Exercise configuration: show results or show only score
5589
                if (0 == $resultsDisabled || 2 == $resultsDisabled) {
5590
                    // For graphics
5591
                    $best_exercise_stats = Event::get_best_exercise_results_by_user(
5592
                        $exerciseId,
5593
                        $courseId,
5594
                        $sessionId
5595
                    );
5596
5597
                    $to_graph_exercise_result[$exerciseId] = [
5598
                        'title' => $exerciseTitle,
5599
                        'data' => $best_exercise_stats,
5600
                    ];
5601
5602
                    $latest_attempt_url = '';
5603
                    $best_score = $position = $percentage_score_result = '-';
5604
                    $graph = $normal_graph = null;
5605
5606
                    // Getting best results for the course
5607
                    $best_score_data = ExerciseLib::get_best_attempt_in_course(
5608
                        $exerciseId,
5609
                        $courseId,
5610
                        $sessionId
5611
                    );
5612
5613
                    $best_score = '';
5614
                    if (!empty($best_score_data)) {
5615
                        $best_score = ExerciseLib::show_score(
5616
                            $best_score_data['score'],
5617
                            $best_score_data['max_score']
5618
                        );
5619
                    }
5620
5621
                    if ($attempts > 0) {
5622
                        $exercise_stat = ExerciseLib::get_best_attempt_by_user(
5623
                            api_get_user_id(),
5624
                            $exerciseId,
5625
                            $courseId,
5626
                            $sessionId
5627
                        );
5628
5629
                        if (!empty($exercise_stat)) {
5630
                            // Always getting the BEST attempt for current user
5631
                            $score = $exercise_stat['score'];
5632
                            $weighting = $exercise_stat['max_score'];
5633
                            $exe_id = $exercise_stat['exe_id'];
5634
5635
                            $latest_attempt_url .= api_get_path(WEB_CODE_PATH).
5636
                                'exercise/result.php?id='.$exe_id.'&cid='.$courseId.'&show_headers=1&sid='.$sessionId;
5637
5638
                            $percentage_score_result = Display::url(
5639
                                ExerciseLib::show_score($score, $weighting),
5640
                                $latest_attempt_url
5641
                            );
5642
5643
                            $my_score = 0;
5644
                            if (!empty($weighting) && 0 != intval($weighting)) {
5645
                                $my_score = $score / $weighting;
5646
                            }
5647
5648
                            // Ranking among all users
5649
                            if (is_int($user_list)) {
5650
                                // Backwards-compatibility: convert single user id into array
5651
                                $user_list = [$user_list];
5652
                            }
5653
5654
                            $position = ExerciseLib::get_exercise_result_ranking(
5655
                                $my_score,
5656
                                $exe_id,
5657
                                $exerciseId,
5658
                                $courseId,      // IMPORTANT: pass courseId (int), not course code
5659
                                $sessionId,
5660
                                $user_list
5661
                            );
5662
5663
                            $graph = self::generate_exercise_result_thumbnail_graph(
5664
                                $to_graph_exercise_result[$exerciseId]
5665
                            );
5666
                            $normal_graph = self::generate_exercise_result_graph(
5667
                                $to_graph_exercise_result[$exerciseId]
5668
                            );
5669
                        }
5670
                    }
5671
5672
                    $html .= Display::div(
5673
                        $normal_graph,
5674
                        [
5675
                            'id' => 'main_graph_'.$exerciseId,
5676
                            'class' => 'dialog',
5677
                            'style' => 'display:none',
5678
                        ]
5679
                    );
5680
5681
                    if (empty($graph)) {
5682
                        $graph = '-';
5683
                    } else {
5684
                        $graph = Display::url(
5685
                            '<img src="'.$graph.'" >',
5686
                            $normal_graph,
5687
                            [
5688
                                'id' => $exerciseId,
5689
                                'class' => 'expand-image',
5690
                            ]
5691
                        );
5692
                    }
5693
5694
                    $html .= Display::tag('td', $attempts);
5695
                    $html .= Display::tag('td', $percentage_score_result);
5696
                    $html .= Display::tag('td', $position);
5697
                    $html .= Display::tag('td', $best_score);
5698
                    $html .= Display::tag('td', $graph);
5699
                } else {
5700
                    // Exercise configuration: NO results
5701
                    $html .= Display::tag('td', $attempts);
5702
                    $html .= Display::tag('td', '-');
5703
                    $html .= Display::tag('td', '-');
5704
                    $html .= Display::tag('td', '-');
5705
                    $html .= Display::tag('td', '-');
5706
                }
5707
5708
                $html .= '</tr>';
5709
            }
5710
        } else {
5711
            $html .= '<tr><td colspan="5">'.get_lang('There is no test for the moment').'</td></tr>';
5712
        }
5713
        $html .= '</tbody></table></div>';
5714
5715
        // ---------------------------------------------------------------------
5716
        // Learning paths section
5717
        // ---------------------------------------------------------------------
5718
        $columnHeaders = [
5719
            'lp' => get_lang('Learning paths'),
5720
            'time' => get_lang('Time spent'),
5721
            'progress' => get_lang('Progress'),
5722
            'score' => get_lang('Score'),
5723
            'best_score' => get_lang('Best score'),
5724
            'last_connection' => get_lang('Latest login'),
5725
        ];
5726
5727
        $headers = '';
5728
        $trackingColumns = api_get_setting('session.tracking_columns', true);
5729
5730
        if (is_array($trackingColumns)
5731
            && isset($trackingColumns['my_progress_lp'])
5732
            && is_array($trackingColumns['my_progress_lp'])
5733
        ) {
5734
            foreach ($columnHeaders as $key => $value) {
5735
                if (!isset($trackingColumns['my_progress_lp'][$key]) ||
5736
                    false == $trackingColumns['my_progress_lp'][$key]
5737
                ) {
5738
                    unset($columnHeaders[$key]);
5739
                }
5740
            }
5741
        }
5742
5743
        $columnHeadersKeys = array_keys($columnHeaders);
5744
        foreach ($columnHeaders as $columnName) {
5745
            $headers .= Display::tag('th', $columnName);
5746
        }
5747
5748
        // LP table results
5749
        $html .= '<div class="table-responsive">';
5750
        $html .= '<table class="table table-striped table-hover">';
5751
        $html .= '<thead><tr>';
5752
        $html .= $headers;
5753
        $html .= '</tr></thead><tbody>';
5754
5755
        $list = new LearnpathList(
5756
            api_get_user_id(),
5757
            ['real_id' => $courseId],
5758
            $sessionId,
5759
            'resource.publishedOn',
5760
            true,
5761
            null,
5762
            true
5763
        );
5764
5765
        $lp_list = $list->get_flat_list();
5766
5767
        if (!empty($lp_list)) {
5768
            foreach ($lp_list as $lp_id => $learnpath) {
5769
                if (!$learnpath['lp_visibility']) {
5770
                    continue;
5771
                }
5772
5773
                $progress = self::get_avg_student_progress(
5774
                    $userId,
5775
                    $course,
5776
                    [$lp_id],
5777
                    $session
5778
                );
5779
                $last_connection_in_lp = self::get_last_connection_time_in_lp(
5780
                    $userId,
5781
                    $course->getCode(),
5782
                    $lp_id,
5783
                    $sessionId
5784
                );
5785
5786
                $time_spent_in_lp = self::get_time_spent_in_lp(
5787
                    $userId,
5788
                    $course,
5789
                    [$lp_id],
5790
                    $sessionId
5791
                );
5792
                $percentage_score = self::get_avg_student_score(
5793
                    $userId,
5794
                    $course,
5795
                    [$lp_id],
5796
                    $session
5797
                );
5798
5799
                $bestScore = self::get_avg_student_score(
5800
                    $userId,
5801
                    $course,
5802
                    [$lp_id],
5803
                    $session,
5804
                    false,
5805
                    false,
5806
                    true
5807
                );
5808
5809
                if (is_numeric($progress)) {
5810
                    $progress = $progress.'%';
5811
                }
5812
                if (is_numeric($percentage_score)) {
5813
                    $percentage_score = $percentage_score.'%';
5814
                } else {
5815
                    $percentage_score = '0%';
5816
                }
5817
5818
                if (is_numeric($bestScore)) {
5819
                    $bestScore = $bestScore.'%';
5820
                } else {
5821
                    $bestScore = '-';
5822
                }
5823
5824
                $time_spent_in_lp = api_time_to_hms($time_spent_in_lp);
5825
                $last_connection = '-';
5826
                if (!empty($last_connection_in_lp)) {
5827
                    $last_connection = api_convert_and_format_date(
5828
                        $last_connection_in_lp,
5829
                        DATE_TIME_FORMAT_LONG
5830
                    );
5831
                }
5832
5833
                $url = api_get_path(WEB_CODE_PATH).
5834
                    "lp/lp_controller.php?cid={$courseId}&sid=$sessionId&lp_id=$lp_id&action=view";
5835
5836
                $html .= '<tr class="row_even">';
5837
5838
                if (in_array('lp', $columnHeadersKeys)) {
5839
                    if (0 == $learnpath['lp_visibility']) {
5840
                        $html .= Display::tag('td', $learnpath['lp_name']);
5841
                    } else {
5842
                        $html .= Display::tag(
5843
                            'td',
5844
                            Display::url(
5845
                                $learnpath['lp_name'],
5846
                                $url,
5847
                                ['target' => SESSION_LINK_TARGET]
5848
                            )
5849
                        );
5850
                    }
5851
                }
5852
5853
                if (in_array('time', $columnHeadersKeys)) {
5854
                    $html .= Display::tag('td', $time_spent_in_lp);
5855
                }
5856
5857
                if (in_array('progress', $columnHeadersKeys)) {
5858
                    $html .= Display::tag('td', $progress);
5859
                }
5860
5861
                if (in_array('score', $columnHeadersKeys)) {
5862
                    $html .= Display::tag('td', $percentage_score);
5863
                }
5864
5865
                if (in_array('best_score', $columnHeadersKeys)) {
5866
                    $html .= Display::tag('td', $bestScore);
5867
                }
5868
5869
                if (in_array('last_connection', $columnHeadersKeys)) {
5870
                    $html .= Display::tag('td', $last_connection, ['width' => '180px']);
5871
                }
5872
5873
                $html .= '</tr>';
5874
            }
5875
        } else {
5876
            $html .= '<tr>
5877
                <td colspan="4" align="center">
5878
                    '.get_lang('No learning path').'
5879
                </td>
5880
              </tr>';
5881
        }
5882
5883
        $html .= '</tbody></table></div>';
5884
5885
        // User skills section for this course
5886
        $html .= self::displayUserSkills($userId, $courseId, $sessionId);
5887
5888
        return $html;
5889
    }
5890
5891
    /**
5892
     * Generates an histogram.
5893
     *
5894
     * @param array $names      list of exercise names
5895
     * @param array $my_results my results 0 to 100
5896
     * @param array $average    average scores 0-100
5897
     *
5898
     * @return string
5899
     */
5900
    public static function generate_session_exercise_graph($names, $my_results, $average)
5901
    {
5902
        //$html = api_get_js('chartjs/Chart.js');
5903
        $canvas = Display::tag('canvas', '', ['id' => 'session_graph_chart']);
5904
        $html = Display::tag('div', $canvas, ['style' => 'width:100%']);
5905
        $jsStr = " var data = {
5906
                       labels:".json_encode($names).",
5907
                       datasets: [
5908
                       {
5909
                         label: '".get_lang('My results')."',
5910
                         backgroundColor: 'rgb(255, 99, 132)',
5911
                         stack: 'Stack1',
5912
                         data: ".json_encode($my_results).",
5913
                        },
5914
                        {
5915
                         label: '".get_lang('Average score')."',
5916
                         backgroundColor: 'rgb(75, 192, 192)',
5917
                         stack: 'Stack2',
5918
                         data: ".json_encode($average).",
5919
                        },
5920
                        ],
5921
                    };
5922
                    var ctx = document.getElementById('session_graph_chart').getContext('2d');
5923
                    var myBarChart = new Chart(ctx, {
5924
                    type: 'bar',
5925
                    data: data,
5926
                    options: {
5927
                            title: {
5928
                                    display: true,
5929
                                    text: '".get_lang('Progress of own exercises results over time, against learners average')."'
5930
                            },
5931
                            tooltips: {
5932
                                    mode: 'index',
5933
                                    intersect: false
5934
                            },
5935
                            responsive: true,
5936
                            scales: {
5937
                                yAxes: [{
5938
                                    ticks: {
5939
                                        // Include a dollar sign in the ticks
5940
                                        callback: function(value, index, values) {
5941
                                            return value + '%';
5942
                                        }
5943
                                    }
5944
                                }]
5945
                            }
5946
                    }
5947
                });";
5948
        $html .= Display::tag('script', $jsStr);
5949
5950
        return $html;
5951
    }
5952
5953
    /**
5954
     * Returns a thumbnail of the function generate_exercise_result_graph.
5955
     *
5956
     * @param array $attempts
5957
     */
5958
    public static function generate_exercise_result_thumbnail_graph($attempts)
5959
    {
5960
        //$exercise_title = $attempts['title'];
5961
        $attempts = $attempts['data'];
5962
        $my_exercise_result_array = $exercise_result = [];
5963
        if (empty($attempts)) {
5964
            return null;
5965
        }
5966
5967
        foreach ($attempts as $attempt) {
5968
            if (api_get_user_id() == $attempt['exe_user_id']) {
5969
                if (0 != $attempt['max_score']) {
5970
                    $my_exercise_result_array[] = $attempt['score'] / $attempt['max_score'];
5971
                }
5972
            } else {
5973
                if (0 != $attempt['max_score']) {
5974
                    $exercise_result[] = $attempt['score'] / $attempt['max_score'];
5975
                }
5976
            }
5977
        }
5978
5979
        // Getting best result
5980
        rsort($my_exercise_result_array);
5981
        $my_exercise_result = 0;
5982
        if (isset($my_exercise_result_array[0])) {
5983
            $my_exercise_result = $my_exercise_result_array[0] * 100;
5984
        }
5985
5986
        $max = 100;
5987
        $pieces = 5;
5988
        $part = round($max / $pieces);
5989
        $x_axis = [];
5990
        $final_array = [];
5991
        $my_final_array = [];
5992
5993
        for ($i = 1; $i <= $pieces; $i++) {
5994
            $sum = 1;
5995
            if (1 == $i) {
5996
                $sum = 0;
5997
            }
5998
            $min = ($i - 1) * $part + $sum;
5999
            $max = ($i) * $part;
6000
            $x_axis[] = $min." - ".$max;
6001
            $count = 0;
6002
            foreach ($exercise_result as $result) {
6003
                $percentage = $result * 100;
6004
                if ($percentage >= $min && $percentage <= $max) {
6005
                    //echo ' is > ';
6006
                    $count++;
6007
                }
6008
            }
6009
            //echo '<br />';
6010
            $final_array[] = $count;
6011
6012
            if ($my_exercise_result >= $min && $my_exercise_result <= $max) {
6013
                $my_final_array[] = 1;
6014
            } else {
6015
                $my_final_array[] = 0;
6016
            }
6017
        }
6018
6019
        // Fix to remove the data of the user with my data
6020
        for ($i = 0; $i <= count($my_final_array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
6021
            if (!empty($my_final_array[$i])) {
6022
                $my_final_array[$i] = $final_array[$i] + 1; //Add my result
6023
                $final_array[$i] = 0;
6024
            }
6025
        }
6026
6027
        // Dataset definition
6028
        $dataSet = new pData();
6029
        $dataSet->addPoints($final_array, 'Serie1');
6030
        $dataSet->addPoints($my_final_array, 'Serie2');
6031
        $dataSet->normalize(100, "%");
6032
        $dataSet->loadPalette(api_get_path(SYS_CODE_PATH).'palettes/pchart/default.color', true);
6033
6034
        // Cache definition
6035
        $cachePath = api_get_path(SYS_ARCHIVE_PATH);
6036
        $myCache = new pCache(['CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)]);
6037
        $chartHash = $myCache->getHash($dataSet);
6038
        if ($myCache->isInCache($chartHash)) {
6039
            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
6040
            $myCache->saveFromCache($chartHash, $imgPath);
6041
            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
6042
        } else {
6043
            /* Create the pChart object */
6044
            $widthSize = 80;
6045
            $heightSize = 35;
6046
            $fontSize = 2;
6047
6048
            $myPicture = new pImage($widthSize, $heightSize, $dataSet);
6049
6050
            /* Turn of Antialiasing */
6051
            $myPicture->Antialias = false;
6052
6053
            /* Add a border to the picture */
6054
            $myPicture->drawRectangle(
6055
                0,
6056
                0,
6057
                $widthSize - 1,
6058
                $heightSize - 1,
6059
                ['R' => 0, 'G' => 0, 'B' => 0]
6060
            );
6061
6062
            /* Set the default font */
6063
            $myPicture->setFontProperties(
6064
                [
6065
                    'FontName' => api_get_path(
6066
                            SYS_FONTS_PATH
6067
                        ).'opensans/OpenSans-Regular.ttf',
6068
                    'FontSize' => $fontSize,
6069
                ]
6070
            );
6071
6072
            /* Do not write the chart title */
6073
            /* Define the chart area */
6074
            $myPicture->setGraphArea(5, 5, $widthSize - 5, $heightSize - 5);
6075
6076
            /* Draw the scale */
6077
            $scaleSettings = [
6078
                'GridR' => 200,
6079
                'GridG' => 200,
6080
                'GridB' => 200,
6081
                'DrawSubTicks' => true,
6082
                'CycleBackground' => true,
6083
                'Mode' => SCALE_MODE_MANUAL,
6084
                'ManualScale' => [
6085
                    '0' => [
6086
                        'Min' => 0,
6087
                        'Max' => 100,
6088
                    ],
6089
                ],
6090
            ];
6091
            $myPicture->drawScale($scaleSettings);
6092
6093
            /* Turn on shadow computing */
6094
            $myPicture->setShadow(
6095
                true,
6096
                [
6097
                    'X' => 1,
6098
                    'Y' => 1,
6099
                    'R' => 0,
6100
                    'G' => 0,
6101
                    'B' => 0,
6102
                    'Alpha' => 10,
6103
                ]
6104
            );
6105
6106
            /* Draw the chart */
6107
            $myPicture->setShadow(
6108
                true,
6109
                [
6110
                    'X' => 1,
6111
                    'Y' => 1,
6112
                    'R' => 0,
6113
                    'G' => 0,
6114
                    'B' => 0,
6115
                    'Alpha' => 10,
6116
                ]
6117
            );
6118
            $settings = [
6119
                'DisplayValues' => true,
6120
                'DisplaySize' => $fontSize,
6121
                'DisplayR' => 0,
6122
                'DisplayG' => 0,
6123
                'DisplayB' => 0,
6124
                'DisplayOrientation' => ORIENTATION_HORIZONTAL,
6125
                'Gradient' => false,
6126
                'Surrounding' => 5,
6127
                'InnerSurrounding' => 5,
6128
            ];
6129
            $myPicture->drawStackedBarChart($settings);
6130
6131
            /* Save and write in cache */
6132
            $myCache->writeToCache($chartHash, $myPicture);
6133
            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
6134
            $myCache->saveFromCache($chartHash, $imgPath);
6135
            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
6136
        }
6137
6138
        return $imgPath;
6139
    }
6140
6141
    /**
6142
     * Generates a big graph with the number of best results.
6143
     *
6144
     * @param	array
6145
     */
6146
    public static function generate_exercise_result_graph($attempts)
6147
    {
6148
        $exercise_title = strip_tags($attempts['title']);
6149
        $attempts = $attempts['data'];
6150
        $my_exercise_result_array = $exercise_result = [];
6151
        if (empty($attempts)) {
6152
            return null;
6153
        }
6154
        foreach ($attempts as $attempt) {
6155
            if (api_get_user_id() == $attempt['exe_user_id']) {
6156
                if (0 != $attempt['max_score']) {
6157
                    $my_exercise_result_array[] = $attempt['score'] / $attempt['max_score'];
6158
                }
6159
            } else {
6160
                if (0 != $attempt['max_score']) {
6161
                    $exercise_result[] = $attempt['score'] / $attempt['max_score'];
6162
                }
6163
            }
6164
        }
6165
6166
        //Getting best result
6167
        rsort($my_exercise_result_array);
6168
        $my_exercise_result = 0;
6169
        if (isset($my_exercise_result_array[0])) {
6170
            $my_exercise_result = $my_exercise_result_array[0] * 100;
6171
        }
6172
6173
        $max = 100;
6174
        $pieces = 5;
6175
        $part = round($max / $pieces);
6176
        $x_axis = [];
6177
        $final_array = [];
6178
        $my_final_array = [];
6179
6180
        for ($i = 1; $i <= $pieces; $i++) {
6181
            $sum = 1;
6182
            if (1 == $i) {
6183
                $sum = 0;
6184
            }
6185
            $min = ($i - 1) * $part + $sum;
6186
            $max = ($i) * $part;
6187
            $x_axis[] = $min." - ".$max;
6188
            $count = 0;
6189
            foreach ($exercise_result as $result) {
6190
                $percentage = $result * 100;
6191
                if ($percentage >= $min && $percentage <= $max) {
6192
                    $count++;
6193
                }
6194
            }
6195
            $final_array[] = $count;
6196
6197
            if ($my_exercise_result >= $min && $my_exercise_result <= $max) {
6198
                $my_final_array[] = 1;
6199
            } else {
6200
                $my_final_array[] = 0;
6201
            }
6202
        }
6203
6204
        //Fix to remove the data of the user with my data
6205
6206
        for ($i = 0; $i <= count($my_final_array); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
6207
            if (!empty($my_final_array[$i])) {
6208
                $my_final_array[$i] = $final_array[$i] + 1; //Add my result
6209
                $final_array[$i] = 0;
6210
            }
6211
        }
6212
6213
        // Dataset definition
6214
        $dataSet = new pData();
6215
        $dataSet->addPoints($final_array, 'Serie1');
6216
        $dataSet->addPoints($my_final_array, 'Serie2');
6217
        $dataSet->addPoints($x_axis, 'Serie3');
6218
6219
        $dataSet->setSerieDescription('Serie1', get_lang('Score'));
6220
        $dataSet->setSerieDescription('Serie2', get_lang('My results'));
6221
        $dataSet->setAbscissa('Serie3');
6222
6223
        $dataSet->setXAxisName(get_lang('Score'));
6224
        $dataSet->normalize(100, "%");
6225
6226
        $dataSet->loadPalette(api_get_path(SYS_CODE_PATH).'palettes/pchart/default.color', true);
6227
6228
        // Cache definition
6229
        $cachePath = api_get_path(SYS_ARCHIVE_PATH);
6230
        $myCache = new pCache(['CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)]);
6231
        $chartHash = $myCache->getHash($dataSet);
6232
6233
        if ($myCache->isInCache($chartHash)) {
6234
            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
6235
            $myCache->saveFromCache($chartHash, $imgPath);
6236
            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
6237
        } else {
6238
            /* Create the pChart object */
6239
            $widthSize = 480;
6240
            $heightSize = 250;
6241
            $fontSize = 8;
6242
            $myPicture = new pImage($widthSize, $heightSize, $dataSet);
6243
6244
            /* Turn of Antialiasing */
6245
            $myPicture->Antialias = false;
6246
6247
            /* Add a border to the picture */
6248
            $myPicture->drawRectangle(0, 0, $widthSize - 1, $heightSize - 1, ['R' => 0, 'G' => 0, 'B' => 0]);
6249
6250
            /* Set the default font */
6251
            $myPicture->setFontProperties(
6252
                [
6253
                    'FontName' => api_get_path(
6254
                            SYS_FONTS_PATH
6255
                        ).'opensans/OpenSans-Regular.ttf',
6256
                    'FontSize' => 10,
6257
                ]
6258
            );
6259
6260
            /* Write the chart title */
6261
            $myPicture->drawText(
6262
                250,
6263
                20,
6264
                $exercise_title,
6265
                [
6266
                    'FontSize' => 12,
6267
                    'Align' => TEXT_ALIGN_BOTTOMMIDDLE,
6268
                ]
6269
            );
6270
6271
            /* Define the chart area */
6272
            $myPicture->setGraphArea(50, 50, $widthSize - 20, $heightSize - 30);
6273
6274
            /* Draw the scale */
6275
            $scaleSettings = [
6276
                'GridR' => 200,
6277
                'GridG' => 200,
6278
                'GridB' => 200,
6279
                'DrawSubTicks' => true,
6280
                'CycleBackground' => true,
6281
                'Mode' => SCALE_MODE_MANUAL,
6282
                'ManualScale' => [
6283
                    '0' => [
6284
                        'Min' => 0,
6285
                        'Max' => 100,
6286
                    ],
6287
                ],
6288
            ];
6289
            $myPicture->drawScale($scaleSettings);
6290
6291
            /* Turn on shadow computing */
6292
            $myPicture->setShadow(true, ['X' => 1, 'Y' => 1, 'R' => 0, 'G' => 0, 'B' => 0, 'Alpha' => 10]);
6293
6294
            /* Draw the chart */
6295
            $myPicture->setShadow(true, ['X' => 1, 'Y' => 1, 'R' => 0, 'G' => 0, 'B' => 0, 'Alpha' => 10]);
6296
            $settings = [
6297
                'DisplayValues' => true,
6298
                'DisplaySize' => $fontSize,
6299
                'DisplayR' => 0,
6300
                'DisplayG' => 0,
6301
                'DisplayB' => 0,
6302
                'DisplayOrientation' => ORIENTATION_HORIZONTAL,
6303
                'Gradient' => false,
6304
                'Surrounding' => 30,
6305
                'InnerSurrounding' => 25,
6306
            ];
6307
            $myPicture->drawStackedBarChart($settings);
6308
6309
            $legendSettings = [
6310
                'Mode' => LEGEND_HORIZONTAL,
6311
                'Style' => LEGEND_NOBORDER,
6312
            ];
6313
            $myPicture->drawLegend($widthSize / 2, 30, $legendSettings);
6314
6315
            /* Write and save into cache */
6316
            $myCache->writeToCache($chartHash, $myPicture);
6317
            $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
6318
            $myCache->saveFromCache($chartHash, $imgPath);
6319
            $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
6320
        }
6321
6322
        return $imgPath;
6323
    }
6324
6325
    /**
6326
     * @param FormValidator $form
6327
     *
6328
     * @return mixed
6329
     */
6330
    public static function setUserSearchForm($form)
6331
    {
6332
        $form->addElement('text', 'keyword', get_lang('Keyword'));
6333
        $form->addSelect(
6334
            'active',
6335
            get_lang('Status'),
6336
            [1 => get_lang('active'), 0 => get_lang('inactive')]
6337
        );
6338
6339
        $form->addSelect(
6340
            'sleeping_days',
6341
            get_lang('Inactive days'),
6342
            [
6343
                '',
6344
                1 => 1,
6345
                5 => 5,
6346
                15 => 15,
6347
                30 => 30,
6348
                60 => 60,
6349
                90 => 90,
6350
                120 => 120,
6351
            ]
6352
        );
6353
6354
        $form->addButtonSearch(get_lang('Search'));
6355
6356
        return $form;
6357
    }
6358
6359
    /**
6360
     * Get the progress of a exercise.
6361
     *
6362
     * @param int    $sessionId  The session ID (session.id)
6363
     * @param int    $courseId   The course ID (course.id)
6364
     * @param int    $exerciseId The quiz ID (c_quiz.id)
6365
     * @param string $date_from
6366
     * @param string $date_to
6367
     * @param array  $options    An array of options you can pass to the query (limit, where and order)
6368
     *
6369
     * @return array An array with the data of exercise(s) progress
6370
     */
6371
    public static function get_exercise_progress(
6372
        $sessionId = 0,
6373
        $courseId = 0,
6374
        $exerciseId = 0,
6375
        $date_from = null,
6376
        $date_to = null,
6377
        $options = []
6378
    ) {
6379
        $sessionId = intval($sessionId);
6380
        $courseId = intval($courseId);
6381
        $exerciseId = intval($exerciseId);
6382
        $date_from = Database::escape_string($date_from);
6383
        $date_to = Database::escape_string($date_to);
6384
        /*
6385
         * This method gets the data by blocks, as previous attempts at one single
6386
         * query made it take ages. The logic of query division is described below
6387
         */
6388
        // Get tables names
6389
        $tuser = Database::get_main_table(TABLE_MAIN_USER);
6390
        $tquiz = Database::get_course_table(TABLE_QUIZ_TEST);
6391
        $tquiz_answer = Database::get_course_table(TABLE_QUIZ_ANSWER);
6392
        $tquiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
6393
        $tquiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
6394
        $ttrack_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
6395
        $ttrack_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
6396
6397
        $sessions = [];
6398
        $courses = [];
6399
        // if session ID is defined but course ID is empty, get all the courses
6400
        // from that session
6401
        if (!empty($sessionId) && empty($courseId)) {
6402
            // $courses is an array of course int id as index and course details hash as value
6403
            $courses = SessionManager::get_course_list_by_session_id($sessionId);
6404
            $sessions[$sessionId] = api_get_session_info($sessionId);
6405
        } elseif (empty($sessionId) && !empty($courseId)) {
6406
            // if, to the contrary, course is defined but not sessions, get the sessions that include this course
6407
            // $sessions is an array like: [0] => ('id' => 3, 'title' => 'Session 35'), [1] => () etc;
6408
            $course = api_get_course_info_by_id($courseId);
6409
            $sessionsTemp = SessionManager::get_session_by_course($courseId);
6410
            $courses[$courseId] = $course;
6411
            foreach ($sessionsTemp as $sessionItem) {
6412
                $sessions[$sessionItem['id']] = $sessionItem;
6413
            }
6414
        } elseif (!empty($courseId) && !empty($sessionId)) {
6415
            //none is empty
6416
            $course = api_get_course_info_by_id($courseId);
6417
            $courses[$courseId] = [$course['code']];
6418
            $courses[$courseId]['code'] = $course['code'];
6419
            $sessions[$sessionId] = api_get_session_info($sessionId);
6420
        } else {
6421
            //both are empty, not enough data, return an empty array
6422
            return [];
6423
        }
6424
        // Now we have two arrays of courses and sessions with enough data to proceed
6425
        // If no course could be found, we shouldn't return anything.
6426
        // Course sessions can be empty (then we only return the pure-course-context results)
6427
        if (count($courses) < 1) {
6428
            return [];
6429
        }
6430
6431
        $data = [];
6432
        // The following loop is less expensive than what it seems:
6433
        // - if a course was defined, then we only loop through sessions
6434
        // - if a session was defined, then we only loop through courses
6435
        // - if a session and a course were defined, then we only loop once
6436
        foreach ($courses as $courseIdx => $courseData) {
6437
            $where = '';
6438
            $whereParams = [];
6439
            $whereSessionParams = '';
6440
            if (count($sessions > 0)) {
6441
                foreach ($sessions as $sessionIdx => $sessionData) {
6442
                    if (!empty($sessionIdx)) {
6443
                        $whereSessionParams .= $sessionIdx.',';
6444
                    }
6445
                }
6446
                $whereSessionParams = substr($whereSessionParams, 0, -1);
6447
            }
6448
6449
            if (!empty($exerciseId)) {
6450
                $exerciseId = intval($exerciseId);
6451
                $where .= ' AND q.iid = %d ';
6452
                $whereParams[] = $exerciseId;
6453
            }
6454
6455
            /*
6456
             * This feature has been disabled for now, to avoid having to
6457
             * join two very large tables
6458
            //2 = show all questions (wrong and correct answered)
6459
            if ($answer != 2) {
6460
                $answer = intval($answer);
6461
                //$where .= ' AND qa.correct = %d';
6462
                //$whereParams[] = $answer;
6463
            }
6464
            */
6465
6466
            $limit = '';
6467
            if (!empty($options['limit'])) {
6468
                $limit = " LIMIT ".$options['limit'];
6469
            }
6470
6471
            if (!empty($options['where'])) {
6472
                $where .= ' AND '.Database::escape_string($options['where']);
6473
            }
6474
6475
            $order = '';
6476
            if (!empty($options['order'])) {
6477
                $order = " ORDER BY ".$options['order'];
6478
            }
6479
6480
            if (!empty($date_to) && !empty($date_from)) {
6481
                $where .= sprintf(" AND (te.start_date BETWEEN '%s 00:00:00' AND '%s 23:59:59')", $date_from, $date_to);
6482
            }
6483
6484
            $sql = "SELECT
6485
                te.session_id,
6486
                ta.id as attempt_id,
6487
                te.exe_user_id as user_id,
6488
                te.exe_id as exercise_attempt_id,
6489
                ta.question_id,
6490
                ta.answer as answer_id,
6491
                ta.tms as time,
6492
                te.exe_exo_id as quiz_id,
6493
                CONCAT ('c', q.c_id, '_e', q.iid) as exercise_id,
6494
                q.title as quiz_title,
6495
                qq.description as description
6496
                FROM $ttrack_exercises te
6497
                INNER JOIN $ttrack_attempt ta
6498
                ON ta.exe_id = te.exe_id
6499
                INNER JOIN $tquiz q
6500
                ON q.iid = te.exe_exo_id
6501
                INNER JOIN $tquiz_rel_question rq
6502
                ON rq.quiz_id = q.iid AND rq.c_id = q.c_id
6503
                INNER JOIN $tquiz_question qq
6504
                ON
6505
                    qq.iid = rq.question_id AND
6506
                    qq.c_id = rq.c_id AND
6507
                    qq.position = rq.question_order AND
6508
                    ta.question_id = rq.question_id
6509
                WHERE
6510
                    te.c_id = $courseIdx ".(empty($whereSessionParams) ? '' : "AND te.session_id IN ($whereSessionParams)")."
6511
                    AND q.c_id = $courseIdx
6512
                    $where $order $limit";
6513
            $sql_query = vsprintf($sql, $whereParams);
6514
6515
            // Now browse through the results and get the data
6516
            $rs = Database::query($sql_query);
6517
            $userIds = [];
6518
            $questionIds = [];
6519
            $answerIds = [];
6520
            while ($row = Database::fetch_array($rs)) {
6521
                //only show if exercise is visible
6522
                if (api_get_item_visibility($courseData, 'quiz', $row['exercise_id'])) {
6523
                    $userIds[$row['user_id']] = $row['user_id'];
6524
                    $questionIds[$row['question_id']] = $row['question_id'];
6525
                    $answerIds[$row['question_id']][$row['answer_id']] = $row['answer_id'];
6526
                    $row['session'] = $sessions[$row['session_id']];
6527
                    $data[] = $row;
6528
                }
6529
            }
6530
            // Now fill questions data. Query all questions and answers for this test to avoid
6531
            $sqlQuestions = "SELECT tq.c_id, tq.iid as question_id, tq.question, tqa.iid,
6532
                            tqa.answer, tqa.correct, tq.position, tqa.iid as answer_id
6533
                            FROM $tquiz_question tq, $tquiz_answer tqa
6534
                            WHERE
6535
                                tqa.question_id = tq.iid AND
6536
                                tqa.c_id = tq.c_id AND
6537
                                tq.c_id = $courseIdx AND
6538
                                tq.iid IN (".implode(',', $questionIds).")";
6539
6540
            $resQuestions = Database::query($sqlQuestions);
6541
            $answer = [];
6542
            $question = [];
6543
            while ($rowQuestion = Database::fetch_assoc($resQuestions)) {
6544
                $questionId = $rowQuestion['question_id'];
6545
                $answerId = $rowQuestion['answer_id'];
6546
                $answer[$questionId][$answerId] = [
6547
                    'position' => $rowQuestion['position'],
6548
                    'question' => $rowQuestion['question'],
6549
                    'answer' => $rowQuestion['answer'],
6550
                    'correct' => $rowQuestion['correct'],
6551
                ];
6552
                $question[$questionId]['question'] = $rowQuestion['question'];
6553
            }
6554
6555
            // Now fill users data
6556
            $sqlUsers = "SELECT id as user_id, username, lastname, firstname
6557
                         FROM $tuser
6558
                         WHERE active <> ".USER_SOFT_DELETED." AND id IN (".implode(',', $userIds).")";
6559
            $resUsers = Database::query($sqlUsers);
6560
            while ($rowUser = Database::fetch_assoc($resUsers)) {
6561
                $users[$rowUser['user_id']] = $rowUser;
6562
            }
6563
6564
            foreach ($data as $id => $row) {
6565
                $rowQuestId = $row['question_id'];
6566
                $rowAnsId = $row['answer_id'];
6567
                $data[$id]['session'] = $sessions[$row['session_id']]['title'];
6568
                $data[$id]['firstname'] = $users[$row['user_id']]['firstname'];
6569
                $data[$id]['lastname'] = $users[$row['user_id']]['lastname'];
6570
                $data[$id]['username'] = $users[$row['user_id']]['username'];
6571
                $data[$id]['answer'] = $answer[$rowQuestId][$rowAnsId]['answer'];
6572
                $data[$id]['correct'] = (0 == $answer[$rowQuestId][$rowAnsId]['correct'] ? get_lang('No') : get_lang('Yes'));
6573
                $data[$id]['question'] = $question[$rowQuestId]['question'];
6574
                $data[$id]['question_id'] = $rowQuestId;
6575
                $data[$id]['description'] = $row['description'];
6576
            }
6577
6578
            /*
6579
            The minimum expected array structure at the end is:
6580
            attempt_id,
6581
            session title,
6582
            exercise_id,
6583
            quiz_title,
6584
            username,
6585
            lastname,
6586
            firstname,
6587
            time,
6588
            question_id,
6589
            question,
6590
            answer,
6591
            */
6592
        }
6593
6594
        return $data;
6595
    }
6596
6597
    /**
6598
     * @param string              $tool
6599
     * @param SessionEntity |null $session
6600
     *
6601
     * @return CStudentPublication|null
6602
     */
6603
    public static function getLastStudentPublication(
6604
        User $user,
6605
             $tool,
6606
        Course $course,
6607
        SessionEntity $session = null
6608
    ) {
6609
        // @todo
6610
        return null;
6611
6612
        return Database::getManager()
0 ignored issues
show
Unused Code introduced by
return Database::getMana...)->getOneOrNullResult() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
6613
            ->createQuery("
6614
                SELECT csp
6615
                FROM ChamiloCourseBundle:CStudentPublication csp
6616
                INNER JOIN ChamiloCourseBundle:CItemProperty cip
6617
                    WITH (
6618
                        csp.iid = cip.ref AND
6619
                        csp.session = cip.session AND
6620
                        csp.cId = cip.course AND
6621
                        csp.userId = cip.lasteditUserId
6622
                    )
6623
                WHERE
6624
                    cip.session = :session AND cip.course = :course AND cip.lasteditUserId = :user AND cip.tool = :tool
6625
                ORDER BY csp.iid DESC
6626
            ")
6627
            ->setMaxResults(1)
6628
            ->setParameters([
6629
                'tool' => $tool,
6630
                'session' => $session,
6631
                'course' => $course,
6632
                'user' => $user,
6633
            ])
6634
            ->getOneOrNullResult();
6635
    }
6636
6637
    /**
6638
     * Get the HTML code for show a block with the achieved user skill on course/session.
6639
     *
6640
     * @param int  $userId
6641
     * @param ?int  $courseId
6642
     * @param ?int  $sessionId
6643
     * @param ?bool $forceView forces the view of the skills, not checking for deeper access
6644
     *
6645
     * @return string
6646
     */
6647
    public static function displayUserSkills(int $userId, ?int $courseId = 0, ?int $sessionId = 0, ?bool $forceView = false): string
6648
    {
6649
        if (false === SkillModel::isAllowed($userId, false) && !$forceView) {
6650
            return '';
6651
        }
6652
        $skillManager = new SkillModel();
6653
6654
        return $skillManager->getUserSkillsTable($userId, $courseId, $sessionId)['table'];
6655
    }
6656
6657
    /**
6658
     * Return time spent by the given user in the given course/session, from the track_e_access_complete table, and
6659
     * breakdown time items in different array elements
6660
     * @param int $userId
6661
     * @param int $courseId
6662
     * @param int $sessionId
6663
     *
6664
     * @return array
6665
     * @throws \Doctrine\DBAL\Exception
6666
     */
6667
    public static function getCalculateTime(int $userId, int $courseId, int $sessionId): array
6668
    {
6669
        if (empty($userId) || empty($courseId)) {
6670
            return [];
6671
        }
6672
6673
        $sql = "SELECT MIN(date_reg) min, MAX(date_reg) max
6674
                FROM track_e_access_complete
6675
                WHERE
6676
                    user_id = $userId AND
6677
                    c_id = $courseId AND
6678
                    session_id = $sessionId AND
6679
                    login_as = 0
6680
                ORDER BY date_reg ASC
6681
                LIMIT 1";
6682
        $rs = Database::query($sql);
6683
6684
        $firstConnection = '';
6685
        $lastConnection = '';
6686
        if (Database::num_rows($rs) > 0) {
6687
            $value = Database::fetch_array($rs);
6688
            $firstConnection = $value['min'];
6689
            $lastConnection = $value['max'];
6690
        }
6691
6692
        $sql = "SELECT * FROM track_e_access_complete
6693
                WHERE
6694
                    user_id = $userId AND
6695
                    c_id = $courseId AND
6696
                    session_id = $sessionId AND
6697
                    login_as = 0 AND current_id <> 0";
6698
6699
        $res = Database::query($sql);
6700
        $reg = [];
6701
        while ($row = Database::fetch_assoc($res)) {
6702
            $reg[$row['id']] = $row;
6703
            $reg[$row['id']]['date_reg'] = strtotime($row['date_reg']);
6704
        }
6705
6706
        $sessions = [];
6707
        foreach ($reg as $key => $value) {
6708
            $sessions[$value['current_id']][$value['tool']][] = $value;
6709
        }
6710
6711
        $quizTime = 0;
6712
        $result = [];
6713
        $totalTime = 0;
6714
        $lpTime = [];
6715
        $lpDetailTime = [];
6716
        foreach ($sessions as $listPerTool) {
6717
            $min = 0;
6718
            $max = 0;
6719
            $sessionDiff = 0;
6720
            foreach ($listPerTool as $tool => $results) {
6721
                $beforeItem = [];
6722
                foreach ($results as $item) {
6723
                    if (empty($beforeItem)) {
6724
                        $beforeItem = $item;
6725
                        if (empty($min)) {
6726
                            $min = $item['date_reg'];
6727
                        }
6728
6729
                        if (empty($max)) {
6730
                            $max = $item['date_reg'];
6731
                        }
6732
                        continue;
6733
                    }
6734
6735
                    $partialTime = $item['date_reg'] - $beforeItem['date_reg'];
6736
                    if ($item['date_reg'] > $max) {
6737
                        $max = $item['date_reg'];
6738
                    }
6739
6740
                    if (empty($min)) {
6741
                        $min = $item['date_reg'];
6742
                    }
6743
6744
                    if ($item['date_reg'] < $min) {
6745
                        $min = $item['date_reg'];
6746
                    }
6747
6748
                    switch ($tool) {
6749
                        case TOOL_AGENDA:
6750
                        case TOOL_FORUM:
6751
                        case TOOL_ANNOUNCEMENT:
6752
                        case TOOL_COURSE_DESCRIPTION:
6753
                        case TOOL_SURVEY:
6754
                        case TOOL_NOTEBOOK:
6755
                        case TOOL_GRADEBOOK:
6756
                        case TOOL_DROPBOX:
6757
                        case 'Reports':
6758
                        case 'Videoconference':
6759
                        case TOOL_LINK:
6760
                        case TOOL_CHAT:
6761
                        case 'course-main':
6762
                            if (!isset($result[$tool])) {
6763
                                $result[$tool] = 0;
6764
                            }
6765
                            $result[$tool] += $partialTime;
6766
                            break;
6767
                        case TOOL_LEARNPATH:
6768
                            if ($item['tool_id'] != $beforeItem['tool_id']) {
6769
                                break;
6770
                            }
6771
                            if (!isset($lpTime[$item['tool_id']])) {
6772
                                $lpTime[$item['tool_id']] = 0;
6773
                            }
6774
6775
                            // Saving the attempt id "action_details"
6776
                            if (!empty($item['tool_id'])) {
6777
                                if (!empty($item['tool_id_detail'])) {
6778
                                    if (!isset($lpDetailTime[$item['tool_id']][$item['tool_id_detail']][$item['action_details']])) {
6779
                                        $lpDetailTime[$item['tool_id']][$item['tool_id_detail']][$item['action_details']] = 0;
6780
                                    }
6781
                                    $lpDetailTime[$item['tool_id']][$item['tool_id_detail']][$item['action_details']] += $partialTime;
6782
                                }
6783
                                $lpTime[$item['tool_id']] += $partialTime;
6784
                            }
6785
                            break;
6786
                        case TOOL_QUIZ:
6787
                            if (!isset($lpTime[$item['action_details']])) {
6788
                                $lpTime[$item['action_details']] = 0;
6789
                            }
6790
                            if ('learnpath_id' === $beforeItem['action']) {
6791
                                $lpTime[$item['action_details']] += $partialTime;
6792
                            } else {
6793
                                $quizTime += $partialTime;
6794
                            }
6795
                            break;
6796
                    }
6797
                    $beforeItem = $item;
6798
                }
6799
            }
6800
6801
            $sessionDiff += $max - $min;
6802
            if ($sessionDiff > 0) {
6803
                $totalTime += $sessionDiff;
6804
            }
6805
        }
6806
6807
        $totalLp = 0;
6808
        foreach ($lpTime as $value) {
6809
            $totalLp += $value;
6810
        }
6811
6812
        $result['learnpath_detailed'] = $lpDetailTime;
6813
        $result[TOOL_LEARNPATH] = $lpTime;
6814
        $result[TOOL_QUIZ] = $quizTime;
6815
        $result['total_learnpath'] = $totalLp;
6816
        $result['total_time'] = $totalTime;
6817
        $result['number_connections'] = count($sessions);
6818
        $result['first'] = $firstConnection;
6819
        $result['last'] = $lastConnection;
6820
6821
        return $result;
6822
    }
6823
6824
    /**
6825
     * Gets the IP of a given user, using the last login before the given date.
6826
     *
6827
     * @param int User ID
6828
     * @param string Datetime
6829
     * @param ?bool Whether to return the IP as a link or just as an IP
6830
     * @param ?string If defined and return_as_link if true, will be used as the text to be shown as the link
6831
     *
6832
     * @return string IP address (or false on error)
6833
     * @assert (0,0) === false
6834
     * @throws \Doctrine\DBAL\Exception
6835
     */
6836
    public static function get_ip_from_user_event(
6837
        int $user_id,
6838
        string $event_date,
6839
        ?bool $return_as_link = false,
6840
        ?string $body_replace = null
6841
    ): mixed {
6842
        if (empty($user_id) || empty($event_date)) {
6843
            return false;
6844
        }
6845
        $user_id = intval($user_id);
6846
        $event_date = Database::escape_string($event_date);
6847
        $table_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
6848
        $sql_ip = "SELECT login_date, user_ip
6849
                   FROM $table_login
6850
                   WHERE login_user_id = $user_id AND login_date < '$event_date'
6851
                   ORDER BY login_date DESC LIMIT 1";
6852
        $ip = '';
6853
        $res_ip = Database::query($sql_ip);
6854
        if (false !== $res_ip && Database::num_rows($res_ip) > 0) {
6855
            $row_ip = Database::fetch_row($res_ip);
6856
            if ($return_as_link) {
6857
                $ip = Display::url(
6858
                    (empty($body_replace) ? $row_ip[1] : $body_replace),
6859
                    'http://www.whatsmyip.org/ip-geo-location/?ip='.$row_ip[1],
6860
                    ['title' => get_lang('Trace IP'), 'target' => '_blank']
6861
                );
6862
            } else {
6863
                $ip = $row_ip[1];
6864
            }
6865
        }
6866
6867
        return $ip;
6868
    }
6869
6870
    /**
6871
     * @param int   $userId
6872
     * @param array $courseInfo
6873
     * @param ?int   $sessionId
6874
     *
6875
     * @return array
6876
     */
6877
    public static function getToolInformation(
6878
        int $userId,
6879
        array $courseInfo,
6880
        ?int $sessionId = 0
6881
    ): array {
6882
        $csvContent = [];
6883
        $courseToolInformation = '';
6884
        $headerTool = [
6885
            [get_lang('Title')],
6886
            [get_lang('Created at')],
6887
            [get_lang('Updated at')],
6888
        ];
6889
6890
        $headerListForCSV = [];
6891
        foreach ($headerTool as $item) {
6892
            $headerListForCSV[] = $item[0];
6893
        }
6894
6895
        $courseForumInformationArray = getForumCreatedByUser(
6896
            $userId,
6897
            $courseInfo,
6898
            $sessionId
6899
        );
6900
6901
        if (!empty($courseForumInformationArray)) {
6902
            $csvContent[] = [];
6903
            $csvContent[] = [get_lang('Forums')];
6904
            $csvContent[] = $headerListForCSV;
6905
            foreach ($courseForumInformationArray as $row) {
6906
                $csvContent[] = $row;
6907
            }
6908
6909
            $courseToolInformation .= Display::page_subheader2(
6910
                get_lang('Forums')
6911
            );
6912
            $courseToolInformation .= Display::return_sortable_table(
6913
                $headerTool,
6914
                $courseForumInformationArray
6915
            );
6916
        }
6917
6918
        $courseWorkInformationArray = getWorkCreatedByUser(
6919
            $userId,
6920
            $courseInfo['real_id'],
6921
            $sessionId
6922
        );
6923
6924
        if (!empty($courseWorkInformationArray)) {
6925
            $csvContent[] = null;
6926
            $csvContent[] = [get_lang('Assignments')];
6927
            $csvContent[] = $headerListForCSV;
6928
6929
            foreach ($courseWorkInformationArray as $row) {
6930
                $csvContent[] = $row;
6931
            }
6932
            $csvContent[] = null;
6933
6934
            $courseToolInformation .= Display::page_subheader2(
6935
                get_lang('Assignments')
6936
            );
6937
            $courseToolInformation .= Display::return_sortable_table(
6938
                $headerTool,
6939
                $courseWorkInformationArray
6940
            );
6941
        }
6942
6943
        $courseToolInformationTotal = null;
6944
        if (!empty($courseToolInformation)) {
6945
            $sessionTitle = null;
6946
            if (!empty($sessionId)) {
6947
                $sessionTitle = ' ('.api_get_session_name($sessionId).')';
6948
            }
6949
6950
            $courseToolInformationTotal .= Display::page_subheader(
6951
                $courseInfo['title'].$sessionTitle
6952
            );
6953
            $courseToolInformationTotal .= $courseToolInformation;
6954
        }
6955
6956
        return [
6957
            'array' => $csvContent,
6958
            'html' => $courseToolInformationTotal,
6959
        ];
6960
    }
6961
6962
    /**
6963
     * @param int $sessionId
6964
     *
6965
     * @return bool
6966
     */
6967
    public static function isAllowToTrack($sessionId)
6968
    {
6969
        return
6970
            api_is_platform_admin(true, true) ||
6971
            (!empty($sessionId) && api_get_session_entity($sessionId)->hasUserAsGeneralCoach(api_get_user_entity())) ||
6972
            api_is_allowed_to_create_course() ||
6973
            api_is_course_tutor() ||
6974
            api_is_course_admin();
6975
    }
6976
6977
    public static function getCourseLpProgress($userId, $sessionId)
6978
    {
6979
        $controller = new IndexManager(get_lang('My courses'));
6980
        $data = $controller->returnCoursesAndSessions($userId);
6981
        $courseList = $data['courses'];
6982
        $result = [];
6983
        if ($courseList) {
6984
            //$counter = 1;
6985
            foreach ($courseList as $course) {
6986
                $courseId = $course['course_id'];
6987
                $courseInfo = api_get_course_info_by_id($courseId);
6988
                if (empty($courseInfo)) {
6989
                    continue;
6990
                }
6991
                $courseCode = $courseInfo['code'];
6992
                $lpTimeList = self::getCalculateTime($userId, $courseId, $sessionId);
6993
6994
                // total progress
6995
                $list = new LearnpathList(
6996
                    $userId,
6997
                    $courseInfo,
6998
                    0,
6999
                    'resource.publishedOn',
7000
                    true,
7001
                    null,
7002
                    true
7003
                );
7004
7005
                $list = $list->get_flat_list();
7006
                $totalProgress = 0;
7007
                $totalTime = 0;
7008
                if (!empty($list)) {
7009
                    foreach ($list as $lp_id => $learnpath) {
7010
                        if (!$learnpath['lp_visibility']) {
7011
                            continue;
7012
                        }
7013
                        $lpProgress = self::get_avg_student_progress($userId, $courseCode, [$lp_id], $sessionId);
7014
                        $time = isset($lpTimeList[TOOL_LEARNPATH][$lp_id]) ? $lpTimeList[TOOL_LEARNPATH][$lp_id] : 0;
7015
                        if (100 == $lpProgress) {
7016
                            if (!empty($time)) {
7017
                                $timeInMinutes = $time / 60;
7018
                                $min = (int) learnpath::getAccumulateWorkTimePrerequisite($lp_id, $courseId);
7019
                                if ($timeInMinutes >= $min) {
7020
                                    $totalProgress++;
7021
                                }
7022
                            }
7023
                        }
7024
                        $totalTime += $time;
7025
                    }
7026
7027
                    if (!empty($totalProgress)) {
7028
                        $totalProgress = (float) api_number_format($totalProgress / count($list) * 100, 2);
7029
                    }
7030
                }
7031
7032
                $progress = self::get_avg_student_progress($userId, $courseCode, [], $sessionId);
7033
7034
                $result[] = [
7035
                    'module' => $courseInfo['title'],
7036
                    'progress' => $progress,
7037
                    'qualification' => $totalProgress,
7038
                    'activeTime' => $totalTime,
7039
                ];
7040
            }
7041
        }
7042
7043
        return $result;
7044
    }
7045
7046
    /**
7047
     * @param int $userId
7048
     * @param int $courseId
7049
     * @param int $sessionId
7050
     *
7051
     * @return int
7052
     */
7053
    public static function getNumberOfCourseAccessDates($userId, $courseId, $sessionId)
7054
    {
7055
        $tblTrackCourseAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
7056
        $sessionCondition = api_get_session_condition($sessionId);
7057
        $courseId = (int) $courseId;
7058
        $userId = (int) $userId;
7059
7060
        $sql = "SELECT COUNT(DISTINCT (DATE(login_course_date))) AS c
7061
            FROM $tblTrackCourseAccess
7062
            WHERE c_id = $courseId $sessionCondition AND user_id = $userId";
7063
7064
        $result = Database::fetch_assoc(Database::query($sql));
7065
7066
        return (int) $result['c'];
7067
    }
7068
7069
    public static function processUserDataMove(
7070
        $user_id,
7071
        $course_info,
7072
        $origin_session_id,
7073
        $new_session_id,
7074
        $update_database,
7075
        $debug = false
7076
    ) {
7077
        // Begin with the import process
7078
        $origin_course_code = $course_info['code'];
7079
        $course_id = $course_info['real_id'];
7080
        $user_id = (int) $user_id;
7081
        $origin_session_id = (int) $origin_session_id;
7082
        $new_session_id = (int) $new_session_id;
7083
        $session = api_get_session_entity($new_session_id);
7084
        $em = Database::getManager();
7085
7086
        $TABLETRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
7087
        $TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
7088
        $TBL_TRACK_E_COURSE_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
7089
        $TBL_TRACK_E_LAST_ACCESS = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
7090
        $TBL_LP_VIEW = Database::get_course_table(TABLE_LP_VIEW);
7091
        $TBL_NOTEBOOK = Database::get_course_table(TABLE_NOTEBOOK);
7092
        $TBL_STUDENT_PUBLICATION = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
7093
        $TBL_STUDENT_PUBLICATION_ASSIGNMENT = Database::get_course_table(TABLE_STUDENT_PUBLICATION_ASSIGNMENT);
7094
        $TBL_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
7095
7096
        $TBL_DROPBOX_FILE = Database::get_course_table(TABLE_DROPBOX_FILE);
7097
        $TBL_DROPBOX_POST = Database::get_course_table(TABLE_DROPBOX_POST);
7098
        $TBL_AGENDA = Database::get_course_table(TABLE_AGENDA);
7099
7100
        //1. track_e_exercises
7101
        //ORIGINAL COURSE
7102
        $sessionCondition = api_get_session_condition($origin_session_id);
7103
        $sql = "SELECT * FROM $TABLETRACK_EXERCICES
7104
                WHERE c_id = $course_id AND exe_user_id = $user_id  $sessionCondition";
7105
        $res = Database::query($sql);
7106
        $list = [];
7107
        while ($row = Database::fetch_assoc($res)) {
7108
            $list[$row['exe_id']] = $row;
7109
        }
7110
7111
        $result_message = [];
7112
        $result_message_compare = [];
7113
        if (!empty($list)) {
7114
            foreach ($list as $exe_id => $data) {
7115
                if ($update_database) {
7116
                    $sql = "UPDATE $TABLETRACK_EXERCICES SET session_id = '$new_session_id' WHERE exe_id = $exe_id";
7117
                    Database::query($sql);
7118
7119
                    //$sql = "UPDATE $TBL_TRACK_ATTEMPT SET session_id = '$new_session_id' WHERE exe_id = $exe_id";
7120
                    //Database::query($sql);
7121
7122
                    $repoTrackQualify = $em->getRepository(TrackEAttemptQualify::class);
7123
                    /** @var TrackEAttemptQualify $trackQualify */
7124
                    $trackQualify = $repoTrackQualify->findBy([
7125
                        'exeId' => $exe_id
7126
                    ]);
7127
                    if ($trackQualify) {
7128
                        $trackQualify->setSessionId($new_session_id);
7129
                        $em->persist($trackQualify);
7130
                        $em->flush();
7131
                    }
7132
7133
                    if (!isset($result_message[$TABLETRACK_EXERCICES])) {
7134
                        $result_message[$TABLETRACK_EXERCICES] = 0;
7135
                    }
7136
                    $result_message[$TABLETRACK_EXERCICES]++;
7137
                } else {
7138
                    if (!empty($data['orig_lp_id']) && !empty($data['orig_lp_item_id'])) {
7139
                        $result_message['TRACK_E_EXERCISES'][$exe_id] = $data;
7140
                    } else {
7141
                        $result_message['TRACK_E_EXERCISES_IN_LP'][$exe_id] = $data;
7142
                    }
7143
                }
7144
            }
7145
        }
7146
7147
        // DESTINY COURSE
7148
        if (!$update_database) {
7149
            $sql = "SELECT * FROM $TABLETRACK_EXERCICES
7150
                    WHERE
7151
                        c_id = $course_id AND
7152
                        session_id = $new_session_id AND
7153
                        exe_user_id = $user_id ";
7154
            $res = Database::query($sql);
7155
            $list = [];
7156
            while ($row = Database::fetch_assoc($res)) {
7157
                $list[$row['exe_id']] = $row;
7158
            }
7159
7160
            if (!empty($list)) {
7161
                foreach ($list as $exe_id => $data) {
7162
                    if ($update_database) {
7163
                        $sql = "UPDATE $TABLETRACK_EXERCICES
7164
                                SET session_id = '$new_session_id'
7165
                                WHERE exe_id = $exe_id";
7166
                        Database::query($sql);
7167
                        $result_message[$TABLETRACK_EXERCICES]++;
7168
                    } else {
7169
                        if (!empty($data['orig_lp_id']) && !empty($data['orig_lp_item_id'])) {
7170
                            $result_message_compare['TRACK_E_EXERCISES'][$exe_id] = $data;
7171
                        } else {
7172
                            $result_message_compare['TRACK_E_EXERCISES_IN_LP'][$exe_id] = $data;
7173
                        }
7174
                    }
7175
                }
7176
            }
7177
        }
7178
7179
        // 2.track_e_attempt, track_e_attempt_recording, track_e_downloads
7180
        // Nothing to do because there are not relationship with a session
7181
        // 3. track_e_course_access
7182
        $sql = "SELECT * FROM $TBL_TRACK_E_COURSE_ACCESS
7183
                WHERE c_id = $course_id AND session_id = $origin_session_id  AND user_id = $user_id ";
7184
        $res = Database::query($sql);
7185
        $list = [];
7186
        while ($row = Database::fetch_assoc($res)) {
7187
            $list[$row['course_access_id']] = $row;
7188
        }
7189
7190
        if (!empty($list)) {
7191
            foreach ($list as $id => $data) {
7192
                if ($update_database) {
7193
                    $sql = "UPDATE $TBL_TRACK_E_COURSE_ACCESS
7194
                            SET session_id = $new_session_id
7195
                            WHERE course_access_id = $id";
7196
                    if ($debug) {
7197
                        echo $sql;
7198
                    }
7199
                    Database::query($sql);
7200
                    if (!isset($result_message[$TBL_TRACK_E_COURSE_ACCESS])) {
7201
                        $result_message[$TBL_TRACK_E_COURSE_ACCESS] = 0;
7202
                    }
7203
                    $result_message[$TBL_TRACK_E_COURSE_ACCESS]++;
7204
                }
7205
            }
7206
        }
7207
7208
        // 4. track_e_lastaccess
7209
        $sql = "SELECT access_id FROM $TBL_TRACK_E_LAST_ACCESS
7210
                WHERE
7211
                    c_id = $course_id AND
7212
                    session_id = $origin_session_id AND
7213
                    access_user_id = $user_id ";
7214
        $res = Database::query($sql);
7215
        $list = [];
7216
        while ($row = Database::fetch_assoc($res)) {
7217
            $list[] = $row['access_id'];
7218
        }
7219
7220
        if (!empty($list)) {
7221
            foreach ($list as $id) {
7222
                if ($update_database) {
7223
                    $sql = "UPDATE $TBL_TRACK_E_LAST_ACCESS
7224
                            SET session_id = $new_session_id
7225
                            WHERE access_id = $id";
7226
                    if ($debug) {
7227
                        echo $sql;
7228
                    }
7229
                    Database::query($sql);
7230
                    if (!isset($result_message[$TBL_TRACK_E_LAST_ACCESS])) {
7231
                        $result_message[$TBL_TRACK_E_LAST_ACCESS] = 0;
7232
                    }
7233
                    $result_message[$TBL_TRACK_E_LAST_ACCESS]++;
7234
                }
7235
            }
7236
        }
7237
7238
        // 5. lp_item_view
7239
        // CHECK ORIGIN
7240
        $sql = "SELECT * FROM $TBL_LP_VIEW
7241
                WHERE user_id = $user_id AND session_id = $origin_session_id AND c_id = $course_id ";
7242
        $res = Database::query($sql);
7243
7244
        // Getting the list of LPs in the new session
7245
        $lp_list = new LearnpathList($user_id, $course_info, $new_session_id);
7246
        $flat_list = $lp_list->get_flat_list();
7247
        $list = [];
7248
        while ($row = Database::fetch_assoc($res)) {
7249
            // Checking if the LP exist in the new session
7250
            //if (in_array($row['lp_id'], array_keys($flat_list))) {
7251
            $list[$row['id']] = $row;
7252
            //}
7253
        }
7254
7255
        if (!empty($list)) {
7256
            foreach ($list as $id => $data) {
7257
                if ($update_database) {
7258
                    $sql = "UPDATE $TBL_LP_VIEW
7259
                            SET session_id = $new_session_id
7260
                            WHERE c_id = $course_id AND iid = $id ";
7261
                    if ($debug) {
7262
                        var_dump($sql);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($sql) looks like debug code. Are you sure you do not want to remove it?
Loading history...
7263
                    }
7264
                    $res = Database::query($sql);
7265
                    if ($debug) {
7266
                        var_dump($res);
7267
                    }
7268
                    if (!isset($result_message[$TBL_LP_VIEW])) {
7269
                        $result_message[$TBL_LP_VIEW] = 0;
7270
                    }
7271
                    $result_message[$TBL_LP_VIEW]++;
7272
                } else {
7273
                    // Getting all information of that lp_item_id
7274
                    $score = self::get_avg_student_score(
7275
                        $user_id,
7276
                        $origin_course_code,
7277
                        [$data['lp_id']],
7278
                        $origin_session_id
7279
                    );
7280
                    $progress = self::get_avg_student_progress(
7281
                        $user_id,
7282
                        $origin_course_code,
7283
                        [$data['lp_id']],
7284
                        $origin_session_id
7285
                    );
7286
                    $result_message['LP_VIEW'][$data['lp_id']] = [
7287
                        'score' => $score,
7288
                        'progress' => $progress,
7289
                    ];
7290
                }
7291
            }
7292
        }
7293
7294
        // Check destination.
7295
        if (!$update_database) {
7296
            $sql = "SELECT * FROM $TBL_LP_VIEW
7297
                    WHERE user_id = $user_id AND session_id = $new_session_id AND c_id = $course_id";
7298
            $res = Database::query($sql);
7299
7300
            // Getting the list of LPs in the new session
7301
            $lp_list = new LearnpathList($user_id, $course_info, $new_session_id);
7302
            $flat_list = $lp_list->get_flat_list();
7303
7304
            $list = [];
7305
            while ($row = Database::fetch_assoc($res)) {
7306
                //Checking if the LP exist in the new session
7307
                //if (in_array($row['lp_id'], array_keys($flat_list))) {
7308
                $list[$row['id']] = $row;
7309
                //}
7310
            }
7311
7312
            if (!empty($list)) {
7313
                foreach ($list as $id => $data) {
7314
                    // Getting all information of that lp_item_id
7315
                    $score = self::get_avg_student_score(
7316
                        $user_id,
7317
                        $origin_course_code,
7318
                        [$data['lp_id']],
7319
                        $new_session_id
7320
                    );
7321
                    $progress = self::get_avg_student_progress(
7322
                        $user_id,
7323
                        $origin_course_code,
7324
                        [$data['lp_id']],
7325
                        $new_session_id
7326
                    );
7327
                    $result_message_compare['LP_VIEW'][$data['lp_id']] = [
7328
                        'score' => $score,
7329
                        'progress' => $progress,
7330
                    ];
7331
                }
7332
            }
7333
        }
7334
7335
        // 6. Agenda
7336
        // calendar_event_attachment no problems no session_id
7337
        $sql = "SELECT ref FROM $TBL_ITEM_PROPERTY
7338
                WHERE tool = 'calendar_event' AND insert_user_id = $user_id AND c_id = $course_id ";
7339
        $res = Database::query($sql);
7340
        while ($row = Database::fetch_assoc($res)) {
7341
            $id = $row['ref'];
7342
            if ($update_database) {
7343
                $sql = "UPDATE $TBL_AGENDA SET session_id = $new_session_id WHERE c_id = $course_id AND iid = $id ";
7344
                if ($debug) {
7345
                    var_dump($sql);
7346
                }
7347
                $res_update = Database::query($sql);
7348
                if ($debug) {
7349
                    var_dump($res_update);
7350
                }
7351
                if (!isset($result_message['agenda'])) {
7352
                    $result_message['agenda'] = 0;
7353
                }
7354
                $result_message['agenda']++;
7355
            }
7356
        }
7357
7358
        // 7. Forum ?? So much problems when trying to import data
7359
        // 8. Student publication - Works
7360
        $sql = "SELECT ref FROM $TBL_ITEM_PROPERTY
7361
                WHERE tool = 'work' AND insert_user_id = $user_id AND c_id = $course_id";
7362
        if ($debug) {
7363
            echo $sql;
7364
        }
7365
        $res = Database::query($sql);
7366
        while ($row = Database::fetch_assoc($res)) {
7367
            $id = $row['ref'];
7368
            $sql = "SELECT * FROM $TBL_STUDENT_PUBLICATION
7369
                    WHERE iid = $id AND session_id = $origin_session_id AND c_id = $course_id";
7370
            $sub_res = Database::query($sql);
7371
            if (Database::num_rows($sub_res) > 0) {
7372
                $data = Database::fetch_assoc($sub_res);
7373
                if ($debug) {
7374
                    var_dump($data);
7375
                }
7376
                $parent_id = $data['parent_id'];
7377
                if (isset($data['parent_id']) && !empty($data['parent_id'])) {
7378
                    $sql = "SELECT * FROM $TBL_STUDENT_PUBLICATION
7379
                            WHERE iid = $parent_id AND c_id = $course_id";
7380
                    $select_res = Database::query($sql);
7381
                    $parent_data = Database::fetch_assoc($select_res);
7382
                    if ($debug) {
7383
                        var_dump($parent_data);
7384
                    }
7385
7386
                    $sys_course_path = api_get_path(SYS_COURSE_PATH);
7387
                    $course_dir = $sys_course_path.$course_info['path'];
7388
                    $base_work_dir = $course_dir.'/work';
7389
7390
                    // Creating the parent folder in the session if does not exists already
7391
                    //@todo ugly fix
7392
                    $search_this = "folder_moved_from_session_id_$origin_session_id";
7393
                    $search_this2 = $parent_data['url'];
7394
                    $sql = "SELECT * FROM $TBL_STUDENT_PUBLICATION
7395
                            WHERE description like '%$search_this%' AND
7396
                                  url LIKE '%$search_this2%' AND
7397
                                  session_id = $new_session_id AND
7398
                                  c_id = $course_id
7399
                            ORDER BY id desc  LIMIT 1";
7400
                    if ($debug) {
7401
                        echo $sql;
7402
                    }
7403
                    $sub_res = Database::query($sql);
7404
                    $num_rows = Database::num_rows($sub_res);
7405
7406
                    $new_parent_id = 0;
7407
                    if ($num_rows > 0) {
7408
                        $new_result = Database::fetch_assoc($sub_res);
7409
                        $created_dir = $new_result['url'];
7410
                        $new_parent_id = $new_result['id'];
7411
                    } else {
7412
                        if ($update_database) {
7413
                            $dir_name = substr($parent_data['url'], 1);
7414
                            $created_dir = create_unexisting_work_directory($base_work_dir, $dir_name);
7415
                            $created_dir = '/'.$created_dir;
7416
                            $now = new DateTime(api_get_utc_datetime(), new DateTimeZone('UTC'));
7417
                            // Creating directory
7418
                            $publication = (new CStudentPublication())
7419
                                ->setTitle($parent_data['title'])
7420
                                ->setDescription(
7421
                                    $parent_data['description']."folder_moved_from_session_id_$origin_session_id"
7422
                                )
7423
                                ->setActive(false)
7424
                                ->setAccepted(true)
7425
                                ->setPostGroupId(0)
7426
                                ->setHasProperties($parent_data['has_properties'])
7427
                                ->setWeight($parent_data['weight'])
7428
                                ->setContainsFile($parent_data['contains_file'])
7429
                                ->setFiletype('folder')
7430
                                ->setSentDate($now)
7431
                                ->setQualification($parent_data['qualification'])
7432
                                ->setParentId(0)
7433
                                ->setQualificatorId(0)
7434
                                ->setUserId($parent_data['user_id'])
7435
                                ->setAllowTextAssignment($parent_data['allow_text_assignment'])
7436
                                ->setSession($session);
7437
7438
                            $publication->setDocumentId($parent_data['document_id']);
7439
7440
                            Database::getManager()->persist($publication);
7441
                            Database::getManager()->flush();
7442
                            $id = $publication->getIid();
7443
                            //Folder created
7444
                            //api_item_property_update($course_info, 'work', $id, 'DirectoryCreated', api_get_user_id());
7445
                            $new_parent_id = $id;
7446
                            if (!isset($result_message[$TBL_STUDENT_PUBLICATION.' - new folder created called: '.$created_dir])) {
7447
                                $result_message[$TBL_STUDENT_PUBLICATION.' - new folder created called: '.$created_dir] = 0;
7448
                            }
7449
                            $result_message[$TBL_STUDENT_PUBLICATION.' - new folder created called: '.$created_dir]++;
7450
                        }
7451
                    }
7452
7453
                    //Creating student_publication_assignment if exists
7454
                    $sql = "SELECT * FROM $TBL_STUDENT_PUBLICATION_ASSIGNMENT
7455
                            WHERE publication_id = $parent_id AND c_id = $course_id";
7456
                    if ($debug) {
7457
                        var_dump($sql);
7458
                    }
7459
                    $rest_select = Database::query($sql);
7460
                    if (Database::num_rows($rest_select) > 0) {
7461
                        if ($update_database && $new_parent_id) {
7462
                            $assignment_data = Database::fetch_assoc($rest_select);
7463
                            $sql_add_publication = "INSERT INTO ".$TBL_STUDENT_PUBLICATION_ASSIGNMENT." SET
7464
                                    	c_id = '$course_id',
7465
                                       expires_on          = '".$assignment_data['expires_on']."',
7466
                                       ends_on              = '".$assignment_data['ends_on']."',
7467
                                       add_to_calendar      = '".$assignment_data['add_to_calendar']."',
7468
                                       enable_qualification = '".$assignment_data['enable_qualification']."',
7469
                                       publication_id       = '".$new_parent_id."'";
7470
                            if ($debug) {
7471
                                echo $sql_add_publication;
7472
                            }
7473
                            Database::query($sql_add_publication);
7474
                            $id = (int) Database::insert_id();
7475
                            if ($id) {
7476
                                $sql_update = "UPDATE $TBL_STUDENT_PUBLICATION
7477
                                           SET  has_properties = '".$id."',
7478
                                                view_properties = '1'
7479
                                           WHERE iid = ".$new_parent_id;
7480
                                if ($debug) {
7481
                                    echo $sql_update;
7482
                                }
7483
                                Database::query($sql_update);
7484
                                if (!isset($result_message[$TBL_STUDENT_PUBLICATION_ASSIGNMENT])) {
7485
                                    $result_message[$TBL_STUDENT_PUBLICATION_ASSIGNMENT] = 0;
7486
                                }
7487
                                $result_message[$TBL_STUDENT_PUBLICATION_ASSIGNMENT]++;
7488
                            }
7489
                        }
7490
                    }
7491
7492
                    $doc_url = $data['url'];
7493
                    $new_url = str_replace($parent_data['url'], $created_dir, $doc_url);
7494
7495
                    if ($update_database) {
7496
                        // Creating a new work
7497
                        $data['sent_date'] = new DateTime($data['sent_date'], new DateTimeZone('UTC'));
7498
7499
                        $data['post_group_id'] = (int) $data['post_group_id'];
7500
                        $publication = (new CStudentPublication())
7501
                            ->setTitle($data['title'])
7502
                            ->setDescription($data['description'].' file moved')
7503
                            ->setActive($data['active'])
7504
                            ->setAccepted($data['accepted'])
7505
                            ->setPostGroupId($data['post_group_id'])
7506
                            ->setSentDate($data['sent_date'])
7507
                            ->setParentId($new_parent_id)
7508
                            ->setWeight($data['weight'])
7509
                            ->setHasProperties(0)
7510
                            ->setWeight($data['weight'])
7511
                            ->setContainsFile($data['contains_file'])
7512
                            ->setSession($session)
7513
                            ->setUserId($data['user_id'])
7514
                            ->setFiletype('file')
7515
                            ->setDocumentId(0)
7516
                        ;
7517
7518
                        $em->persist($publication);
7519
                        $em->flush();
7520
7521
                        $id = $publication->getIid();
7522
                        /*api_item_property_update(
7523
                            $course_info,
7524
                            'work',
7525
                            $id,
7526
                            'DocumentAdded',
7527
                            $user_id,
7528
                            null,
7529
                            null,
7530
                            null,
7531
                            null,
7532
                            $new_session_id
7533
                        );*/
7534
                        if (!isset($result_message[$TBL_STUDENT_PUBLICATION])) {
7535
                            $result_message[$TBL_STUDENT_PUBLICATION] = 0;
7536
                        }
7537
                        $result_message[$TBL_STUDENT_PUBLICATION]++;
7538
                        $full_file_name = $course_dir.'/'.$doc_url;
7539
                        $new_file = $course_dir.'/'.$new_url;
7540
7541
                        if (file_exists($full_file_name)) {
7542
                            // deleting old assignment
7543
                            $result = copy($full_file_name, $new_file);
7544
                            if ($result) {
7545
                                unlink($full_file_name);
7546
                                if (isset($data['id'])) {
7547
                                    $sql = "DELETE FROM $TBL_STUDENT_PUBLICATION WHERE id= ".$data['id'];
7548
                                    if ($debug) {
7549
                                        var_dump($sql);
7550
                                    }
7551
                                    Database::query($sql);
7552
                                }
7553
                                api_item_property_update(
7554
                                    $course_info,
7555
                                    'work',
7556
                                    $data['id'],
7557
                                    'DocumentDeleted',
7558
                                    api_get_user_id()
7559
                                );
7560
                            }
7561
                        }
7562
                    }
7563
                }
7564
            }
7565
        }
7566
7567
        //9. Survey   Pending
7568
        //10. Dropbox - not neccesary to move categories (no presence of session_id)
7569
        $sql = "SELECT id FROM $TBL_DROPBOX_FILE
7570
                WHERE uploader_id = $user_id AND session_id = $origin_session_id AND c_id = $course_id";
7571
        if ($debug) {
7572
            var_dump($sql);
7573
        }
7574
        $res = Database::query($sql);
7575
        while ($row = Database::fetch_assoc($res)) {
7576
            $id = (int) $row['id'];
7577
            if ($update_database) {
7578
                $sql = "UPDATE $TBL_DROPBOX_FILE SET session_id = $new_session_id WHERE c_id = $course_id AND iid = $id";
7579
                if ($debug) {
7580
                    var_dump($sql);
7581
                }
7582
                Database::query($sql);
7583
                if ($debug) {
7584
                    var_dump($res);
7585
                }
7586
7587
                $sql = "UPDATE $TBL_DROPBOX_POST SET session_id = $new_session_id WHERE file_id = $id";
7588
                if ($debug) {
7589
                    var_dump($sql);
7590
                }
7591
                Database::query($sql);
7592
                if ($debug) {
7593
                    var_dump($res);
7594
                }
7595
                if (!isset($result_message[$TBL_DROPBOX_FILE])) {
7596
                    $result_message[$TBL_DROPBOX_FILE] = 0;
7597
                }
7598
                $result_message[$TBL_DROPBOX_FILE]++;
7599
            }
7600
        }
7601
7602
        // 11. Notebook
7603
        /*$sql = "SELECT notebook_id FROM $TBL_NOTEBOOK
7604
                WHERE
7605
                    user_id = $user_id AND
7606
                    session_id = $origin_session_id AND
7607
                    course = '$origin_course_code' AND
7608
                    c_id = $course_id";
7609
        if ($debug) {
7610
            var_dump($sql);
7611
        }
7612
        $res = Database::query($sql);
7613
        while ($row = Database::fetch_assoc($res)) {
7614
            $id = $row['notebook_id'];
7615
            if ($update_database) {
7616
                $sql = "UPDATE $TBL_NOTEBOOK
7617
                        SET session_id = $new_session_id
7618
                        WHERE c_id = $course_id AND notebook_id = $id";
7619
                if ($debug) {
7620
                    var_dump($sql);
7621
                }
7622
                $res = Database::query($sql);
7623
                if ($debug) {
7624
                    var_dump($res);
7625
                }
7626
            }
7627
        }*/
7628
7629
        if ($update_database) {
7630
            echo Display::return_message(get_lang('Stats moved.'));
7631
            if (is_array($result_message)) {
7632
                foreach ($result_message as $table => $times) {
7633
                    echo 'Table '.$table.' - '.$times.' records updated <br />';
7634
                }
7635
            }
7636
        } else {
7637
            echo '<h4>'.get_lang('User information for this course').'</h4>';
7638
            echo '<br />';
7639
            echo '<table class="table" width="100%">';
7640
            echo '<tr>';
7641
            echo '<td width="50%" valign="top">';
7642
7643
            if (0 == $origin_session_id) {
7644
                echo '<h5>'.get_lang('Original course').'</h5>';
7645
            } else {
7646
                echo '<h5>'.get_lang('Original session').' #'.$origin_session_id.'</h5>';
7647
            }
7648
            self::compareUserData($result_message);
7649
            echo '</td>';
7650
            echo '<td width="50%" valign="top">';
7651
            if (0 == $new_session_id) {
7652
                echo '<h5>'.get_lang('Destination course').'</h5>';
7653
            } else {
7654
                echo '<h5>'.get_lang('Destination session').' #'.$new_session_id.'</h5>';
7655
            }
7656
            self::compareUserData($result_message_compare);
7657
            echo '</td>';
7658
            echo '</tr>';
7659
            echo '</table>';
7660
        }
7661
    }
7662
7663
    public static function compareUserData($result_message)
7664
    {
7665
        foreach ($result_message as $table => $data) {
7666
            $title = $table;
7667
            if ('TRACK_E_EXERCISES' === $table) {
7668
                $title = get_lang('Tests');
7669
            } elseif ('TRACK_E_EXERCISES_IN_LP' === $table) {
7670
                $title = get_lang('Tests in learning paths');
7671
            } elseif ('LP_VIEW' === $table) {
7672
                $title = get_lang('Learning paths');
7673
            }
7674
            echo '<br / ><h3>'.get_lang($title).' </h3><hr />';
7675
7676
            if (is_array($data)) {
7677
                foreach ($data as $id => $item) {
7678
                    if ('TRACK_E_EXERCISES' === $table || 'TRACK_E_EXERCISES_IN_LP' === $table) {
7679
                        echo "<br /><h3>".get_lang('Attempt')." #$id</h3>";
7680
                        echo '<h3>';
7681
                        echo get_lang('Test').' #'.$item['exe_exo_id'];
7682
                        echo '</h3>';
7683
                        if (!empty($item['orig_lp_id'])) {
7684
                            echo '<h3>';
7685
                            echo get_lang('Learning path').' #'.$item['orig_lp_id'];
7686
                            echo '</h3>';
7687
                        }
7688
                        // Process data.
7689
                        $array = [
7690
                            'exe_date' => get_lang('Date'),
7691
                            'score' => get_lang('Score'),
7692
                            'max_score' => get_lang('Score'),
7693
                        ];
7694
                        foreach ($item as $key => $value) {
7695
                            if (in_array($key, array_keys($array))) {
7696
                                $key = $array[$key];
7697
                                echo "$key =  $value <br />";
7698
                            }
7699
                        }
7700
                    } else {
7701
                        echo "<br /><h3>".get_lang('Id')." #$id</h3>";
7702
                        // process data
7703
                        foreach ($item as $key => $value) {
7704
                            echo "$key =  $value <br />";
7705
                        }
7706
                    }
7707
                }
7708
            } else {
7709
                echo get_lang('No results found');
7710
            }
7711
        }
7712
    }
7713
7714
    private static function generateQuizzesTable(array $courseInfo, int $sessionId = 0): string
7715
    {
7716
        if (empty($sessionId)) {
7717
            $userList = CourseManager::get_user_list_from_course_code(
7718
                $courseInfo['code'],
7719
                $sessionId,
7720
                null,
7721
                null,
7722
                STUDENT
7723
            );
7724
        } else {
7725
            $userList = CourseManager::get_user_list_from_course_code($courseInfo['code'], $sessionId, null, null, 0);
7726
        }
7727
7728
        $exerciseList = ExerciseLib::get_all_exercises($courseInfo, $sessionId, false, null);
7729
7730
        if (empty($exerciseList)) {
7731
            return Display::return_message(get_lang('There is no test for the moment'));
7732
        }
7733
7734
        $toGraphExerciseResult = [];
7735
7736
        $quizzesTable = new SortableTableFromArray([], 0, 0, 'quizzes');
7737
        $quizzesTable->setHeaders(
7738
            [
7739
                get_lang('Tests'),
7740
                get_lang('Attempts'),
7741
                get_lang('Best attempt'),
7742
                get_lang('Ranking'),
7743
                get_lang('Best result in course'),
7744
                get_lang('Statistics').Display::getMdiIcon(
7745
                    ActionIcon::INFORMATION,
7746
                    'ch-tool-icon',
7747
                    null,
7748
                    ICON_SIZE_SMALL,
7749
                    get_lang('In case of multiple attempts, only shows the best result of each learner')
7750
                ),
7751
            ]
7752
        );
7753
7754
        $webCodePath = api_get_path(WEB_CODE_PATH);
7755
7756
        foreach ($exerciseList as $exercices) {
7757
            $objExercise = new Exercise($courseInfo['real_id']);
7758
            $objExercise->read($exercices['id']);
7759
            $visibleReturn = $objExercise->is_visible();
7760
7761
            // Getting count of attempts by user
7762
            $attempts = Event::count_exercise_attempts_by_user(
7763
                api_get_user_id(),
7764
                $exercices['id'],
7765
                $courseInfo['real_id'],
7766
                $sessionId
7767
            );
7768
7769
            $url = $webCodePath.'exercise/overview.php?'
7770
                .http_build_query(
7771
                    ['cidReq' => $courseInfo['code'], 'id_session' => $sessionId, 'exerciseId' => $exercices['id']]
7772
                );
7773
7774
            if (true == $visibleReturn['value']) {
7775
                $exercices['title'] = Display::url(
7776
                    $exercices['title'],
7777
                    $url,
7778
                    ['target' => SESSION_LINK_TARGET]
7779
                );
7780
            } elseif (-1 == $exercices['active']) {
7781
                $exercices['title'] = sprintf(get_lang('%s (deleted)'), $exercices['title']);
7782
            }
7783
7784
            $quizData = [
7785
                $exercices['title'],
7786
                $attempts,
7787
                '-',
7788
                '-',
7789
                '-',
7790
                '-',
7791
            ];
7792
7793
            // Exercise configuration show results or show only score
7794
            if (!in_array($exercices['results_disabled'], [0, 2])
7795
                || empty($attempts)
7796
            ) {
7797
                $quizzesTable->addRow($quizData);
7798
7799
                continue;
7800
            }
7801
7802
            //For graphics
7803
            $bestExerciseAttempts = Event::get_best_exercise_results_by_user(
7804
                $exercices['id'],
7805
                $courseInfo['real_id'],
7806
                $sessionId
7807
            );
7808
7809
            $toGraphExerciseResult[$exercices['id']] = [
7810
                'title' => $exercices['title'],
7811
                'data' => $bestExerciseAttempts,
7812
            ];
7813
7814
            // Getting best results
7815
            $bestScoreData = ExerciseLib::get_best_attempt_in_course(
7816
                $exercices['id'],
7817
                $courseInfo['real_id'],
7818
                $sessionId
7819
            );
7820
7821
            if (!empty($bestScoreData)) {
7822
                $quizData[5] = ExerciseLib::show_score(
7823
                    $bestScoreData['score'],
7824
                    $bestScoreData['max_score']
7825
                );
7826
            }
7827
7828
            $exerciseAttempt = ExerciseLib::get_best_attempt_by_user(
7829
                api_get_user_id(),
7830
                $exercices['id'],
7831
                $courseInfo['real_id'],
7832
                $sessionId
7833
            );
7834
7835
            if (!empty($exerciseAttempt)) {
7836
                // Always getting the BEST attempt
7837
                $score = $exerciseAttempt['score'];
7838
                $weighting = $exerciseAttempt['max_score'];
7839
                $exeId = $exerciseAttempt['exe_id'];
7840
7841
                $latestAttemptUrl = $webCodePath.'exercise/result.php?'
7842
                    .http_build_query(
7843
                        [
7844
                            'id' => $exeId,
7845
                            'cidReq' => $courseInfo['code'],
7846
                            'show_headers' => 1,
7847
                            'id_session' => $sessionId,
7848
                        ]
7849
                    );
7850
7851
                $quizData[3] = Display::url(
7852
                    ExerciseLib::show_score($score, $weighting),
7853
                    $latestAttemptUrl
7854
                );
7855
7856
                $myScore = !empty($weighting) && 0 != intval($weighting) ? $score / $weighting : 0;
7857
7858
                //@todo this function slows the page
7859
                if (is_int($userList)) {
7860
                    $userList = [$userList];
7861
                }
7862
7863
                $quizData[4] = ExerciseLib::get_exercise_result_ranking(
7864
                    $myScore,
7865
                    $exeId,
7866
                    $exercices['id'],
7867
                    $courseInfo['code'],
7868
                    $sessionId,
7869
                    $userList
7870
                );
7871
                $graph = self::generate_exercise_result_thumbnail_graph($toGraphExerciseResult[$exercices['id']]);
7872
                $normalGraph = self::generate_exercise_result_graph($toGraphExerciseResult[$exercices['id']]);
7873
7874
                $quizData[6] = Display::url(
7875
                    Display::img($graph, '', [], false),
7876
                    $normalGraph,
7877
                    ['id' => $exercices['id'], 'class' => 'expand-image']
7878
                );
7879
            }
7880
7881
            $quizzesTable->addRow($quizData);
7882
        }
7883
7884
        return Display::div(
7885
            $quizzesTable->toHtml(),
7886
            ['class' => 'table-responsive']
7887
        );
7888
    }
7889
7890
    private static function generateLearningPathsTable(int $userId, array $courseInfo, int $sessionId = 0): string
7891
    {
7892
        $columnHeaders = [
7893
            'lp' => get_lang('Learning paths'),
7894
            'time' => get_lang('Time spent'),
7895
            'progress' => get_lang('Progress'),
7896
            'score' => get_lang('Score'),
7897
            'best_score' => get_lang('Best score'),
7898
            'last_connection' => get_lang('Latest login'),
7899
        ];
7900
7901
        $trackingColumns = api_get_setting('session.tracking_columns', true);
7902
7903
        if (isset($trackingColumns['my_progress_lp'])) {
7904
            $columnHeaders = array_filter(
7905
                $columnHeaders,
7906
                function ($columHeader, $key) use ($trackingColumns) {
7907
                    if (!isset($trackingColumns['my_progress_lp'][$key])
7908
                        || false == $trackingColumns['my_progress_lp'][$key]
7909
                    ) {
7910
                        return false;
7911
                    }
7912
7913
                    return true;
7914
                },
7915
                ARRAY_FILTER_USE_BOTH
7916
            );
7917
        }
7918
7919
        if (true === api_get_configuration_value('student_follow_page_add_LP_subscription_info')) {
7920
            $columnHeaders['student_follow_page_add_LP_subscription_info'] = get_lang('Unlock');
7921
        }
7922
7923
        if ('true' === api_get_setting('lp.student_follow_page_add_lp_acquisition_info')) {
7924
            $columnHeaders['student_follow_page_add_lp_acquisition_info'] = get_lang('Acquisition');
7925
        }
7926
7927
        $addLpInvisibleCheckbox = api_get_setting('lp.student_follow_page_add_lp_invisible_checkbox');
7928
7929
        $columnHeadersKeys = array_keys($columnHeaders);
7930
7931
        $learningpathsTable = new SortableTableFromArray([], 0, 0, 'learningpaths');
7932
        $learningpathsTable->setHeaders($columnHeaders);
7933
7934
        // LP table results
7935
        $list = new LearnpathList(
7936
            api_get_user_id(),
7937
            $courseInfo,
7938
            $sessionId,
7939
            'resource.publishedOn',
7940
            true,
7941
            null,
7942
            true
7943
        );
7944
7945
        $lpList = $list->get_flat_list();
7946
7947
        if (empty($lpList)) {
7948
            return Display::return_message(get_lang('No learning path'));
7949
        }
7950
7951
        $webCodePath = api_get_path(WEB_CODE_PATH);
7952
7953
        foreach ($lpList as $lpId => $learnpath) {
7954
            $learningpathData = [];
7955
7956
            if (!$learnpath['lp_visibility']) {
7957
                continue;
7958
            }
7959
7960
            if ($addLpInvisibleCheckbox) {
7961
                if (!StudentFollowPage::isViewVisible($lpId, $userId, $courseInfo['real_id'], $sessionId)) {
7962
                    continue;
7963
                }
7964
            }
7965
7966
            $url = $webCodePath.'lp/lp_controller.php?'
7967
                .http_build_query(
7968
                    ['cidReq' => $courseInfo['code'], 'id_session' => $sessionId, 'lp_id' => $lpId, 'action' => 'view']
7969
                );
7970
7971
            if (in_array('lp', $columnHeadersKeys)) {
7972
                if (0 == $learnpath['lp_visibility']) {
7973
                    $learningpathData[] = $learnpath['lp_name'];
7974
                } else {
7975
                    $learningpathData[] = Display::url(
7976
                        $learnpath['lp_name'],
7977
                        $url,
7978
                        ['target' => SESSION_LINK_TARGET]
7979
                    );
7980
                }
7981
            }
7982
7983
            if (in_array('time', $columnHeadersKeys)) {
7984
                $time_spent_in_lp = self::get_time_spent_in_lp(
7985
                    $userId,
7986
                    $courseInfo['code'],
7987
                    [$lpId],
7988
                    $sessionId
7989
                );
7990
7991
                $learningpathData[] = api_time_to_hms($time_spent_in_lp);
7992
            }
7993
7994
            if (in_array('progress', $columnHeadersKeys)) {
7995
                $progress = self::get_avg_student_progress(
7996
                    $userId,
7997
                    $courseInfo['code'],
7998
                    [$lpId],
7999
                    $sessionId
8000
                );
8001
8002
                if (is_numeric($progress)) {
8003
                    $progress = sprintf(get_lang('%s %%'), $progress);
8004
                }
8005
8006
                $learningpathData[] = $progress;
8007
            }
8008
8009
            if (in_array('score', $columnHeadersKeys)) {
8010
                $percentage_score = self::get_avg_student_score(
8011
                    $userId,
8012
                    $courseInfo['code'],
8013
                    [$lpId],
8014
                    $sessionId
8015
                );
8016
8017
                if (is_numeric($percentage_score)) {
8018
                    $percentage_score = sprintf(get_lang('%s %%'), $percentage_score);
8019
                } else {
8020
                    $percentage_score = sprintf(get_lang('%s %%'), 0);
8021
                }
8022
8023
                $learningpathData[] = $percentage_score;
8024
            }
8025
8026
            if (in_array('best_score', $columnHeadersKeys)) {
8027
                $bestScore = self::get_avg_student_score(
8028
                    $userId,
8029
                    $courseInfo['code'],
8030
                    [$lpId],
8031
                    $sessionId,
8032
                    false,
8033
                    false,
8034
                    true
8035
                );
8036
8037
                if (is_numeric($bestScore)) {
8038
                    $bestScore = sprintf(get_lang('%s %%'), $bestScore);
8039
                } else {
8040
                    $bestScore = '-';
8041
                }
8042
8043
                $learningpathData[] = $bestScore;
8044
            }
8045
8046
            if (in_array('last_connection', $columnHeadersKeys)) {
8047
                $lastConnectionInLp = self::get_last_connection_time_in_lp(
8048
                    $userId,
8049
                    $courseInfo['code'],
8050
                    $lpId,
8051
                    $sessionId
8052
                );
8053
8054
                $lastConnection = '-';
8055
8056
                if (!empty($lastConnectionInLp)) {
8057
                    $lastConnection = api_convert_and_format_date($lastConnectionInLp, DATE_TIME_FORMAT_LONG);
8058
                }
8059
8060
                $learningpathData[] = $lastConnection;
8061
            }
8062
8063
            if (in_array('student_follow_page_add_LP_subscription_info', $columnHeadersKeys)) {
8064
                $learningpathData[] = StudentFollowPage::getLpSubscription(
8065
                    $learnpath,
8066
                    $userId,
8067
                    $courseInfo['real_id'],
8068
                    $sessionId
8069
                );
8070
            }
8071
8072
            if (in_array('student_follow_page_add_lp_acquisition_info', $columnHeadersKeys)) {
8073
                $learningpathData[] = StudentFollowPage::getLpAcquisition(
8074
                    $learnpath,
8075
                    $userId,
8076
                    $courseInfo['real_id'],
8077
                    $sessionId
8078
                );
8079
            }
8080
8081
            $learningpathsTable->addRow($learningpathData);
8082
        }
8083
8084
        return Display::div(
8085
            $learningpathsTable->toHtml(),
8086
            ['class' => 'table-responsive']
8087
        );
8088
    }
8089
8090
    /**
8091
     * Counts the number of student publications for a given course, session, and group.
8092
     *
8093
     * @param int $courseId
8094
     * @param int|null $sessionId
8095
     * @param int|null $groupId
8096
     *
8097
     * @return int The number of student publications.
8098
     */
8099
    public static function countStudentPublications(int $courseId, ?int $sessionId = null, ?int $groupId = null): int
8100
    {
8101
        $repo = Container::getStudentPublicationRepository();
8102
8103
        $course = api_get_course_entity($courseId);
8104
        $session = api_get_session_entity($sessionId);
8105
        $group = api_get_group_entity($groupId);
8106
8107
        $qb = $repo->getResourcesByCourse($course, $session, $group);
8108
        $qb->select('COUNT(resource.iid)');
8109
8110
        return (int) $qb->getQuery()->getSingleScalarResult();
8111
    }
8112
8113
    /**
8114
     * Counts the number of forum posts for a given course, session, and group.
8115
     *
8116
     * @param int $courseId
8117
     * @param int|null $sessionId
8118
     * @param int|null $groupId
8119
     *
8120
     * @return int The number of forum posts.
8121
     */
8122
    public static function countStudentMessages(int $courseId, ?int $sessionId = null, ?int $groupId = null): int
8123
    {
8124
        $repo = Container::getForumPostRepository();
8125
8126
        $course = api_get_course_entity($courseId);
8127
        $session = api_get_session_entity($sessionId);
8128
        $group = api_get_group_entity($groupId);
8129
8130
        $qb = $repo->getResourcesByCourse($course, $session, $group);
8131
        $qb->select('COUNT(resource.iid)');
8132
8133
        return (int) $qb->getQuery()->getSingleScalarResult();
8134
    }
8135
8136
    /**
8137
     * It gets the last finalization date of learnpaths in a course.
8138
     *
8139
     * @return string finalization date formatted or false if it is empty.
8140
     */
8141
    public static function getCourseLpFinalizationDate(
8142
        int $userId,
8143
        int $courseId,
8144
        int $sessionId,
8145
        bool $convertDate = true
8146
    ) {
8147
        $tblLpView = Database::get_course_table(TABLE_LP_VIEW);
8148
        $tblLpItem = Database::get_course_table(TABLE_LP_ITEM);
8149
        $tblLpItemView = Database::get_course_table(TABLE_LP_ITEM_VIEW);
8150
8151
        $sql = "SELECT FROM_UNIXTIME(liv.start_time) as start_date
8152
                FROM $tblLpItemView liv
8153
                INNER JOIN
8154
                    $tblLpView lv ON lv.iid = liv.lp_view_id
8155
                INNER JOIN
8156
                    $tblLpItem li ON li.iid = liv.lp_item_id
8157
                WHERE
8158
                    lv.user_id = $userId AND
8159
                    lv.c_id = $courseId AND
8160
                    lv.session_id = $sessionId AND
8161
                    li.item_type = '".TOOL_LP_FINAL_ITEM."' AND
8162
                    liv.status = 'completed'
8163
                ORDER BY start_date DESC
8164
                LIMIT 1";
8165
8166
        $rs = Database::query($sql);
8167
        $lpFinalDate = Database::result($rs, 0, 0);
8168
8169
        if (empty($lpFinalDate)) {
8170
            return false;
8171
        }
8172
8173
        if ($convertDate) {
8174
            return api_convert_and_format_date($lpFinalDate, DATE_FORMAT_SHORT);
8175
        }
8176
8177
        return $lpFinalDate;
8178
    }
8179
8180
    /**
8181
     * It gets the last finalization date of exercises in a course.
8182
     *
8183
     * @return string finalization date formatted or false if it is empty.
8184
     */
8185
    public static function getCourseQuizLastFinalizationDate(
8186
        int $userId,
8187
        int $courseId,
8188
        int $sessionId,
8189
        bool $convertDate = true
8190
    ) {
8191
        $tblTrackExercise = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
8192
8193
        $sql = "SELECT ex.exe_date
8194
                FROM $tblTrackExercise AS ex
8195
                WHERE
8196
                    ex.c_id = $courseId AND
8197
                    ex.session_id  = $sessionId AND
8198
                    ex.exe_user_id = $userId AND
8199
                    ex.status = ''
8200
                ORDER BY ex.exe_date DESC
8201
                LIMIT 1";
8202
        $rs = Database::query($sql);
8203
        $exeDate = Database::result($rs, 0, 0);
8204
8205
        if (empty($exeDate)) {
8206
            return false;
8207
        }
8208
8209
        if ($convertDate) {
8210
            return api_convert_and_format_date($exeDate, DATE_FORMAT_SHORT);
8211
        }
8212
8213
        return $exeDate;
8214
    }
8215
8216
    /**
8217
     * Return the total time spent in courses (no the total in platform).
8218
     *
8219
     * @return int
8220
     * @throws \Doctrine\DBAL\Exception
8221
     * @throws Exception
8222
     */
8223
    public static function getTotalTimeSpentInCourses(
8224
        string $dateFrom = '',
8225
        string $dateUntil = ''
8226
    ): int {
8227
        $tableTrackLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_COURSE_ACCESS);
8228
        $tableUrlRelUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
8229
        $tableUrl = null;
8230
        $urlCondition = null;
8231
        $conditionTime = null;
8232
        $accessUrlUtil = Container::getAccessUrlUtil();
8233
        if ($accessUrlUtil->isMultiple()) {
8234
            $accessUrlId = $accessUrlUtil->getCurrent()->getId();
8235
            $tableUrl = ", ".$tableUrlRelUser." as url_users";
8236
            $urlCondition = " AND u.user_id = url_users.user_id AND access_url_id = $accessUrlId";
8237
        }
8238
        if (!empty($dateFrom) && !empty($dateUntil)) {
8239
            $dateFrom = Database::escape_string($dateFrom);
8240
            $dateUntil = Database::escape_string($dateUntil);
8241
            $conditionTime = " (login_course_date >= '$dateFrom' AND logout_course_date <= '$dateUntil' ) ";
8242
        }
8243
        $sql = "SELECT SUM(TIMESTAMPDIFF(HOUR, login_course_date, logout_course_date)) diff
8244
    	        FROM $tableTrackLogin u $tableUrl
8245
                WHERE $conditionTime $urlCondition";
8246
        $rs = Database::query($sql);
8247
        $row = Database::fetch_array($rs, 'ASSOC');
8248
        $diff = $row['diff'];
8249
        if ($diff >= 0 and !empty($diff)) {
8250
            return $diff;
8251
        }
8252
        return 0;
8253
    }
8254
}
8255