Statistics::countCoursesByVisibility()   B
last analyzed

Complexity

Conditions 7
Paths 33

Size

Total Lines 47
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
c 0
b 0
f 0
dl 0
loc 47
rs 8.5066
cc 7
nc 33
nop 3
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
5
6
/**
7
 * This class provides some functions for statistics.
8
 */
9
class Statistics
10
{
11
    /**
12
     * Converts a number of bytes in a formatted string.
13
     *
14
     * @param int $size
15
     *
16
     * @return string Formatted file size
17
     */
18
    public static function makeSizeString($size)
19
    {
20
        if ($size < pow(2, 10)) {
21
            return $size." bytes";
22
        }
23
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
24
            return round($size / pow(2, 10), 0)." KB";
25
        }
26
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
27
            return round($size / pow(2, 20), 1)." MB";
28
        }
29
        if ($size > pow(2, 30)) {
30
            return round($size / pow(2, 30), 2)." GB";
31
        }
32
    }
33
34
    /**
35
     * Count courses.
36
     *
37
     * @param string|null $categoryCode Code of a course category.
38
     *                                  Default: count all courses.
39
     * @param string|null $dateFrom     dateFrom
40
     * @param string|null $dateUntil    dateUntil
41
     *
42
     * @return int Number of courses counted
43
     */
44
    public static function countCourses(string $categoryCode = null, string $dateFrom = null, string $dateUntil = null)
45
    {
46
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
47
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
48
        $urlId = api_get_current_access_url_id();
49
        if (api_is_multiple_url_enabled()) {
50
            $sql = "SELECT COUNT(*) AS number
51
                    FROM ".$courseTable." AS c, $accessUrlRelCourseTable AS u
52
                    WHERE u.c_id = c.id AND u.access_url_id = $urlId";
53
            if (isset($categoryCode)) {
54
                $sql .= " AND category_code = '".Database::escape_string($categoryCode)."'";
55
            }
56
        } else {
57
            $sql = "SELECT COUNT(*) AS number
58
                    FROM $courseTable AS c
59
                    WHERE 1 = 1";
60
            if (isset($categoryCode)) {
61
                $sql .= " AND c.category_code = '".Database::escape_string($categoryCode)."'";
62
            }
63
        }
64
65
        if (!empty($dateFrom)) {
66
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
67
            $sql .= " AND c.creation_date >= '$dateFrom' ";
68
        }
69
70
        if (!empty($dateUntil)) {
71
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
72
            $sql .= " AND c.creation_date <= '$dateUntil' ";
73
        }
74
        $res = Database::query($sql);
75
        $obj = Database::fetch_object($res);
76
77
        return $obj->number;
78
    }
79
80
    /**
81
     * Count courses by visibility.
82
     *
83
     * @param array|null  $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
84
     * @param string|null $dateFrom   dateFrom
85
     * @param string|null $dateUntil  dateUntil
86
     *
87
     * @return int Number of courses counted
88
     */
89
    public static function countCoursesByVisibility(
90
        array $visibility = null,
91
        string $dateFrom = null,
92
        string $dateUntil = null
93
    ) {
94
        if (empty($visibility)) {
95
            return 0;
96
        } else {
97
            $visibilityString = '';
98
            $auxArrayVisibility = [];
99
            if (!is_array($visibility)) {
100
                $visibility = [$visibility];
101
            }
102
            foreach ($visibility as $item) {
103
                $auxArrayVisibility[] = (int) $item;
104
            }
105
            $visibilityString = implode(',', $auxArrayVisibility);
106
        }
107
108
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
109
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
110
        $urlId = api_get_current_access_url_id();
111
        if (api_is_multiple_url_enabled()) {
112
            $sql = "SELECT COUNT(*) AS number
113
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
114
                    WHERE u.c_id = c.id AND u.access_url_id = $urlId";
115
        } else {
116
            $sql = "SELECT COUNT(*) AS number
117
                    FROM $courseTable AS c
118
                    WHERE 1 = 1";
119
        }
120
        $sql .= " AND visibility IN ($visibilityString) ";
121
122
        if (!empty($dateFrom)) {
123
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
124
            $sql .= " AND c.creation_date >= '$dateFrom' ";
125
        }
126
127
        if (!empty($dateUntil)) {
128
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
129
            $sql .= " AND c.creation_date <= '$dateUntil' ";
130
        }
131
132
        $res = Database::query($sql);
133
        $obj = Database::fetch_object($res);
134
135
        return $obj->number;
136
    }
137
138
    /**
139
     * Count users.
140
     *
141
     * @param int    $status                user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
142
     * @param string $categoryCode          course category code. Default: count only users without filtering category
143
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
144
     * @param bool   $onlyActive            Count only active users (false to only return currently active users)
145
     *
146
     * @return int Number of users counted
147
     */
148
    public static function countUsers(
149
        $status = null,
150
        $categoryCode = null,
151
        $countInvisibleCourses = true,
152
        $onlyActive = false
153
    ) {
154
        // Database table definitions
155
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
156
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
157
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
158
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
159
        $urlId = api_get_current_access_url_id();
160
        $active_filter = $onlyActive ? ' AND active=1' : '';
161
        $status_filter = isset($status) ? ' AND status = '.intval($status) : '';
162
163
        if (api_is_multiple_url_enabled()) {
164
            $sql = "SELECT COUNT(DISTINCT(u.user_id)) AS number
165
                    FROM $user_table as u, $access_url_rel_user_table as url
166
                    WHERE
167
                        u.user_id = url.user_id AND
168
                        access_url_id = $urlId
169
                        $status_filter $active_filter";
170
            if (isset($categoryCode)) {
171
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
172
                        FROM $course_user_table cu, $course_table c, $access_url_rel_user_table as url
173
                        WHERE
174
                            c.id = cu.c_id AND
175
                            c.category_code = '".Database::escape_string($categoryCode)."' AND
176
                            cu.user_id = url.user_id AND
177
                            access_url_id = $urlId
178
                            $status_filter $active_filter";
179
            }
180
        } else {
181
            $sql = "SELECT COUNT(DISTINCT(user_id)) AS number
182
                    FROM $user_table
183
                    WHERE 1=1 $status_filter $active_filter";
184
            if (isset($categoryCode)) {
185
                $status_filter = isset($status) ? ' AND status = '.intval($status) : '';
186
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
187
                        FROM $course_user_table cu, $course_table c
188
                        WHERE
189
                            c.id = cu.c_id AND
190
                            c.category_code = '".Database::escape_string($categoryCode)."'
191
                            $status_filter
192
                            $active_filter
193
                        ";
194
            }
195
        }
196
197
        $res = Database::query($sql);
198
        $obj = Database::fetch_object($res);
199
200
        return $obj->number;
201
    }
202
203
    /**
204
     * @param string $startDate
205
     * @param string $endDate
206
     *
207
     * @return array
208
     */
209
    public static function getCoursesWithActivity($startDate, $endDate)
210
    {
211
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
212
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
213
        $startDate = Database::escape_string($startDate);
214
        $endDate = Database::escape_string($endDate);
215
216
        $urlId = api_get_current_access_url_id();
217
218
        if (api_is_multiple_url_enabled()) {
219
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
220
                    WHERE
221
                        t.c_id = a.c_id AND
222
                        access_url_id = $urlId AND
223
                        access_date BETWEEN '$startDate' AND '$endDate'
224
                    ";
225
        } else {
226
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t
227
                   access_date BETWEEN '$startDate' AND '$endDate' ";
228
        }
229
230
        $result = Database::query($sql);
231
232
        return Database::store_result($result);
233
    }
234
235
    /**
236
     * Count activities from track_e_default_table.
237
     *
238
     * @return int Number of activities counted
239
     */
240
    public static function getNumberOfActivities($courseId = 0, $sessionId = 0)
241
    {
242
        // Database table definitions
243
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
244
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
245
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
246
        $urlId = api_get_current_access_url_id();
247
        if (api_is_multiple_url_enabled()) {
248
            $sql = "SELECT count(default_id) AS total_number_of_items
249
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
250
                    WHERE
251
                        default_user_id = user.user_id AND
252
                        user.user_id=url.user_id AND
253
                        access_url_id = $urlId";
254
        } else {
255
            $sql = "SELECT count(default_id) AS total_number_of_items
256
                    FROM $track_e_default, $table_user user
257
                    WHERE default_user_id = user.user_id ";
258
        }
259
260
        if (!empty($courseId)) {
261
            $courseId = (int) $courseId;
262
            $sql .= " AND c_id = $courseId";
263
            $sql .= api_get_session_condition($sessionId);
264
        }
265
266
        if (isset($_GET['keyword'])) {
267
            $keyword = Database::escape_string(trim($_GET['keyword']));
268
            $sql .= " AND (
269
                        user.username LIKE '%".$keyword."%' OR
270
                        default_event_type LIKE '%".$keyword."%' OR
271
                        default_value_type LIKE '%".$keyword."%' OR
272
                        default_value LIKE '%".$keyword."%') ";
273
        }
274
        $res = Database::query($sql);
275
        $obj = Database::fetch_object($res);
276
277
        return $obj->total_number_of_items;
278
    }
279
280
    /**
281
     * Get activities data to display.
282
     *
283
     * @param int    $from
284
     * @param int    $numberOfItems
285
     * @param int    $column
286
     * @param string $direction
287
     * @param int    $courseId
288
     * @param int    $sessionId
289
     *
290
     * @return array
291
     */
