Passed
Pull Request — master (#6037)
by Yannick
08:16
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\ResourceLink;
7
use Chamilo\CoreBundle\Entity\UserRelUser;
8
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
9
use Chamilo\CoreBundle\Framework\Container;
10
11
/**
12
 * This class provides some functions for statistics.
13
 */
14
class Statistics
15
{
16
    /**
17
     * Converts a number of bytes in a formatted string.
18
     *
19
     * @param int $size
20
     *
21
     * @return string Formatted file size or empty string if no match
22
     */
23
    public static function makeSizeString(int $size): string
24
    {
25
        if ($size < pow(2, 10)) {
26
            return $size." bytes";
27
        }
28
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
29
            return round($size / pow(2, 10), 0)." KB";
30
        }
31
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
32
            return round($size / pow(2, 20), 1)." MB";
33
        }
34
        if ($size > pow(2, 30)) {
35
            return round($size / pow(2, 30), 2)." GB";
36
        }
37
38
        return '';
39
    }
40
41
    /**
42
     * Count courses.
43
     *
44
     * @param string|null $categoryCode Code of a course category.
45
     *                                  Default: count all courses.
46
     * @param string|null $dateFrom dateFrom
47
     * @param string|null $dateUntil dateUntil
48
     *
49
     * @return int Number of courses counted
50
     * @throws \Doctrine\DBAL\Exception
51
     * @throws Exception
52
     */
53
    public static function countCourses(string $categoryCode = null, string $dateFrom = null, string $dateUntil = null): int
54
    {
55
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
56
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
57
        $accessUrlHelper = Container::getAccessUrlHelper();
58
59
        if ($accessUrlHelper->isMultiple()) {
60
            $accessUrl = $accessUrlHelper->getCurrent();
61
            $urlId = $accessUrl->getId();
62
            $sql = "SELECT COUNT(*) AS number
63
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
64
                    WHERE u.c_id = c.id AND $accessUrlRelCourseTable = $urlId";
65
            if (isset($categoryCode)) {
66
                $sql .= " AND category_code = '".Database::escape_string($categoryCode)."'";
67
            }
68
        } else {
69
            $sql = "SELECT COUNT(*) AS number
70
                    FROM $courseTable AS c
71
                    WHERE 1 = 1";
72
            if (isset($categoryCode)) {
73
                $sql .= " WHERE c.category_code = '".Database::escape_string($categoryCode)."'";
74
            }
75
        }
76
77
        if (!empty($dateFrom)) {
78
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
79
            $sql .= " AND c.creation_date >= '$dateFrom' ";
80
        }
81
        if (!empty($dateUntil)) {
82
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
83
            $sql .= " AND c.creation_date <= '$dateUntil' ";
84
        }
85
86
        $res = Database::query($sql);
87
        $obj = Database::fetch_object($res);
88
89
        return $obj->number;
90
    }
91
92
    /**
93
     * Count courses by visibility.
94
     *
95
     * @param array|null  $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
96
     * @param string|null $dateFrom dateFrom
97
     * @param string|null $dateUntil dateUntil
98
     *
99
     * @return int Number of courses counted
100
     * @throws \Doctrine\DBAL\Exception
101
     * @throws Exception
102
     */
103
    public static function countCoursesByVisibility(
104
        array $visibility = null,
105
        string $dateFrom = null,
106
        string $dateUntil = null
107
    ): int
108
    {
109
        $visibilityString = '';
110
        if (empty($visibility)) {
111
            return 0;
112
        } else {
113
            $auxArrayVisibility = [];
114
            if (!is_array($visibility)) {
115
                $visibility = [$visibility];
116
            }
117
            foreach ($visibility as $item) {
118
                $auxArrayVisibility[] = (int) $item;
119
            }
120
            $visibilityString = implode(',', $auxArrayVisibility);
121
        }
122
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
123
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
124
        $accessUrlHelper = Container::getAccessUrlHelper();
125
126
        if ($accessUrlHelper->isMultiple()) {
127
            $accessUrl = $accessUrlHelper->getCurrent();
128
            $urlId = $accessUrl->getId();
129
            $sql = "SELECT COUNT(*) AS number
130
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
131
                    WHERE u.c_id = c.id AND u.access_url_id = $urlId";
132
        } else {
133
            $sql = "SELECT COUNT(*) AS number
134
                    FROM $courseTable AS c
135
                    WHERE 1 = 1";
136
        }
137
        $sql .= " AND visibility IN ($visibilityString) ";
138
        if (!empty($dateFrom)) {
139
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
140
            $sql .= " AND c.creation_date >= '$dateFrom' ";
141
        }
142
        if (!empty($dateUntil)) {
143
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
144
            $sql .= " AND c.creation_date <= '$dateUntil' ";
145
        }
146
        $res = Database::query($sql);
147
        $obj = Database::fetch_object($res);
148
149
        return $obj->number;
150
    }
151
152
    /**
153
     * Count users.
154
     *
155
     * @param int    $status user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
156
     * @param string $categoryCode course category code. Default: count only users without filtering category
157
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
158
     * @param bool   $onlyActive Count only active users (false to only return currently active users)
159
     *
160
     * @return int Number of users counted
161
     * @throws Exception
162
     */
163
    public static function countUsers(
164
        ?int $status = null,
165
        ?string $categoryCode = null,
166
        ?bool $countInvisibleCourses = true,
167
        ?bool $onlyActive = false
168
    ): int
169
    {
170
        // Database table definitions
171
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
172
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
173
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
174
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
175
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
176
        $tblCourseRelCategory = Database::get_main_table(TABLE_MAIN_COURSE_REL_CATEGORY);
177
178
        $conditions = [];
179
        $conditions[] = "u.active <> " . USER_SOFT_DELETED;
180
        if ($onlyActive) {
181
            $conditions[] = "u.active = 1";
182
        }
183
        if (isset($status)) {
184
            $conditions[] = "u.status = " . $status;
185
        }
186
187
        $where = implode(' AND ', $conditions);
188
189
        $accessUrlHelper = Container::getAccessUrlHelper();
190
191
        if ($accessUrlHelper->isMultiple()) {
192
            $accessUrl = $accessUrlHelper->getCurrent();
193
            $urlId = $accessUrl->getId();
194
            $sql = "SELECT COUNT(DISTINCT(u.id)) AS number
195
                FROM $user_table as u
196
                INNER JOIN $access_url_rel_user_table as url ON u.id = url.user_id
197
                WHERE $where AND url.access_url_id = $urlId";
198
199
            if (isset($categoryCode)) {
200
                $categoryCode = Database::escape_string($categoryCode);
201
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
202
                    FROM $course_user_table cu
203
                    INNER JOIN $course_table c ON c.id = cu.c_id
204
                    INNER JOIN $access_url_rel_user_table as url ON cu.user_id = url.user_id
205
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
206
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
207
                    WHERE $where AND url.access_url_id = $urlId AND cc.code = '$categoryCode'";
208
            }
209
        } else {
210
            $sql = "SELECT COUNT(DISTINCT(id)) AS number
211
                FROM $user_table u
212
                WHERE $where";
213
214
            if (isset($categoryCode)) {
215
                $categoryCode = Database::escape_string($categoryCode);
216
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
217
                    FROM $course_user_table cu
218
                    INNER JOIN $course_table c ON c.id = cu.c_id
219
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
220
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
221
                    INNER JOIN $user_table u ON u.id = cu.user_id
222
                    WHERE $where AND cc.code = '$categoryCode'";
223
            }
224
        }
225
226
        $res = Database::query($sql);
227
        $obj = Database::fetch_object($res);
228
229
        return $obj->number;
230
    }
231
232
    /**
233
     * Get courses IDs from courses with some access_date between the two given dates
234
     * @param string $startDate
235
     * @param string $endDate
236
     *
237
     * @return array
238
     * @throws Exception
239
     */
240
    public static function getCoursesWithActivity(string $startDate, string $endDate): array
