Passed
Push — dependabot/npm_and_yarn/vue-i1... ( 836b78...06776d )
by
unknown
11:15
created

Statistics::getJSChartTemplateWithData()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 69
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 26
nc 16
nop 8
dl 0
loc 69
rs 9.1928
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
5
use Chamilo\CoreBundle\Entity\MessageRelUser;
6
use Chamilo\CoreBundle\Entity\UserRelUser;
7
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
8
use Chamilo\CoreBundle\Framework\Container;
9
10
/**
11
 * This class provides some functions for statistics.
12
 */
13
class Statistics
14
{
15
    /**
16
     * Converts a number of bytes in a formatted string.
17
     *
18
     * @param int $size
19
     *
20
     * @return string Formatted file size or empty string if no match
21
     */
22
    public static function makeSizeString(int $size): string
23
    {
24
        if ($size < pow(2, 10)) {
25
            return $size." bytes";
26
        }
27
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
28
            return round($size / pow(2, 10), 0)." KB";
29
        }
30
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
31
            return round($size / pow(2, 20), 1)." MB";
32
        }
33
        if ($size > pow(2, 30)) {
34
            return round($size / pow(2, 30), 2)." GB";
35
        }
36
37
        return '';
38
    }
39
40
    /**
41
     * Count courses.
42
     *
43
     * @param string|null $categoryCode Code of a course category.
44
     *                                  Default: count all courses.
45
     * @param string|null $dateFrom dateFrom
46
     * @param string|null $dateUntil dateUntil
47
     *
48
     * @return int Number of courses counted
49
     * @throws \Doctrine\DBAL\Exception
50
     * @throws Exception
51
     */
52
    public static function countCourses(string $categoryCode = null, string $dateFrom = null, string $dateUntil = null): int
53
    {
54
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
55
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
56
        $accessUrlHelper = Container::getAccessUrlHelper();
57
58
        if ($accessUrlHelper->isMultiple()) {
59
            $accessUrl = $accessUrlHelper->getCurrent();
60
            $urlId = $accessUrl->getId();
61
            $sql = "SELECT COUNT(*) AS number
62
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
63
                    WHERE u.c_id = c.id AND $accessUrlRelCourseTable = $urlId";
64
            if (isset($categoryCode)) {
65
                $sql .= " AND category_code = '".Database::escape_string($categoryCode)."'";
66
            }
67
        } else {
68
            $sql = "SELECT COUNT(*) AS number
69
                    FROM $courseTable AS c
70
                    WHERE 1 = 1";
71
            if (isset($categoryCode)) {
72
                $sql .= " WHERE c.category_code = '".Database::escape_string($categoryCode)."'";
73
            }
74
        }
75
76
        if (!empty($dateFrom)) {
77
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
78
            $sql .= " AND c.creation_date >= '$dateFrom' ";
79
        }
80
        if (!empty($dateUntil)) {
81
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
82
            $sql .= " AND c.creation_date <= '$dateUntil' ";
83
        }
84
85
        $res = Database::query($sql);
86
        $obj = Database::fetch_object($res);
87
88
        return $obj->number;
89
    }
90
91
    /**
92
     * Count courses by visibility.
93
     *
94
     * @param array|null  $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
95
     * @param string|null $dateFrom dateFrom
96
     * @param string|null $dateUntil dateUntil
97
     *
98
     * @return int Number of courses counted
99
     * @throws \Doctrine\DBAL\Exception
100
     * @throws Exception
101
     */
102
    public static function countCoursesByVisibility(
103
        array $visibility = null,
104
        string $dateFrom = null,
105
        string $dateUntil = null
106
    ): int
107
    {
108
        $visibilityString = '';
109
        if (empty($visibility)) {
110
            return 0;
111
        } else {
112
            $auxArrayVisibility = [];
113
            if (!is_array($visibility)) {
114
                $visibility = [$visibility];
115
            }
116
            foreach ($visibility as $item) {
117
                $auxArrayVisibility[] = (int) $item;
118
            }
119
            $visibilityString = implode(',', $auxArrayVisibility);
120
        }
121
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
122
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
123
        $accessUrlHelper = Container::getAccessUrlHelper();
124
125
        if ($accessUrlHelper->isMultiple()) {
126
            $accessUrl = $accessUrlHelper->getCurrent();
127
            $urlId = $accessUrl->getId();
128
            $sql = "SELECT COUNT(*) AS number
129
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
130
                    WHERE u.c_id = c.id AND u.access_url_id = $urlId";
131
        } else {
132
            $sql = "SELECT COUNT(*) AS number
133
                    FROM $courseTable AS c
134
                    WHERE 1 = 1";
135
        }
136
        $sql .= " AND visibility IN ($visibilityString) ";
137
        if (!empty($dateFrom)) {
138
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
139
            $sql .= " AND c.creation_date >= '$dateFrom' ";
140
        }
141
        if (!empty($dateUntil)) {
142
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
143
            $sql .= " AND c.creation_date <= '$dateUntil' ";
144
        }
145
        $res = Database::query($sql);
146
        $obj = Database::fetch_object($res);
147
148
        return $obj->number;
149
    }
150
151
    /**
152
     * Count users.
153
     *
154
     * @param int    $status user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
155
     * @param string $categoryCode course category code. Default: count only users without filtering category
156
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
157
     * @param bool   $onlyActive Count only active users (false to only return currently active users)
158
     *
159
     * @return int Number of users counted
160
     * @throws Exception
161
     */
162
    public static function countUsers(
163
        ?int $status = null,
164
        ?string $categoryCode = null,
165
        ?bool $countInvisibleCourses = true,
166
        ?bool $onlyActive = false
167
    ): int
168
    {
169
        // Database table definitions
170
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
171
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
172
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
173
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
174
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
175
        $tblCourseRelCategory = Database::get_main_table(TABLE_MAIN_COURSE_REL_CATEGORY);
176
177
        $conditions = [];
178
        $conditions[] = "u.active <> " . USER_SOFT_DELETED;
179
        if ($onlyActive) {
180
            $conditions[] = "u.active = 1";
181
        }
182
        if (isset($status)) {
183
            $conditions[] = "u.status = " . $status;
184
        }
185
186
        $where = implode(' AND ', $conditions);
187
188
        $accessUrlHelper = Container::getAccessUrlHelper();
189
190
        if ($accessUrlHelper->isMultiple()) {
191
            $accessUrl = $accessUrlHelper->getCurrent();
192
            $urlId = $accessUrl->getId();
193
            $sql = "SELECT COUNT(DISTINCT(u.id)) AS number
194
                FROM $user_table as u
195
                INNER JOIN $access_url_rel_user_table as url ON u.id = url.user_id
196
                WHERE $where AND url.access_url_id = $urlId";
197
198
            if (isset($categoryCode)) {
199
                $categoryCode = Database::escape_string($categoryCode);
200
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
201
                    FROM $course_user_table cu
202
                    INNER JOIN $course_table c ON c.id = cu.c_id
203
                    INNER JOIN $access_url_rel_user_table as url ON cu.user_id = url.user_id
204
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
205
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
206
                    WHERE $where AND url.access_url_id = $urlId AND cc.code = '$categoryCode'";
207
            }
208
        } else {
209
            $sql = "SELECT COUNT(DISTINCT(id)) AS number
210
                FROM $user_table u
211
                WHERE $where";
212
213
            if (isset($categoryCode)) {
214
                $categoryCode = Database::escape_string($categoryCode);
215
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
216
                    FROM $course_user_table cu
217
                    INNER JOIN $course_table c ON c.id = cu.c_id
218
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
219
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
220
                    INNER JOIN $user_table u ON u.id = cu.user_id
221
                    WHERE $where AND cc.code = '$categoryCode'";
222
            }
223
        }
224
225
        $res = Database::query($sql);
226
        $obj = Database::fetch_object($res);
227
228
        return $obj->number;
229
    }
230
231
    /**
232
     * Get courses IDs from courses with some access_date between the two given dates
233
     * @param string $startDate
234
     * @param string $endDate
235
     *
236
     * @return array
237
     * @throws Exception
238
     */