292
    public static function getActivitiesData(
293
        $from,
294
        $numberOfItems,
295
        $column,
296
        $direction,
297
        $courseId = 0,
298
        $sessionId = 0
299
    ) {
300
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
301
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
302
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
303
        $urlId = api_get_current_access_url_id();
304
        $column = (int) $column;
305
        $from = (int) $from;
306
        $numberOfItems = (int) $numberOfItems;
307
        $direction = strtoupper($direction);
308
309
        if (!in_array($direction, ['ASC', 'DESC'])) {
310
            $direction = 'DESC';
311
        }
312
313
        if (api_is_multiple_url_enabled()) {
314
            $sql = "SELECT
315
                        default_event_type  as col0,
316
                        default_value_type    as col1,
317
                        default_value        as col2,
318
                        c_id         as col3,
319
                        session_id as col4,
320
                        user.username         as col5,
321
                        user.user_id         as col6,
322
                        default_date         as col7
323
                    FROM $track_e_default as track_default,
324
                    $table_user as user,
325
                    $access_url_rel_user_table as url
326
                    WHERE
327
                        track_default.default_user_id = user.user_id AND
328
                        url.user_id = user.user_id AND
329
                        access_url_id = $urlId";
330
        } else {
331
            $sql = "SELECT
332
                       default_event_type  as col0,
333
                       default_value_type    as col1,
334
                       default_value        as col2,
335
                       c_id         as col3,
336
                       session_id as col4,
337
                       user.username         as col5,
338
                       user.user_id         as col6,
339
                       default_date         as col7
340
                   FROM $track_e_default track_default, $table_user user
341
                   WHERE track_default.default_user_id = user.user_id ";
342
        }
343
344
        if (!empty($_GET['keyword'])) {
345
            $keyword = Database::escape_string(trim($_GET['keyword']));
346
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
347
                        default_event_type LIKE '%".$keyword."%' OR
348
                        default_value_type LIKE '%".$keyword."%' OR
349
                        default_value LIKE '%".$keyword."%') ";
350
        }
351
352
        if (!empty($courseId)) {
353
            $courseId = (int) $courseId;
354
            $sql .= " AND c_id = $courseId";
355
            $sql .= api_get_session_condition($sessionId);
356
        }
357
358
        if (!empty($column) && !empty($direction)) {
359
            $sql .= " ORDER BY col$column $direction";
360
        } else {
361
            $sql .= " ORDER BY col7 DESC ";
362
        }
363
        $sql .= " LIMIT $from, $numberOfItems ";
364
365
        $res = Database::query($sql);
366
        $activities = [];
367
        while ($row = Database::fetch_row($res)) {
368
            if (strpos($row[1], '_object') === false &&
369
                strpos($row[1], '_array') === false
370
            ) {
371
                $row[2] = $row[2];
372
            } else {
373
                if (!empty($row[2])) {
374
                    $originalData = str_replace('\\', '', $row[2]);
375
                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
376
                    if (is_array($row[2]) && !empty($row[2])) {
377
                        $row[2] = implode_with_key(', ', $row[2]);
378
                    } else {
379
                        $row[2] = $originalData;
380
                    }
381
                }
382
            }
383
384
            if (!empty($row['default_date'])) {
385
                $row['default_date'] = api_get_local_time($row['default_date']);
386
            } else {
387
                $row['default_date'] = '-';
388
            }
389
390
            if (!empty($row[7])) {
391
                $row[7] = api_get_local_time($row[7]);
392
            } else {
393
                $row[7] = '-';
394
            }
395
396
            if (!empty($row[5])) {
397
                // Course
398
                if (!empty($row[3])) {
399
                    $row[3] = Display::url(
400
                        $row[3],
401
                        api_get_path(WEB_CODE_PATH).'admin/course_edit.php?id='.$row[3]
402
                    );
403
                } else {
404
                    $row[3] = '-';
405
                }
406
407
                // session
408
                if (!empty($row[4])) {
409
                    $row[4] = Display::url(
410
                        $row[4],
411
                        api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$row[4]
412
                    );
413
                } else {
414
                    $row[4] = '-';
415
                }
416
417
                // User id.
418
                $userIdHash = UserManager::generateUserHash($row[6]);
419
                $row[5] = Display::url(
420
                    $row[5],
421
                    api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&hash='.$userIdHash,
422
                    ['class' => 'ajax']
423
                );
424
425
                $row[6] = Tracking::get_ip_from_user_event(
426
                    $row[6],
427
                    $row[7],
428
                    true
429
                );
430
                if (empty($row[6])) {
431
                    $row[6] = get_lang('Unknown');
432
                }
433
            }
434
            $activities[] = $row;
435
        }
436
437
        return $activities;
438
    }
439
440
    /**
441
     * Get all course categories.
442
     *
443
     * @return array All course categories (code => name)
444
     */
445
    public static function getCourseCategories()
446
    {
447
        $categoryTable = Database::get_main_table(TABLE_MAIN_CATEGORY);
448
        $sql = "SELECT code, name
449
                FROM $categoryTable
450
                ORDER BY tree_pos";
451
        $res = Database::query($sql);
452
        $categories = [null => get_lang('NoCategory')];
453
        while ($category = Database::fetch_object($res)) {
454
            $categories[$category->code] = $category->name;
455
        }
456
457
        return $categories;
458
    }
459
460
    /**
461
     * Rescale data.
462
     *
463
     * @param array $data The data that should be rescaled
464
     * @param int   $max  The maximum value in the rescaled data (default = 500);
465
     *
466
     * @return array The rescaled data, same key as $data
467
     */
468
    public static function rescale($data, $max = 500)
469
    {
470
        $data_max = 1;
471
        foreach ($data as $index => $value) {
472
            $data_max = ($data_max < $value ? $value : $data_max);
473
        }
474
        reset($data);
475
        $result = [];
476
        $delta = $max / $data_max;
477
        foreach ($data as $index => $value) {
478
            $result[$index] = (int) round($value * $delta);
479
        }
480
481
        return $result;
482
    }
483
484
    /**
485
     * Get the number of users by access url .
486
     *
487
     * @param $currentmonth
488
     * @param $lastmonth
489
     * @param $invoicingMonth
490
     * @param $invoicingYear
491
     *
492
     * @return string
493
     */
