Passed
Pull Request — master (#6037)
by
unknown
07:33
created

Statistics::getToolUsageReportByTools()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
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
10
/**
11
 * This class provides some functions for statistics.
12
 */
13
class Statistics
14
{
15
    /**
16
     * Converts a number of bytes in a formatted string.
17
     *
18
     * @param int $size
19
     *
20
     * @return string Formatted file size
21
     */
22
    public static function makeSizeString($size)
23
    {
24
        if ($size < pow(2, 10)) {
25
            return $size." bytes";
26
        }
27
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
28
            return round($size / pow(2, 10), 0)." KB";
29
        }
30
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
31
            return round($size / pow(2, 20), 1)." MB";
32
        }
33
        if ($size > pow(2, 30)) {
34
            return round($size / pow(2, 30), 2)." GB";
35
        }
36
    }
37
38
    /**
39
     * Count courses.
40
     *
41
     * @param string $categoryCode Code of a course category.
42
     *                             Default: count all courses.
43
     *
44
     * @return int Number of courses counted
45
     */
46
    public static function countCourses($categoryCode = null)
47
    {
48
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
49
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
50
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
51
        $urlId = api_get_current_access_url_id();
52
53
        $categoryJoin = '';
54
        $categoryCondition = '';
55
56
        if (!empty($categoryCode)) {
57
            //$categoryJoin = " LEFT JOIN $tblCourseCategory course_category ON course.category_id = course_category.id ";
58
            //$categoryCondition = " course_category.code = '".Database::escape_string($categoryCode)."' ";
59
        }
60
61
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

61
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
62
            $sql = "SELECT COUNT(*) AS number
63
                    FROM ".$course_table." as c, $access_url_rel_course_table as u
64
                    $categoryJoin
65
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
66
            if (isset($categoryCode)) {
67
                $sql .= " AND $categoryCondition";
68
            }
69
        } else {
70
            $sql = "SELECT COUNT(*) AS number
71
                    FROM $course_table $categoryJoin";
72
            if (isset($categoryCode)) {
73
                $sql .= " WHERE $categoryCondition";
74
            }
75
        }
76
77
        $res = Database::query($sql);
78
        $obj = Database::fetch_object($res);
79
80
        return $obj->number;
81
    }
82
83
    /**
84
     * Count courses by visibility.
85
     *
86
     * @param int $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
87
     *
88
     * @return int Number of courses counted
89
     */
90
    public static function countCoursesByVisibility($visibility = null)
91
    {
92
        if (!isset($visibility)) {
93
            return 0;
94
        }
95
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
96
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
97
        $urlId = api_get_current_access_url_id();
98
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

98
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
99
            $sql = "SELECT COUNT(*) AS number
100
                    FROM $course_table as c, $access_url_rel_course_table as u
101
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
102
            if (isset($visibility)) {
103
                $sql .= " AND visibility = ".intval($visibility);
104
            }
105
        } else {
106
            $sql = "SELECT COUNT(*) AS number FROM $course_table ";
107
            if (isset($visibility)) {
108
                $sql .= " WHERE visibility = ".intval($visibility);
109
            }
110
        }
111
        $res = Database::query($sql);
112
        $obj = Database::fetch_object($res);
113
114
        return $obj->number;
115
    }
116
117
    /**
118
     * Count users.
119
     *
120
     * @param int    $status                user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
121
     * @param string $categoryCode          course category code. Default: count only users without filtering category
122
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
123
     * @param bool   $onlyActive            Count only active users (false to only return currently active users)
124
     *
125
     * @return int Number of users counted
126
     */
127
    public static function countUsers(
128
        $status = null,
129
        $categoryCode = null,
130
        $countInvisibleCourses = true,
131
        $onlyActive = false
132
    ) {
133
        // Database table definitions
134
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
135
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
136
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
137
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
138
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
139
        $tblCourseRelCategory = Database::get_main_table(TABLE_MAIN_COURSE_REL_CATEGORY);
140
        $urlId = api_get_current_access_url_id();
141
142
        $conditions = [];
143
        $conditions[] = "u.active <> " . USER_SOFT_DELETED;
144
        if ($onlyActive) {
145
            $conditions[] = "u.active = 1";
146
        }
147
        if (isset($status)) {
148
            $conditions[] = "u.status = " . intval($status);
149
        }
150
151
        $where = implode(' AND ', $conditions);
152
153
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

153
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
154
            $sql = "SELECT COUNT(DISTINCT(u.id)) AS number
155
                FROM $user_table as u
156
                INNER JOIN $access_url_rel_user_table as url ON u.id = url.user_id
157
                WHERE $where AND url.access_url_id = $urlId";
158
159
            if (isset($categoryCode)) {
160
                $categoryCode = Database::escape_string($categoryCode);
161
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
162
                    FROM $course_user_table cu
163
                    INNER JOIN $course_table c ON c.id = cu.c_id
164
                    INNER JOIN $access_url_rel_user_table as url ON cu.user_id = url.user_id
165
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
166
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
167
                    WHERE $where AND url.access_url_id = $urlId AND cc.code = '$categoryCode'";
168
            }
169
        } else {
170
            $sql = "SELECT COUNT(DISTINCT(id)) AS number
171
                FROM $user_table u
172
                WHERE $where";
173
174
            if (isset($categoryCode)) {
175
                $categoryCode = Database::escape_string($categoryCode);
176
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
177
                    FROM $course_user_table cu
178
                    INNER JOIN $course_table c ON c.id = cu.c_id
179
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
180
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
181
                    INNER JOIN $user_table u ON u.id = cu.user_id
182
                    WHERE $where AND cc.code = '$categoryCode'";
183
            }
184
        }
185
186
        $res = Database::query($sql);
187
        $obj = Database::fetch_object($res);
188
189
        return $obj->number;
190
    }
191
192
    /**
193
     * @param string $startDate
194
     * @param string $endDate
195
     *
196
     * @return array
197
     */
198
    public static function getCoursesWithActivity($startDate, $endDate)
199
    {
200
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
201
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
202
        $startDate = Database::escape_string($startDate);
203
        $endDate = Database::escape_string($endDate);
204
205
        $urlId = api_get_current_access_url_id();
206
207
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

207
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
208
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
209
                    WHERE
210
                        t.c_id = a.c_id AND
211
                        access_url_id='".$urlId."' AND
212
                        access_date BETWEEN '$startDate' AND '$endDate'
213
                    ";
214
        } else {
215
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t
216
                   access_date BETWEEN '$startDate' AND '$endDate' ";
217
        }
218
219
        $result = Database::query($sql);
220
221
        return Database::store_result($result);
222
    }
223
224
    /**
225
     * Count activities from track_e_default_table.
226
     *
227
     * @return int Number of activities counted
228
     */
229
    public static function getNumberOfActivities($courseId = 0, $sessionId = 0)