241
    {
242
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
243
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
244
        $startDate = Database::escape_string($startDate);
245
        $endDate = Database::escape_string($endDate);
246
247
        $accessUrlHelper = Container::getAccessUrlHelper();
248
249
        if ($accessUrlHelper->isMultiple()) {
250
            $accessUrl = $accessUrlHelper->getCurrent();
251
            $urlId = $accessUrl->getId();
252
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
253
                    WHERE
254
                        t.c_id = a.c_id AND
255
                        access_url_id = $urlId AND
256
                        access_date BETWEEN '$startDate' AND '$endDate'
257
                    ";
258
        } else {
259
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t
260
                   access_date BETWEEN '$startDate' AND '$endDate' ";
261
        }
262
263
        $result = Database::query($sql);
264
265
        return Database::store_result($result);
266
    }
267
268
    /**
269
     * Count activities from track_e_default_table.
270
     *
271
     * @return int Number of activities counted
272
     * @throws Exception
273
     */
274
    public static function getNumberOfActivities(mixed $courseId = 0, ?int $sessionId = 0): int
275
    {
276
        // Database table definitions
277
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
278
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
279
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
280
        $accessUrlHelper = Container::getAccessUrlHelper();
281
        if (is_array($courseId)) {
282
            // Usually when no param is given, we get an empty array from SortableTable
283
            $courseId = 0;
284
        }
285
286
        if ($accessUrlHelper->isMultiple()) {
287
            $accessUrl = $accessUrlHelper->getCurrent();
288
            $urlId = $accessUrl->getId();
289
            $sql = "SELECT count(default_id) AS total_number_of_items
290
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
291
                    WHERE user.active <> ".USER_SOFT_DELETED." AND
292
                        default_user_id = user.id AND
293
                        user.id = url.user_id AND
294
                        access_url_id = $urlId";
295
        } else {
296
            $sql = "SELECT count(default_id) AS total_number_of_items
297
                    FROM $track_e_default, $table_user user
298
                    WHERE user.active <> ".USER_SOFT_DELETED." AND default_user_id = user.id ";
299
        }
300
301
        if (!empty($courseId)) {
302
            $courseId = (int) $courseId;
303
            $sql .= " AND c_id = $courseId";
304
            $sql .= api_get_session_condition($sessionId);
305
        }
306
307
        if (isset($_GET['keyword'])) {
308
            $keyword = Database::escape_string(trim($_GET['keyword']));
309
            $sql .= " AND (
310
                        user.username LIKE '%".$keyword."%' OR
311
                        default_event_type LIKE '%".$keyword."%' OR
312
                        default_value_type LIKE '%".$keyword."%' OR
313
                        default_value LIKE '%".$keyword."%') ";
314
        }
315
        $res = Database::query($sql);
316
        $obj = Database::fetch_object($res);
317
318
        return $obj->total_number_of_items;
319
    }
320
321
    /**
322
     * Get activities data to display.
323
     *
324
     * @param int    $from
325
     * @param int    $numberOfItems
326
     * @param int    $column
327
     * @param string $direction
328
     * @param ?int   $courseId
329
     * @param ?int   $sessionId
330
     *
331
     * @return array
332
     * @throws Exception
333
     */
334
    public static function getActivitiesData(
335
        int $from,
336
        int $numberOfItems,
337
        int $column,
338
        string $direction,
339
        mixed $courseId = 0,
340
        ?int $sessionId = 0
341
    ): array
342
    {
343
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
344
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
345
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
346
        $direction = strtoupper($direction);
347
        if (is_array($courseId)) {
348
            // Usually when no param is given, we get an empty array from SortableTable
349
            $courseId = 0;
350
        }
351
352
        if (!in_array($direction, ['ASC', 'DESC'])) {
353
            $direction = 'DESC';
354
        }
355
356
        $accessUrlHelper = Container::getAccessUrlHelper();
357
358
        if ($accessUrlHelper->isMultiple()) {
359
            $accessUrl = $accessUrlHelper->getCurrent();
360
            $urlId = $accessUrl->getId();
361
            $sql = "SELECT
362
                        default_event_type  as col0,
363
                        default_value_type    as col1,
364
                        default_value        as col2,
365
                        c_id         as col3,
366
                        session_id as col4,
367
                        user.username         as col5,
368
                        user.id         as col6,
369
                        default_date         as col7
370
                    FROM $track_e_default as track_default,
371
                    $table_user as user,
372
                    $access_url_rel_user_table as url
373
                    WHERE
374
                        user.active <> -1 AND
375
                        track_default.default_user_id = user.id AND
376
                        url.user_id = user.id AND
377
                        access_url_id= $urlId";
378
        } else {
379
            $sql = "SELECT
380
                       default_event_type  as col0,
381
                       default_value_type    as col1,
382
                       default_value        as col2,
383
                       c_id         as col3,
384
                       session_id as col4,
385
                       user.username         as col5,
386
                       user.id         as col6,
387
                       default_date         as col7
388
                   FROM $track_e_default track_default, $table_user user
389
                   WHERE user.active <> ".USER_SOFT_DELETED." AND track_default.default_user_id = user.id ";
390
        }
391
392
        if (!empty($_GET['keyword'])) {
393
            $keyword = Database::escape_string(trim($_GET['keyword']));
394
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
395
                        default_event_type LIKE '%".$keyword."%' OR
396
                        default_value_type LIKE '%".$keyword."%' OR
397
                        default_value LIKE '%".$keyword."%') ";
398
        }
399
400
        if (!empty($courseId)) {
401
            $courseId = (int) $courseId;
402
            $sql .= " AND c_id = $courseId";
403
            $sql .= api_get_session_condition($sessionId);
404
        }
405
406
        if (!empty($column)) {
407
            $sql .= " ORDER BY col$column $direction";
408
        } else {
409
            $sql .= " ORDER BY col7 DESC ";
410
        }
411
        $sql .= " LIMIT $from, $numberOfItems ";
412
413
        $res = Database::query($sql);
414
        $activities = [];
415
        while ($row = Database::fetch_row($res)) {
416
            if (false === strpos($row[1], '_object') &&
417
                false === strpos($row[1], '_array')
418
            ) {
419
                $row[2] = $row[2];
420
            } else {
421
                if (!empty($row[2])) {
422
                    $originalData = str_replace('\\', '', $row[2]);
423
                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
424
                    if (is_array($row[2]) && !empty($row[2])) {
425
                        $row[2] = implode_with_key(', ', $row[2]);
426
                    } else {
427
                        $row[2] = $originalData;
428
                    }
429
                }
430
            }
431
432
            if (!empty($row['default_date'])) {
433
                $row['default_date'] = api_get_local_time($row['default_date']);
434
            } else {
435
                $row['default_date'] = '-';
436
            }
437
438
            if (!empty($row[7])) {
439
                $row[7] = api_get_local_time($row[7]);
440
            } else {
441
                $row[7] = '-';
442
            }
443
444
            if (!empty($row[5])) {
445
                // Course
446
                if (!empty($row[3])) {
447
                    $row[3] = Display::url(
448
                        $row[3],
449
                        api_get_path(WEB_CODE_PATH).'admin/course_edit.php?id='.$row[3]
450
                    );
451
                } else {
452
                    $row[3] = '-';
453
                }
454
455
                // session
456
                if (!empty($row[4])) {
457
                    $row[4] = Display::url(
458
                        $row[4],
459
                        api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$row[4]
460
                    );
461
                } else {
462
                    $row[4] = '-';
463
                }
464
465
                // User id.
466
                $row[5] = Display::url(
467
                    $row[5],
468
                    api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$row[6],
469
                    ['class' => 'ajax']
470
                );
471
472
                $row[6] = Tracking::get_ip_from_user_event(
473
                    $row[6],
474
                    $row[7],
475
                    true
476
                );
477
                if (empty($row[6])) {
478
                    $row[6] = get_lang('Unknown');
479
                }
480
            }
481
            $activities[] = $row;
482
        }
483
484
        return $activities;
485
    }