239
    public static function getCoursesWithActivity(string $startDate, string $endDate): array
240
    {
241
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
242
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
243
        $startDate = Database::escape_string($startDate);
244
        $endDate = Database::escape_string($endDate);
245
246
        $accessUrlHelper = Container::getAccessUrlHelper();
247
248
        if ($accessUrlHelper->isMultiple()) {
249
            $accessUrl = $accessUrlHelper->getCurrent();
250
            $urlId = $accessUrl->getId();
251
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
252
                    WHERE
253
                        t.c_id = a.c_id AND
254
                        access_url_id = $urlId AND
255
                        access_date BETWEEN '$startDate' AND '$endDate'
256
                    ";
257
        } else {
258
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t
259
                   access_date BETWEEN '$startDate' AND '$endDate' ";
260
        }
261
262
        $result = Database::query($sql);
263
264
        return Database::store_result($result);
265
    }
266
267
    /**
268
     * Count activities from track_e_default_table.
269
     *
270
     * @return int Number of activities counted
271
     * @throws Exception
272
     */
273
    public static function getNumberOfActivities(mixed $courseId = 0, ?int $sessionId = 0): int
274
    {
275
        // Database table definitions
276
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
277
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
278
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
279
        $accessUrlHelper = Container::getAccessUrlHelper();
280
        if (is_array($courseId)) {
281
            // Usually when no param is given, we get an empty array from SortableTable
282
            $courseId = 0;
283
        }
284
285
        if ($accessUrlHelper->isMultiple()) {
286
            $accessUrl = $accessUrlHelper->getCurrent();
287
            $urlId = $accessUrl->getId();
288
            $sql = "SELECT count(default_id) AS total_number_of_items
289
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
290
                    WHERE user.active <> ".USER_SOFT_DELETED." AND
291
                        default_user_id = user.id AND
292
                        user.id = url.user_id AND
293
                        access_url_id = $urlId";
294
        } else {
295
            $sql = "SELECT count(default_id) AS total_number_of_items
296
                    FROM $track_e_default, $table_user user
297
                    WHERE user.active <> ".USER_SOFT_DELETED." AND default_user_id = user.id ";
298
        }
299
300
        if (!empty($courseId)) {
301
            $courseId = (int) $courseId;
302
            $sql .= " AND c_id = $courseId";
303
            $sql .= api_get_session_condition($sessionId);
304
        }
305
306
        if (isset($_GET['keyword'])) {
307
            $keyword = Database::escape_string(trim($_GET['keyword']));
308
            $sql .= " AND (
309
                        user.username LIKE '%".$keyword."%' OR
310
                        default_event_type LIKE '%".$keyword."%' OR
311
                        default_value_type LIKE '%".$keyword."%' OR
312
                        default_value LIKE '%".$keyword."%') ";
313
        }
314
        $res = Database::query($sql);
315
        $obj = Database::fetch_object($res);
316
317
        return $obj->total_number_of_items;
318
    }
319
320
    /**
321
     * Get activities data to display.
322
     *
323
     * @param int    $from
324
     * @param int    $numberOfItems
325
     * @param int    $column
326
     * @param string $direction
327
     * @param ?int   $courseId
328
     * @param ?int   $sessionId
329
     *
330
     * @return array
331
     * @throws Exception
332
     */
333
    public static function getActivitiesData(
334
        int $from,
335
        int $numberOfItems,
336
        int $column,
337
        string $direction,
338
        mixed $courseId = 0,
339
        ?int $sessionId = 0
340
    ): array
341
    {
342
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
343
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
344
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
345
        $direction = strtoupper($direction);
346
        if (is_array($courseId)) {
347
            // Usually when no param is given, we get an empty array from SortableTable
348
            $courseId = 0;
349
        }
350
351
        if (!in_array($direction, ['ASC', 'DESC'])) {
352
            $direction = 'DESC';
353
        }
354
355
        $accessUrlHelper = Container::getAccessUrlHelper();
356
357
        if ($accessUrlHelper->isMultiple()) {
358
            $accessUrl = $accessUrlHelper->getCurrent();
359
            $urlId = $accessUrl->getId();
360
            $sql = "SELECT
361
                        default_event_type  as col0,
362
                        default_value_type    as col1,
363
                        default_value        as col2,
364
                        c_id         as col3,
365
                        session_id as col4,
366
                        user.username         as col5,
367
                        user.id         as col6,
368
                        default_date         as col7
369
                    FROM $track_e_default as track_default,
370
                    $table_user as user,
371
                    $access_url_rel_user_table as url
372
                    WHERE
373
                        user.active <> -1 AND
374
                        track_default.default_user_id = user.id AND
375
                        url.user_id = user.id AND
376
                        access_url_id= $urlId";
377
        } else {
378
            $sql = "SELECT
379
                       default_event_type  as col0,
380
                       default_value_type    as col1,
381
                       default_value        as col2,
382
                       c_id         as col3,
383
                       session_id as col4,
384
                       user.username         as col5,
385
                       user.id         as col6,
386
                       default_date         as col7
387
                   FROM $track_e_default track_default, $table_user user
388
                   WHERE user.active <> ".USER_SOFT_DELETED." AND track_default.default_user_id = user.id ";
389
        }
390
391
        if (!empty($_GET['keyword'])) {
392
            $keyword = Database::escape_string(trim($_GET['keyword']));
393
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
394
                        default_event_type LIKE '%".$keyword."%' OR
395
                        default_value_type LIKE '%".$keyword."%' OR
396
                        default_value LIKE '%".$keyword."%') ";
397
        }
398
399
        if (!empty($courseId)) {
400
            $courseId = (int) $courseId;
401
            $sql .= " AND c_id = $courseId";
402
            $sql .= api_get_session_condition($sessionId);
403
        }
404
405
        if (!empty($column)) {
406
            $sql .= " ORDER BY col$column $direction";
407
        } else {
408
            $sql .= " ORDER BY col7 DESC ";
409
        }
410
        $sql .= " LIMIT $from, $numberOfItems ";
411
412
        $res = Database::query($sql);
413
        $activities = [];
414
        while ($row = Database::fetch_row($res)) {
415
            if (false === strpos($row[1], '_object') &&
416
                false === strpos($row[1], '_array')
417
            ) {
418
                $row[2] = $row[2];
419
            } else {
420
                if (!empty($row[2])) {
421
                    $originalData = str_replace('\\', '', $row[2]);
422
                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
423
                    if (is_array($row[2]) && !empty($row[2])) {
424
                        $row[2] = implode_with_key(', ', $row[2]);
425
                    } else {
426
                        $row[2] = $originalData;
427
                    }
428
                }
429
            }
430
431
            if (!empty($row['default_date'])) {
432
                $row['default_date'] = api_get_local_time($row['default_date']);
433
            } else {
434
                $row['default_date'] = '-';
435
            }
436
437
            if (!empty($row[7])) {
438
                $row[7] = api_get_local_time($row[7]);
439
            } else {
440
                $row[7] = '-';
441
            }
442
443
            if (!empty($row[5])) {
444
                // Course
445
                if (!empty($row[3])) {
446
                    $row[3] = Display::url(
447
                        $row[3],
448
                        api_get_path(WEB_CODE_PATH).'admin/course_edit.php?id='.$row[3]
449
                    );
450
                } else {
451
                    $row[3] = '-';
452
                }
453
454
                // session
455
                if (!empty($row[4])) {
456
                    $row[4] = Display::url(
457
                        $row[4],
458
                        api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$row[4]
459
                    );
460
                } else {
461
                    $row[4] = '-';
462
                }
463
464
                // User id.
465
                $row[5] = Display::url(
466
                    $row[5],
467
                    api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$row[6],
468
                    ['class' => 'ajax']
469
                );
470
471
                $row[6] = Tracking::get_ip_from_user_event(
472
                    $row[6],
473
                    $row[7],
474
                    true
475
                );
476
                if (empty($row[6])) {
477
                    $row[6] = get_lang('Unknown');
478
                }
479
            }
480
            $activities[] = $row;
481
        }
482
483
        return $activities;