230
    {
231
        // Database table definitions
232
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
233
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
234
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
235
        $urlId = api_get_current_access_url_id();
236
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

236
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
237
            $sql = "SELECT count(default_id) AS total_number_of_items
238
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
239
                    WHERE user.active <> ".USER_SOFT_DELETED." AND
240
                        default_user_id = user.id AND
241
                        user.id=url.user_id AND
242
                        access_url_id = '".$urlId."'";
243
        } else {
244
            $sql = "SELECT count(default_id) AS total_number_of_items
245
                    FROM $track_e_default, $table_user user
246
                    WHERE user.active <> ".USER_SOFT_DELETED." AND default_user_id = user.id ";
247
        }
248
249
        if (!empty($courseId)) {
250
            $courseId = (int) $courseId;
251
            $sql .= " AND c_id = $courseId";
252
            $sql .= api_get_session_condition($sessionId);
253
        }
254
255
        if (isset($_GET['keyword'])) {
256
            $keyword = Database::escape_string(trim($_GET['keyword']));
257
            $sql .= " AND (
258
                        user.username LIKE '%".$keyword."%' OR
259
                        default_event_type LIKE '%".$keyword."%' OR
260
                        default_value_type LIKE '%".$keyword."%' OR
261
                        default_value LIKE '%".$keyword."%') ";
262
        }
263
        $res = Database::query($sql);
264
        $obj = Database::fetch_object($res);
265
266
        return $obj->total_number_of_items;
267
    }
268
269
    /**
270
     * Get activities data to display.
271
     *
272
     * @param int    $from
273
     * @param int    $numberOfItems
274
     * @param int    $column
275
     * @param string $direction
276
     * @param int    $courseId
277
     * @param int    $sessionId
278
     *
279
     * @return array
280
     */
281
    public static function getActivitiesData(
282
        $from,
283
        $numberOfItems,
284
        $column,
285
        $direction,
286
        $courseId = 0,
287
        $sessionId = 0
288
    ) {
289
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
290
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
291
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
292
        $urlId = api_get_current_access_url_id();
293
        $column = (int) $column;
294
        $from = (int) $from;
295
        $numberOfItems = (int) $numberOfItems;
296
        $direction = strtoupper($direction);
297
298
        if (!in_array($direction, ['ASC', 'DESC'])) {
299
            $direction = 'DESC';
300
        }
301
302
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

302
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
303
            $sql = "SELECT
304
                        default_event_type  as col0,
305
                        default_value_type    as col1,
306
                        default_value        as col2,
307
                        c_id         as col3,
308
                        session_id as col4,
309
                        user.username         as col5,
310
                        user.id         as col6,
311
                        default_date         as col7
312
                    FROM $track_e_default as track_default,
313
                    $table_user as user,
314
                    $access_url_rel_user_table as url
315
                    WHERE
316
                        user.active <> -1 AND
317
                        track_default.default_user_id = user.id AND
318
                        url.user_id = user.id AND
319
                        access_url_id= $urlId ";
320
        } else {
321
            $sql = "SELECT
322
                       default_event_type  as col0,
323
                       default_value_type    as col1,
324
                       default_value        as col2,
325
                       c_id         as col3,
326
                       session_id as col4,
327
                       user.username         as col5,
328
                       user.id         as col6,
329
                       default_date         as col7
330
                   FROM $track_e_default track_default, $table_user user
331
                   WHERE user.active <> ".USER_SOFT_DELETED." AND track_default.default_user_id = user.id ";
332
        }
333
334
        if (!empty($_GET['keyword'])) {
335
            $keyword = Database::escape_string(trim($_GET['keyword']));
336
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
337
                        default_event_type LIKE '%".$keyword."%' OR
338
                        default_value_type LIKE '%".$keyword."%' OR
339
                        default_value LIKE '%".$keyword."%') ";
340
        }
341
342
        if (!empty($courseId)) {
343
            $courseId = (int) $courseId;
344
            $sql .= " AND c_id = $courseId";
345
            $sql .= api_get_session_condition($sessionId);
346
        }
347
348
        if (!empty($column) && !empty($direction)) {
349
            $sql .= " ORDER BY col$column $direction";
350
        } else {
351
            $sql .= " ORDER BY col7 DESC ";
352
        }
353
        $sql .= " LIMIT $from, $numberOfItems ";
354
355
        $res = Database::query($sql);
356
        $activities = [];
357
        while ($row = Database::fetch_row($res)) {
358
            if (false === strpos($row[1], '_object') &&
359
                false === strpos($row[1], '_array')
360
            ) {
361
                $row[2] = $row[2];
362
            } else {
363
                if (!empty($row[2])) {
364
                    $originalData = str_replace('\\', '', $row[2]);
365
                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
366
                    if (is_array($row[2]) && !empty($row[2])) {
367
                        $row[2] = implode_with_key(', ', $row[2]);
368
                    } else {
369
                        $row[2] = $originalData;
370
                    }
371
                }
372
            }
373
374
            if (!empty($row['default_date'])) {
375
                $row['default_date'] = api_get_local_time($row['default_date']);
376
            } else {
377
                $row['default_date'] = '-';
378
            }
379
380
            if (!empty($row[7])) {
381
                $row[7] = api_get_local_time($row[7]);
382
            } else {
383
                $row[7] = '-';
384
            }
385
386
            if (!empty($row[5])) {
387
                // Course
388
                if (!empty($row[3])) {
389
                    $row[3] = Display::url(
390
                        $row[3],
391
                        api_get_path(WEB_CODE_PATH).'admin/course_edit.php?id='.$row[3]
392
                    );
393
                } else {
394
                    $row[3] = '-';
395
                }
396
397
                // session
398
                if (!empty($row[4])) {
399
                    $row[4] = Display::url(
400
                        $row[4],
401
                        api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$row[4]
402
                    );
403
                } else {
404
                    $row[4] = '-';
405
                }
406
407
                // User id.
408
                $row[5] = Display::url(
409
                    $row[5],
410
                    api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$row[6],
411
                    ['class' => 'ajax']
412
                );
413
414
                $row[6] = Tracking::get_ip_from_user_event(
415
                    $row[6],
416
                    $row[7],
417
                    true
418
                );
419
                if (empty($row[6])) {
420
                    $row[6] = get_lang('Unknown');
421
                }
422
            }
423
            $activities[] = $row;
424
        }
425
426
        return $activities;
427
    }
428
429
    /**
430
     * Rescale data.
431
     *
432
     * @param array $data The data that should be rescaled
433
     * @param int   $max  The maximum value in the rescaled data (default = 500);
434
     *
435
     * @return array The rescaled data, same key as $data
436
     */
437
    public static function rescale($data, $max = 500)