486
487
    /**
488
     * Show statistics.
489
     *
490
     * @param string $title      The title
491
     * @param array  $stats
492
     * @param ?bool   $showTotal
493
     * @param ?bool   $isFileSize
494
     *
495
     * @return string HTML table
496
     */
497
    public static function printStats(
498
        string $title,
499
        array $stats,
500
        ?bool $showTotal = true,
501
        ?bool $isFileSize = false
502
    ): string
503
    {
504
        $total = 0;
505
        $content = '<table class="table table-hover table-striped data_table stats_table" cellspacing="0" cellpadding="3" width="90%">
506
            <thead><tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr></thead><tbody>';
507
        $i = 0;
508
        foreach ($stats as $subtitle => $number) {
509
            $total += $number;
510
        }
511
512
        foreach ($stats as $subtitle => $number) {
513
            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...
514
                $number_label = number_format($number, 0, ',', '.');
515
            } else {
516
                $number_label = self::makeSizeString($number);
517
            }
518
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
519
520
            $content .= '<tr class="row_'.(0 == $i % 2 ? 'odd' : 'even').'">
521
                    <td width="25%" style="vertical-align:top;">'.$subtitle.'</td>
522
                    <td width="60%">'.Display::bar_progress($percentage, false).'</td>
523
                    <td width="5%" align="right" style="vertical-align:top;">'.$number_label.'</td>';
524
            if ($showTotal) {
525
                $content .= '<td width="5%" align="right"> '.$percentage.'%</td>';
526
            }
527
            $content .= '</tr>';
528
            $i++;
529
        }
530
        $content .= '</tbody>';
531
        if ($showTotal) {
532
            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...
533
                $total_label = number_format($total, 0, ',', '.');
534
            } else {
535
                $total_label = self::makeSizeString($total);
536
            }
537
            $content .= '
538
                <tfoot><tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr></tfoot>
539
            ';
540
        }
541
        $content .= '</table>';
542
543
        return $content;
544
    }
545
546
    /**
547
     * Show some stats about the number of logins.
548
     *
549
     * @param string $type month, hour or day
550
     * @return string HTML block
551
     * @throws Exception
552
     */
553
    public static function printLoginStats(string $type): string
554
    {
555
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
556
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
557
        $table_url = null;
558
        $where_url = null;
559
        $now = api_get_utc_datetime();
560
        $where_url_last = ' WHERE login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
561
        $accessUrlHelper = Container::getAccessUrlHelper();
562
563
        if ($accessUrlHelper->isMultiple()) {
564
            $accessUrl = $accessUrlHelper->getCurrent();
565
            $urlId = $accessUrl->getId();
566
            $table_url = ", $access_url_rel_user_table";
567
            $where_url = " WHERE login_user_id=user_id AND access_url_id = $urlId";
568
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
569
        }
570
571
        $period = get_lang('Month');
572
        $periodCollection = api_get_months_long();
573
        $sql = "SELECT
574
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
575
                count( login_id ) AS number_of_logins
576
                FROM $table $table_url $where_url
577
                GROUP BY stat_date
578
                ORDER BY login_date DESC";
579
        $sql_last_x = null;
580
581
        switch ($type) {
582
            case 'hour':
583
                $period = get_lang('Hour');
584
                $sql = "SELECT
585
                          DATE_FORMAT( login_date, '%H') AS stat_date,
586
                          count( login_id ) AS number_of_logins
587
                        FROM $table $table_url $where_url
588
                        GROUP BY stat_date
589
                        ORDER BY stat_date ";
590
                $sql_last_x = "SELECT
591
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
592
                                count( login_id ) AS number_of_logins
593
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
594
                               GROUP BY stat_date
595
                               ORDER BY stat_date ";
596
                break;
597
            case 'day':
598
                $periodCollection = api_get_week_days_long();
599
                $period = get_lang('Day');
600
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date ,
601
                        count( login_id ) AS number_of_logins
602
                        FROM  $table $table_url $where_url
603
                        GROUP BY stat_date
604
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
605
                $sql_last_x = "SELECT
606
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
607
                                count( login_id ) AS number_of_logins
608
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
609
                               GROUP BY stat_date
610
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
611
                break;
612
        }
613
614
        $content = '';
615
        if ($sql_last_x) {
616
            $res_last_x = Database::query($sql_last_x);
617
            $result_last_x = [];
618
            while ($obj = Database::fetch_object($res_last_x)) {
619
                $stat_date = ('day' === $type) ? $periodCollection[$obj->stat_date] : $obj->stat_date;
620
                $result_last_x[$stat_date] = $obj->number_of_logins;
621
            }
622
            $content .= self::printStats(get_lang('Last logins').' ('.$period.')', $result_last_x, true);
623
            flush(); //flush web request at this point to see something already while the full data set is loading
624
            $content .= '<br />';
625
        }
626
        $res = Database::query($sql);
627
        $result = [];
628
        while ($obj = Database::fetch_object($res)) {
629
            $stat_date = $obj->stat_date;
630
            switch ($type) {
631
                case 'month':
632
                    $stat_date = explode('-', $stat_date);
633
                    $stat_date[1] = $periodCollection[(int) $stat_date[1] - 1];
634
                    $stat_date = implode(' ', $stat_date);
635
                    break;
636
                case 'day':
637
                    $stat_date = $periodCollection[$stat_date];
638
                    break;
639
            }
640
            $result[$stat_date] = $obj->number_of_logins;
641
        }
642
        $content .= self::printStats(get_lang('All logins').' ('.$period.')', $result, true);
643
644
        return $content;
645
    }
646
647
    /**
648
     * Print the number of recent logins.
649
     *
650
     * @param ?bool  $distinct        whether to only give distinct users stats, or *all* logins
651
     * @param ?int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
652
     * @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)
653
     *
654
     * @throws Exception
655
     *
656
     * @return string HTML table
657
     */
658
    public static function printRecentLoginStats(?bool $distinct = false, ?int $sessionDuration = 0, ?array $periods = []): string
659
    {
660
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
661
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
662
        $table_url = '';
663
        $where_url = '';
664
        $accessUrlHelper = Container::getAccessUrlHelper();
665
666
        if ($accessUrlHelper->isMultiple()) {
667
            $accessUrl = $accessUrlHelper->getCurrent();
668
            $urlId = $accessUrl->getId();
669
            $table_url = ", $access_url_rel_user_table";
670
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
671
        }
672
673
        $now = api_get_utc_datetime();
674
        $field = 'login_id';
675
        if ($distinct) {
676
            $field = 'DISTINCT(login_user_id)';
677
        }
678
679
        if (empty($periods)) {
680
            $periods = [1, 7, 31];
681
        }
682
        $sqlList = [];
683
684
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
685
        foreach ($periods as $day) {
686
            $date = new DateTime($now);
687
            $startDate = $date->format('Y-m-d').' 00:00:00';
688
            $endDate = $date->format('Y-m-d').' 23:59:59';
689
690
            if ($day > 1) {
691
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
692
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
693
            }
694
695
            $localDate = api_get_local_time($startDate, null, null, false, false);
696
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
697
698
            $label = sprintf(get_lang('Last %s days'), $day);
699
            if (1 == $day) {
700
                $label = get_lang('Today');
701
            }
702
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
703
            $sql = "SELECT count($field) AS number
704
                    FROM $table $table_url
705
                    WHERE ";
706
            if (0 == $sessionDuration) {
707
                $sql .= " logout_date != login_date AND ";
708
            } else {
709
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
710
            }
711
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
712
                        $where_url";
713
            $sqlList[$label] = $sql;
714
        }
715
716
        $sql = "SELECT count($field) AS number
717
                FROM $table $table_url ";
718
        if (0 == $sessionDuration) {
719
            $sql .= " WHERE logout_date != login_date $where_url";
720
        } else {
721
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
722
        }
723
        $sqlList[get_lang('Total')] = $sql;
724
        $totalLogin = [];
725
        foreach ($sqlList as $label => $query) {
726
            $res = Database::query($query);
727
            $obj = Database::fetch_object($res);
728
            $totalLogin[$label] = $obj->number;
729
        }
730
731
        if ($distinct) {
732
            $content = self::printStats(get_lang('Distinct users logins'), $totalLogin, false);
733
        } else {
734
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
735
        }
736
737
        return $content;
738
    }