484
    }
485
486
    /**
487
     * Show statistics.
488
     *
489
     * @param string $title      The title
490
     * @param array  $stats
491
     * @param ?bool   $showTotal
492
     * @param ?bool   $isFileSize
493
     *
494
     * @return string HTML table
495
     */
496
    public static function printStats(
497
        string $title,
498
        array $stats,
499
        ?bool $showTotal = true,
500
        ?bool $isFileSize = false
501
    ): string
502
    {
503
        $total = 0;
504
        $content = '<table class="table table-hover table-striped data_table stats_table" cellspacing="0" cellpadding="3" width="90%">
505
            <thead><tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr></thead><tbody>';
506
        $i = 0;
507
        foreach ($stats as $subtitle => $number) {
508
            $total += $number;
509
        }
510
511
        foreach ($stats as $subtitle => $number) {
512
            if (!$isFileSize) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isFileSize of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
513
                $number_label = number_format($number, 0, ',', '.');
514
            } else {
515
                $number_label = self::makeSizeString($number);
516
            }
517
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
518
519
            $content .= '<tr class="row_'.(0 == $i % 2 ? 'odd' : 'even').'">
520
                    <td width="25%" style="vertical-align:top;">'.$subtitle.'</td>
521
                    <td width="60%">'.Display::bar_progress($percentage, false).'</td>
522
                    <td width="5%" align="right" style="vertical-align:top;">'.$number_label.'</td>';
523
            if ($showTotal) {
524
                $content .= '<td width="5%" align="right"> '.$percentage.'%</td>';
525
            }
526
            $content .= '</tr>';
527
            $i++;
528
        }
529
        $content .= '</tbody>';
530
        if ($showTotal) {
531
            if (!$isFileSize) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $isFileSize of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
532
                $total_label = number_format($total, 0, ',', '.');
533
            } else {
534
                $total_label = self::makeSizeString($total);
535
            }
536
            $content .= '
537
                <tfoot><tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr></tfoot>
538
            ';
539
        }
540
        $content .= '</table>';
541
542
        return $content;
543
    }
544
545
    /**
546
     * Show some stats about the number of logins.
547
     *
548
     * @param string $type month, hour or day
549
     * @return string HTML block
550
     * @throws Exception
551
     */
552
    public static function printLoginStats(string $type): string
553
    {
554
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
555
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
556
        $table_url = null;
557
        $where_url = null;
558
        $now = api_get_utc_datetime();
559
        $where_url_last = ' WHERE login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
560
        $accessUrlHelper = Container::getAccessUrlHelper();
561
562
        if ($accessUrlHelper->isMultiple()) {
563
            $accessUrl = $accessUrlHelper->getCurrent();
564
            $urlId = $accessUrl->getId();
565
            $table_url = ", $access_url_rel_user_table";
566
            $where_url = " WHERE login_user_id=user_id AND access_url_id = $urlId";
567
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
568
        }
569
570
        $period = get_lang('Month');
571
        $periodCollection = api_get_months_long();
572
        $sql = "SELECT
573
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
574
                count( login_id ) AS number_of_logins
575
                FROM $table $table_url $where_url
576
                GROUP BY stat_date
577
                ORDER BY login_date DESC";
578
        $sql_last_x = null;
579
580
        switch ($type) {
581
            case 'hour':
582
                $period = get_lang('Hour');
583
                $sql = "SELECT
584
                          DATE_FORMAT( login_date, '%H') AS stat_date,
585
                          count( login_id ) AS number_of_logins
586
                        FROM $table $table_url $where_url
587
                        GROUP BY stat_date
588
                        ORDER BY stat_date ";
589
                $sql_last_x = "SELECT
590
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
591
                                count( login_id ) AS number_of_logins
592
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
593
                               GROUP BY stat_date
594
                               ORDER BY stat_date ";
595
                break;
596
            case 'day':
597
                $periodCollection = api_get_week_days_long();
598
                $period = get_lang('Day');
599
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date ,
600
                        count( login_id ) AS number_of_logins
601
                        FROM  $table $table_url $where_url
602
                        GROUP BY stat_date
603
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
604
                $sql_last_x = "SELECT
605
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
606
                                count( login_id ) AS number_of_logins
607
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
608
                               GROUP BY stat_date
609
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
610
                break;
611
        }
612
613
        $content = '';
614
        if ($sql_last_x) {
615
            $res_last_x = Database::query($sql_last_x);
616
            $result_last_x = [];
617
            while ($obj = Database::fetch_object($res_last_x)) {
618
                $stat_date = ('day' === $type) ? $periodCollection[$obj->stat_date] : $obj->stat_date;
619
                $result_last_x[$stat_date] = $obj->number_of_logins;
620
            }
621
            $content .= self::printStats(get_lang('Last logins').' ('.$period.')', $result_last_x, true);
622
            flush(); //flush web request at this point to see something already while the full data set is loading
623
            $content .= '<br />';
624
        }
625
        $res = Database::query($sql);
626
        $result = [];
627
        while ($obj = Database::fetch_object($res)) {
628
            $stat_date = $obj->stat_date;
629
            switch ($type) {
630
                case 'month':
631
                    $stat_date = explode('-', $stat_date);
632
                    $stat_date[1] = $periodCollection[(int) $stat_date[1] - 1];
633
                    $stat_date = implode(' ', $stat_date);
634
                    break;
635
                case 'day':
636
                    $stat_date = $periodCollection[$stat_date];
637
                    break;
638
            }
639
            $result[$stat_date] = $obj->number_of_logins;
640
        }
641
        $content .= self::printStats(get_lang('All logins').' ('.$period.')', $result, true);
642
643
        return $content;
644
    }
645
646
    /**
647
     * Print the number of recent logins.
648
     *
649
     * @param ?bool  $distinct        whether to only give distinct users stats, or *all* logins
650
     * @param ?int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
651
     * @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)
652
     *
653
     * @throws Exception
654
     *
655
     * @return string HTML table
656
     */
657
    public static function printRecentLoginStats(?bool $distinct = false, ?int $sessionDuration = 0, ?array $periods = []): string
658
    {
659
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
660
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
661
        $table_url = '';
662
        $where_url = '';
663
        $accessUrlHelper = Container::getAccessUrlHelper();
664
665
        if ($accessUrlHelper->isMultiple()) {
666
            $accessUrl = $accessUrlHelper->getCurrent();
667
            $urlId = $accessUrl->getId();
668
            $table_url = ", $access_url_rel_user_table";
669
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
670
        }
671
672
        $now = api_get_utc_datetime();
673
        $field = 'login_id';
674
        if ($distinct) {
675
            $field = 'DISTINCT(login_user_id)';
676
        }
677
678
        if (empty($periods)) {
679
            $periods = [1, 7, 31];
680
        }
681
        $sqlList = [];
682
683
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
684
        foreach ($periods as $day) {
685
            $date = new DateTime($now);
686
            $startDate = $date->format('Y-m-d').' 00:00:00';
687
            $endDate = $date->format('Y-m-d').' 23:59:59';
688
689
            if ($day > 1) {
690
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
691
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
692
            }
693
694
            $localDate = api_get_local_time($startDate, null, null, false, false);
695
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
696
697
            $label = sprintf(get_lang('Last %s days'), $day);
698
            if (1 == $day) {
699
                $label = get_lang('Today');
700
            }
701
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
702
            $sql = "SELECT count($field) AS number
703
                    FROM $table $table_url
704
                    WHERE ";
705
            if (0 == $sessionDuration) {
706
                $sql .= " logout_date != login_date AND ";
707
            } else {
708
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
709
            }
710
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
711
                        $where_url";
712
            $sqlList[$label] = $sql;
713
        }
714
715
        $sql = "SELECT count($field) AS number
716
                FROM $table $table_url ";
717
        if (0 == $sessionDuration) {
718
            $sql .= " WHERE logout_date != login_date $where_url";
719
        } else {
720
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
721
        }
722
        $sqlList[get_lang('Total')] = $sql;
723
        $totalLogin = [];
724
        foreach ($sqlList as $label => $query) {
725
            $res = Database::query($query);
726
            $obj = Database::fetch_object($res);
727
            $totalLogin[$label] = $obj->number;
728
        }
729
730
        if ($distinct) {
731
            $content = self::printStats(get_lang('Distinct users logins'), $totalLogin, false);
732
        } else {
733
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
734
        }
735
736
        return $content;
737
    }