438
    {
439
        $data_max = 1;
440
        foreach ($data as $index => $value) {
441
            $data_max = ($data_max < $value ? $value : $data_max);
442
        }
443
        reset($data);
444
        $result = [];
445
        $delta = $max / $data_max;
446
        foreach ($data as $index => $value) {
447
            $result[$index] = (int) round($value * $delta);
448
        }
449
450
        return $result;
451
    }
452
453
    /**
454
     * Show statistics.
455
     *
456
     * @param string $title      The title
457
     * @param array  $stats
458
     * @param bool   $showTotal
459
     * @param bool   $isFileSize
460
     *
461
     * @return string HTML table
462
     */
463
    public static function printStats(
464
        $title,
465
        $stats,
466
        $showTotal = true,
467
        $isFileSize = false
468
    ) {
469
        $total = 0;
470
        $content = '<table class="table table-hover table-striped data_table stats_table" cellspacing="0" cellpadding="3" width="90%">
471
            <thead><tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr></thead><tbody>';
472
        $i = 0;
473
        foreach ($stats as $subtitle => $number) {
474
            $total += $number;
475
        }
476
477
        foreach ($stats as $subtitle => $number) {
478
            if (!$isFileSize) {
479
                $number_label = number_format($number, 0, ',', '.');
480
            } else {
481
                $number_label = self::makeSizeString($number);
482
            }
483
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
484
485
            $content .= '<tr class="row_'.(0 == $i % 2 ? 'odd' : 'even').'">
486
                    <td width="25%" style="vertical-align:top;">'.$subtitle.'</td>
487
                    <td width="60%">'.Display::bar_progress($percentage, false).'</td>
488
                    <td width="5%" align="right" style="vertical-align:top;">'.$number_label.'</td>';
489
            if ($showTotal) {
490
                $content .= '<td width="5%" align="right"> '.$percentage.'%</td>';
491
            }
492
            $content .= '</tr>';
493
            $i++;
494
        }
495
        $content .= '</tbody>';
496
        if ($showTotal) {
497
            if (!$isFileSize) {
498
                $total_label = number_format($total, 0, ',', '.');
499
            } else {
500
                $total_label = self::makeSizeString($total);
501
            }
502
            $content .= '
503
                <tfoot><tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr></tfoot>
504
            ';
505
        }
506
        $content .= '</table>';
507
508
        return $content;
509
    }
510
511
    /**
512
     * Show some stats about the number of logins.
513
     *
514
     * @param string $type month, hour or day
515
     */
516
    public static function printLoginStats($type)
517
    {
518
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
519
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
520
        $urlId = api_get_current_access_url_id();
521
522
        $table_url = null;
523
        $where_url = null;
524
        $now = api_get_utc_datetime();
525
        $where_url_last = ' WHERE login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
526
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

526
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
527
            $table_url = ", $access_url_rel_user_table";
528
            $where_url = " WHERE login_user_id=user_id AND access_url_id='".$urlId."'";
529
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
530
        }
531
532
        $period = get_lang('Month');
533
        $periodCollection = api_get_months_long();
534
        $sql = "SELECT
535
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
536
                count( login_id ) AS number_of_logins
537
                FROM $table $table_url $where_url
538
                GROUP BY stat_date
539
                ORDER BY login_date DESC";
540
        $sql_last_x = null;
541
542
        switch ($type) {
543
            case 'hour':
544
                $period = get_lang('Hour');
545
                $sql = "SELECT
546
                          DATE_FORMAT( login_date, '%H') AS stat_date,
547
                          count( login_id ) AS number_of_logins
548
                        FROM $table $table_url $where_url
549
                        GROUP BY stat_date
550
                        ORDER BY stat_date ";
551
                $sql_last_x = "SELECT
552
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
553
                                count( login_id ) AS number_of_logins
554
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
555
                               GROUP BY stat_date
556
                               ORDER BY stat_date ";
557
                break;
558
            case 'day':
559
                $periodCollection = api_get_week_days_long();
560
                $period = get_lang('Day');
561
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date ,
562
                        count( login_id ) AS number_of_logins
563
                        FROM  $table $table_url $where_url
564
                        GROUP BY stat_date
565
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
566
                $sql_last_x = "SELECT
567
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
568
                                count( login_id ) AS number_of_logins
569
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
570
                               GROUP BY stat_date
571
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
572
                break;
573
        }
574
575
        $content = '';
576
        if ($sql_last_x) {
577
            $res_last_x = Database::query($sql_last_x);
578
            $result_last_x = [];
579
            while ($obj = Database::fetch_object($res_last_x)) {
580
                $stat_date = ('day' === $type) ? $periodCollection[$obj->stat_date] : $obj->stat_date;
581
                $result_last_x[$stat_date] = $obj->number_of_logins;
582
            }
583
            $content .= self::printStats(get_lang('LastLogins').' ('.$period.')', $result_last_x, true);
584
            flush(); //flush web request at this point to see something already while the full data set is loading
585
            $content .= '<br />';
586
        }
587
        $res = Database::query($sql);
588
        $result = [];
589
        while ($obj = Database::fetch_object($res)) {
590
            $stat_date = $obj->stat_date;
591
            switch ($type) {
592
                case 'month':
593
                    $stat_date = explode('-', $stat_date);
594
                    $stat_date[1] = $periodCollection[$stat_date[1] - 1];
595
                    $stat_date = implode(' ', $stat_date);
596
                    break;
597
                case 'day':
598
                    $stat_date = $periodCollection[$stat_date];
599
                    break;
600
            }
601
            $result[$stat_date] = $obj->number_of_logins;
602
        }
603
        $content .= self::printStats(get_lang('AllLogins').' ('.$period.')', $result, true);
604
605
        return $content;
606
    }
607
608
    /**
609
     * Print the number of recent logins.
610
     *
611
     * @param bool  $distinct        whether to only give distinct users stats, or *all* logins
612
     * @param int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
613
     * @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)
614
     *
615
     * @throws Exception
616
     *
617
     * @return string HTML table
618
     */
619
    public static function printRecentLoginStats($distinct = false, $sessionDuration = 0, $periods = [])
620
    {
621
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
622
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
623
        $urlId = api_get_current_access_url_id();
624
        $table_url = '';
625
        $where_url = '';
626
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

626
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
627
            $table_url = ", $access_url_rel_user_table";
628
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
629
        }
630
631
        $now = api_get_utc_datetime();
632
        $field = 'login_id';
633
        if ($distinct) {
634
            $field = 'DISTINCT(login_user_id)';
635
        }
636
637
        if (empty($periods)) {
638
            $periods = [1, 7, 31];
639
        }
640
        $sqlList = [];