739
740
    /**
741
     * Get the number of recent logins.
742
     *
743
     * @param ?bool $distinct            Whether to only give distinct users stats, or *all* logins
744
     * @param ?int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
745
     * @param ?bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
746
     *
747
     * @throws Exception
748
     *
749
     * @return array
750
     */
751
    public static function getRecentLoginStats(?bool $distinct = false, ?int $sessionDuration = 0, ?bool $completeMissingDays = true): array
752
    {
753
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
754
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
755
        $table_url = '';
756
        $where_url = '';
757
        $accessUrlHelper = Container::getAccessUrlHelper();
758
759
        if ($accessUrlHelper->isMultiple()) {
760
            $accessUrl = $accessUrlHelper->getCurrent();
761
            $urlId = $accessUrl->getId();
762
            $table_url = ", $access_url_rel_user_table";
763
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
764
        }
765
766
        $now = api_get_utc_datetime();
767
        $date = new DateTime($now);
768
        $date->sub(new DateInterval('P31D'));
769
        $newDate = $date->format('Y-m-d h:i:s');
770
        $totalLogin = self::buildDatesArray($newDate, $now, true);
771
772
        $field = 'login_id';
773
        if ($distinct) {
774
            $field = 'DISTINCT(login_user_id)';
775
        }
776
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
777
778
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
779
                FROM $table $table_url
780
                WHERE ";
781
        if (0 == $sessionDuration) {
782
            $sql .= " logout_date != login_date AND ";
783
        } else {
784
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
785
        }
786
        $sql .= " login_date >= '$newDate' $where_url
787
                GROUP BY date(login_date)";
788
789
        $res = Database::query($sql);
790
        while ($row = Database::fetch_assoc($res)) {
791
            $monthAndDay = substr($row['login_date'], 5, 5);
792
            $totalLogin[$monthAndDay] = $row['number'];
793
        }
794
795
        return $totalLogin;
796
    }
797
798
    /**
799
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
800
     * @throws Exception
801
     */
802
    public static function getToolsStats(): array
803
    {
804
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
805
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
806
807
        $tools = [
808
            'announcement',
809
            'assignment',
810
            'calendar_event',
811
            'chat',
812
            'course_description',
813
            'document',
814
            'dropbox',
815
            'group',
816
            'learnpath',
817
            'link',
818
            'quiz',
819
            'student_publication',
820
            'user',
821
            'forum',
822
        ];
823
        $tool_names = [];
824
        foreach ($tools as $tool) {
825
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
826
        }
827
        $accessUrlHelper = Container::getAccessUrlHelper();
828
829
        if ($accessUrlHelper->isMultiple()) {
830
            $accessUrl = $accessUrlHelper->getCurrent();
831
            $urlId = $accessUrl->getId();
832
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
833
                    FROM $table t , $access_url_rel_course_table a
834
                    WHERE
835
                        access_tool IN ('".implode("','", $tools)."') AND
836
                        t.c_id = a.c_id AND
837
                        access_url_id = $urlId
838
                        GROUP BY access_tool
839
                    ";
840
        } else {
841
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
842
                    FROM $table
843
                    WHERE access_tool IN ('".implode("','", $tools)."')
844
                    GROUP BY access_tool ";
845
        }
846
847
        $res = Database::query($sql);
848
        $result = [];
849
        while ($obj = Database::fetch_object($res)) {
850
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
851
        }
852
853
        return $result;
854
    }
855
856
    /**
857
     * Show some stats about the accesses to the different course tools.
858
     *
859
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
860
     *
861
     * @return string HTML table
862
     * @throws Exception
863
     */
864
    public static function printToolStats($result = null): string
865
    {
866
        if (empty($result)) {
867
            $result = self::getToolsStats();
868
        }
869
870
        return self::printStats(get_lang('Tools access'), $result, true);
871
    }
872
873
    /**
874
     * Returns some stats about the number of courses per language.
875
     * @throws Exception
876
     */
877
    public static function printCourseByLanguageStats(): array
878
    {
879
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
880
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
881
        $accessUrlHelper = Container::getAccessUrlHelper();
882
883
        if ($accessUrlHelper->isMultiple()) {
884
            $accessUrl = $accessUrlHelper->getCurrent();
885
            $urlId = $accessUrl->getId();
886
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
887
                    FROM $table as c, $access_url_rel_course_table as u
888
                    WHERE u.c_id = c.id AND access_url_id = $urlId
889
                    GROUP BY course_language
890
                    ORDER BY number_of_courses DESC";
891
        } else {
892
            $sql = "SELECT course_language, count( code ) AS number_of_courses
893
                   FROM $table GROUP BY course_language
894
                   ORDER BY number_of_courses DESC";
895
        }
896
        $res = Database::query($sql);
897
        $result = [];
898
        while ($obj = Database::fetch_object($res)) {
899
            $result[$obj->course_language] = $obj->number_of_courses;
900
        }
901
902
        return $result;
903
    }
904
905
    /**
906
     * Shows the number of users having their picture uploaded in Dokeos.
907
     * @throws Exception
908
     */
909
    public static function printUserPicturesStats(): string
910
    {
911
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
912
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
913
        $url_condition = null;
914
        $url_condition2 = null;
915
        $table = null;
916
        $accessUrlHelper = Container::getAccessUrlHelper();
917
918
        if ($accessUrlHelper->isMultiple()) {
919
            $accessUrl = $accessUrlHelper->getCurrent();
920
            $urlId = $accessUrl->getId();
921
            $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.id AND access_url_id='".$urlId."'";
922
            $url_condition2 = " AND url.user_id=u.id AND access_url_id = $urlId";
923
            $table = ", $access_url_rel_user_table as url ";
924
        }
925
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
926
        $res = Database::query($sql);
927
        $count1 = Database::fetch_object($res);
928
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table
929
               WHERE LENGTH(picture_uri) > 0 $url_condition2";
930
931
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
932
933
        $res = Database::query($sql);
934
        $count2 = Database::fetch_object($res);
935
        // #users without picture
936
        $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...
937
        $result[get_lang('Yes')] = $count2->n; // #users with picture
938
939
        return self::printStats(get_lang('Number of users').' ('.get_lang('Picture').')', $result, true);
940
    }
941
942
    /**
943
     * Print important activities report page
944
     */
945
    public static function printActivitiesStats(): string