494
    public static function printInvoicingByAccessUrl(
495
        $currentMonth,
496
        $lastMonth,
497
        $invoicingMonth,
498
        $invoicingYear
499
    ) {
500
        $tblTrackAccess = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
501
        $tblAccessUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
502
        $tblAccessUrl = Database::get_main_table(TABLE_MAIN_ACCESS_URL);
503
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
504
        $tblCourse = Database::get_main_table(TABLE_MAIN_COURSE);
505
        $tblSession = Database::get_main_table(TABLE_MAIN_SESSION);
506
507
        $urls = api_get_access_url_from_user(api_get_user_id());
508
        $allowFullUrlAccess = array_search(1, $urls);
509
        $whereAccessUrl = '';
510
        if (!empty($urls) && false === $allowFullUrlAccess) {
511
            $whereAccessUrl = ' AND access_url_rel_user.access_url_id IN('.implode(',', $urls).')';
512
        }
513
514
        $sql = '
515
		    SELECT
516
		        DISTINCT access_url.description AS client,
517
		        user.lastname,
518
		        user.firstname,
519
		        course.code AS course_code,
520
		        MIN(DATE_FORMAT(track_e_access.access_date,"%d/%m/%Y")) AS start_date,
521
		        access_session_id,
522
		        CONCAT(coach.lastname,\' \',coach.firstname) AS trainer
523
		    FROM '.$tblTrackAccess.' AS track_e_access
524
            JOIN '.$tblAccessUrlUser.' AS access_url_rel_user ON access_url_rel_user.user_id=track_e_access.access_user_id
525
            JOIN '.$tblAccessUrl.' AS access_url ON access_url.id=access_url_rel_user.access_url_id
526
            JOIN '.$tblUser.' AS user ON user.user_id=access_user_id
527
            JOIN '.$tblCourse.' AS course ON course.id=track_e_access.c_id
528
            JOIN '.$tblSession.' AS session ON session.id=track_e_access.access_session_id
529
            JOIN '.$tblUser.' AS coach ON coach.user_id=session.id_coach
530
            WHERE
531
                access_session_id > 0 AND
532
                access_date LIKE \''.$currentMonth.'%\' AND
533
                user.status = '.STUDENT.' AND
534
                CONCAT(access_user_id,\'-\',access_session_id) NOT IN (SELECT CONCAT(access_user_id,\'-\',access_session_id) FROM '.$tblTrackAccess.' WHERE access_session_id > 0
535
                AND access_date LIKE \''.$lastMonth.'%\')
536
                '.$whereAccessUrl.'
537
            GROUP BY
538
                user.lastname,code
539
            ORDER BY
540
                access_url.description,
541
                user.lastname,
542
                user.firstname,
543
                track_e_access.access_date
544
            DESC,course.code
545
		';
546
        $result = Database::query($sql);
547
548
        $monthList = api_get_months_long();
549
        array_unshift($monthList, '');
550
551
        $nMonth = (int) $invoicingMonth;
552
        $content = '<h2>'.get_lang('NumberOfUsers').' '.strtolower($monthList[$nMonth]).' '.$invoicingYear.'</h2><br>';
553
554
        $form = new FormValidator('invoice_month', 'get', api_get_self().'?report=invoicing&invoicing_month='.$invoicingMonth.'&invoicing_year='.$invoicingYear);
555
        $form->addSelect('invoicing_month', get_lang('Month'), $monthList);
556
        $currentYear = date("Y");
557
        $yearList = [''];
558
        for ($i = 0; $i < 3; $i++) {
559
            $y = $currentYear - $i;
560
            $yearList[$y] = $y;
561
        }
562
        $form->addSelect('invoicing_year', get_lang('Year'), $yearList);
563
        $form->addButtonSend(get_lang('Filter'));
564
        $form->addHidden('report', 'invoicing');
565
        $content .= $form->returnForm();
566
567
        $content .= '<br>';
568
        $content .= '<table border=1 class="table table-bordered data_table">';
569
        $content .= '<tr>
570
            <th class="th-header">'.get_lang('Portal').'</th>
571
            <th class="th-header">'.get_lang('LastName').'</th>
572
            <th class="th-header">'.get_lang('FirstName').'</th>
573
            <th class="th-header">'.get_lang('Code').'</th>
574
            <th class="th-header">'.get_lang('StartDate').'</th>
575
            <th class="th-header">'.get_lang('SessionId').'</th>
576
            <th class="th-header">'.get_lang('Trainer').'</th>
577
        </tr>';
578
        $countusers = 0;
579
        $lastname = '';
580
        $lastportal = '';
581
        while ($row = Database::fetch_array($result)) {
582
            if (($row['client'] != $lastportal) && ($countusers > 0)) {
583
                $content .= '<tr class="row_odd"><td colspan=7>'.get_lang('TotalUser').' '.$lastportal.' : '.$countusers.'</td></tr>';
584
                $countusers = 0;
585
            }
586
            $content .= '<tr>
587
                <td>'.$row['client'].'</td>
588
                <td>'.$row['lastname'].'</td>
589
                <td>'.$row['firstname'].'</td>
590
                <td>'.$row['course_code'].'</td>
591
                <td>'.$row['start_date'].'</td>
592
                <td>'.$row['access_session_id'].'</td>
593
                <td>'.$row['trainer'].'</td>
594
            </tr>';
595
            if ($lastname != $row['lastname'].$row['firstname']) {
596
                $countusers++;
597
            }
598
            $lastname = $row['lastname'].$row['firstname'];
599
            $lastportal = $row['client'];
600
        }
601
        $content .= '<tr class="row_odd">
602
            <td colspan=7>'.get_lang('TotalUser').' '.$lastportal.' : '.$countusers.'</td>
603
        </tr>';
604
        $content .= '</table>';
605
606
        return $content;
607
    }
608
609
    /**
610
     * Show statistics.
611
     *
612
     * @param string $title      The title
613
     * @param array  $stats
614
     * @param bool   $showTotal
615
     * @param bool   $isFileSize
616
     *
617
     * @return string HTML table
618
     */
619
    public static function printStats(
620
        $title,
621
        $stats,
622
        $showTotal = true,
623
        $isFileSize = false
624
    ) {
625
        $total = 0;
626
        $content = '<table class="table table-hover table-striped data_table" cellspacing="0" cellpadding="3" width="90%">
627
            <thead><tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr></thead><tbody>';
628
        $i = 0;
629
        foreach ($stats as $subtitle => $number) {
630
            $total += $number;
631
        }
632
633
        foreach ($stats as $subtitle => $number) {
634
            if (!$isFileSize) {
635
                $number_label = number_format($number, 0, ',', '.');
636
            } else {
637
                $number_label = self::makeSizeString($number);
638
            }
639
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
640
641
            $content .= '<tr class="row_'.($i % 2 == 0 ? 'odd' : 'even').'">
642
                    <td width="25%" style="vertical-align:top;">'.$subtitle.'</td>
643
                    <td width="60%">'.Display::bar_progress($percentage, false).'</td>
644
                    <td width="5%" align="right" style="vertical-align:top;">'.$number_label.'</td>';
645
            if ($showTotal) {
646
                $content .= '<td width="5%" align="right"> '.$percentage.'%</td>';
647
            }
648
            $content .= '</tr>';
649
            $i++;
650
        }
651
        $content .= '</tbody>';
652
        if ($showTotal) {
653
            if (!$isFileSize) {
654
                $total_label = number_format($total, 0, ',', '.');
655
            } else {
656
                $total_label = self::makeSizeString($total);
657
            }
658
            $content .= '
659
                <tfoot><tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr></tfoot>
660
            ';
661
        }
662
        $content .= '</table>';
663
664
        return $content;
665
    }
666
667
    /**
668
     * Show some stats about the number of logins.
669
     *
670
     * @param string $type month, hour or day
671
     */
672
    public static function printLoginStats($type)
673
    {
674
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
675
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
676
        $urlId = api_get_current_access_url_id();
677
678
        $table_url = null;
679
        $where_url = null;
680
        $now = api_get_utc_datetime();
681
        $where_url_last = ' WHERE login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
682
        if (api_is_multiple_url_enabled()) {
683
            $table_url = ", $access_url_rel_user_table";
684
            $where_url = " WHERE login_user_id=user_id AND access_url_id = $urlId";
685
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
686
        }
687
688
        $period = get_lang('PeriodMonth');
689
        $periodCollection = api_get_months_long();
690
        $sql = "SELECT
691
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
692
                count( login_id ) AS number_of_logins
693
                FROM $table $table_url $where_url
694
                GROUP BY stat_date
695
                ORDER BY login_date DESC";
696
        $sql_last_x = null;
697
698
        switch ($type) {
699
            case 'hour':
700
                $period = get_lang('PeriodHour');
701
                $sql = "SELECT
702
                          DATE_FORMAT( login_date, '%H') AS stat_date,
703
                          count( login_id ) AS number_of_logins
704
                        FROM $table $table_url $where_url
705
                        GROUP BY stat_date
706
                        ORDER BY stat_date ";
707
                $sql_last_x = "SELECT
708
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
709
                                count( login_id ) AS number_of_logins
710
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
711
                               GROUP BY stat_date
712
                               ORDER BY stat_date ";
713
                break;
714
            case 'day':
715
                $periodCollection = api_get_week_days_long();
716
                $period = get_lang('PeriodDay');
717
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date ,
718
                        count( login_id ) AS number_of_logins
719
                        FROM  $table $table_url $where_url
720
                        GROUP BY stat_date
721
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
722
                $sql_last_x = "SELECT
723
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
724
                                count( login_id ) AS number_of_logins
725
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
726
                               GROUP BY stat_date
727
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
728
                break;
729
        }
730
731
        $content = '';
732
        if ($sql_last_x) {
733
            $res_last_x = Database::query($sql_last_x);
734
            $result_last_x = [];
735
            while ($obj = Database::fetch_object($res_last_x)) {
736
                $stat_date = ($type === 'day') ? $periodCollection[$obj->stat_date] : $obj->stat_date;
737
                $result_last_x[$stat_date] = $obj->number_of_logins;
738
            }
739
            $content .= self::printStats(get_lang('LastLogins').' ('.$period.')', $result_last_x, true);
740
            flush(); //flush web request at this point to see something already while the full data set is loading
741
            $content .= '<br />';
742
        }
743
        $res = Database::query($sql);
744
        $result = [];
745
        while ($obj = Database::fetch_object($res)) {
746
            $stat_date = $obj->stat_date;
747
            switch ($type) {
748
                case 'month':
749
                    $stat_date = explode('-', $stat_date);
750
                    $stat_date[1] = $periodCollection[$stat_date[1] - 1];
751
                    $stat_date = implode(' ', $stat_date);
752
                    break;
753
                case 'day':
754
                    $stat_date = $periodCollection[$stat_date];
755
                    break;
756
            }
757
            $result[$stat_date] = $obj->number_of_logins;
758
        }
759
        $content .= self::printStats(get_lang('AllLogins').' ('.$period.')', $result, true);
760
761
        return $content;
762
    }
763
764
    /**
765
     * Print the number of recent logins.
766
     *
767
     * @param bool  $distinct        whether to only give distinct users stats, or *all* logins
768
     * @param int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
769
     * @param array $periods         List of number of days we want to query (default: [1, 7, 31] for last 1 day, last 7 days, last 31 days)
770
     *
771
     * @throws Exception
772
     *
773
     * @return string HTML table
774
     */
775
    public static function printRecentLoginStats($distinct = false, $sessionDuration = 0, $periods = [])
776
    {
777
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
778
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
779
        $urlId = api_get_current_access_url_id();
780
        $table_url = '';
781
        $where_url = '';
782
        if (api_is_multiple_url_enabled()) {
783
            $table_url = ", $access_url_rel_user_table";
784
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
785
        }
786
787
        $now = api_get_utc_datetime();
788
        $field = 'login_id';
789
        if ($distinct) {
790
            $field = 'DISTINCT(login_user_id)';
791
        }
792
793
        if (empty($periods)) {
794
            $periods = [1, 7, 31];
795
        }
796
        $sqlList = [];
797
798
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
799
        foreach ($periods as $day) {
800
            $date = new DateTime($now);
801
            $startDate = $date->format('Y-m-d').' 00:00:00';
802
            $endDate = $date->format('Y-m-d').' 23:59:59';
803
804
            if ($day > 1) {
805
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
806
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
807
            }
808
809
            $localDate = api_get_local_time($startDate, null, null, false, false);
810
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
811
812
            $label = sprintf(get_lang('LastXDays'), $day);
813
            if ($day == 1) {
814
                $label = get_lang('Today');
815
            }
816
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
817
            $sql = "SELECT count($field) AS number
818
                    FROM $table $table_url
819
                    WHERE ";
820
            if ($sessionDuration == 0) {
821
                $sql .= " logout_date != login_date AND ";
822
            } else {
823
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
824
            }
825
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
826
                        $where_url";
827
            $sqlList[$label] = $sql;
828
        }
829
830
        $sql = "SELECT count($field) AS number
831
                FROM $table $table_url ";
832
        if ($sessionDuration == 0) {
833
            $sql .= " WHERE logout_date != login_date $where_url";
834
        } else {
835
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
836
        }
837
        $sqlList[get_lang('Total')] = $sql;
838
        $totalLogin = [];
839
        foreach ($sqlList as $label => $query) {
840
            $res = Database::query($query);
841
            $obj = Database::fetch_object($res);
842
            $totalLogin[$label] = $obj->number;
843
        }
844
845
        if ($distinct) {
846
            $content = self::printStats(get_lang('DistinctUsersLogins'), $totalLogin, false);
847
        } else {
848
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
849
        }
850
851
        return $content;
852
    }
853
854
    /**
855
     * Get the number of recent logins.
856
     *
857
     * @param bool $distinct            Whether to only give distinct users stats, or *all* logins
858
     * @param int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
859
     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
860
     *
861
     * @throws Exception
862
     *
863
     * @return array
864
     */
865
    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
866
    {
867
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
868
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
869
        $urlId = api_get_current_access_url_id();
870
        $table_url = '';
871
        $where_url = '';
872
        if (api_is_multiple_url_enabled()) {
873
            $table_url = ", $access_url_rel_user_table";
874
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
875
        }
876
877
        $now = api_get_utc_datetime();
878
        $date = new DateTime($now);
879
        $date->sub(new DateInterval('P31D'));
880
        $newDate = $date->format('Y-m-d h:i:s');
881
        $totalLogin = self::buildDatesArray($newDate, $now, true);
882
883
        $field = 'login_id';
884
        if ($distinct) {
885
            $field = 'DISTINCT(login_user_id)';
886
        }
887
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
888
889
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
890
                FROM $table $table_url
891
                WHERE ";
892
        if ($sessionDuration == 0) {
893
            $sql .= " logout_date != login_date AND ";
894
        } else {
895
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
896
        }
897
        $sql .= " login_date >= '$newDate' $where_url
898
                GROUP BY date(login_date)";
899
900
        $res = Database::query($sql);
901
        while ($row = Database::fetch_array($res, 'ASSOC')) {
902
            $monthAndDay = substr($row['login_date'], 5, 5);
903
            $totalLogin[$monthAndDay] = $row['number'];
904
        }
905
906
        return $totalLogin;
907
    }
908
909
    /**
910
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
911
     */
912
    public static function getToolsStats()
913
    {
914
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
915
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
916
        $urlId = api_get_current_access_url_id();
917
918
        $tools = [
919
            'announcement',
920
            'assignment',
921
            'calendar_event',
922
            'chat',
923
            'course_description',
924
            'document',
925
            'dropbox',
926
            'group',
927
            'learnpath',
928
            'link',
929
            'quiz',
930
            'student_publication',
931
            'user',
932
            'forum',
933
        ];
934
        $tool_names = [];
935
        foreach ($tools as $tool) {
936
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
937
        }
938
        if (api_is_multiple_url_enabled()) {
939
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
940
                    FROM $table t , $access_url_rel_course_table a
941
                    WHERE
942
                        access_tool IN ('".implode("','", $tools)."') AND
943
                        t.c_id = a.c_id AND
944
                        access_url_id = $urlId
945
                        GROUP BY access_tool
946
                    ";
947
        } else {
948
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
949
                    FROM $table
950
                    WHERE access_tool IN ('".implode("','", $tools)."')
951
                    GROUP BY access_tool ";
952
        }
953
954
        $res = Database::query($sql);
955
        $result = [];
956
        while ($obj = Database::fetch_object($res)) {
957
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
958
        }
959
960
        return $result;
961
    }
962
963
    /**
964
     * Show some stats about the accesses to the different course tools.
965
     *
966
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
967
     *
968
     * @return string HTML table
969
     */
970
    public static function printToolStats($result = null)
971
    {
972
        if (empty($result)) {
973
            $result = self::getToolsStats();
974
        }
975
976
        return self::printStats(get_lang('PlatformToolAccess'), $result, true);
977
    }
978
979
    /**
980
     * Show some stats about the number of courses per language.
981
     */
982
    public static function printCourseByLanguageStats()
983
    {
984
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
985
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
986
        $urlId = api_get_current_access_url_id();
987
        if (api_is_multiple_url_enabled()) {
988
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
989
                    FROM $table as c, $access_url_rel_course_table as u
990
                    WHERE u.c_id = c.id AND access_url_id = $urlId
991
                    GROUP BY course_language
992
                    ORDER BY number_of_courses DESC";
993
        } else {
994
            $sql = "SELECT course_language, count( code ) AS number_of_courses
995
                   FROM $table GROUP BY course_language
996
                   ORDER BY number_of_courses DESC";
997
        }
998
        $res = Database::query($sql);
999
        $result = [];
1000
        while ($obj = Database::fetch_object($res)) {
1001
            $result[$obj->course_language] = $obj->number_of_courses;
1002
        }
1003
1004
        return $result;
1005
    }
1006
1007
    /**
1008
     * Shows the number of users having their picture uploaded in Dokeos.
1009
     */
1010
    public static function printUserPicturesStats()
1011
    {
1012
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1013
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1014
        $urlId = api_get_current_access_url_id();
1015
        $url_condition = null;
1016
        $url_condition2 = null;
1017
        $table = null;
1018
        if (api_is_multiple_url_enabled()) {
1019
            $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.user_id AND access_url_id = $urlId";
1020
            $url_condition2 = " AND url.user_id=u.user_id AND access_url_id = $urlId";
1021
            $table = ", $access_url_rel_user_table as url ";
1022
        }
1023
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
1024
        $res = Database::query($sql);
1025
        $count1 = Database::fetch_object($res);
1026
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table ".
1027
               "WHERE LENGTH(picture_uri) > 0 $url_condition2";
1028
        $res = Database::query($sql);
1029
        $count2 = Database::fetch_object($res);
1030
        // #users without picture
1031
        $result[get_lang('No')] = $count1->n - $count2->n;
1032
        $result[get_lang('Yes')] = $count2->n; // #users with picture
1033
1034
        return self::printStats(get_lang('CountUsers').' ('.get_lang('UserPicture').')', $result, true);
1035
    }
1036
1037
    /**
1038
     * Important activities.
1039
     */
1040
    public static function printActivitiesStats()
1041
    {
1042
        $content = '<h4>'.get_lang('ImportantActivities').'</h4>';
1043
        // Create a search-box
1044
        $form = new FormValidator(
1045
            'search_simple',
1046
            'get',
1047
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
1048
            '',
1049
            'width=200px',
1050
            false
1051
        );
1052
        $renderer = &$form->defaultRenderer();
1053
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
1054
        $form->addHidden('report', 'activities');
1055
        $form->addHidden('activities_direction', 'DESC');
1056
        $form->addHidden('activities_column', '4');
1057
        $form->addElement('text', 'keyword', get_lang('Keyword'));
1058
        $form->addButtonSearch(get_lang('Search'), 'submit');
1059
        $content .= '<div class="actions">';
1060
        $content .= $form->returnForm();
1061
        $content .= '</div>';
1062
1063
        if (!empty($_GET['keyword'])) {
1064
            $table = new SortableTable(
1065
                'activities',
1066
                ['Statistics', 'getNumberOfActivities'],
1067
                ['Statistics', 'getActivitiesData'],
1068
                7,
1069
                50,
1070
                'DESC'
1071
            );
1072
            $parameters = [];
1073
1074
            $parameters['report'] = 'activities';
1075
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
1076
1077
            $table->set_additional_parameters($parameters);
1078
            $table->set_header(0, get_lang('EventType'));
1079
            $table->set_header(1, get_lang('DataType'));
1080
            $table->set_header(2, get_lang('Value'));
1081
            $table->set_header(3, get_lang('Course'));
1082
            $table->set_header(4, get_lang('Session'));
1083
            $table->set_header(5, get_lang('UserName'));
1084
            $table->set_header(6, get_lang('IPAddress'));
1085
            $table->set_header(7, get_lang('Date'));
1086
            $content .= $table->return_table();
1087
        }
1088
1089
        $content .= '<div class="alert alert-info">'.get_lang('ImportantActivities').' : '.'<br>';
1090
        $prefix = 'LOG_';
1091
        $userDefinedConstants = get_defined_constants(true)['user'];
1092
        $filteredConstants = array_filter($userDefinedConstants, function ($constantName) use ($prefix) {
1093
            return strpos($constantName, $prefix) === 0;
1094
        }, ARRAY_FILTER_USE_KEY);
1095
        $constantNames = array_keys($filteredConstants);
1096
        $link = api_get_self().'?report=activities&activities_direction=DESC&activities_column=7&keyword=';
1097
        foreach ($constantNames as $constantName) {
1098
            if ($constantName != 'LOG_WS') {
1099
                if (substr($constantName, -3) == '_ID') {
1100
                    continue;
1101
                }
1102
                $content .= '- <a href="'.$link.constant($constantName).'">'.constant($constantName).'</a><br>'.PHP_EOL;
1103
            } else {
1104
                $constantValue = constant($constantName);
1105
                $reflection = new ReflectionClass('Rest');
1106
                $constants = $reflection->getConstants();
1107
                foreach ($constants as $name => $value) {
1108
                    $content .= '- <a href="'.$link.$constantValue.$value.'">'.$constantValue.$value.'</a><br>'.PHP_EOL;
1109
                }
1110
            }
1111
        }
1112
        $content .= '</div>';
1113
1114
        return $content;
1115
    }
1116
1117
    /**
1118
     * Shows statistics about the time of last visit to each course.
1119
     */
1120
    public static function printCourseLastVisit()
1121
    {
1122
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
1123
        $urlId = api_get_current_access_url_id();
1124
1125
        $columns[0] = 't.c_id';
1126
        $columns[1] = 'access_date';
1127
        $sql_order[SORT_ASC] = 'ASC';
1128
        $sql_order[SORT_DESC] = 'DESC';
1129
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
1130
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
1131
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
1132
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
1133
1134
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
1135
            $direction = SORT_ASC;
1136
        }
1137
        $form = new FormValidator('courselastvisit', 'get');
1138
        $form->addElement('hidden', 'report', 'courselastvisit');
1139
        $form->addText('date_diff', get_lang('Days'), true);
1140
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
1141
        $form->addButtonSearch(get_lang('Search'), 'submit');
1142
        if (!isset($_GET['date_diff'])) {
1143
            $defaults['date_diff'] = 60;
1144
        } else {
1145
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
1146
        }
1147
        $form->setDefaults($defaults);
1148
        $content = $form->returnForm();
1149
1150
        $values = $form->exportValues();
1151
        $date_diff = $values['date_diff'];
1152
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
1153
        if (api_is_multiple_url_enabled()) {
1154
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
1155
                   WHERE
1156
                        t.c_id = a.c_id AND
1157
                        access_url_id = $urlId
1158
                   GROUP BY t.c_id
1159
                   HAVING t.c_id <> ''
1160
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1161
        } else {
1162
            $sql = "SELECT * FROM $table t
1163
                   GROUP BY t.c_id
1164
                   HAVING t.c_id <> ''
1165
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1166
        }
1167
        $sql .= ' ORDER BY '.$columns[$column].' '.$sql_order[$direction];
1168
        $from = ($page_nr - 1) * $per_page;
1169
        $sql .= ' LIMIT '.$from.','.$per_page;
1170
1171
        $content .= '<p>'.get_lang('LastAccess').' &gt;= '.$date_diff.' '.get_lang('Days').'</p>';
1172
        $res = Database::query($sql);
1173
        if (Database::num_rows($res) > 0) {
1174
            $courses = [];
1175
            while ($obj = Database::fetch_object($res)) {
1176
                $courseInfo = api_get_course_info_by_id($obj->c_id);
1177
                $course = [];
1178
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
1179
                // Allow sort by date hiding the numerical date
1180
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
1181
                $courses[] = $course;
1182
            }
1183
            $parameters['date_diff'] = $date_diff;
1184
            $parameters['report'] = 'courselastvisit';
1185
            $table_header[] = [get_lang("CourseCode"), true];
1186
            $table_header[] = [get_lang("LastAccess"), true];
1187
1188
            ob_start();
1189
            Display::display_sortable_table(
1190
                $table_header,
1191
                $courses,
1192
                ['column' => $column, 'direction' => $direction],
1193
                [],
1194
                $parameters
1195
            );
1196
            $content .= ob_get_contents();
1197
            ob_end_clean();
1198
        } else {
1199
            $content = get_lang('NoSearchResults');
1200
        }
1201
1202
        return $content;
1203
    }
1204
1205
    /**
1206
     * Displays the statistics of the messages sent and received by each user in the social network.
1207
     *
1208
     * @param string $messageType Type of message: 'sent' or 'received'
1209
     *
1210
     * @return array Message list
1211
     */
1212
    public static function getMessages($messageType)
1213
    {
1214
        $message_table = Database::get_main_table(TABLE_MESSAGE);
1215
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1216
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1217
1218
        $urlId = api_get_current_access_url_id();
1219
1220
        switch ($messageType) {
1221
            case 'sent':
1222
                $field = 'user_sender_id';
1223
                break;
1224
            case 'received':
1225
                $field = 'user_receiver_id';
1226
                break;
1227
        }
1228
1229
        if (api_is_multiple_url_enabled()) {
1230
            $sql = "SELECT lastname, firstname, username, COUNT($field) AS count_message
1231
                FROM $access_url_rel_user_table as url, $message_table m
1232
                LEFT JOIN $user_table u ON m.$field = u.user_id
1233
                WHERE  url.user_id = m.$field AND  access_url_id = $urlId
1234
                GROUP BY m.$field
1235
                ORDER BY count_message DESC ";
1236
        } else {
1237
            $sql = "SELECT lastname, firstname, username, COUNT($field) AS count_message
1238
                FROM $message_table m
1239
                LEFT JOIN $user_table u ON m.$field = u.user_id
1240
                GROUP BY m.$field ORDER BY count_message DESC ";
1241
        }
1242
        $res = Database::query($sql);
1243
        $messages_sent = [];
1244
        while ($messages = Database::fetch_array($res)) {
1245
            if (empty($messages['username'])) {
1246
                $messages['username'] = get_lang('Unknown');
1247
            }
1248
            $users = api_get_person_name(
1249
                $messages['firstname'],
1250
                $messages['lastname']
1251
            ).'<br />('.$messages['username'].')';
1252
            $messages_sent[$users] = $messages['count_message'];
1253
        }
1254
1255
        return $messages_sent;
1256
    }
1257
1258
    /**
1259
     * Count the number of friends for social network users.
1260
     */
1261
    public static function getFriends()
1262
    {
1263
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
1264
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1265
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1266
        $urlId = api_get_current_access_url_id();
1267
1268
        if (api_is_multiple_url_enabled()) {
1269
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1270
                    FROM $access_url_rel_user_table as url, $user_friend_table uf
1271
                    LEFT JOIN $user_table u
1272
                    ON (uf.user_id = u.user_id)
1273
                    WHERE
1274
                        uf.relation_type <> '".USER_RELATION_TYPE_RRHH."' AND
1275
                        uf.user_id = url.user_id AND
1276
                        access_url_id = $urlId
1277
                    GROUP BY uf.user_id
1278
                    ORDER BY count_friend DESC ";
1279
        } else {
1280
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1281
                    FROM $user_friend_table uf
1282
                    LEFT JOIN $user_table u
1283
                    ON (uf.user_id = u.user_id)
1284
                    WHERE uf.relation_type <> '".USER_RELATION_TYPE_RRHH."'
1285
                    GROUP BY uf.user_id
1286
                    ORDER BY count_friend DESC ";
1287
        }
1288
        $res = Database::query($sql);
1289
        $list_friends = [];
1290
        while ($friends = Database::fetch_array($res)) {
1291
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1292
            $list_friends[$users] = $friends['count_friend'];
1293
        }
1294
1295
        return $list_friends;
1296
    }
1297
1298
    /**
1299
     * Print the number of users that didn't login for a certain period of time.
1300
     */
1301
    public static function printUsersNotLoggedInStats()
1302
    {
1303
        $totalLogin = [];
1304
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1305
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1306
        $urlId = api_get_current_access_url_id();
1307
        $total = self::countUsers();
1308
        if (api_is_multiple_url_enabled()) {
1309
            $table_url = ", $access_url_rel_user_table";
1310
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
1311
        } else {
1312
            $table_url = '';
1313
            $where_url = '';
1314
        }
1315
        $now = api_get_utc_datetime();
1316
        $sql[get_lang('ThisDay')] =
1317
            "SELECT count(distinct(login_user_id)) AS number ".
1318
            " FROM $table $table_url ".
1319
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1320
        $sql[get_lang('Last7days')] =
1321
            "SELECT count(distinct(login_user_id)) AS number ".
1322
            " FROM $table $table_url ".
1323
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1324
        $sql[get_lang('Last31days')] =
1325
            "SELECT count(distinct(login_user_id)) AS number ".
1326
            " FROM $table $table_url ".
1327
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1328
        $sql[sprintf(get_lang('LastXMonths'), 6)] =
1329
            "SELECT count(distinct(login_user_id)) AS number ".
1330
            " FROM $table $table_url ".
1331
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1332
        $sql[get_lang('NeverConnected')] =
1333
            "SELECT count(distinct(login_user_id)) AS number ".
1334
            " FROM $table $table_url WHERE 1=1 $where_url";
1335
        foreach ($sql as $index => $query) {
1336
            $res = Database::query($query);
1337
            $obj = Database::fetch_object($res);
1338
            $r = $total - $obj->number;
1339
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1340
        }
1341
1342
        return self::printStats(
1343
            get_lang('StatsUsersDidNotLoginInLastPeriods'),
1344
            $totalLogin,
1345
            false
1346
        );
1347
    }
1348
1349
    /**
1350
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1351
     * within the provided range (including limits). Dates are assumed to be
1352
     * given in UTC.
1353
     *
1354
     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
1355
     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
1356
     * @param bool   $removeYear Whether to remove the year in the results (for easier reading)
1357
     *
1358
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1359
     */
1360
    public static function buildDatesArray($startDate, $endDate, $removeYear = false)
1361
    {
1362
        if (strlen($startDate) > 10) {
1363
            $startDate = substr($startDate, 0, 10);
1364
        }
1365
        if (strlen($endDate) > 10) {
1366
            $endDate = substr($endDate, 0, 10);
1367
        }
1368
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1369
            return false;
1370
        }
1371
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1372
            return false;
1373
        }