641
642
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
643
        foreach ($periods as $day) {
644
            $date = new DateTime($now);
645
            $startDate = $date->format('Y-m-d').' 00:00:00';
646
            $endDate = $date->format('Y-m-d').' 23:59:59';
647
648
            if ($day > 1) {
649
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
650
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
651
            }
652
653
            $localDate = api_get_local_time($startDate, null, null, false, false);
654
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
655
656
            $label = sprintf(get_lang('Last %s days'), $day);
657
            if (1 == $day) {
658
                $label = get_lang('Today');
659
            }
660
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
661
            $sql = "SELECT count($field) AS number
662
                    FROM $table $table_url
663
                    WHERE ";
664
            if (0 == $sessionDuration) {
665
                $sql .= " logout_date != login_date AND ";
666
            } else {
667
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
668
            }
669
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
670
                        $where_url";
671
            $sqlList[$label] = $sql;
672
        }
673
674
        $sql = "SELECT count($field) AS number
675
                FROM $table $table_url ";
676
        if (0 == $sessionDuration) {
677
            $sql .= " WHERE logout_date != login_date $where_url";
678
        } else {
679
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
680
        }
681
        $sqlList[get_lang('Total')] = $sql;
682
        $totalLogin = [];
683
        foreach ($sqlList as $label => $query) {
684
            $res = Database::query($query);
685
            $obj = Database::fetch_object($res);
686
            $totalLogin[$label] = $obj->number;
687
        }
688
689
        if ($distinct) {
690
            $content = self::printStats(get_lang('DistinctUsersLogins'), $totalLogin, false);
691
        } else {
692
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
693
        }
694
695
        return $content;
696
    }
697
698
    /**
699
     * Get the number of recent logins.
700
     *
701
     * @param bool $distinct            Whether to only give distinct users stats, or *all* logins
702
     * @param int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
703
     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
704
     *
705
     * @throws Exception
706
     *
707
     * @return array
708
     */
709
    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
710
    {
711
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
712
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
713
        $urlId = api_get_current_access_url_id();
714
        $table_url = '';
715
        $where_url = '';
716
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

716
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
717
            $table_url = ", $access_url_rel_user_table";
718
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
719
        }
720
721
        $now = api_get_utc_datetime();
722
        $date = new DateTime($now);
723
        $date->sub(new DateInterval('P31D'));
724
        $newDate = $date->format('Y-m-d h:i:s');
725
        $totalLogin = self::buildDatesArray($newDate, $now, true);
726
727
        $field = 'login_id';
728
        if ($distinct) {
729
            $field = 'DISTINCT(login_user_id)';
730
        }
731
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
732
733
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
734
                FROM $table $table_url
735
                WHERE ";
736
        if (0 == $sessionDuration) {
737
            $sql .= " logout_date != login_date AND ";
738
        } else {
739
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
740
        }
741
        $sql .= " login_date >= '$newDate' $where_url
742
                GROUP BY date(login_date)";
743
744
        $res = Database::query($sql);
745
        while ($row = Database::fetch_assoc($res)) {
746
            $monthAndDay = substr($row['login_date'], 5, 5);
747
            $totalLogin[$monthAndDay] = $row['number'];
748
        }
749
750
        return $totalLogin;
751
    }
752
753
    /**
754
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
755
     */
756
    public static function getToolsStats()
757
    {
758
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
759
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
760
        $urlId = api_get_current_access_url_id();
761
762
        $tools = [
763
            'announcement',
764
            'assignment',
765
            'calendar_event',
766
            'chat',
767
            'course_description',
768
            'document',
769
            'dropbox',
770
            'group',
771
            'learnpath',
772
            'link',
773
            'quiz',
774
            'student_publication',
775
            'user',
776
            'forum',
777
        ];
778
        $tool_names = [];
779
        foreach ($tools as $tool) {
780
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
781
        }
782
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

782
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
783
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
784
                    FROM $table t , $access_url_rel_course_table a
785
                    WHERE
786
                        access_tool IN ('".implode("','", $tools)."') AND
787
                        t.c_id = a.c_id AND
788
                        access_url_id='".$urlId."'
789
                        GROUP BY access_tool
790
                    ";
791
        } else {
792
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
793
                    FROM $table
794
                    WHERE access_tool IN ('".implode("','", $tools)."')
795
                    GROUP BY access_tool ";
796
        }
797
798
        $res = Database::query($sql);
799
        $result = [];
800
        while ($obj = Database::fetch_object($res)) {
801
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
802
        }
803
804
        return $result;
805
    }
806
807
    /**
808
     * Show some stats about the accesses to the different course tools.
809
     *
810
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
811
     *
812
     * @return string HTML table
813
     */
814
    public static function printToolStats($result = null)
815
    {
816
        if (empty($result)) {
817
            $result = self::getToolsStats();
818
        }
819
820
        return self::printStats(get_lang('Tools access'), $result, true);
821
    }
822
823
    /**
824
     * Show some stats about the number of courses per language.
825
     */
826
    public static function printCourseByLanguageStats()
827
    {
828
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
829
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
830
        $urlId = api_get_current_access_url_id();
831
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

831
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
832
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
833
                    FROM $table as c, $access_url_rel_course_table as u
834
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'
835
                    GROUP BY course_language
836
                    ORDER BY number_of_courses DESC";
837
        } else {
838
            $sql = "SELECT course_language, count( code ) AS number_of_courses
839
                   FROM $table GROUP BY course_language
840
                   ORDER BY number_of_courses DESC";
841
        }
842
        $res = Database::query($sql);
843
        $result = [];
844
        while ($obj = Database::fetch_object($res)) {
845
            $result[$obj->course_language] = $obj->number_of_courses;
846
        }
847
848
        return $result;
849
    }
850
851
    /**
852
     * Shows the number of users having their picture uploaded in Dokeos.
853
     */
854
    public static function printUserPicturesStats()
855
    {
856
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
857
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
858
        $urlId = api_get_current_access_url_id();
859
        $url_condition = null;
860
        $url_condition2 = null;
861
        $table = null;
862
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

862
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
863
            $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.id AND access_url_id='".$urlId."'";
864
            $url_condition2 = " AND url.user_id=u.id AND access_url_id='".$urlId."'";
865
            $table = ", $access_url_rel_user_table as url ";
866
        }
867
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
868
        $res = Database::query($sql);
869
        $count1 = Database::fetch_object($res);
870
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table ".
871
               "WHERE LENGTH(picture_uri) > 0 $url_condition2";
872
873
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
874
875
        $res = Database::query($sql);
876
        $count2 = Database::fetch_object($res);
877
        // #users without picture
878
        $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...
879
        $result[get_lang('Yes')] = $count2->n; // #users with picture
880
881
        return self::printStats(get_lang('Number of users').' ('.get_lang('Picture').')', $result, true);
882
    }
883
884
    /**
885
     * Important activities.
886
     */
887
    public static function printActivitiesStats()