946
    {
947
        $content = '<h4>'.get_lang('Important activities').'</h4>';
948
        // Create a search-box
949
        $form = new FormValidator(
950
            'search_simple',
951
            'get',
952
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
953
            '',
954
            ['style' => 'width:200px']
955
        );
956
        $renderer = &$form->defaultRenderer();
957
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
958
        $form->addHidden('report', 'activities');
959
        $form->addHidden('activities_direction', 'DESC');
960
        $form->addHidden('activities_column', '4');
961
        $form->addElement('text', 'keyword', get_lang('Keyword'));
962
        $form->addButtonSearch(get_lang('Search'), 'submit');
963
        $content .= '<div class="actions">';
964
        $content .= $form->returnForm();
965
        $content .= '</div>';
966
967
        if (!empty($_GET['keyword'])) {
968
            $table = new SortableTable(
969
                'activities',
970
                ['Statistics', 'getNumberOfActivities'],
971
                ['Statistics', 'getActivitiesData'],
972
                7,
973
                50,
974
                'DESC'
975
            );
976
            $parameters = [];
977
978
            $parameters['report'] = 'activities';
979
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
980
981
            $table->set_additional_parameters($parameters);
982
            $table->set_header(0, get_lang('Event type'));
983
            $table->set_header(1, get_lang('Data type'));
984
            $table->set_header(2, get_lang('Value'));
985
            $table->set_header(3, get_lang('Course'));
986
            $table->set_header(4, get_lang('Session'));
987
            $table->set_header(5, get_lang('Username'));
988
            $table->set_header(6, get_lang('IP address'));
989
            $table->set_header(7, get_lang('Date'));
990
            $content .= $table->return_table();
991
        }
992
993
        $content .= '<div class="alert alert-info">'.get_lang('Important activities').' : '.'<br>';
994
        $prefix = 'LOG_';
995
        $userDefinedConstants = get_defined_constants(true)['user'];
996
        $filteredConstants = array_filter($userDefinedConstants, function ($constantName) use ($prefix) {
997
            return strpos($constantName, $prefix) === 0;
998
        }, ARRAY_FILTER_USE_KEY);
999
        $constantNames = array_keys($filteredConstants);
1000
        $link = api_get_self().'?report=activities&activities_direction=DESC&activities_column=7&keyword=';
1001
        foreach ($constantNames as $constantName) {
1002
            if ($constantName != 'LOG_WS') {
1003
                if (substr($constantName, -3) == '_ID') {
1004
                    continue;
1005
                }
1006
                $content .= '- <a href="'.$link.constant($constantName).'">'.constant($constantName).'</a><br>'.PHP_EOL;
1007
            } else {
1008
                $constantValue = constant($constantName);
1009
                $reflection = new ReflectionClass('Rest');
1010
                $constants = $reflection->getConstants();
1011
                foreach ($constants as $name => $value) {
1012
                    $content .= '- <a href="'.$link.$constantValue.$value.'">'.$constantValue.$value.'</a><br>'.PHP_EOL;
1013
                }
1014
            }
1015
        }
1016
        $content .= '</div>';
1017
1018
        return $content;
1019
    }
1020
1021
    /**
1022
     * Shows statistics about the time of last visit to each course.
1023
     * @throws Exception
1024
     */
1025
    public static function printCourseLastVisit(): string
1026
    {
1027
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
1028
        $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...
1029
        $columns[1] = 'access_date';
1030
        $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...
1031
        $sql_order[SORT_DESC] = 'DESC';
1032
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
1033
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
1034
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
1035
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
1036
1037
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
1038
            $direction = SORT_ASC;
1039
        }
1040
        $form = new FormValidator('courselastvisit', 'get');
1041
        $form->addElement('hidden', 'report', 'courselastvisit');
1042
        $form->addText('date_diff', get_lang('days'), true);
1043
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
1044
        $form->addButtonSearch(get_lang('Search'), 'submit');
1045
        if (!isset($_GET['date_diff'])) {
1046
            $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...
1047
        } else {
1048
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
1049
        }
1050
        $form->setDefaults($defaults);
1051
        $content = $form->returnForm();
1052
1053
        $values = $form->exportValues();
1054
        $date_diff = $values['date_diff'];
1055
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
1056
        $accessUrlHelper = Container::getAccessUrlHelper();
1057
1058
        if ($accessUrlHelper->isMultiple()) {
1059
            $accessUrl = $accessUrlHelper->getCurrent();
1060
            $urlId = $accessUrl->getId();
1061
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
1062
                   WHERE
1063
                        c_id = a.c_id AND
1064
                        access_url_id = $urlId
1065
                   GROUP BY c_id
1066
                   HAVING c_id <> ''
1067
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1068
        } else {
1069
            $sql = "SELECT * FROM $table t
1070
                   GROUP BY c_id
1071
                   HAVING c_id <> ''
1072
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1073
        }
1074
        $sql .= ' ORDER BY `'.$columns[$column].'` '.$sql_order[$direction];
1075
        $from = ($page_nr - 1) * $per_page;
1076
        $sql .= ' LIMIT '.$from.','.$per_page;
1077
1078
        $content .= '<p>'.get_lang('Latest access').' &gt;= '.$date_diff.' '.get_lang('days').'</p>';
1079
        $res = Database::query($sql);
1080
        if (Database::num_rows($res) > 0) {
1081
            $courses = [];
1082
            while ($obj = Database::fetch_object($res)) {
1083
                $courseInfo = api_get_course_info_by_id($obj->c_id);
1084
                $course = [];
1085
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
1086
                // Allow sort by date hiding the numerical date
1087
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
1088
                $courses[] = $course;
1089
            }
1090
            $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...
1091
            $parameters['report'] = 'courselastvisit';
1092
            $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...
1093
            $table_header[] = [get_lang("Latest access"), true];
1094
1095
            ob_start();
1096
            Display:: display_sortable_table(
1097
                $table_header,
1098
                $courses,
1099
                ['column' => $column, 'direction' => $direction],
1100
                [],
1101
                $parameters
1102
            );
1103
            $content .= ob_get_contents();
1104
            ob_end_clean();
1105
        } else {
1106
            $content = get_lang('No search results');
1107
        }
1108
1109
        return $content;
1110
    }
1111
1112
    /**
1113
     * Displays the statistics of the messages sent and received by each user in the social network.
1114
     *
1115
     * @param string $messageType Type of message: 'sent' or 'received'
1116
     *
1117
     * @return array Message list
1118
     */
1119
    public static function getMessages(string $messageType): array
1120
    {
1121
        $messageTable = Database::get_main_table(TABLE_MESSAGE);
1122
        $messageRelUserTable = Database::get_main_table(TABLE_MESSAGE_REL_USER);
1123
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1124
        $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1125
1126
        switch ($messageType) {
1127
            case 'sent':
1128
                $field = 'm.user_sender_id';
1129
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_SENDER;
1130
                break;
1131
            case 'received':
1132
                $field = 'mru.user_id';
1133
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_TO;
1134
                break;
1135
        }
1136
1137
        $accessUrlHelper = Container::getAccessUrlHelper();
1138
1139
        if ($accessUrlHelper->isMultiple()) {
1140
            $accessUrl = $accessUrlHelper->getCurrent();
1141
            $urlId = $accessUrl->getId();
1142
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1143
            FROM $messageTable m
1144
            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...
1145
            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...
1146
            INNER JOIN $accessUrlRelUserTable url ON u.id = url.user_id
1147
            WHERE url.access_url_id = $urlId
1148
            AND u.active <> " . USER_SOFT_DELETED . "
1149
            GROUP BY $field
1150
            ORDER BY count_message DESC";
1151
        } else {
1152
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1153
            FROM $messageTable m
1154
            INNER JOIN $messageRelUserTable mru ON $joinCondition
1155
            INNER JOIN $userTable u ON $field = u.id
1156
            WHERE u.active <> " . USER_SOFT_DELETED . "
1157
            GROUP BY $field
1158
            ORDER BY count_message DESC";
1159
        }
1160
        $res = Database::query($sql);
1161
        $messages_sent = [];
1162
        while ($messages = Database::fetch_array($res)) {
1163
            if (empty($messages['username'])) {
1164
                $messages['username'] = get_lang('Unknown');
1165
            }
1166
            $users = api_get_person_name(
1167
                    $messages['firstname'],
1168
                    $messages['lastname']
1169
                ) . '<br />(' . $messages['username'] . ')';
1170
            $messages_sent[$users] = $messages['count_message'];
1171
        }
1172
1173
        return $messages_sent;
1174
    }
1175
1176
    /**
1177
     * Count the number of friends for each social network users.
1178
     * @throws Exception
1179
     */
1180
    public static function getFriends(): array
1181
    {
1182
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
1183
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1184
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1185
1186
        $accessUrlHelper = Container::getAccessUrlHelper();
1187
1188
        if ($accessUrlHelper->isMultiple()) {
1189
            $accessUrl = $accessUrlHelper->getCurrent();
1190
            $urlId = $accessUrl->getId();
1191
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1192
                    FROM $access_url_rel_user_table as url, $user_friend_table uf
1193
                    LEFT JOIN $user_table u
1194
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1195
                    WHERE
1196
                        uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."' AND
1197
                        uf.user_id = url.user_id AND
1198
                        access_url_id = $urlId
1199
                    GROUP BY uf.user_id
1200
                    ORDER BY count_friend DESC ";
1201
        } else {
1202
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1203
                    FROM $user_friend_table uf
1204
                    LEFT JOIN $user_table u
1205
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1206
                    WHERE uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."'
1207
                    GROUP BY uf.user_id
1208
                    ORDER BY count_friend DESC ";
1209
        }
1210
        $res = Database::query($sql);
1211
        $list_friends = [];
1212
        while ($friends = Database::fetch_array($res)) {
1213
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1214
            $list_friends[$users] = $friends['count_friend'];
1215
        }
1216
1217
        return $list_friends;
1218
    }