738
739
    /**
740
     * Get the number of recent logins.
741
     *
742
     * @param ?bool $distinct            Whether to only give distinct users stats, or *all* logins
743
     * @param ?int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
744
     * @param ?bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
745
     *
746
     * @throws Exception
747
     *
748
     * @return array
749
     */
750
    public static function getRecentLoginStats(?bool $distinct = false, ?int $sessionDuration = 0, ?bool $completeMissingDays = true): array
751
    {
752
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
753
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
754
        $table_url = '';
755
        $where_url = '';
756
        $accessUrlHelper = Container::getAccessUrlHelper();
757
758
        if ($accessUrlHelper->isMultiple()) {
759
            $accessUrl = $accessUrlHelper->getCurrent();
760
            $urlId = $accessUrl->getId();
761
            $table_url = ", $access_url_rel_user_table";
762
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
763
        }
764
765
        $now = api_get_utc_datetime();
766
        $date = new DateTime($now);
767
        $date->sub(new DateInterval('P31D'));
768
        $newDate = $date->format('Y-m-d h:i:s');
769
        $totalLogin = self::buildDatesArray($newDate, $now, true);
770
771
        $field = 'login_id';
772
        if ($distinct) {
773
            $field = 'DISTINCT(login_user_id)';
774
        }
775
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
776
777
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
778
                FROM $table $table_url
779
                WHERE ";
780
        if (0 == $sessionDuration) {
781
            $sql .= " logout_date != login_date AND ";
782
        } else {
783
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
784
        }
785
        $sql .= " login_date >= '$newDate' $where_url
786
                GROUP BY date(login_date)";
787
788
        $res = Database::query($sql);
789
        while ($row = Database::fetch_assoc($res)) {
790
            $monthAndDay = substr($row['login_date'], 5, 5);
791
            $totalLogin[$monthAndDay] = $row['number'];
792
        }
793
794
        return $totalLogin;
795
    }
796
797
    /**
798
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
799
     * @throws Exception
800
     */
801
    public static function getToolsStats(): array
802
    {
803
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
804
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
805
806
        $tools = [
807
            'announcement',
808
            'assignment',
809
            'calendar_event',
810
            'chat',
811
            'course_description',
812
            'document',
813
            'dropbox',
814
            'group',
815
            'learnpath',
816
            'link',
817
            'quiz',
818
            'student_publication',
819
            'user',
820
            'forum',
821
        ];
822
        $tool_names = [];
823
        foreach ($tools as $tool) {
824
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
825
        }
826
        $accessUrlHelper = Container::getAccessUrlHelper();
827
828
        if ($accessUrlHelper->isMultiple()) {
829
            $accessUrl = $accessUrlHelper->getCurrent();
830
            $urlId = $accessUrl->getId();
831
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
832
                    FROM $table t , $access_url_rel_course_table a
833
                    WHERE
834
                        access_tool IN ('".implode("','", $tools)."') AND
835
                        t.c_id = a.c_id AND
836
                        access_url_id = $urlId
837
                        GROUP BY access_tool
838
                    ";
839
        } else {
840
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
841
                    FROM $table
842
                    WHERE access_tool IN ('".implode("','", $tools)."')
843
                    GROUP BY access_tool ";
844
        }
845
846
        $res = Database::query($sql);
847
        $result = [];
848
        while ($obj = Database::fetch_object($res)) {
849
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
850
        }
851
852
        return $result;
853
    }
854
855
    /**
856
     * Show some stats about the accesses to the different course tools.
857
     *
858
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
859
     *
860
     * @return string HTML table
861
     * @throws Exception
862
     */
863
    public static function printToolStats($result = null): string
864
    {
865
        if (empty($result)) {
866
            $result = self::getToolsStats();
867
        }
868
869
        return self::printStats(get_lang('Tools access'), $result, true);
870
    }
871
872
    /**
873
     * Returns some stats about the number of courses per language.
874
     * @throws Exception
875
     */
876
    public static function printCourseByLanguageStats(): array
877
    {
878
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
879
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
880
        $accessUrlHelper = Container::getAccessUrlHelper();
881
882
        if ($accessUrlHelper->isMultiple()) {
883
            $accessUrl = $accessUrlHelper->getCurrent();
884
            $urlId = $accessUrl->getId();
885
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
886
                    FROM $table as c, $access_url_rel_course_table as u
887
                    WHERE u.c_id = c.id AND access_url_id = $urlId
888
                    GROUP BY course_language
889
                    ORDER BY number_of_courses DESC";
890
        } else {
891
            $sql = "SELECT course_language, count( code ) AS number_of_courses
892
                   FROM $table GROUP BY course_language
893
                   ORDER BY number_of_courses DESC";
894
        }
895
        $res = Database::query($sql);
896
        $result = [];
897
        while ($obj = Database::fetch_object($res)) {
898
            $result[$obj->course_language] = $obj->number_of_courses;
899
        }
900
901
        return $result;
902
    }
903
904
    /**
905
     * Shows the number of users having their picture uploaded in Dokeos.
906
     * @throws Exception
907
     */
908
    public static function printUserPicturesStats(): string
909
    {
910
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
911
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
912
        $url_condition = null;
913
        $url_condition2 = null;
914
        $table = null;
915
        $accessUrlHelper = Container::getAccessUrlHelper();
916
917
        if ($accessUrlHelper->isMultiple()) {
918
            $accessUrl = $accessUrlHelper->getCurrent();
919
            $urlId = $accessUrl->getId();
920
            $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.id AND access_url_id='".$urlId."'";
921
            $url_condition2 = " AND url.user_id=u.id AND access_url_id = $urlId";
922
            $table = ", $access_url_rel_user_table as url ";
923
        }
924
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
925
        $res = Database::query($sql);
926
        $count1 = Database::fetch_object($res);
927
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table
928
               WHERE LENGTH(picture_uri) > 0 $url_condition2";
929
930
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
931
932
        $res = Database::query($sql);
933
        $count2 = Database::fetch_object($res);
934
        // #users without picture
935
        $result[get_lang('No')] = $count1->n - $count2->n;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
936
        $result[get_lang('Yes')] = $count2->n; // #users with picture
937
938
        return self::printStats(get_lang('Number of users').' ('.get_lang('Picture').')', $result, true);
939
    }
940
941
    /**
942
     * Print important activities report page
943
     */
944
    public static function printActivitiesStats(): string