1374
        $startTimestamp = strtotime($startDate);
1375
        $endTimestamp = strtotime($endDate);
1376
        $list = [];
1377
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1378
            $datetime = api_get_utc_datetime($time);
1379
            if ($removeYear) {
1380
                $datetime = substr($datetime, 5, 5);
1381
            } else {
1382
                $dateTime = substr($datetime, 0, 10);
1383
            }
1384
            $list[$datetime] = 0;
1385
        }
1386
1387
        return $list;
1388
    }
1389
1390
    /**
1391
     * Prepare the JS code to load a chart.
1392
     *
1393
     * @param string $url     URL for AJAX data generator
1394
     * @param string $type    bar, line, pie, etc
1395
     * @param string $options Additional options to the chart (see chart-specific library)
1396
     * @param string A JS code for loading the chart together with a call to AJAX data generator
1397
     */
1398
    public static function getJSChartTemplate($url, $type = 'pie', $options = '', $elementId = 'canvas')
1399
    {
1400
        $chartCode = '
1401
        <script>
1402
        $(function() {
1403
            $.ajax({
1404
                url: "'.$url.'",
1405
                type: "POST",
1406
                success: function(data) {
1407
                    Chart.defaults.global.responsive = true;
1408
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1409
                    var chart = new Chart(ctx, {
1410
                        type: "'.$type.'",
1411
                        data: data,
1412
                        options: {'.$options.'}
1413
                    });
1414
                    var title = chart.options.title.text;
1415
                    $("#'.$elementId.'_title").html(title);
1416
                    $("#'.$elementId.'_table").html(data.table);
1417
                }
1418
            });
1419
        });
1420
        </script>';
1421
1422
        return $chartCode;
1423
    }