1219
1220
    /**
1221
     * Returns the number of users that didn't log in for a certain period of time.
1222
     * @throws Exception
1223
     */
1224
    public static function printUsersNotLoggedInStats(): string
1225
    {
1226
        $totalLogin = [];
1227
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1228
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1229
        $total = self::countUsers();
1230
        $accessUrlHelper = Container::getAccessUrlHelper();
1231
1232
        if ($accessUrlHelper->isMultiple()) {
1233
            $accessUrl = $accessUrlHelper->getCurrent();
1234
            $urlId = $accessUrl->getId();
1235
            $table_url = ", $access_url_rel_user_table";
1236
            $where_url = " AND login_user_id=user_id AND access_url_id = $urlId";
1237
        } else {
1238
            $table_url = '';
1239
            $where_url = '';
1240
        }
1241
        $now = api_get_utc_datetime();
1242
        $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...
1243
            "SELECT count(distinct(login_user_id)) AS number ".
1244
            " FROM $table $table_url ".
1245
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1246
        $sql[get_lang('In the last 7 days')] =
1247
            "SELECT count(distinct(login_user_id)) AS number ".
1248
            " FROM $table $table_url ".
1249
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1250
        $sql[get_lang('In the last 31 days')] =
1251
            "SELECT count(distinct(login_user_id)) AS number ".
1252
            " FROM $table $table_url ".
1253
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1254
        $sql[sprintf(get_lang('Last %d months'), 6)] =
1255
            "SELECT count(distinct(login_user_id)) AS number ".
1256
            " FROM $table $table_url ".
1257
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1258
        $sql[get_lang('Never connected')] =
1259
            "SELECT count(distinct(login_user_id)) AS number ".
1260
            " FROM $table $table_url WHERE 1=1 $where_url";
1261
        foreach ($sql as $index => $query) {
1262
            $res = Database::query($query);
1263
            $obj = Database::fetch_object($res);
1264
            $r = $total - $obj->number;
1265
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1266
        }
1267
1268
        return self::printStats(
1269
            get_lang('Not logged in for some time'),
1270
            $totalLogin,
1271
            false
1272
        );
1273
    }
1274
1275
    /**
1276
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1277
     * within the provided range (including limits). Dates are assumed to be
1278
     * given in UTC.
1279
     *
1280
     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
1281
     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
1282
     * @param ?bool   $removeYear Whether to remove the year in the results (for easier reading)
1283
     *
1284
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1285
     */
1286
    public static function buildDatesArray(string $startDate, string $endDate, ?bool $removeYear = false): mixed
1287
    {
1288
        if (strlen($startDate) > 10) {
1289
            $startDate = substr($startDate, 0, 10);
1290
        }
1291
        if (strlen($endDate) > 10) {
1292
            $endDate = substr($endDate, 0, 10);
1293
        }
1294
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1295
            return false;
1296
        }
1297
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1298
            return false;
1299
        }
1300
        $startTimestamp = strtotime($startDate);
1301
        $endTimestamp = strtotime($endDate);
1302
        $list = [];
1303
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1304
            $datetime = api_get_utc_datetime($time);
1305
            if ($removeYear) {
1306
                $datetime = substr($datetime, 5, 5);
1307
            } else {
1308
                $dateTime = substr($datetime, 0, 10);
1309
            }
1310
            $list[$datetime] = 0;
1311
        }
1312
1313
        return $list;
1314
    }
1315
1316
    /**
1317
     * Prepare the JS code to load a chart.
1318
     *
1319
     * @param string $url     URL for AJAX data generator
1320
     * @param ?string $type    bar, line, pie, etc (defaults to 'pie')
1321
     * @param ?string $options Additional options to the chart (see chart-specific library)
1322
     * @param ?string A JS code for loading the chart together with a call to AJAX data generator
1323
     */
1324
    public static function getJSChartTemplate(string $url, ?string $type = 'pie', ?string $options = '', ?string $elementId = 'canvas')
1325
    {
1326
        return '
1327
        <script>
1328
        $(function() {
1329
            $.ajax({
1330
                url: "'.$url.'",
1331
                type: "POST",
1332
                success: function(data) {
1333
                    Chart.defaults.responsive = false;
1334
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1335
                    ctx.canvas.width = 420;
1336
                    ctx.canvas.height = 420;
1337
                    var chart = new Chart(ctx, {
1338
                        type: "'.$type.'",
1339
                        data: data,
1340
                        options: {
1341
                            plugins: {
1342
                                '.$options.'
1343
                            },
1344
                            cutout: "25%"
1345
                        }
1346
                    });
1347
                    var title = chart.options.plugins.title.text;
1348
                    $("#'.$elementId.'_title").html(title);
1349
                    $("#'.$elementId.'_table").html(data.table);
1350
                }
1351
            });
1352
        });
1353
        </script>';
1354
1355
    }
1356
1357
    /**
1358
     * Return template for a JS chart
1359
     * @param $data
1360
     * @param $type
1361
     * @param $options
1362
     * @param $elementId
1363
     * @param $responsive
1364
     * @param $onClickHandler
1365
     * @param $extraButtonHandler
1366
     * @param $canvasDimensions
1367
     * @return string
1368
     */
1369
    public static function getJSChartTemplateWithData(
1370
        $data,
1371
        ?string $type = 'pie',
1372
        ?string $options = '',
1373
        ?string $elementId = 'canvas',
1374
        ?bool $responsive = true,
1375
        ?string $onClickHandler = '',
1376
        ?string $extraButtonHandler = '',
1377
        ?array $canvasDimensions = ['width' => 420, 'height' => 420]
1378
    ): string {
1379
        $data = json_encode($data);
1380
        $responsiveValue = $responsive ? 'true' : 'false';
1381
1382
        $indexAxisOption = '';
1383
        if ($type === 'bar') {
1384
            $indexAxisOption = 'indexAxis: "y",';
1385
        }
1386
1387
        $onClickScript = '';
1388
        if (!empty($onClickHandler)) {
1389
            $onClickScript = '
1390
                onClick: function(evt) {
1391
                    '.$onClickHandler.'
1392
                },
1393
            ';
1394
        }
1395
1396
        $canvasSize = '';
1397
        if ($responsiveValue === 'false') {
1398
            $canvasSize = '
1399
            ctx.canvas.width = '.$canvasDimensions['width'].';
1400
            ctx.canvas.height = '.$canvasDimensions['height'].';
1401
            ';
1402
        }
1403
1404
        return '
1405
        <script>
1406
            $(function() {
1407
                Chart.defaults.responsive = '.$responsiveValue.';
1408
                var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1409
                '.$canvasSize.'
1410
                var chart = new Chart(ctx, {
1411
                    type: "'.$type.'",
1412
                    data: '.$data.',
1413
                    options: {
1414
                        plugins: {
1415
                            '.$options.',
1416
                            datalabels: {
1417
                                anchor: "end",
1418
                                align: "left",
1419
                                formatter: function(value) {
1420
                                    return value;
1421
                                },
1422
                                color: "#000"
1423
                            },
1424
                        },
1425
                        '.$indexAxisOption.'
1426
                        scales: {
1427
                            x: { beginAtZero: true },
1428
                            y: { barPercentage: 0.5 }
1429
                        },
1430
                        '.$onClickScript.'
1431
                    }
1432
                });
1433
                var title = chart.options.plugins.title.text;
1434
                $("#'.$elementId.'_title").html(title);
1435
                $("#'.$elementId.'_table").html(chart.data.datasets[0].data);
1436
1437
                '.$extraButtonHandler.'
1438
            });
1439
        </script>';
1440
    }