945
    {
946
        $content = '<h4>'.get_lang('Important activities').'</h4>';
947
        // Create a search-box
948
        $form = new FormValidator(
949
            'search_simple',
950
            'get',
951
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
952
            '',
953
            ['style' => 'width:200px']
954
        );
955
        $renderer = &$form->defaultRenderer();
956
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
957
        $form->addHidden('report', 'activities');
958
        $form->addHidden('activities_direction', 'DESC');
959
        $form->addHidden('activities_column', '4');
960
        $form->addElement('text', 'keyword', get_lang('Keyword'));
961
        $form->addButtonSearch(get_lang('Search'), 'submit');
962
        $content .= '<div class="actions">';
963
        $content .= $form->returnForm();
964
        $content .= '</div>';
965
966
        if (!empty($_GET['keyword'])) {
967
            $table = new SortableTable(
968
                'activities',
969
                ['Statistics', 'getNumberOfActivities'],
970
                ['Statistics', 'getActivitiesData'],
971
                7,
972
                50,
973
                'DESC'
974
            );
975
            $parameters = [];
976
977
            $parameters['report'] = 'activities';
978
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
979
980
            $table->set_additional_parameters($parameters);
981
            $table->set_header(0, get_lang('Event type'));
982
            $table->set_header(1, get_lang('Data type'));
983
            $table->set_header(2, get_lang('Value'));
984
            $table->set_header(3, get_lang('Course'));
985
            $table->set_header(4, get_lang('Session'));
986
            $table->set_header(5, get_lang('Username'));
987
            $table->set_header(6, get_lang('IP address'));
988
            $table->set_header(7, get_lang('Date'));
989
            $content .= $table->return_table();
990
        }
991
992
        $content .= '<div class="alert alert-info">'.get_lang('Important activities').' : '.'<br>';
993
        $prefix = 'LOG_';
994
        $userDefinedConstants = get_defined_constants(true)['user'];
995
        $filteredConstants = array_filter($userDefinedConstants, function ($constantName) use ($prefix) {
996
            return strpos($constantName, $prefix) === 0;
997
        }, ARRAY_FILTER_USE_KEY);
998
        $constantNames = array_keys($filteredConstants);
999
        $link = api_get_self().'?report=activities&activities_direction=DESC&activities_column=7&keyword=';
1000
        foreach ($constantNames as $constantName) {
1001
            if ($constantName != 'LOG_WS') {
1002
                if (substr($constantName, -3) == '_ID') {
1003
                    continue;
1004
                }
1005
                $content .= '- <a href="'.$link.constant($constantName).'">'.constant($constantName).'</a><br>'.PHP_EOL;
1006
            } else {
1007
                $constantValue = constant($constantName);
1008
                $reflection = new ReflectionClass('Rest');
1009
                $constants = $reflection->getConstants();
1010
                foreach ($constants as $name => $value) {
1011
                    $content .= '- <a href="'.$link.$constantValue.$value.'">'.$constantValue.$value.'</a><br>'.PHP_EOL;
1012
                }
1013
            }
1014
        }
1015
        $content .= '</div>';
1016
1017
        return $content;
1018
    }
1019
1020
    /**
1021
     * Shows statistics about the time of last visit to each course.
1022
     * @throws Exception
1023
     */
1024
    public static function printCourseLastVisit(): string
1025
    {
1026
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
1027
        $columns[0] = 'c_id';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$columns was never initialized. Although not strictly required by PHP, it is generally a good practice to add $columns = array(); before regardless.
Loading history...
1028
        $columns[1] = 'access_date';
1029
        $sql_order[SORT_ASC] = 'ASC';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sql_order was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql_order = array(); before regardless.
Loading history...
1030
        $sql_order[SORT_DESC] = 'DESC';
1031
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
1032
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
1033
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
1034
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
1035
1036
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
1037
            $direction = SORT_ASC;
1038
        }
1039
        $form = new FormValidator('courselastvisit', 'get');
1040
        $form->addElement('hidden', 'report', 'courselastvisit');
1041
        $form->addText('date_diff', get_lang('days'), true);
1042
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
1043
        $form->addButtonSearch(get_lang('Search'), 'submit');
1044
        if (!isset($_GET['date_diff'])) {
1045
            $defaults['date_diff'] = 60;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$defaults was never initialized. Although not strictly required by PHP, it is generally a good practice to add $defaults = array(); before regardless.
Loading history...
1046
        } else {
1047
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
1048
        }
1049
        $form->setDefaults($defaults);
1050
        $content = $form->returnForm();
1051
1052
        $values = $form->exportValues();
1053
        $date_diff = $values['date_diff'];
1054
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
1055
        $accessUrlHelper = Container::getAccessUrlHelper();
1056
1057
        if ($accessUrlHelper->isMultiple()) {
1058
            $accessUrl = $accessUrlHelper->getCurrent();
1059
            $urlId = $accessUrl->getId();
1060
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
1061
                   WHERE
1062
                        c_id = a.c_id AND
1063
                        access_url_id = $urlId
1064
                   GROUP BY c_id
1065
                   HAVING c_id <> ''
1066
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1067
        } else {
1068
            $sql = "SELECT * FROM $table t
1069
                   GROUP BY c_id
1070
                   HAVING c_id <> ''
1071
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1072
        }
1073
        $sql .= ' ORDER BY `'.$columns[$column].'` '.$sql_order[$direction];
1074
        $from = ($page_nr - 1) * $per_page;
1075
        $sql .= ' LIMIT '.$from.','.$per_page;
1076
1077
        $content .= '<p>'.get_lang('Latest access').' &gt;= '.$date_diff.' '.get_lang('days').'</p>';
1078
        $res = Database::query($sql);
1079
        if (Database::num_rows($res) > 0) {
1080
            $courses = [];
1081
            while ($obj = Database::fetch_object($res)) {
1082
                $courseInfo = api_get_course_info_by_id($obj->c_id);
1083
                $course = [];
1084
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
1085
                // Allow sort by date hiding the numerical date
1086
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
1087
                $courses[] = $course;
1088
            }
1089
            $parameters['date_diff'] = $date_diff;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
1090
            $parameters['report'] = 'courselastvisit';
1091
            $table_header[] = [get_lang("Course code"), true];
0 ignored issues
show
Comprehensibility Best Practice introduced by
$table_header was never initialized. Although not strictly required by PHP, it is generally a good practice to add $table_header = array(); before regardless.
Loading history...
1092
            $table_header[] = [get_lang("Latest access"), true];
1093
1094
            ob_start();
1095
            Display:: display_sortable_table(
1096
                $table_header,
1097
                $courses,
1098
                ['column' => $column, 'direction' => $direction],
1099
                [],
1100
                $parameters
1101
            );
1102
            $content .= ob_get_contents();
1103
            ob_end_clean();
1104
        } else {
1105
            $content = get_lang('No search results');
1106
        }
1107
1108
        return $content;
1109
    }
1110
1111
    /**
1112
     * Displays the statistics of the messages sent and received by each user in the social network.
1113
     *
1114
     * @param string $messageType Type of message: 'sent' or 'received'
1115
     *
1116
     * @return array Message list
1117
     */
1118
    public static function getMessages(string $messageType): array
1119
    {
1120
        $messageTable = Database::get_main_table(TABLE_MESSAGE);
1121
        $messageRelUserTable = Database::get_main_table(TABLE_MESSAGE_REL_USER);
1122
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1123
        $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1124
1125
        switch ($messageType) {
1126
            case 'sent':
1127
                $field = 'm.user_sender_id';
1128
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_SENDER;
1129
                break;
1130
            case 'received':
1131
                $field = 'mru.user_id';
1132
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_TO;
1133
                break;
1134
        }
1135
1136
        $accessUrlHelper = Container::getAccessUrlHelper();
1137
1138
        if ($accessUrlHelper->isMultiple()) {
1139
            $accessUrl = $accessUrlHelper->getCurrent();
1140
            $urlId = $accessUrl->getId();
1141
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1142
            FROM $messageTable m
1143
            INNER JOIN $messageRelUserTable mru ON $joinCondition
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $joinCondition does not seem to be defined for all execution paths leading up to this point.
Loading history...
1144
            INNER JOIN $userTable u ON $field = u.id
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field does not seem to be defined for all execution paths leading up to this point.
Loading history...
1145
            INNER JOIN $accessUrlRelUserTable url ON u.id = url.user_id
1146
            WHERE url.access_url_id = $urlId
1147
            AND u.active <> " . USER_SOFT_DELETED . "
1148
            GROUP BY $field
1149
            ORDER BY count_message DESC";
1150
        } else {
1151
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1152
            FROM $messageTable m
1153
            INNER JOIN $messageRelUserTable mru ON $joinCondition
1154
            INNER JOIN $userTable u ON $field = u.id
1155
            WHERE u.active <> " . USER_SOFT_DELETED . "
1156
            GROUP BY $field
1157
            ORDER BY count_message DESC";
1158
        }
1159
        $res = Database::query($sql);
1160
        $messages_sent = [];
1161
        while ($messages = Database::fetch_array($res)) {
1162
            if (empty($messages['username'])) {
1163
                $messages['username'] = get_lang('Unknown');
1164
            }
1165
            $users = api_get_person_name(
1166
                    $messages['firstname'],
1167
                    $messages['lastname']
1168
                ) . '<br />(' . $messages['username'] . ')';
1169
            $messages_sent[$users] = $messages['count_message'];
1170
        }
1171
1172
        return $messages_sent;
1173
    }