888
    {
889
        $content = '<h4>'.get_lang('Important activities').'</h4>';
890
        // Create a search-box
891
        $form = new FormValidator(
892
            'search_simple',
893
            'get',
894
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
895
            '',
896
            ['style' => 'width:200px'],
897
            false
898
        );
899
        $renderer = &$form->defaultRenderer();
900
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
901
        $form->addHidden('report', 'activities');
902
        $form->addHidden('activities_direction', 'DESC');
903
        $form->addHidden('activities_column', '4');
904
        $form->addElement('text', 'keyword', get_lang('Keyword'));
905
        $form->addButtonSearch(get_lang('Search'), 'submit');
906
        $content .= '<div class="actions">';
907
        $content .= $form->returnForm();
908
        $content .= '</div>';
909
910
        $table = new SortableTable(
911
            'activities',
912
            ['Statistics', 'getNumberOfActivities'],
913
            ['Statistics', 'getActivitiesData'],
914
            7,
915
            50,
916
            'DESC'
917
        );
918
        $parameters = [];
919
920
        $parameters['report'] = 'activities';
921
        if (isset($_GET['keyword'])) {
922
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
923
        }
924
925
        $table->set_additional_parameters($parameters);
926
        $table->set_header(0, get_lang('Event type'));
927
        $table->set_header(1, get_lang('Data type'));
928
        $table->set_header(2, get_lang('Value'));
929
        $table->set_header(3, get_lang('Course'));
930
        $table->set_header(4, get_lang('Session'));
931
        $table->set_header(5, get_lang('Username'));
932
        $table->set_header(6, get_lang('IP address'));
933
        $table->set_header(7, get_lang('Date'));
934
        $content .= $table->return_table();
935
936
        return $content;
937
    }
938
939
    /**
940
     * Shows statistics about the time of last visit to each course.
941
     */
942
    public static function printCourseLastVisit()
943
    {
944
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
945
        $urlId = api_get_current_access_url_id();
946
947
        $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...
948
        $columns[1] = 'access_date';
949
        $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...
950
        $sql_order[SORT_DESC] = 'DESC';
951
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
952
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
953
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
954
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
955
956
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
957
            $direction = SORT_ASC;
958
        }
959
        $form = new FormValidator('courselastvisit', 'get');
960
        $form->addElement('hidden', 'report', 'courselastvisit');
961
        $form->addText('date_diff', get_lang('days'), true);
962
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
963
        $form->addButtonSearch(get_lang('Search'), 'submit');
964
        if (!isset($_GET['date_diff'])) {
965
            $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...
966
        } else {
967
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
968
        }
969
        $form->setDefaults($defaults);
970
        $content = $form->returnForm();
971
972
        $values = $form->exportValues();
973
        $date_diff = $values['date_diff'];
974
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
975
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

975
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
976
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
977
                   WHERE
978
                        c_id = a.c_id AND
979
                        access_url_id='".$urlId."'
980
                   GROUP BY c_id
981
                   HAVING c_id <> ''
982
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
983
        } else {
984
            $sql = "SELECT * FROM $table t
985
                   GROUP BY c_id
986
                   HAVING c_id <> ''
987
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
988
        }
989
        $sql .= ' ORDER BY `'.$columns[$column].'` '.$sql_order[$direction];
990
        $from = ($page_nr - 1) * $per_page;
991
        $sql .= ' LIMIT '.$from.','.$per_page;
992
993
        $content .= '<p>'.get_lang('Latest access').' &gt;= '.$date_diff.' '.get_lang('days').'</p>';
994
        $res = Database::query($sql);
995
        if (Database::num_rows($res) > 0) {
996
            $courses = [];
997
            while ($obj = Database::fetch_object($res)) {
998
                $courseInfo = api_get_course_info_by_id($obj->c_id);
999
                $course = [];
1000
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
1001
                // Allow sort by date hiding the numerical date
1002
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
1003
                $courses[] = $course;
1004
            }
1005
            $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...
1006
            $parameters['report'] = 'courselastvisit';
1007
            $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...
1008
            $table_header[] = [get_lang("Latest access"), true];
1009
1010
            ob_start();
1011
            Display:: display_sortable_table(
1012
                $table_header,
1013
                $courses,
1014
                ['column' => $column, 'direction' => $direction],
1015
                [],
1016
                $parameters
1017
            );
1018
            $content .= ob_get_contents();
1019
            ob_end_clean();
1020
        } else {
1021
            $content = get_lang('No search results');
1022
        }
1023
1024
        return $content;
1025
    }
1026
1027
    /**
1028
     * Displays the statistics of the messages sent and received by each user in the social network.
1029
     *
1030
     * @param string $messageType Type of message: 'sent' or 'received'
1031
     *
1032
     * @return array Message list
1033
     */
1034
    public static function getMessages($messageType)
1035
    {
1036
        $messageTable = Database::get_main_table(TABLE_MESSAGE);
1037
        $messageRelUserTable = Database::get_main_table(TABLE_MESSAGE_REL_USER);
1038
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1039
        $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1040
1041
        $urlId = api_get_current_access_url_id();
1042
1043
        switch ($messageType) {
1044
            case 'sent':
1045
                $field = 'm.user_sender_id';
1046
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_SENDER;
1047
                break;
1048
            case 'received':
1049
                $field = 'mru.user_id';
1050
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_TO;
1051
                break;
1052
        }
1053
1054
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1054
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1055
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1056
            FROM $messageTable m
1057
            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...
1058
            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...
1059
            INNER JOIN $accessUrlRelUserTable url ON u.id = url.user_id
1060
            WHERE url.access_url_id = $urlId
1061
            AND u.active <> " . USER_SOFT_DELETED . "
1062
            GROUP BY $field
1063
            ORDER BY count_message DESC";
1064
        } else {
1065
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1066
            FROM $messageTable m
1067
            INNER JOIN $messageRelUserTable mru ON $joinCondition
1068
            INNER JOIN $userTable u ON $field = u.id
1069
            WHERE u.active <> " . USER_SOFT_DELETED . "
1070
            GROUP BY $field
1071
            ORDER BY count_message DESC";
1072
        }
1073
        $res = Database::query($sql);
1074
        $messages_sent = [];
1075
        while ($messages = Database::fetch_array($res)) {
1076
            if (empty($messages['username'])) {
1077
                $messages['username'] = get_lang('Unknown');
1078
            }
1079
            $users = api_get_person_name(
1080
                    $messages['firstname'],
1081
                    $messages['lastname']
1082
                ) . '<br />(' . $messages['username'] . ')';
1083
            $messages_sent[$users] = $messages['count_message'];
1084
        }
1085
1086
        return $messages_sent;
1087
    }
1088
1089
    /**
1090
     * Count the number of friends for social network users.
1091
     */
1092
    public static function getFriends()