1441
1442
    public static function buildJsChartData(array $all, string $chartName): array
1443
    {
1444
        $list = [];
1445
        $palette = ChamiloApi::getColorPalette(true, true);
1446
        foreach ($all as $tick => $tock) {
1447
            $list['labels'][] = $tick;
1448
        }
1449
1450
        $list['datasets'][0]['label'] = $chartName;
1451
        $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
1452
1453
        $i = 0;
1454
        foreach ($all as $tick => $tock) {
1455
            $j = $i % count($palette);
1456
            $list['datasets'][0]['data'][] = $tock;
1457
            $list['datasets'][0]['backgroundColor'][] = $palette[$j];
1458
            $i++;
1459
        }
1460
1461
        $scoreDisplay = ScoreDisplay::instance();
1462
        $table = new HTML_Table(['class' => 'data_table stats_table']);
1463
        $headers = [
1464
            get_lang('Name'),
1465
            get_lang('Count'),
1466
            get_lang('Percentage'),
1467
        ];
1468
        $row = 0;
1469
        $column = 0;
1470
        foreach ($headers as $header) {
1471
            $table->setHeaderContents($row, $column, $header);
1472
            $column++;
1473
        }
1474
1475
        $total = 0;
1476
        foreach ($all as $name => $value) {
1477
            $total += $value;
1478
        }
1479
        $row++;
1480
        foreach ($all as $name => $value) {
1481
            $table->setCellContents($row, 0, $name);
1482
            $table->setCellContents($row, 1, $value);
1483
            $table->setCellContents($row, 2, $scoreDisplay->display_score([$value, $total], SCORE_PERCENT));
1484
            $row++;
1485
        }
1486
        $table = Display::page_subheader2($chartName).$table->toHtml();
1487
1488
        return ['chart' => $list, 'table' => $table];
1489
    }
1490
1491
    /**
1492
     * Display the Logins By Date report and allow export its result to XLS.
1493
     */
1494
    public static function printLoginsByDate(): mixed