1174
1175
    /**
1176
     * Count the number of friends for each social network users.
1177
     * @throws Exception
1178
     */
1179
    public static function getFriends(): array
1180
    {
1181
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
1182
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1183
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1184
1185
        $accessUrlHelper = Container::getAccessUrlHelper();
1186
1187
        if ($accessUrlHelper->isMultiple()) {
1188
            $accessUrl = $accessUrlHelper->getCurrent();
1189
            $urlId = $accessUrl->getId();
1190
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1191
                    FROM $access_url_rel_user_table as url, $user_friend_table uf
1192
                    LEFT JOIN $user_table u
1193
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1194
                    WHERE
1195
                        uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."' AND
1196
                        uf.user_id = url.user_id AND
1197
                        access_url_id = $urlId
1198
                    GROUP BY uf.user_id
1199
                    ORDER BY count_friend DESC ";
1200
        } else {
1201
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1202
                    FROM $user_friend_table uf
1203
                    LEFT JOIN $user_table u
1204
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1205
                    WHERE uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."'
1206
                    GROUP BY uf.user_id
1207
                    ORDER BY count_friend DESC ";
1208
        }
1209
        $res = Database::query($sql);
1210
        $list_friends = [];
1211
        while ($friends = Database::fetch_array($res)) {
1212
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1213
            $list_friends[$users] = $friends['count_friend'];
1214
        }
1215
1216
        return $list_friends;
1217
    }
1218
1219
    /**
1220
     * Returns the number of users that didn't log in for a certain period of time.
1221
     * @throws Exception
1222
     */
1223
    public static function printUsersNotLoggedInStats(): string
1224
    {
1225
        $totalLogin = [];
1226
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1227
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1228
        $total = self::countUsers();
1229
        $accessUrlHelper = Container::getAccessUrlHelper();
1230
1231
        if ($accessUrlHelper->isMultiple()) {
1232
            $accessUrl = $accessUrlHelper->getCurrent();
1233
            $urlId = $accessUrl->getId();
1234
            $table_url = ", $access_url_rel_user_table";
1235
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
1236
        } else {
1237
            $table_url = '';
1238
            $where_url = '';
1239
        }
1240
        $now = api_get_utc_datetime();
1241
        $sql[get_lang('This day')] =
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.
Loading history...
1242
            "SELECT count(distinct(login_user_id)) AS number ".
1243
            " FROM $table $table_url ".
1244
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1245
        $sql[get_lang('In the last 7 days')] =
1246
            "SELECT count(distinct(login_user_id)) AS number ".
1247
            " FROM $table $table_url ".
1248
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1249
        $sql[get_lang('In the last 31 days')] =
1250
            "SELECT count(distinct(login_user_id)) AS number ".
1251
            " FROM $table $table_url ".
1252
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1253
        $sql[sprintf(get_lang('Last %d months'), 6)] =
1254
            "SELECT count(distinct(login_user_id)) AS number ".
1255
            " FROM $table $table_url ".
1256
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1257
        $sql[get_lang('Never connected')] =
1258
            "SELECT count(distinct(login_user_id)) AS number ".
1259
            " FROM $table $table_url WHERE 1=1 $where_url";
1260
        foreach ($sql as $index => $query) {
1261
            $res = Database::query($query);
1262
            $obj = Database::fetch_object($res);
1263
            $r = $total - $obj->number;
1264
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1265
        }
1266
1267
        return self::printStats(
1268
            get_lang('Not logged in for some time'),
1269
            $totalLogin,
1270
            false
1271
        );
1272
    }
1273
1274
    /**
1275
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1276
     * within the provided range (including limits). Dates are assumed to be
1277
     * given in UTC.
1278
     *
1279
     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
1280
     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
1281
     * @param ?bool   $removeYear Whether to remove the year in the results (for easier reading)
1282
     *
1283
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1284
     */
1285
    public static function buildDatesArray(string $startDate, string $endDate, ?bool $removeYear = false): mixed
1286
    {
1287
        if (strlen($startDate) > 10) {
1288
            $startDate = substr($startDate, 0, 10);
1289
        }
1290
        if (strlen($endDate) > 10) {
1291
            $endDate = substr($endDate, 0, 10);
1292
        }
1293
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1294
            return false;
1295
        }
1296
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1297
            return false;
1298
        }
1299
        $startTimestamp = strtotime($startDate);
1300
        $endTimestamp = strtotime($endDate);
1301
        $list = [];
1302
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1303
            $datetime = api_get_utc_datetime($time);
1304
            if ($removeYear) {
1305
                $datetime = substr($datetime, 5, 5);
1306
            } else {
1307
                $dateTime = substr($datetime, 0, 10);
1308
            }
1309
            $list[$datetime] = 0;
1310
        }
1311
1312
        return $list;
1313
    }
1314
1315
    /**
1316
     * Prepare the JS code to load a chart.
1317
     *
1318
     * @param string $url     URL for AJAX data generator
1319
     * @param ?string $type    bar, line, pie, etc (defaults to 'pie')
1320
     * @param ?string $options Additional options to the chart (see chart-specific library)
1321
     * @param ?string A JS code for loading the chart together with a call to AJAX data generator
1322
     */
1323
    public static function getJSChartTemplate(string $url, ?string $type = 'pie', ?string $options = '', ?string $elementId = 'canvas')
1324
    {
1325
        return '
1326
        <script>
1327
        $(function() {
1328
            $.ajax({
1329
                url: "'.$url.'",
1330
                type: "POST",
1331
                success: function(data) {
1332
                    Chart.defaults.responsive = false;
1333
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1334
                    ctx.canvas.width = 420;
1335
                    ctx.canvas.height = 420;
1336
                    var chart = new Chart(ctx, {
1337
                        type: "'.$type.'",
1338
                        data: data,
1339
                        options: {
1340
                            plugins: {
1341
                                '.$options.'
1342
                            },
1343
                            cutout: "25%"
1344
                        }
1345
                    });
1346
                    var title = chart.options.plugins.title.text;
1347
                    $("#'.$elementId.'_title").html(title);
1348
                    $("#'.$elementId.'_table").html(data.table);
1349
                }
1350
            });
1351
        });
1352
        </script>';
1353
1354
    }
1355
1356
    /**
1357
     * Return template for a JS chart
1358
     * @param $data
1359
     * @param $type
1360
     * @param $options
1361
     * @param $elementId
1362
     * @param $responsive
1363
     * @param $onClickHandler
1364
     * @param $extraButtonHandler
1365
     * @param $canvasDimensions
1366
     * @return string
1367
     */
1368
    public static function getJSChartTemplateWithData(
1369
        $data,
1370
        ?string $type = 'pie',
1371
        ?string $options = '',
1372
        ?string $elementId = 'canvas',
1373
        ?bool $responsive = true,
1374
        ?string $onClickHandler = '',
1375
        ?string $extraButtonHandler = '',
1376
        ?array $canvasDimensions = ['width' => 420, 'height' => 420]
1377
    ): string {
1378
        $data = json_encode($data);
1379
        $responsiveValue = $responsive ? 'true' : 'false';
1380
1381
        $indexAxisOption = '';
1382
        if ($type === 'bar') {
1383
            $indexAxisOption = 'indexAxis: "y",';
1384
        }
1385
1386
        $onClickScript = '';
1387
        if (!empty($onClickHandler)) {
1388
            $onClickScript = '
1389
                onClick: function(evt) {
1390
                    '.$onClickHandler.'
1391
                },
1392
            ';
1393
        }
1394
1395
        $canvasSize = '';
1396
        if ($responsiveValue === 'false') {
1397
            $canvasSize = '
1398
            ctx.canvas.width = '.$canvasDimensions['width'].';
1399
            ctx.canvas.height = '.$canvasDimensions['height'].';
1400
            ';
1401
        }
1402
1403
        return '
1404
        <script>
1405
            $(function() {
1406
                Chart.defaults.responsive = '.$responsiveValue.';
1407
                var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1408
                '.$canvasSize.'
1409
                var chart = new Chart(ctx, {
1410
                    type: "'.$type.'",
1411
                    data: '.$data.',
1412
                    options: {
1413
                        plugins: {
1414
                            '.$options.',
1415
                            datalabels: {
1416
                                anchor: "end",
1417
                                align: "left",
1418
                                formatter: function(value) {
1419
                                    return value;
1420
                                },
1421
                                color: "#000"
1422
                            },
1423
                        },
1424
                        '.$indexAxisOption.'
1425
                        scales: {
1426
                            x: { beginAtZero: true },
1427
                            y: { barPercentage: 0.5 }
1428
                        },
1429
                        '.$onClickScript.'
1430
                    }
1431
                });
1432
                var title = chart.options.plugins.title.text;
1433
                $("#'.$elementId.'_title").html(title);
1434
                $("#'.$elementId.'_table").html(chart.data.datasets[0].data);
1435
1436
                '.$extraButtonHandler.'
1437
            });
1438
        </script>';
1439
    }
