Passed
Pull Request — master (#6037)
by Yannick
19:16 queued 11:05
created

Statistics::getJSChartTemplate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 4
dl 0
loc 26
rs 10
c 0
b 0
f 0
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