1093
    {
1094
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
1095
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1096
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1097
        $urlId = api_get_current_access_url_id();
1098
1099
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1099
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1100
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1101
                    FROM $access_url_rel_user_table as url, $user_friend_table uf
1102
                    LEFT JOIN $user_table u
1103
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1104
                    WHERE
1105
                        uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."' AND
1106
                        uf.user_id = url.user_id AND
1107
                        access_url_id = '".$urlId."'
1108
                    GROUP BY uf.user_id
1109
                    ORDER BY count_friend DESC ";
1110
        } else {
1111
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1112
                    FROM $user_friend_table uf
1113
                    LEFT JOIN $user_table u
1114
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1115
                    WHERE uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."'
1116
                    GROUP BY uf.user_id
1117
                    ORDER BY count_friend DESC ";
1118
        }
1119
        $res = Database::query($sql);
1120
        $list_friends = [];
1121
        while ($friends = Database::fetch_array($res)) {
1122
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1123
            $list_friends[$users] = $friends['count_friend'];
1124
        }
1125
1126
        return $list_friends;
1127
    }
1128
1129
    /**
1130
     * Print the number of users that didn't login for a certain period of time.
1131
     */
1132
    public static function printUsersNotLoggedInStats()
1133
    {
1134
        $totalLogin = [];
1135
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1136
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1137
        $urlId = api_get_current_access_url_id();
1138
        $total = self::countUsers();
1139
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1139
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1140
            $table_url = ", $access_url_rel_user_table";
1141
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
1142
        } else {
1143
            $table_url = '';
1144
            $where_url = '';
1145
        }
1146
        $now = api_get_utc_datetime();
1147
        $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...
1148
            "SELECT count(distinct(login_user_id)) AS number ".
1149
            " FROM $table $table_url ".
1150
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1151
        $sql[get_lang('In the last 7 days')] =
1152
            "SELECT count(distinct(login_user_id)) AS number ".
1153
            " FROM $table $table_url ".
1154
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1155
        $sql[get_lang('In the last 31 days')] =
1156
            "SELECT count(distinct(login_user_id)) AS number ".
1157
            " FROM $table $table_url ".
1158
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1159
        $sql[sprintf(get_lang('Last %d months'), 6)] =
1160
            "SELECT count(distinct(login_user_id)) AS number ".
1161
            " FROM $table $table_url ".
1162
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1163
        $sql[get_lang('Never connected')] =
1164
            "SELECT count(distinct(login_user_id)) AS number ".
1165
            " FROM $table $table_url WHERE 1=1 $where_url";
1166
        foreach ($sql as $index => $query) {
1167
            $res = Database::query($query);
1168
            $obj = Database::fetch_object($res);
1169
            $r = $total - $obj->number;
1170
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1171
        }
1172
1173
        return self::printStats(
1174
            get_lang('Not logged in for some time'),
1175
            $totalLogin,
1176
            false
1177
        );
1178
    }
1179
1180
    /**
1181
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1182
     * within the provided range (including limits). Dates are assumed to be
1183
     * given in UTC.
1184
     *
1185
     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
1186
     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
1187
     * @param bool   $removeYear Whether to remove the year in the results (for easier reading)
1188
     *
1189
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1190
     */
1191
    public static function buildDatesArray($startDate, $endDate, $removeYear = false)
1192
    {
1193
        if (strlen($startDate) > 10) {
1194
            $startDate = substr($startDate, 0, 10);
1195
        }
1196
        if (strlen($endDate) > 10) {
1197
            $endDate = substr($endDate, 0, 10);
1198
        }
1199
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1200
            return false;
1201
        }
1202
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1203
            return false;
1204
        }
1205
        $startTimestamp = strtotime($startDate);
1206
        $endTimestamp = strtotime($endDate);
1207
        $list = [];
1208
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1209
            $datetime = api_get_utc_datetime($time);
1210
            if ($removeYear) {
1211
                $datetime = substr($datetime, 5, 5);
1212
            } else {
1213
                $dateTime = substr($datetime, 0, 10);
1214
            }
1215
            $list[$datetime] = 0;
1216
        }
1217
1218
        return $list;
1219
    }
1220
1221
    /**
1222
     * Prepare the JS code to load a chart.
1223
     *
1224
     * @param string $url     URL for AJAX data generator
1225
     * @param string $type    bar, line, pie, etc
1226
     * @param string $options Additional options to the chart (see chart-specific library)
1227
     * @param string A JS code for loading the chart together with a call to AJAX data generator
1228
     */
1229
    public static function getJSChartTemplate($url, $type = 'pie', $options = '', $elementId = 'canvas')
1230
    {
1231
        $chartCode = '
1232
        <script>
1233
        $(function() {
1234
            $.ajax({
1235
                url: "'.$url.'",
1236
                type: "POST",
1237
                success: function(data) {
1238
                    Chart.defaults.responsive = false;
1239
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1240
                    ctx.canvas.width = 420;
1241
                    ctx.canvas.height = 420;
1242
                    var chart = new Chart(ctx, {
1243
                        type: "'.$type.'",
1244
                        data: data,
1245
                        options: {
1246
                            plugins: {
1247
                                '.$options.'
1248
                            },
1249
                            cutout: "25%"
1250
                        }
1251
                    });
1252
                    var title = chart.options.plugins.title.text;
1253
                    $("#'.$elementId.'_title").html(title);
1254
                    $("#'.$elementId.'_table").html(data.table);
1255
                }
1256
            });
1257
        });
1258
        </script>';
1259
1260
        return $chartCode;
1261
    }
1262
1263
    public static function getJSChartTemplateWithData(
1264
        $data,
1265
        $type = 'pie',
1266
        $options = '',
1267
        $elementId = 'canvas',
1268
        $responsive = true,
1269
        $onClickHandler = '',
1270
        $extraButtonHandler = '',
1271
        $canvasDimensions = ['width' => 420, 'height' => 420]
1272
    ): string {
1273
        $data = json_encode($data);
1274
        $responsiveValue = $responsive ? 'true' : 'false';
1275
1276
        $indexAxisOption = '';
1277
        if ($type === 'bar') {
1278
            $indexAxisOption = 'indexAxis: "y",';
1279
        }
1280
1281
        $onClickScript = '';
1282
        if (!empty($onClickHandler)) {
1283
            $onClickScript = '
1284
                onClick: function(evt) {
1285
                    '.$onClickHandler.'
1286
                },
1287
            ';
1288
        }
1289
1290
        $canvasSize = '';
1291
        if ($responsiveValue === 'false') {
1292
            $canvasSize = '
1293
            ctx.canvas.width = '.$canvasDimensions['width'].';
1294
            ctx.canvas.height = '.$canvasDimensions['height'].';
1295
            ';
1296
        }
1297
1298
        $chartCode = '
1299
        <script>
1300
            $(function() {
1301
                Chart.defaults.responsive = '.$responsiveValue.';
1302
                var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1303
                '.$canvasSize.'
1304
                var chart = new Chart(ctx, {
1305
                    type: "'.$type.'",
1306
                    data: '.$data.',
1307
                    options: {
1308
                        plugins: {
1309
                            '.$options.',
1310
                            datalabels: {
1311
                                anchor: "end",
1312
                                align: "left",
1313
                                formatter: function(value) {
1314
                                    return value;
1315
                                },
1316
                                color: "#000"
1317
                            },
1318
                        },
1319
                        '.$indexAxisOption.'
1320
                        scales: {
1321
                            x: { beginAtZero: true },
1322
                            y: { barPercentage: 0.5 }
1323
                        },
1324
                        '.$onClickScript.'
1325
                    }
1326
                });
1327
                var title = chart.options.plugins.title.text;
1328
                $("#'.$elementId.'_title").html(title);
1329
                $("#'.$elementId.'_table").html(chart.data.datasets[0].data);
1330
1331
                '.$extraButtonHandler.'
1332
            });
1333
        </script>';
1334
1335
        return $chartCode;
1336
    }