1440
1441
    public static function buildJsChartData(array $all, string $chartName): array
1442
    {
1443
        $list = [];
1444
        $palette = ChamiloApi::getColorPalette(true, true);
1445
        foreach ($all as $tick => $tock) {
1446
            $list['labels'][] = $tick;
1447
        }
1448
1449
        $list['datasets'][0]['label'] = $chartName;
1450
        $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
1451
1452
        $i = 0;
1453
        foreach ($all as $tick => $tock) {
1454
            $j = $i % count($palette);
1455
            $list['datasets'][0]['data'][] = $tock;
1456
            $list['datasets'][0]['backgroundColor'][] = $palette[$j];
1457
            $i++;
1458
        }
1459
1460
        $scoreDisplay = ScoreDisplay::instance();
1461
        $table = new HTML_Table(['class' => 'data_table stats_table']);
1462
        $headers = [
1463
            get_lang('Name'),
1464
            get_lang('Count'),
1465
            get_lang('Percentage'),
1466
        ];
1467
        $row = 0;
1468
        $column = 0;
1469
        foreach ($headers as $header) {
1470
            $table->setHeaderContents($row, $column, $header);
1471
            $column++;
1472
        }
1473
1474
        $total = 0;
1475
        foreach ($all as $name => $value) {
1476
            $total += $value;
1477
        }
1478
        $row++;
1479
        foreach ($all as $name => $value) {
1480
            $table->setCellContents($row, 0, $name);
1481
            $table->setCellContents($row, 1, $value);
1482
            $table->setCellContents($row, 2, $scoreDisplay->display_score([$value, $total], SCORE_PERCENT));
1483
            $row++;
1484
        }
1485
        $table = Display::page_subheader2($chartName).$table->toHtml();
1486
1487
        return ['chart' => $list, 'table' => $table];
1488
    }
1489
1490
    /**
1491
     * Display the Logins By Date report and allow export its result to XLS.
1492
     */
1493
    public static function printLoginsByDate(): mixed