1424
1425
    public static function getJSChartTemplateWithData($data, $type = 'pie', $options = '', $elementId = 'canvas')
1426
    {
1427
        $data = json_encode($data);
1428
        $chartCode = '
1429
        <script>
1430
            $(function() {
1431
                Chart.defaults.global.responsive = true;
1432
                var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1433
                var chart = new Chart(ctx, {
1434
                    type: "'.$type.'",
1435
                    data: '.$data.',
1436
                    options: {'.$options.'}
1437
                });
1438
            });
1439
        </script>';
1440
1441
        return $chartCode;
1442
    }
1443
1444
    public static function buildJsChartData($all, $chartName)
1445
    {
1446
        $list = [];
1447
        $palette = ChamiloApi::getColorPalette(true, true);
1448
        foreach ($all as $tick => $tock) {
1449
            $list['labels'][] = $tick;
1450
        }
1451
1452
        $list['datasets'][0]['label'] = $chartName;
1453
        $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
1454
1455
        $i = 0;
1456
        foreach ($all as $tick => $tock) {
1457
            $j = $i % count($palette);
1458
            $list['datasets'][0]['data'][] = $tock;
1459
            $list['datasets'][0]['backgroundColor'][] = $palette[$j];
1460
            $i++;
1461
        }
1462
1463
        $scoreDisplay = ScoreDisplay::instance();
1464
        $table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
1465
        $headers = [
1466
            get_lang('Name'),
1467
            get_lang('Count'),
1468
            get_lang('Percentage'),
1469
        ];
1470
        $row = 0;
1471
        $column = 0;
1472
        foreach ($headers as $header) {
1473
            $table->setHeaderContents($row, $column, $header);
1474
            $column++;
1475
        }
1476
1477
        $total = 0;
1478
        foreach ($all as $name => $value) {
1479
            $total += $value;
1480
        }
1481
        $row++;
1482
        foreach ($all as $name => $value) {
1483
            $table->setCellContents($row, 0, $name);
1484
            $table->setCellContents($row, 1, $value);
1485
            $table->setCellContents($row, 2, $scoreDisplay->display_score([$value, $total], SCORE_PERCENT));
1486
            $row++;
1487
        }
1488
        $table = Display::page_subheader2($chartName).$table->toHtml();
1489
1490
        return ['chart' => $list, 'table' => $table];
1491
    }