1495
    {
1496
        if (isset($_GET['export']) && 'xls' === $_GET['export']) {
1497
            $result = self::getLoginsByDate($_GET['start'], $_GET['end']);
1498
            $data = [[get_lang('Username'), get_lang('First name'), get_lang('Last name'), get_lang('Total time')]];
1499
1500
            foreach ($result as $i => $item) {
1501
                $data[] = [
1502
                    $item['username'],
1503
                    $item['firstname'],
1504
                    $item['lastname'],
1505
                    api_time_to_hms($item['time_count']),
1506
                ];
1507
            }
1508
1509
            Export::arrayToXls($data);
1510
            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...
1511
        }
1512
1513
        $content = Display::page_header(get_lang('Logins by date'));
1514
1515
        $actions = '';
1516
        $form = new FormValidator('frm_logins_by_date', 'get');
1517
        $form->addDateRangePicker(
1518
            'daterange',
1519
            get_lang('Date range'),
1520
            true,
1521
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1522
        );
1523
        $form->addHidden('report', 'logins_by_date');
1524
        $form->addButtonFilter(get_lang('Search'));
1525
1526
        if ($form->validate()) {
1527
            $values = $form->exportValues();
1528
1529
            $result = self::getLoginsByDate($values['daterange_start'], $values['daterange_end']);
1530
1531
            if (!empty($result)) {
1532
                $actions = Display::url(
1533
                    Display::getMdiIcon(ActionIcon::EXPORT_SPREADSHEET, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Export to XLS')),
1534
                    api_get_self().'?'.http_build_query(
1535
                        [
1536
                            'report' => 'logins_by_date',
1537
                            'export' => 'xls',
1538
                            'start' => Security::remove_XSS($values['daterange_start']),
1539
                            'end' => Security::remove_XSS($values['daterange_end']),
1540
                        ]
1541
                    )
1542
                );
1543
            }
1544
1545
            $table = new HTML_Table(['class' => 'data_table stats_table']);
1546
            $table->setHeaderContents(0, 0, get_lang('Username'));
1547
            $table->setHeaderContents(0, 1, get_lang('First name'));
1548
            $table->setHeaderContents(0, 2, get_lang('Last name'));
1549
            $table->setHeaderContents(0, 3, get_lang('Total time'));
1550
1551
            foreach ($result as $i => $item) {
1552
                $table->setCellContents($i + 1, 0, $item['username']);
1553
                $table->setCellContents($i + 1, 1, $item['firstname']);
1554
                $table->setCellContents($i + 1, 2, $item['lastname']);
1555
                $table->setCellContents($i + 1, 3, api_time_to_hms($item['time_count']));
1556
            }
1557
1558
            $table->setColAttributes(0, ['class' => 'text-center']);
1559
            $table->setColAttributes(3, ['class' => 'text-center']);
1560
            $content = $table->toHtml();
1561
        }
1562
1563
        $content .= $form->returnForm();
1564
1565
        if (!empty($actions)) {
1566
            $content .= Display::toolbarAction('logins_by_date_toolbar', [$actions]);
1567
        }
1568
1569
        return $content;
1570
    }
1571
1572
    /**
1573
     * Return HTML table for the student boss role, for the given user ID
1574
     * @param int $bossId
1575
     * @return string
1576
     */
1577
    public static function getBossTable(int $bossId): string
1578
    {
1579
        $students = UserManager::getUsersFollowedByStudentBoss(
1580
            $bossId,
1581
            0,
1582
            false,
1583
            false,
1584
            false,
1585
            null,
1586
            null,
1587
            null,
1588
            null,
1589
            1
1590
        );
1591
1592
        if (!empty($students)) {
1593
            $table = new HTML_Table(['class' => 'table table-responsive', 'id' => 'table_'.$bossId]);
1594
            $headers = [
1595
                get_lang('Name'),
1596
            ];
1597
            $row = 0;
1598
            $column = 0;
1599
            foreach ($headers as $header) {
1600
                $table->setHeaderContents($row, $column, $header);
1601
                $column++;
1602
            }
1603
            $row++;
1604
            foreach ($students as $student) {
1605
                $column = 0;
1606
                $content = api_get_person_name($student['firstname'], $student['lastname']);
1607
                $content = '<div style="width: 200px; overflow-wrap: break-word;">'.$content.'</div>';
1608
                $table->setCellContents(
1609
                    $row,
1610
                    $column++,
1611
                    $content
1612
                );
1613
                $row++;
1614
            }
1615
1616
            return $table->toHtml();
1617
        }
1618
1619
        return '<table id="table_'.$bossId.'"></table>';
1620
    }
1621
1622
    /**
1623
     * @param string $startDate
1624
     * @param string $endDate
1625
     *
1626
     * @return array
1627
     * @throws Exception
1628
     */
1629
    public static function getLoginsByDate(string $startDate, string $endDate): array
1630
    {
1631
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1632
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1633
1634
        if (empty($startDate) || empty($endDate)) {
1635
            return [];
1636
        }
1637
1638
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1639
        $tblLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1640
        $urlJoin = '';
1641
        $urlWhere = '';
1642
1643
        $accessUrlHelper = Container::getAccessUrlHelper();
1644
1645
        if ($accessUrlHelper->isMultiple()) {
1646
            $accessUrl = $accessUrlHelper->getCurrent();
1647
            $urlId = $accessUrl->getId();
1648
            $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1649
1650
            $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1651
            $urlWhere = "AND au.access_url_id = $urlId";
1652
        }
1653
1654
        $sql = "SELECT u.id,
1655
                    u.firstname,
1656
                    u.lastname,
1657
                    u.username,
1658
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1659
                FROM $tblUser u
1660
                INNER JOIN $tblLogin l
1661
                ON u.id = l.login_user_id
1662
                $urlJoin
1663
                WHERE u.active <> ".USER_SOFT_DELETED." AND l.login_date BETWEEN '$startDate' AND '$endDate'
1664
                $urlWhere
1665
                GROUP BY u.id";
1666
1667
        $stmt = Database::query($sql);
1668
1669
        return Database::store_result($stmt, 'ASSOC');
1670
    }
1671
1672
    /**
1673
     * Gets the number of new users registered between two dates.
1674
     * @throws Exception
1675
     */
1676
    public static function getNewUserRegistrations(string $startDate, string $endDate): array
1677
    {
1678
        $sql = "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') as reg_date, COUNT(*) as user_count
1679
            FROM user
1680
            WHERE created_at BETWEEN '$startDate' AND '$endDate'
1681
            GROUP BY reg_date";
1682
1683
        $result = Database::query($sql);
1684
        $data = [];
1685
        while ($row = Database::fetch_array($result)) {
1686
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1687
            $data[] = ['date' => $row['reg_date'], 'count' => $userCount];
1688
        }
1689
1690
        return $data;
1691
    }
1692
1693
    /**
1694
     * Gets the number of users registered by creator (creator_id) between two dates.
1695
     * @throws Exception
1696
     */
1697
    public static function getUserRegistrationsByCreator(string $startDate, string $endDate): array
1698
    {
1699
        $sql = "SELECT u.creator_id, COUNT(u.id) as user_count, c.firstname, c.lastname
1700
                FROM user u
1701
                LEFT JOIN user c ON u.creator_id = c.id
1702
                WHERE u.created_at BETWEEN '$startDate' AND '$endDate'
1703
                AND u.creator_id IS NOT NULL
1704
                GROUP BY u.creator_id";
1705
1706
        $result = Database::query($sql);
1707
        $data = [];
1708
        while ($row = Database::fetch_array($result)) {
1709
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1710
            $name = trim($row['firstname'] . ' ' . $row['lastname']);
1711
            if (!empty($name)) {
1712
                $data[] = [
1713
                    'name' => $name,
1714
                    'count' => $userCount
1715
                ];
1716
            }
1717
        }
1718
1719
        return $data;
1720
    }
1721
1722
    /**
1723
     * Initializes an array with dates between two given dates, setting each date's value to 0.
1724
     * @throws Exception
1725
     */
1726
    public static function initializeDateRangeArray(string $startDate, string $endDate): array
1727
    {
1728
        $dateRangeArray = [];
1729
        $currentDate = new DateTime($startDate);
1730
        $endDate = new DateTime($endDate);
1731
1732
        // Loop through the date range and initialize each date with 0
1733
        while ($currentDate <= $endDate) {
1734
            $formattedDate = $currentDate->format('Y-m-d');
1735
            $dateRangeArray[$formattedDate] = 0;
1736
            $currentDate->modify('+1 day');
1737
        }
1738
1739
        return $dateRangeArray;
1740
    }
1741
1742
    /**
1743
     * Checks if the difference between two dates is more than one month.
1744
     * @throws Exception
1745
     */
1746
    public static function isMoreThanAMonth(string $dateStart, string $dateEnd): bool
1747
    {
1748
        $startDate = new DateTime($dateStart);
1749
        $endDate = new DateTime($dateEnd);
1750
1751
        $diff = $startDate->diff($endDate);
1752
1753
        if ($diff->y >= 1) {
1754
            return true;
1755
        }
1756
1757
        if ($diff->m > 1) {
1758
            return true;
1759
        }
1760
1761
        if ($diff->m == 1) {
1762
            return $diff->d > 0;
1763
        }
1764
1765
        return false;
1766
    }
1767
1768
    /**
1769
     * Groups registration data by month.
1770
     * @throws Exception
1771
     */
1772
    public static function groupByMonth(array $registrations): array
1773
    {
1774
        $groupedData = [];
1775
1776
        foreach ($registrations as $registration) {
1777
            $monthYear = (new DateTime($registration['date']))->format('Y-m');
1778
            if (isset($groupedData[$monthYear])) {
1779
                $groupedData[$monthYear] += $registration['count'];
1780
            } else {
1781
                $groupedData[$monthYear] = $registration['count'];
1782
            }
1783
        }
1784
1785
        return $groupedData;
1786
    }
1787
1788
    /**
1789
     * Retrieves the available tools using the repository.
1790
     */
1791
    public static function getAvailableTools(): array
1792
    {
1793
        $em = Database::getManager();
1794
        $repo = $em->getRepository(ResourceLink::class);
1795
1796
        return $repo->getAvailableTools();
1797
    }
1798
1799
    /**
1800
     * Generates a report of tool usage based on the provided tool IDs.
1801
     */
1802
    public static function getToolUsageReportByTools(array $toolIds): array
1803
    {
1804
        $em = Database::getManager();
1805
        $repo = $em->getRepository(ResourceLink::class);
1806
1807
        return $repo->getToolUsageReportByTools($toolIds);
1808
    }
1809
1810
    /**
1811
     * Return de number of certificates generated.
1812
     * This function is resource intensive.
1813
     * @throws \Doctrine\DBAL\Exception
1814
     * @throws Exception
1815
     */
1816
    public static function countCertificatesByQuarter(string $dateFrom = null, string $dateUntil = null): int
1817
    {
1818
        $tableGradebookCertificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
1819
        $condition = "";
1820
        if (!empty($dateFrom) && !empty($dateUntil)) {
1821
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1822
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1823
            $condition = "WHERE (created_at BETWEEN '$dateFrom' AND '$dateUntil')";
1824
        } elseif (!empty($dateFrom)) {
1825
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1826
            $condition = "WHERE created_at >= '$dateFrom'";
1827
        } elseif (!empty($dateUntil)) {
1828
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1829
            $condition = "WHERE created_at <= '$dateUntil'";
1830
        }
1831
        $sql = "
1832
            SELECT count(*) AS count
1833
            FROM $tableGradebookCertificate
1834
            $condition
1835
        ";
1836
        $response = Database::query($sql);
1837
        $obj = Database::fetch_object($response);
1838
        return $obj->count;
1839
    }
1840
1841
    /**
1842
     * Get the number of logins by dates.
1843
     * This function is resource intensive.
1844
     * @throws Exception
1845
     */
1846
    public static function getSessionsByDuration(string $dateFrom, string $dateUntil): array
1847
    {
1848
        $results = [
1849
            '0' => 0,
1850
            '5' => 0,
1851
            '10' => 0,
1852
            '15' => 0,
1853
            '30' => 0,
1854
            '60' => 0,
1855
        ];
1856
        if (!empty($dateFrom) && !empty($dateUntil)) {
1857
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1858
            $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1859
            $urlId = api_get_current_access_url_id();
1860
            $tableUrl = '';
1861
            $whereUrl = '';
1862
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1863
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1864
            $accessUrlHelper = Container::getAccessUrlHelper();
1865
1866
            if ($accessUrlHelper->isMultiple()) {
1867
                $accessUrl = $accessUrlHelper->getCurrent();
1868
                $urlId = $accessUrl->getId();
1869
                $tableUrl = ", $accessUrlRelUserTable";
1870
                $whereUrl = " AND login_user_id = user_id AND access_url_id = $urlId";
1871
            }
1872
            $sql = "SELECT login_id, TIMESTAMPDIFF(SECOND, login_date, logout_date) AS duration
1873
            FROM $table $tableUrl
1874
            WHERE login_date >= '$dateFrom'
1875
            AND logout_date <= '$dateUntil'
1876
            $whereUrl
1877
            ";
1878
            $res = Database::query($sql);
1879
            while ($session = Database::fetch_array($res)) {
1880
                if ($session['duration'] > 3600) {
1881
                    $results['60']++;
1882
                } elseif ($session['duration'] > 1800) {
1883
                    $results['30']++;
1884
                } elseif ($session['duration'] > 900) {
1885
                    $results['15']++;
1886
                } elseif ($session['duration'] > 600) {
1887
                    $results['10']++;
1888
                } elseif ($session['duration'] > 300) {
1889
                    $results['5']++;
1890
                } else {
1891
                    $results['0']++;
1892
                }
1893
            }
1894
        }
1895
        return $results;
1896
    }
1897
}
1898