1337
1338
    public static function buildJsChartData($all, $chartName)
1339
    {
1340
        $list = [];
1341
        $palette = ChamiloApi::getColorPalette(true, true);
1342
        foreach ($all as $tick => $tock) {
1343
            $list['labels'][] = $tick;
1344
        }
1345
1346
        $list['datasets'][0]['label'] = $chartName;
1347
        $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
1348
1349
        $i = 0;
1350
        foreach ($all as $tick => $tock) {
1351
            $j = $i % count($palette);
1352
            $list['datasets'][0]['data'][] = $tock;
1353
            $list['datasets'][0]['backgroundColor'][] = $palette[$j];
1354
            $i++;
1355
        }
1356
1357
        $scoreDisplay = ScoreDisplay::instance();
1358
        $table = new HTML_Table(['class' => 'data_table stats_table']);
1359
        $headers = [
1360
            get_lang('Name'),
1361
            get_lang('Count'),
1362
            get_lang('Percentage'),
1363
        ];
1364
        $row = 0;
1365
        $column = 0;
1366
        foreach ($headers as $header) {
1367
            $table->setHeaderContents($row, $column, $header);
1368
            $column++;
1369
        }
1370
1371
        $total = 0;
1372
        foreach ($all as $name => $value) {
1373
            $total += $value;
1374
        }
1375
        $row++;
1376
        foreach ($all as $name => $value) {
1377
            $table->setCellContents($row, 0, $name);
1378
            $table->setCellContents($row, 1, $value);
1379
            $table->setCellContents($row, 2, $scoreDisplay->display_score([$value, $total], SCORE_PERCENT));
1380
            $row++;
1381
        }
1382
        $table = Display::page_subheader2($chartName).$table->toHtml();
1383
1384
        return ['chart' => $list, 'table' => $table];
1385
    }
1386
1387
    /**
1388
     * Display the Logins By Date report and allow export its result to XLS.
1389
     */
1390
    public static function printLoginsByDate()
1391
    {
1392
        if (isset($_GET['export']) && 'xls' === $_GET['export']) {
1393
            $result = self::getLoginsByDate($_GET['start'], $_GET['end']);
1394
            $data = [[get_lang('Username'), get_lang('First name'), get_lang('Last name'), get_lang('Total time')]];
1395
1396
            foreach ($result as $i => $item) {
1397
                $data[] = [
1398
                    $item['username'],
1399
                    $item['firstname'],
1400
                    $item['lastname'],
1401
                    api_time_to_hms($item['time_count']),
1402
                ];
1403
            }
1404
1405
            Export::arrayToXls($data);
1406
            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...
1407
        }
1408
1409
        $content = Display::page_header(get_lang('Logins by date'));
1410
1411
        $actions = '';
1412
        $form = new FormValidator('frm_logins_by_date', 'get');
1413
        $form->addDateRangePicker(
1414
            'daterange',
1415
            get_lang('Date range'),
1416
            true,
1417
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1418
        );
1419
        $form->addHidden('report', 'logins_by_date');
1420
        $form->addButtonFilter(get_lang('Search'));
1421
1422
        if ($form->validate()) {
1423
            $values = $form->exportValues();
1424
1425
            $result = self::getLoginsByDate($values['daterange_start'], $values['daterange_end']);
1426
1427
            if (!empty($result)) {
1428
                $actions = Display::url(
1429
                    Display::getMdiIcon(ActionIcon::EXPORT_SPREADSHEET, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('ExportToXls')),
1430
                    api_get_self().'?'.http_build_query(
1431
                        [
1432
                            'report' => 'logins_by_date',
1433
                            'export' => 'xls',
1434
                            'start' => Security::remove_XSS($values['daterange_start']),
1435
                            'end' => Security::remove_XSS($values['daterange_end']),
1436
                        ]
1437
                    )
1438
                );
1439
            }
1440
1441
            $table = new HTML_Table(['class' => 'data_table stats_table']);
1442
            $table->setHeaderContents(0, 0, get_lang('Username'));
1443
            $table->setHeaderContents(0, 1, get_lang('First name'));
1444
            $table->setHeaderContents(0, 2, get_lang('Last name'));
1445
            $table->setHeaderContents(0, 3, get_lang('Total time'));
1446
1447
            foreach ($result as $i => $item) {
1448
                $table->setCellContents($i + 1, 0, $item['username']);
1449
                $table->setCellContents($i + 1, 1, $item['firstname']);
1450
                $table->setCellContents($i + 1, 2, $item['lastname']);
1451
                $table->setCellContents($i + 1, 3, api_time_to_hms($item['time_count']));
1452
            }
1453
1454
            $table->setColAttributes(0, ['class' => 'text-center']);
1455
            $table->setColAttributes(3, ['class' => 'text-center']);
1456
            $content = $table->toHtml();
1457
        }
1458
1459
        $content .= $form->returnForm();
1460
1461
        if (!empty($actions)) {
1462
            $content .= Display::toolbarAction('logins_by_date_toolbar', [$actions]);
1463
        }
1464
1465
        return $content;
1466
    }
1467
1468
    public static function getBossTable($bossId)