1492
1493
    /**
1494
     * Display learnpath results from lti provider.
1495
     *
1496
     * @return false|string
1497
     */
1498
    public static function printLtiLearningPath()
1499
    {
1500
        $pluginLtiProvider = ('true' === api_get_plugin_setting('lti_provider', 'enabled'));
1501
1502
        if (!$pluginLtiProvider) {
1503
            return false;
1504
        }
1505
1506
        $content = Display::page_header(get_lang('LearningPathLTI'));
1507
        $actions = '';
1508
        $form = new FormValidator('frm_lti_tool_lp', 'get');
1509
        $form->addDateRangePicker(
1510
            'daterange',
1511
            get_lang('DateRange'),
1512
            true,
1513
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1514
        );
1515
        $form->addHidden('report', 'lti_tool_lp');
1516
        $form->addButtonFilter(get_lang('Search'));
1517
1518
        if ($form->validate()) {
1519
            $values = $form->exportValues();
1520
1521
            $result = self::getLtiLearningPathByDate($values['daterange_start'], $values['daterange_end']);
1522
1523
            $table = new HTML_Table(['class' => 'table table-bordered data_table']);
1524
            $table->setHeaderContents(0, 0, get_lang('URL'));
1525
            $table->setHeaderContents(0, 1, get_lang('ToolLp'));
1526
            $table->setHeaderContents(0, 2, get_lang('LastName'));
1527
            $table->setHeaderContents(0, 3, get_lang('FirstName'));
1528
            $table->setHeaderContents(0, 4, get_lang('FirstAccess'));
1529
            $i = 1;
1530
            foreach ($result as $item) {
1531
                if (!empty($item['learnpaths'])) {
1532
                    foreach ($item['learnpaths'] as $lpId => $lpValues) {
1533
                        $lpName = learnpath::getLpNameById($lpId);
1534
                        if (count($lpValues['users']) > 0) {
1535
                            foreach ($lpValues['users'] as $user) {
1536
                                $table->setCellContents($i, 0, $item['issuer']);
1537
                                $table->setCellContents($i, 1, $lpName);
1538
                                $table->setCellContents($i, 2, $user['lastname']);
1539
                                $table->setCellContents($i, 3, $user['firstname']);
1540
                                $table->setCellContents($i, 4, $user['first_access']);
1541
                                $i++;
1542
                            }
1543
                        }
1544
                    }
1545
                }
1546
            }
1547
            $content = $table->toHtml();
1548
        }
1549
1550
        $content .= $form->returnForm();
1551
        if (!empty($actions)) {
1552
            $content .= Display::toolbarAction('lti_tool_lp_toolbar', [$actions]);
1553
        }
1554
1555
        return $content;
1556
    }
1557
1558
    /**
1559
     * Display the Logins By Date report and allow export its result to XLS.
1560
     */
1561
    public static function printLoginsByDate()
1562
    {
1563
        if (isset($_GET['export']) && 'xls' === $_GET['export']) {
1564
            $result = self::getLoginsByDate($_GET['start'], $_GET['end']);
1565
            $data = [[get_lang('Username'), get_lang('FirstName'), get_lang('LastName'), get_lang('TotalTime')]];
1566
1567
            foreach ($result as $i => $item) {
1568
                $data[] = [
1569
                    $item['username'],
1570
                    $item['firstname'],
1571
                    $item['lastname'],
1572
                    api_time_to_hms($item['time_count']),
1573
                ];
1574
            }
1575
1576
            Export::arrayToXls($data);
1577
            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...
1578
        }
1579
1580
        $content = Display::page_header(get_lang('LoginsByDate'));
1581
1582
        $actions = '';
1583
        $form = new FormValidator('frm_logins_by_date', 'get');
1584
        $form->addDateRangePicker(
1585
            'daterange',
1586
            get_lang('DateRange'),
1587
            true,
1588
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1589
        );
1590
        $form->addHidden('report', 'logins_by_date');
1591
        $form->addButtonFilter(get_lang('Search'));
1592
1593
        if ($form->validate()) {
1594
            $values = $form->exportValues();
1595
1596
            $result = self::getLoginsByDate($values['daterange_start'], $values['daterange_end']);
1597
1598
            if (!empty($result)) {
1599
                $actions = Display::url(
1600
                    Display::return_icon('excel.png', get_lang('ExportToXls'), [], ICON_SIZE_MEDIUM),
1601
                    api_get_self().'?'.http_build_query(
1602
                        [
1603
                            'report' => 'logins_by_date',
1604
                            'export' => 'xls',
1605
                            'start' => Security::remove_XSS($values['daterange_start']),
1606
                            'end' => Security::remove_XSS($values['daterange_end']),
1607
                        ]
1608
                    )
1609
                );
1610
            }
1611
1612
            $table = new HTML_Table(['class' => 'table table-hover table-striped data_table']);
1613
            $table->setHeaderContents(0, 0, get_lang('Username'));
1614
            $table->setHeaderContents(0, 1, get_lang('FirstName'));
1615
            $table->setHeaderContents(0, 2, get_lang('LastName'));
1616
            $table->setHeaderContents(0, 3, get_lang('TotalTime'));
1617
1618
            foreach ($result as $i => $item) {
1619
                $table->setCellContents($i + 1, 0, $item['username']);
1620
                $table->setCellContents($i + 1, 1, $item['firstname']);
1621
                $table->setCellContents($i + 1, 2, $item['lastname']);
1622
                $table->setCellContents($i + 1, 3, api_time_to_hms($item['time_count']));
1623
            }
1624
1625
            $table->setColAttributes(0, ['class' => 'text-center']);
1626
            $table->setColAttributes(3, ['class' => 'text-center']);
1627
            $content = $table->toHtml();
1628
        }
1629
1630
        $content .= $form->returnForm();
1631
1632
        if (!empty($actions)) {
1633
            $content .= Display::toolbarAction('logins_by_date_toolbar', [$actions]);
1634
        }
1635
1636
        return $content;
1637
    }
1638
1639
    public static function getBossTable($bossId): string