1494
    {
1495
        if (isset($_GET['export']) && 'xls' === $_GET['export']) {
1496
            $result = self::getLoginsByDate($_GET['start'], $_GET['end']);
1497
            $data = [[get_lang('Username'), get_lang('First name'), get_lang('Last name'), get_lang('Total time')]];
1498
1499
            foreach ($result as $i => $item) {
1500
                $data[] = [
1501
                    $item['username'],
1502
                    $item['firstname'],
1503
                    $item['lastname'],
1504
                    api_time_to_hms($item['time_count']),
1505
                ];
1506
            }
1507
1508
            Export::arrayToXls($data);
1509
            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...
1510
        }
1511
1512
        $content = Display::page_header(get_lang('Logins by date'));
1513
1514
        $actions = '';
1515
        $form = new FormValidator('frm_logins_by_date', 'get');
1516
        $form->addDateRangePicker(
1517
            'daterange',
1518
            get_lang('Date range'),
1519
            true,
1520
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1521
        );
1522
        $form->addHidden('report', 'logins_by_date');
1523
        $form->addButtonFilter(get_lang('Search'));
1524
1525
        if ($form->validate()) {
1526
            $values = $form->exportValues();
1527
1528
            $result = self::getLoginsByDate($values['daterange_start'], $values['daterange_end']);
1529
1530
            if (!empty($result)) {
1531
                $actions = Display::url(
1532
                    Display::getMdiIcon(ActionIcon::EXPORT_SPREADSHEET, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Export to XLS')),
1533
                    api_get_self().'?'.http_build_query(
1534
                        [
1535
                            'report' => 'logins_by_date',
1536
                            'export' => 'xls',
1537
                            'start' => Security::remove_XSS($values['daterange_start']),
1538
                            'end' => Security::remove_XSS($values['daterange_end']),
1539
                        ]
1540
                    )
1541
                );
1542
            }
1543
1544
            $table = new HTML_Table(['class' => 'data_table stats_table']);
1545
            $table->setHeaderContents(0, 0, get_lang('Username'));
1546
            $table->setHeaderContents(0, 1, get_lang('First name'));
1547
            $table->setHeaderContents(0, 2, get_lang('Last name'));
1548
            $table->setHeaderContents(0, 3, get_lang('Total time'));
1549
1550
            foreach ($result as $i => $item) {
1551
                $table->setCellContents($i + 1, 0, $item['username']);
1552
                $table->setCellContents($i + 1, 1, $item['firstname']);
1553
                $table->setCellContents($i + 1, 2, $item['lastname']);
1554
                $table->setCellContents($i + 1, 3, api_time_to_hms($item['time_count']));
1555
            }
1556
1557
            $table->setColAttributes(0, ['class' => 'text-center']);
1558
            $table->setColAttributes(3, ['class' => 'text-center']);
1559
            $content = $table->toHtml();
1560
        }
1561
1562
        $content .= $form->returnForm();
1563
1564
        if (!empty($actions)) {
1565
            $content .= Display::toolbarAction('logins_by_date_toolbar', [$actions]);
1566
        }
1567
1568
        return $content;
1569
    }
1570
1571
    /**
1572
     * Return HTML table for the student boss role, for the given user ID
1573
     * @param int $bossId
1574
     * @return string
1575
     */
1576
    public static function getBossTable(int $bossId): string
1577
    {
1578
        $students = UserManager::getUsersFollowedByStudentBoss(
1579
            $bossId,
1580
            0,
1581
            false,
1582
            false,
1583
            false,
1584
            null,
1585
            null,
1586
            null,
1587
            null,
1588
            1
1589
        );
1590
1591
        if (!empty($students)) {
1592
            $table = new HTML_Table(['class' => 'table table-responsive', 'id' => 'table_'.$bossId]);
1593
            $headers = [
1594
                get_lang('Name'),
1595
            ];
1596
            $row = 0;
1597
            $column = 0;
1598
            foreach ($headers as $header) {
1599
                $table->setHeaderContents($row, $column, $header);
1600
                $column++;
1601
            }
1602
            $row++;
1603
            foreach ($students as $student) {
1604
                $column = 0;
1605
                $content = api_get_person_name($student['firstname'], $student['lastname']);
1606
                $content = '<div style="width: 200px; overflow-wrap: break-word;">'.$content.'</div>';
1607
                $table->setCellContents(
1608
                    $row,
1609
                    $column++,
1610
                    $content
1611
                );
1612
                $row++;
1613
            }
1614
1615
            return $table->toHtml();
1616
        }
1617
1618
        return '<table id="table_'.$bossId.'"></table>';
1619
    }
1620
1621
    /**
1622
     * @param string $startDate
1623
     * @param string $endDate
1624
     *
1625
     * @return array
1626
     * @throws Exception
1627
     */
1628
    public static function getLoginsByDate(string $startDate, string $endDate): array
1629
    {
1630
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1631
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1632
1633
        if (empty($startDate) || empty($endDate)) {
1634
            return [];
1635
        }
1636
1637
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1638
        $tblLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1639
        $urlJoin = '';
1640
        $urlWhere = '';
1641
1642
        $accessUrlHelper = Container::getAccessUrlHelper();
1643
1644
        if ($accessUrlHelper->isMultiple()) {
1645
            $accessUrl = $accessUrlHelper->getCurrent();
1646
            $urlId = $accessUrl->getId();
1647
            $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1648
1649
            $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1650
            $urlWhere = "AND au.access_url_id = $urlId";
1651
        }
1652
1653
        $sql = "SELECT u.id,
1654
                    u.firstname,
1655
                    u.lastname,
1656
                    u.username,
1657
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1658
                FROM $tblUser u
1659
                INNER JOIN $tblLogin l
1660
                ON u.id = l.login_user_id
1661
                $urlJoin
1662
                WHERE u.active <> ".USER_SOFT_DELETED." AND l.login_date BETWEEN '$startDate' AND '$endDate'
1663
                $urlWhere
1664
                GROUP BY u.id";
1665
1666
        $stmt = Database::query($sql);
1667
1668
        return Database::store_result($stmt, 'ASSOC');
1669
    }
1670
1671
    /**
1672
     * Gets the number of new users registered between two dates.
1673
     * @throws Exception
1674
     */
1675
    public static function getNewUserRegistrations(string $startDate, string $endDate): array
1676
    {
1677
        $sql = "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') as reg_date, COUNT(*) as user_count
1678
            FROM user
1679
            WHERE created_at BETWEEN '$startDate' AND '$endDate'
1680
            GROUP BY reg_date";
1681
1682
        $result = Database::query($sql);
1683
        $data = [];
1684
        while ($row = Database::fetch_array($result)) {
1685
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1686
            $data[] = ['date' => $row['reg_date'], 'count' => $userCount];
1687
        }
1688
1689
        return $data;
1690
    }
1691
1692
    /**
1693
     * Gets the number of users registered by creator (creator_id) between two dates.
1694
     * @throws Exception
1695
     */
1696
    public static function getUserRegistrationsByCreator(string $startDate, string $endDate): array
1697
    {
1698
        $sql = "SELECT u.creator_id, COUNT(u.id) as user_count, c.firstname, c.lastname
1699
                FROM user u
1700
                LEFT JOIN user c ON u.creator_id = c.id
1701
                WHERE u.created_at BETWEEN '$startDate' AND '$endDate'
1702
                AND u.creator_id IS NOT NULL
1703
                GROUP BY u.creator_id";
1704
1705
        $result = Database::query($sql);
1706
        $data = [];
1707
        while ($row = Database::fetch_array($result)) {
1708
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1709
            $name = trim($row['firstname'] . ' ' . $row['lastname']);
1710
            if (!empty($name)) {
1711
                $data[] = [
1712
                    'name' => $name,
1713
                    'count' => $userCount
1714
                ];
1715
            }
1716
        }
1717
1718
        return $data;
1719
    }
1720
1721
    /**
1722
     * Initializes an array with dates between two given dates, setting each date's value to 0.
1723
     * @throws Exception
1724
     */
1725
    public static function initializeDateRangeArray(string $startDate, string $endDate): array
1726
    {
1727
        $dateRangeArray = [];
1728
        $currentDate = new DateTime($startDate);
1729
        $endDate = new DateTime($endDate);
1730
1731
        // Loop through the date range and initialize each date with 0
1732
        while ($currentDate <= $endDate) {
1733
            $formattedDate = $currentDate->format('Y-m-d');
1734
            $dateRangeArray[$formattedDate] = 0;
1735
            $currentDate->modify('+1 day');
1736
        }
1737
1738
        return $dateRangeArray;
1739
    }
1740
1741
    /**
1742
     * Checks if the difference between two dates is more than one month.
1743
     * @throws Exception
1744
     */
1745
    public static function isMoreThanAMonth(string $dateStart, string $dateEnd): bool
1746
    {
1747
        $startDate = new DateTime($dateStart);
1748
        $endDate = new DateTime($dateEnd);
1749
1750
        $diff = $startDate->diff($endDate);
1751
1752
        if ($diff->y >= 1) {
1753
            return true;
1754
        }
1755
1756
        if ($diff->m > 1) {
1757
            return true;
1758
        }
1759
1760
        if ($diff->m == 1) {
1761
            return $diff->d > 0;
1762
        }
1763
1764
        return false;
1765
    }
1766
1767
    /**
1768
     * Groups registration data by month.
1769
     * @throws Exception
1770
     */
1771
    public static function groupByMonth(array $registrations): array
1772
    {
1773
        $groupedData = [];
1774
1775
        foreach ($registrations as $registration) {
1776
            $monthYear = (new DateTime($registration['date']))->format('Y-m');
1777
            if (isset($groupedData[$monthYear])) {
1778
                $groupedData[$monthYear] += $registration['count'];
1779
            } else {
1780
                $groupedData[$monthYear] = $registration['count'];
1781
            }
1782
        }
1783
1784
        return $groupedData;
1785
    }
1786
1787
    /**
1788
     * Return de number of certificates generated.
1789
     * This function is resource intensive.
1790
     * @throws \Doctrine\DBAL\Exception
1791
     * @throws Exception
1792
     */
1793
    public static function countCertificatesByQuarter(string $dateFrom = null, string $dateUntil = null): int
1794
    {
1795
        $tableGradebookCertificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
1796
        $condition = "";
1797
        if (!empty($dateFrom) && !empty($dateUntil)) {
1798
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1799
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1800
            $condition = "WHERE (created_at BETWEEN '$dateFrom' AND '$dateUntil')";
1801
        } elseif (!empty($dateFrom)) {
1802
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1803
            $condition = "WHERE created_at >= '$dateFrom'";
1804
        } elseif (!empty($dateUntil)) {
1805
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1806
            $condition = "WHERE created_at <= '$dateUntil'";
1807
        }
1808
        $sql = "
1809
            SELECT count(*) AS count
1810
            FROM $tableGradebookCertificate
1811
            $condition
1812
        ";
1813
        $response = Database::query($sql);
1814
        $obj = Database::fetch_object($response);
1815
        return $obj->count;
1816
    }
1817
1818
    /**
1819
     * Get the number of logins by dates.
1820
     * This function is resource intensive.
1821
     * @throws Exception
1822
     */
1823
    public static function getSessionsByDuration(string $dateFrom, string $dateUntil): array
1824
    {
1825
        $results = [
1826
            '0' => 0,
1827
            '5' => 0,
1828
            '10' => 0,
1829
            '15' => 0,
1830
            '30' => 0,
1831
            '60' => 0,
1832
        ];
1833
        if (!empty($dateFrom) && !empty($dateUntil)) {
1834
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1835
            $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1836
            $urlId = api_get_current_access_url_id();
1837
            $tableUrl = '';
1838
            $whereUrl = '';
1839
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1840
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1841
            $accessUrlHelper = Container::getAccessUrlHelper();
1842
1843
            if ($accessUrlHelper->isMultiple()) {
1844
                $accessUrl = $accessUrlHelper->getCurrent();
1845
                $urlId = $accessUrl->getId();
1846
                $tableUrl = ", $accessUrlRelUserTable";
1847
                $whereUrl = " AND login_user_id = user_id AND access_url_id = $urlId";
1848
            }
1849
            $sql = "SELECT login_id, TIMESTAMPDIFF(SECOND, login_date, logout_date) AS duration
1850
            FROM $table $tableUrl
1851
            WHERE login_date >= '$dateFrom'
1852
            AND logout_date <= '$dateUntil'
1853
            $whereUrl
1854
            ";
1855
            $res = Database::query($sql);
1856
            while ($session = Database::fetch_array($res)) {
1857
                if ($session['duration'] > 3600) {
1858
                    $results['60']++;
1859
                } elseif ($session['duration'] > 1800) {
1860
                    $results['30']++;
1861
                } elseif ($session['duration'] > 900) {
1862
                    $results['15']++;
1863
                } elseif ($session['duration'] > 600) {
1864
                    $results['10']++;
1865
                } elseif ($session['duration'] > 300) {
1866
                    $results['5']++;
1867
                } else {
1868
                    $results['0']++;
1869
                }
1870
            }
1871
        }
1872
        return $results;
1873
    }
1874
}
1875