1469
    {
1470
        $students = UserManager::getUsersFollowedByStudentBoss(
1471
            $bossId,
1472
            0,
1473
            false,
1474
            false,
1475
            false,
1476
            null,
1477
            null,
1478
            null,
1479
            null,
1480
            1
1481
        );
1482
1483
        if (!empty($students)) {
1484
            $table = new HTML_Table(['class' => 'table table-responsive', 'id' => 'table_'.$bossId]);
1485
            $headers = [
1486
                get_lang('Name'),
1487
                //get_lang('LastName'),
1488
            ];
1489
            $row = 0;
1490
            $column = 0;
1491
            foreach ($headers as $header) {
1492
                $table->setHeaderContents($row, $column, $header);
1493
                $column++;
1494
            }
1495
            $row++;
1496
            foreach ($students as $student) {
1497
                $column = 0;
1498
                $content = api_get_person_name($student['firstname'], $student['lastname']).'';
1499
                $content = '<div style="width: 200px; overflow-wrap: break-word;">'.$content.'</div>';
1500
                $table->setCellContents(
1501
                    $row,
1502
                    $column++,
1503
                    $content
1504
                );
1505
                $row++;
1506
            }
1507
1508
            return $table->toHtml();
1509
        }
1510
1511
        return '<table id="table_'.$bossId.'"></table>';
1512
    }
1513
1514
    /**
1515
     * @param string $startDate
1516
     * @param string $endDate
1517
     *
1518
     * @return array
1519
     */
1520
    private static function getLoginsByDate($startDate, $endDate)
1521
    {
1522
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1523
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1524
1525
        if (empty($startDate) || empty($endDate)) {
1526
            return [];
1527
        }
1528
1529
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1530
        $tblLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1531
        $urlJoin = '';
1532
        $urlWhere = '';
1533
1534
        if (api_is_multiple_url_enabled()) {
0 ignored issues
show
Deprecated Code introduced by
The function api_is_multiple_url_enabled() has been deprecated: Use AccessUrlHelper::isMultiple ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1534
        if (/** @scrutinizer ignore-deprecated */ api_is_multiple_url_enabled()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1535
            $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1536
1537
            $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1538
            $urlWhere = 'AND au.access_url_id = '.api_get_current_access_url_id();
1539
        }
1540
1541
        $sql = "SELECT u.id,
1542
                    u.firstname,
1543
                    u.lastname,
1544
                    u.username,
1545
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1546
                FROM $tblUser u
1547
                INNER JOIN $tblLogin l
1548
                ON u.id = l.login_user_id
1549
                $urlJoin
1550
                WHERE u.active <> ".USER_SOFT_DELETED." AND l.login_date BETWEEN '$startDate' AND '$endDate'
1551
                $urlWhere
1552
                GROUP BY u.id";
1553
1554
        $stmt = Database::query($sql);
1555
1556
        return Database::store_result($stmt, 'ASSOC');
1557
    }
1558
1559
    /**
1560
     * Gets the number of new users registered between two dates.
1561
     */
1562
    public static function getNewUserRegistrations(string $startDate, string $endDate): array
1563
    {
1564
        $sql = "SELECT DATE_FORMAT(registration_date, '%Y-%m-%d') as reg_date, COUNT(*) as user_count
1565
            FROM user
1566
            WHERE registration_date BETWEEN '$startDate' AND '$endDate'
1567
            GROUP BY reg_date";
1568
1569
        $result = Database::query($sql);
1570
        $data = [];
1571
        while ($row = Database::fetch_array($result)) {
1572
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1573
            $data[] = ['date' => $row['reg_date'], 'count' => $userCount];
1574
        }
1575
1576
        return $data;
1577
    }
1578
1579
    /**
1580
     * Gets the number of users registered by creator (creator_id) between two dates.
1581
     */
1582
    public static function getUserRegistrationsByCreator(string $startDate, string $endDate): array
1583
    {
1584
        $sql = "SELECT u.creator_id, COUNT(u.id) as user_count, c.firstname, c.lastname
1585
                FROM user u
1586
                LEFT JOIN user c ON u.creator_id = c.id
1587
                WHERE u.registration_date BETWEEN '$startDate' AND '$endDate'
1588
                AND u.creator_id IS NOT NULL
1589
                GROUP BY u.creator_id";
1590
1591
        $result = Database::query($sql);
1592
        $data = [];
1593
        while ($row = Database::fetch_array($result)) {
1594
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1595
            $name = trim($row['firstname'] . ' ' . $row['lastname']);
1596
            if (!empty($name)) {
1597
                $data[] = [
1598
                    'name' => $name,
1599
                    'count' => $userCount
1600
                ];
1601
            }
1602
        }
1603
1604
        return $data;
1605
    }
1606
1607
    /**
1608
     * Initializes an array with dates between two given dates, setting each date's value to 0.
1609
     */
1610
    public static function initializeDateRangeArray(string $startDate, string $endDate): array
1611
    {
1612
        $dateRangeArray = [];
1613
        $currentDate = new DateTime($startDate);
1614
        $endDate = new DateTime($endDate);
1615
1616
        // Loop through the date range and initialize each date with 0
1617
        while ($currentDate <= $endDate) {
1618
            $formattedDate = $currentDate->format('Y-m-d');
1619
            $dateRangeArray[$formattedDate] = 0;
1620
            $currentDate->modify('+1 day');
1621
        }
1622
1623
        return $dateRangeArray;
1624
    }
1625
1626
    /**
1627
     * Checks if the difference between two dates is more than one month.
1628
     */
1629
    public static function isMoreThanAMonth(string $dateStart, string $dateEnd): bool
1630
    {
1631
        $startDate = new DateTime($dateStart);
1632
        $endDate = new DateTime($dateEnd);
1633
1634
        $diff = $startDate->diff($endDate);
1635
1636
        if ($diff->y >= 1) {
1637
            return true;
1638
        }
1639
1640
        if ($diff->m > 1) {
1641
            return true;
1642
        }
1643
1644
        if ($diff->m == 1) {
1645
            return $diff->d > 0;
1646
        }
1647
1648
        return false;
1649
    }
1650
1651
    /**
1652
     * Groups registration data by month.
1653
     */
1654
    public static function groupByMonth(array $registrations): array
1655
    {
1656
        $groupedData = [];
1657
1658
        foreach ($registrations as $registration) {
1659
            $monthYear = (new DateTime($registration['date']))->format('Y-m');
1660
            if (isset($groupedData[$monthYear])) {
1661
                $groupedData[$monthYear] += $registration['count'];
1662
            } else {
1663
                $groupedData[$monthYear] = $registration['count'];
1664
            }
1665
        }
1666
1667
        return $groupedData;
1668
    }
1669
1670
    /**
1671
     * Retrieves the available tools using the repository.
1672
     */
1673
    public static function getAvailableTools(): array
1674
    {
1675
        $em = Database::getManager();
1676
        $repo = $em->getRepository(ResourceLink::class);
1677
1678
        return $repo->getAvailableTools();
1679
    }
1680
1681
    /**
1682
     * Generates a report of tool usage based on the provided tool IDs.
1683
     */
1684
    public static function getToolUsageReportByTools(array $toolIds): array
1685
    {
1686
        $em = Database::getManager();
1687
        $repo = $em->getRepository(ResourceLink::class);
1688
1689
        return $repo->getToolUsageReportByTools($toolIds);
1690
    }
1691
1692
}
1693