1640
    {
1641
        $students = UserManager::getUsersFollowedByStudentBoss($bossId);
1642
1643
        if (!empty($students)) {
1644
            $table = new HTML_Table(['class' => 'table table-responsive', 'id' => 'table_'.$bossId]);
1645
            $headers = [
1646
                get_lang('Name'),
1647
                //get_lang('LastName'),
1648
            ];
1649
            $row = 0;
1650
            $column = 0;
1651
            foreach ($headers as $header) {
1652
                $table->setHeaderContents($row, $column, $header);
1653
                $column++;
1654
            }
1655
            $row++;
1656
            foreach ($students as $student) {
1657
                $column = 0;
1658
                $content = api_get_person_name($student['firstname'], $student['lastname']).'';
1659
                $content = '<div style="width: 200px; overflow-wrap: break-word;">'.$content.'</div>';
1660
                $table->setCellContents(
1661
                    $row,
1662
                    $column++,
1663
                    $content
1664
                );
1665
                $row++;
1666
            }
1667
1668
            return $table->toHtml();
1669
        }
1670
1671
        return '<table id="table_'.$bossId.'"></table>';
1672
    }
1673
1674
    /**
1675
     * Return a list of logins by date.
1676
     *
1677
     * @param string $startDate Start date in YYYY-MM-DD format
1678
     * @param string $endDate   End date in YYYY-MM-DD format
1679
     */
1680
    public static function getLoginsByDate($startDate, $endDate): array
1681
    {
1682
        /** @var DateTime $startDate */
1683
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1684
        /** @var DateTime $endDate */
1685
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1686
1687
        if (empty($startDate) || empty($endDate)) {
1688
            return [];
1689
        }
1690
1691
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1692
        $tblLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1693
        $urlJoin = '';
1694
        $urlWhere = '';
1695
1696
        if (api_is_multiple_url_enabled()) {
1697
            $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1698
1699
            $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1700
            $urlWhere = 'AND au.access_url_id = '.api_get_current_access_url_id();
1701
        }
1702
1703
        $sql = "SELECT u.id,
1704
                    u.firstname,
1705
                    u.lastname,
1706
                    u.username,
1707
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1708
                FROM $tblUser u
1709
                INNER JOIN $tblLogin l ON u.id = l.login_user_id
1710
                $urlJoin
1711
                WHERE l.login_date BETWEEN '$startDate' AND '$endDate'
1712
                $urlWhere
1713
                GROUP BY u.id";
1714
1715
        $stmt = Database::query($sql);
1716
        $result = Database::store_result($stmt, 'ASSOC');
1717
1718
        return $result;
1719
    }
1720
1721
    /**
1722
     * Return de number of certificates generated.
1723
     * This function is resource intensive.
1724
     */
1725
    public static function countCertificatesByQuarter(string $dateFrom = null, string $dateUntil = null): int
1726
    {
1727
        $tableGradebookCertificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
1728
1729
        $condition = "";
1730
1731
        if (!empty($dateFrom) && !empty($dateUntil)) {
1732
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1733
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1734
            $condition = "WHERE (created_at BETWEEN '$dateFrom' AND '$dateUntil')";
1735
        } elseif (!empty($dateFrom)) {
1736
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1737
            $condition = "WHERE created_at >= '$dateFrom'";
1738
        } elseif (!empty($dateUntil)) {
1739
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1740
            $condition = "WHERE created_at <= '$dateUntil'";
1741
        }
1742
1743
        $sql = "
1744
            SELECT count(*) AS count
1745
            FROM $tableGradebookCertificate
1746
            $condition
1747
        ";
1748
1749
        $response = Database::query($sql);
1750
        $obj = Database::fetch_object($response);
1751
1752
        return $obj->count;
1753
    }
1754
1755
    /**
1756
     * Get the number of logins by dates.
1757
     * This function is resource intensive.
1758
     */
1759
    public static function getSessionsByDuration(string $dateFrom, string $dateUntil): array
1760
    {
1761
        $results = [
1762
            '0' => 0,
1763
            '5' => 0,
1764
            '10' => 0,
1765
            '15' => 0,
1766
            '30' => 0,
1767
            '60' => 0,
1768
        ];
1769
1770
        if (!empty($dateFrom) && !empty($dateUntil)) {
1771
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1772
            $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1773
            $urlId = api_get_current_access_url_id();
1774
            $tableUrl = '';
1775
            $whereUrl = '';
1776
1777
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1778
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1779
1780
            if (api_is_multiple_url_enabled()) {
1781
                $tableUrl = ", $accessUrlRelUserTable";
1782
                $whereUrl = " AND login_user_id = user_id AND access_url_id = $urlId";
1783
            }
1784
1785
            $sql = "SELECT login_id, TIMESTAMPDIFF(SECOND, login_date, logout_date) AS duration
1786
            FROM $table $tableUrl
1787
            WHERE login_date >= '$dateFrom'
1788
            AND logout_date <= '$dateUntil'
1789
            $whereUrl
1790
            ";
1791
1792
            $res = Database::query($sql);
1793
1794
            while ($session = Database::fetch_array($res)) {
1795
                if ($session['duration'] > 3600) {
1796
                    $results['60']++;
1797
                } elseif ($session['duration'] > 1800) {
1798
                    $results['30']++;
1799
                } elseif ($session['duration'] > 900) {
1800
                    $results['15']++;
1801
                } elseif ($session['duration'] > 600) {
1802
                    $results['10']++;
1803
                } elseif ($session['duration'] > 300) {
1804
                    $results['5']++;
1805
                } else {
1806
                    $results['0']++;
1807
                }
1808
            }
1809
        }
1810
1811
        return $results;
1812
    }
1813
1814
    /**
1815
     * Return duplicate users at a SortableTableFromArray object.
1816
     *
1817
     * @param string $type The type of duplication we are checking for ('name' or 'email')
1818
     */
1819
    public static function returnDuplicatedUsersTable(
1820
        string $type = 'name',
1821
        array $additionalExtraFieldsInfo
1822
    ): SortableTableFromArray {
1823
        if ($type == 'email') {
1824
            $usersInfo = Statistics::getDuplicatedUserMails($additionalExtraFieldsInfo);
1825
        } else {
1826
            $usersInfo = Statistics::getDuplicatedUsers($additionalExtraFieldsInfo);
1827
        }
1828
1829
        $column = 0;
1830
1831
        $table = new SortableTableFromArray($usersInfo);
1832
        $table->set_additional_parameters([
1833
            'report' => 'duplicated_users',
1834
            'additional_profile_field' => array_keys($additionalExtraFieldsInfo),
1835
        ]);
1836
        $table->set_header($column++, get_lang('Id'));
1837
1838
        if ($type == 'email') {
1839
            $table->set_header($column++, get_lang('Email'));
1840
        }
1841
        if (api_is_western_name_order()) {
1842
            $table->set_header($column++, get_lang('FirstName'));
1843
            $table->set_header($column++, get_lang('LastName'));
1844
        } else {
1845
            $table->set_header($column++, get_lang('LastName'));
1846
            $table->set_header($column++, get_lang('FirstName'));
1847
        }
1848
        if ($type == 'name') {
1849
            $table->set_header($column++, get_lang('Email'));
1850
        }
1851
1852
        $table->set_header($column++, get_lang('RegistrationDate'));
1853
        $table->set_column_filter(
1854
            $column - 1,
1855
            function ($value) {
1856
                return api_convert_and_format_date($value, DATE_TIME_FORMAT_LONG);
1857
            }
1858
        );
1859
        $table->set_header($column++, get_lang('FirstLoginInPlatform'));
1860
        $table->set_header($column++, get_lang('LatestLoginInPlatform'));
1861
        $table->set_header($column++, get_lang('Role'));
1862
        $table->set_column_filter(
1863
            $column - 1,
1864
            function ($value) {
1865
                return api_get_status_langvars()[$value];
1866
            }
1867
        );
1868
        $table->set_header(
1869
            $column++,
1870
            get_lang('Courses').' <small class="block">'.get_lang('SubscriptionCount').'<small>'
1871
        );
1872
        $table->set_header(
1873
            $column++,
1874
            get_lang('Sessions').' <small class="block">'.get_lang('SubscriptionCount').'<small>'
1875
        );
1876
1877
        foreach ($additionalExtraFieldsInfo as $fieldInfo) {
1878
            $table->set_header($column++, $fieldInfo['display_text']);
1879
        }
1880
1881
        $table->set_header($column++, get_lang('Active'));
1882
        $table->set_column_filter(
1883
            $column - 1,
1884
            function ($value) {
1885
                if ('1' == $value) {
1886
                    return get_lang('Active');
1887
                }
1888
1889
                if ('0' == $value) {
1890
                    return get_lang('Inactive');
1891
                }
1892
1893
                return get_lang('ActionNotAllowed');
1894
            }
1895
        );
1896
        $table->set_header($column, get_lang('Actions'));
1897
        $table->set_column_filter(
1898
            $column,
1899
            [UserManager::class, 'getActiveFilterForTable']
1900
        );
1901
        $table->setHideColumn(0);
1902
        $table->actionButtons = [
1903
            'export_excel' => [
1904
                'label' => get_lang('ExportAsXLS'),
1905
                'icon' => Display::return_icon('export_excel.png'),
1906
            ],
1907
            'export_csv' => [
1908
                'label' => get_lang('ExportAsCSV'),
1909
                'icon' => Display::return_icon('export_csv.png'),
1910
            ],
1911
        ];
1912
1913
        return $table;
1914
    }
1915
1916
    /**
1917
     * Exports a user report by course and session to an Excel file.
1918
     */
1919
    public static function exportUserReportByCourseSession(int $courseId, ?string $startDate = null, ?string $endDate = null): void
1920
    {
1921
        $courseInfo = api_get_course_info_by_id($courseId);
1922
        $sessions = SessionManager::get_session_by_course($courseId, $startDate, $endDate);
1923
1924
        $headers = [
1925
            get_lang('CourseName'),
1926
            get_lang('SessionName'),
1927
            get_lang('LastName'),
1928
            get_lang('FirstName'),
1929
            get_lang('UserName'),
1930
            get_lang('Email'),
1931
            get_lang('EndDate'),
1932
            get_lang('Score'),
1933
            get_lang('Progress'),
1934
        ];
1935
1936
        $extraField = new ExtraField('user');
1937
        $extraFields = $extraField->get_all(['filter = ?' => 1], 'option_order');
1938
1939
        foreach ($extraFields as $field) {
1940
            $headers[] = $field['variable'];
1941
        }
1942
1943
        $exportData = [$headers];
1944
        foreach ($sessions as $session) {
1945
            $sessionId = (int) $session['id'];
1946
            $students = SessionManager::get_users_by_session($sessionId);
1947
            $extraValueObj = new ExtraFieldValue('user');
1948
1949
            foreach ($students as $student) {
1950
                $studentId = $student['user_id'];
1951
                $studentInfo = api_get_user_info($studentId);
1952
                $courseCode = $courseInfo['code'];
1953
1954
                $lastConnection = Tracking::getLastConnectionTimeInSessionCourseLp($studentId, $courseCode, $sessionId);
1955
                $lastConnectionFormatted = $lastConnection ? date('Y-m-d', $lastConnection) : '';
1956
1957
                $averageScore = round(Tracking::getAverageStudentScore($studentId, $courseCode, [], $sessionId));
1958
                $averageProgress = round(Tracking::get_avg_student_progress($studentId, $courseCode, [], $sessionId));
1959
1960
                $userData = [
1961
                    $courseInfo['name'],
1962
                    $session['name'],
1963
                    $studentInfo['lastname'],
1964
                    $studentInfo['firstname'],
1965
                    $studentInfo['username'],
1966
                    $studentInfo['mail'],
1967
                    $lastConnectionFormatted,
1968
                    $averageScore,
1969
                    $averageProgress,
1970
                ];
1971
1972
                foreach ($extraFields as $field) {
1973
                    $extraValue = $extraValueObj->get_values_by_handler_and_field_id($studentId, $field['id'], true);
1974
                    $userData[] = $extraValue['value'] ?? '';
1975
                }
1976
1977
                $exportData[] = $userData;
1978
            }
1979
        }
1980
1981
        Export::arrayToXls($exportData, 'session_report_'.$courseInfo['code'].'_'.date('Y-m-d'));
1982
    }
1983
1984
    /**
1985
     * It gets lti learnpath results by date.
1986
     *
1987
     * @param string $startDate Start date in YYYY-MM-DD format
1988
     * @param string $endDate   End date in YYYY-MM-DD format
1989
     */
1990
    private static function getLtiLearningPathByDate(string $startDate, string $endDate): array
1991
    {
1992
        /** @var DateTime $startDate */
1993
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1994
        /** @var DateTime $endDate */
1995
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1996
1997
        if (empty($startDate) || empty($endDate)) {
1998
            return [];
1999
        }
2000
2001
        require_once api_get_path(SYS_PLUGIN_PATH).'lti_provider/LtiProviderPlugin.php';
2002
2003
        $plugin = LtiProviderPlugin::create();
2004
2005
        $result = $plugin->getToolLearnPathResult($startDate, $endDate);
2006
2007
        return $result;
2008
    }
2009
2010
    /**
2011
     * Get a list of users duplicated (firstname and lastname are both the same).
2012
     *
2013
     * @param array $additionalExtraFieldsInfo A list of extra fields we want to get in return, additional to the user details
2014
     */
2015
    private static function getDuplicatedUsers(array $additionalExtraFieldsInfo): array
2016
    {
2017
        $sql = "SELECT firstname, lastname, COUNT(*) as count
2018
            FROM user
2019
            GROUP BY firstname, lastname
2020
            HAVING count > 1
2021
            ORDER BY lastname, firstname"
2022
        ;
2023
2024
        $result = Database::query($sql);
2025
2026
        if (1 > Database::num_rows($result)) {
2027
            return [];
2028
        }
2029
2030
        $usersInfo = [];
2031
2032
        while ($rowStat = Database::fetch_assoc($result)) {
2033
            $firstname = Database::escape_string($rowStat['firstname']);
2034
            $lastname = Database::escape_string($rowStat['lastname']);
2035
            $subsql = "SELECT id, email, registration_date, status, active
2036
                FROM user WHERE firstname = '$firstname' AND lastname = '$lastname'"
2037
            ;
2038
2039
            $subResult = Database::query($subsql);
2040
2041
            if (1 > Database::num_rows($subResult)) {
2042
                continue;
2043
            }
2044
2045
            $objExtraValue = new ExtraFieldValue('user');
2046
2047
            while ($rowUser = Database::fetch_assoc($subResult)) {
2048
                $studentId = $rowUser['id'];
2049
2050
                $studentInfo = [];
2051
                $studentInfo[] = $rowUser['id'];
2052
2053
                if (api_is_western_name_order()) {
2054
                    $studentInfo[] = $rowStat['firstname'];
2055
                    $studentInfo[] = $rowStat['lastname'];
2056
                } else {
2057
                    $studentInfo[] = $rowStat['lastname'];
2058
                    $studentInfo[] = $rowStat['firstname'];
2059
                }
2060
2061
                $studentInfo[] = $rowUser['email'];
2062
                $studentInfo[] = $rowUser['registration_date'];
2063
                $studentInfo[] = Tracking::get_first_connection_date(
2064
                    $studentId,
2065
                    DATE_TIME_FORMAT_LONG
2066
                );
2067
                $studentInfo[] = Tracking::get_last_connection_date(
2068
                    $studentId,
2069
                    true,
2070
                    false,
2071
                    DATE_TIME_FORMAT_LONG
2072
                );
2073
                $studentInfo[] = $rowUser['status'];
2074
                $studentInfo[] = Tracking::count_course_per_student($studentId);
2075
                $studentInfo[] = Tracking::countSessionsPerStudent($studentId);
2076
2077
                foreach ($additionalExtraFieldsInfo as $fieldInfo) {
2078
                    $extraValue = $objExtraValue->get_values_by_handler_and_field_id($studentId, $fieldInfo['id'], true);
2079
                    $studentInfo[] = $extraValue['value'] ?? null;
2080
                }
2081
2082
                $studentInfo[] = $rowUser['active']; // once to show status
2083
                $studentInfo[] = $rowUser['active']; // twice to show actions
2084
2085
                $usersInfo[] = $studentInfo;
2086
            }
2087
        }
2088
2089
        return $usersInfo;
2090
    }
2091
2092
    /**
2093
     * Get a list of duplicated user emails.
2094
     *
2095
     * @param array $additionalExtraFieldsInfo A list of extra fields we want to get in return, additional to the user details
2096
     */
2097
    private static function getDuplicatedUserMails(array $additionalExtraFieldsInfo): array
2098
    {
2099
        $sql = "SELECT email, COUNT(*) as count
2100
            FROM user
2101
            GROUP BY email
2102
            HAVING count > 1
2103
            ORDER BY email"
2104
        ;
2105
2106
        $result = Database::query($sql);
2107
2108
        if (1 > Database::num_rows($result)) {
2109
            return [];
2110
        }
2111
2112
        $usersInfo = [];
2113
2114
        while ($rowStat = Database::fetch_assoc($result)) {
2115
            $email = Database::escape_string($rowStat['email']);
2116
            $subsql = "SELECT id, firstname, lastname, registration_date, status, active
2117
                FROM user WHERE email = '$email'"
2118
            ;
2119
2120
            $subResult = Database::query($subsql);
2121
2122
            if (1 > Database::num_rows($subResult)) {
2123
                continue;
2124
            }
2125
2126
            $objExtraValue = new ExtraFieldValue('user');
2127
2128
            while ($rowUser = Database::fetch_assoc($subResult)) {
2129
                $studentId = $rowUser['id'];
2130
2131
                $studentInfo = [];
2132
                $studentInfo[] = $rowUser['id'];
2133
2134
                $studentInfo[] = $rowStat['email'];
2135
                if (api_is_western_name_order()) {
2136
                    $studentInfo[] = $rowUser['firstname'];
2137
                    $studentInfo[] = $rowUser['lastname'];
2138
                } else {
2139
                    $studentInfo[] = $rowUser['lastname'];
2140
                    $studentInfo[] = $rowUser['firstname'];
2141
                }
2142
2143
                $studentInfo[] = $rowUser['registration_date'];
2144
                $studentInfo[] = Tracking::get_first_connection_date(
2145
                    $studentId,
2146
                    DATE_TIME_FORMAT_LONG
2147
                );
2148
                $studentInfo[] = Tracking::get_last_connection_date(
2149
                    $studentId,
2150
                    true,
2151
                    false,
2152
                    DATE_TIME_FORMAT_LONG
2153
                );
2154
                $studentInfo[] = $rowUser['status'];
2155
                $studentInfo[] = Tracking::count_course_per_student($studentId);
2156
                $studentInfo[] = Tracking::countSessionsPerStudent($studentId);
2157
2158
                foreach ($additionalExtraFieldsInfo as $fieldInfo) {
2159
                    $extraValue = $objExtraValue->get_values_by_handler_and_field_id($studentId, $fieldInfo['id'], true);
2160
                    $studentInfo[] = $extraValue['value'] ?? null;
2161
                }
2162
2163
                $studentInfo[] = $rowUser['active']; // once to show status
2164
                $studentInfo[] = $rowUser['active']; // twice to show actions
2165
2166
                $usersInfo[] = $studentInfo;
2167
            }
2168
        }
2169
2170
        return $usersInfo;
2171
    }
2172
}
2173