Passed
Pull Request — master (#6042)
by Yannick
08:29
created

Statistics::getJSChartTemplateWithData()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 73
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 27
c 2
b 0
f 0
nc 16
nop 8
dl 0
loc 73
rs 9.1768

How to fix   Long Method    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
5
use Chamilo\CoreBundle\Entity\MessageRelUser;
6
use Chamilo\CoreBundle\Entity\UserRelUser;
7
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
8
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
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|null $categoryCode Code of a course category.
42
     *                                  Default: count all courses.
43
     * @param string|null $dateFrom dateFrom
44
     * @param string|null $dateUntil dateUntil
45
     *
46
     * @return int Number of courses counted
47
     * @throws \Doctrine\DBAL\Exception
48
     */
49
    public static function countCourses(string $categoryCode = null, string $dateFrom = null, string $dateUntil = null): int
50
    {
51
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
52
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
53
        $urlId = api_get_current_access_url_id();
54
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

54
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
55
            $sql = "SELECT COUNT(*) AS number
56
                    FROM ".$courseTable." AS c, $accessUrlRelCourseTable AS u
57
                    WHERE u.c_id = c.id AND $accessUrlRelCourseTable='".$urlId."'";
58
            if (isset($categoryCode)) {
59
                $sql .= " AND category_code = '".Database::escape_string($categoryCode)."'";
60
            }
61
        } else {
62
            $sql = "SELECT COUNT(*) AS number
63
                    FROM $courseTable AS c
64
                    WHERE 1 = 1";
65
            if (isset($categoryCode)) {
66
                $sql .= " WHERE c.category_code = '".Database::escape_string($categoryCode)."'";
67
            }
68
        }
69
70
        if (!empty($dateFrom)) {
71
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
72
            $sql .= " AND c.creation_date >= '$dateFrom' ";
73
        }
74
        if (!empty($dateUntil)) {
75
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
76
            $sql .= " AND c.creation_date <= '$dateUntil' ";
77
        }
78
79
        $res = Database::query($sql);
80
        $obj = Database::fetch_object($res);
81
82
        return $obj->number;
83
    }
84
85
    /**
86
     * Count courses by visibility.
87
     *
88
     * @param array|null  $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
89
     * @param string|null $dateFrom dateFrom
90
     * @param string|null $dateUntil dateUntil
91
     *
92
     * @return int Number of courses counted
93
     * @throws \Doctrine\DBAL\Exception
94
     */
95
    public static function countCoursesByVisibility(
96
        array $visibility = null,
97
        string $dateFrom = null,
98
        string $dateUntil = null
99
    ): int
100
    {
101
        if (empty($visibility)) {
102
            return 0;
103
        } else {
104
            $visibilityString = '';
105
            $auxArrayVisibility = [];
106
            if (!is_array($visibility)) {
107
                $visibility = [$visibility];
108
            }
109
            foreach ($visibility as $item) {
110
                $auxArrayVisibility[] = (int) $item;
111
            }
112
            $visibilityString = implode(',', $auxArrayVisibility);
113
        }
114
        $courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
115
        $accessUrlRelCourseTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
116
        $urlId = api_get_current_access_url_id();
117
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

117
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
118
            $sql = "SELECT COUNT(*) AS number
119
                    FROM $courseTable AS c, $accessUrlRelCourseTable AS u
120
                    WHERE u.c_id = c.id AND u.access_url_id='".$urlId."'";
121
        } else {
122
            $sql = "SELECT COUNT(*) AS number
123
                    FROM $courseTable AS c
124
                    WHERE 1 = 1";
125
        }
126
        $sql .= " AND visibility IN ($visibilityString) ";
127
        if (!empty($dateFrom)) {
128
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
129
            $sql .= " AND c.creation_date >= '$dateFrom' ";
130
        }
131
        if (!empty($dateUntil)) {
132
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
133
            $sql .= " AND c.creation_date <= '$dateUntil' ";
134
        }
135
        $res = Database::query($sql);
136
        $obj = Database::fetch_object($res);
137
138
        return $obj->number;
139
    }
140
141
    /**
142
     * Count users.
143
     *
144
     * @param int    $status                user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
145
     * @param string $categoryCode          course category code. Default: count only users without filtering category
146
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
147
     * @param bool   $onlyActive            Count only active users (false to only return currently active users)
148
     *
149
     * @return int Number of users counted
150
     */
151
    public static function countUsers(
152
        $status = null,
153
        $categoryCode = null,
154
        $countInvisibleCourses = true,
155
        $onlyActive = false
156
    ) {
157
        // Database table definitions
158
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
159
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
160
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
161
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
162
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
163
        $tblCourseRelCategory = Database::get_main_table(TABLE_MAIN_COURSE_REL_CATEGORY);
164
        $urlId = api_get_current_access_url_id();
165
166
        $conditions = [];
167
        $conditions[] = "u.active <> " . USER_SOFT_DELETED;
168
        if ($onlyActive) {
169
            $conditions[] = "u.active = 1";
170
        }
171
        if (isset($status)) {
172
            $conditions[] = "u.status = " . intval($status);
173
        }
174
175
        $where = implode(' AND ', $conditions);
176
177
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

177
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
178
            $sql = "SELECT COUNT(DISTINCT(u.id)) AS number
179
                FROM $user_table as u
180
                INNER JOIN $access_url_rel_user_table as url ON u.id = url.user_id
181
                WHERE $where AND url.access_url_id = $urlId";
182
183
            if (isset($categoryCode)) {
184
                $categoryCode = Database::escape_string($categoryCode);
185
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
186
                    FROM $course_user_table cu
187
                    INNER JOIN $course_table c ON c.id = cu.c_id
188
                    INNER JOIN $access_url_rel_user_table as url ON cu.user_id = url.user_id
189
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
190
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
191
                    WHERE $where AND url.access_url_id = $urlId AND cc.code = '$categoryCode'";
192
            }
193
        } else {
194
            $sql = "SELECT COUNT(DISTINCT(id)) AS number
195
                FROM $user_table u
196
                WHERE $where";
197
198
            if (isset($categoryCode)) {
199
                $categoryCode = Database::escape_string($categoryCode);
200
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
201
                    FROM $course_user_table cu
202
                    INNER JOIN $course_table c ON c.id = cu.c_id
203
                    INNER JOIN $tblCourseRelCategory crc ON crc.course_id = c.id
204
                    INNER JOIN $tblCourseCategory cc ON cc.id = crc.course_category_id
205
                    INNER JOIN $user_table u ON u.id = cu.user_id
206
                    WHERE $where AND cc.code = '$categoryCode'";
207
            }
208
        }
209
210
        $res = Database::query($sql);
211
        $obj = Database::fetch_object($res);
212
213
        return $obj->number;
214
    }
215
216
    /**
217
     * @param string $startDate
218
     * @param string $endDate
219
     *
220
     * @return array
221
     */
222
    public static function getCoursesWithActivity($startDate, $endDate)
223
    {
224
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
225
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
226
        $startDate = Database::escape_string($startDate);
227
        $endDate = Database::escape_string($endDate);
228
229
        $urlId = api_get_current_access_url_id();
230
231
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

231
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
232
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
233
                    WHERE
234
                        t.c_id = a.c_id AND
235
                        access_url_id='".$urlId."' AND
236
                        access_date BETWEEN '$startDate' AND '$endDate'
237
                    ";
238
        } else {
239
            $sql = "SELECT DISTINCT(t.c_id) FROM $table t
240
                   access_date BETWEEN '$startDate' AND '$endDate' ";
241
        }
242
243
        $result = Database::query($sql);
244
245
        return Database::store_result($result);
246
    }
247
248
    /**
249
     * Count activities from track_e_default_table.
250
     *
251
     * @return int Number of activities counted
252
     */
253
    public static function getNumberOfActivities($courseId = 0, $sessionId = 0)
254
    {
255
        // Database table definitions
256
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
257
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
258
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
259
        $urlId = api_get_current_access_url_id();
260
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

260
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
261
            $sql = "SELECT count(default_id) AS total_number_of_items
262
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
263
                    WHERE user.active <> ".USER_SOFT_DELETED." AND
264
                        default_user_id = user.id AND
265
                        user.id=url.user_id AND
266
                        access_url_id = '".$urlId."'";
267
        } else {
268
            $sql = "SELECT count(default_id) AS total_number_of_items
269
                    FROM $track_e_default, $table_user user
270
                    WHERE user.active <> ".USER_SOFT_DELETED." AND default_user_id = user.id ";
271
        }
272
273
        if (!empty($courseId)) {
274
            $courseId = (int) $courseId;
275
            $sql .= " AND c_id = $courseId";
276
            $sql .= api_get_session_condition($sessionId);
277
        }
278
279
        if (isset($_GET['keyword'])) {
280
            $keyword = Database::escape_string(trim($_GET['keyword']));
281
            $sql .= " AND (
282
                        user.username LIKE '%".$keyword."%' OR
283
                        default_event_type LIKE '%".$keyword."%' OR
284
                        default_value_type LIKE '%".$keyword."%' OR
285
                        default_value LIKE '%".$keyword."%') ";
286
        }
287
        $res = Database::query($sql);
288
        $obj = Database::fetch_object($res);
289
290
        return $obj->total_number_of_items;
291
    }
292
293
    /**
294
     * Get activities data to display.
295
     *
296
     * @param int    $from
297
     * @param int    $numberOfItems
298
     * @param int    $column
299
     * @param string $direction
300
     * @param int    $courseId
301
     * @param int    $sessionId
302
     *
303
     * @return array
304
     */
305
    public static function getActivitiesData(
306
        $from,
307
        $numberOfItems,
308
        $column,
309
        $direction,
310
        $courseId = 0,
311
        $sessionId = 0
312
    ) {
313
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
314
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
315
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
316
        $urlId = api_get_current_access_url_id();
317
        $column = (int) $column;
318
        $from = (int) $from;
319
        $numberOfItems = (int) $numberOfItems;
320
        $direction = strtoupper($direction);
321
322
        if (!in_array($direction, ['ASC', 'DESC'])) {
323
            $direction = 'DESC';
324
        }
325
326
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

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

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

550
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
551
            $table_url = ", $access_url_rel_user_table";
552
            $where_url = " WHERE login_user_id=user_id AND access_url_id='".$urlId."'";
553
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
554
        }
555
556
        $period = get_lang('Month');
557
        $periodCollection = api_get_months_long();
558
        $sql = "SELECT
559
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
560
                count( login_id ) AS number_of_logins
561
                FROM $table $table_url $where_url
562
                GROUP BY stat_date
563
                ORDER BY login_date DESC";
564
        $sql_last_x = null;
565
566
        switch ($type) {
567
            case 'hour':
568
                $period = get_lang('Hour');
569
                $sql = "SELECT
570
                          DATE_FORMAT( login_date, '%H') AS stat_date,
571
                          count( login_id ) AS number_of_logins
572
                        FROM $table $table_url $where_url
573
                        GROUP BY stat_date
574
                        ORDER BY stat_date ";
575
                $sql_last_x = "SELECT
576
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
577
                                count( login_id ) AS number_of_logins
578
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
579
                               GROUP BY stat_date
580
                               ORDER BY stat_date ";
581
                break;
582
            case 'day':
583
                $periodCollection = api_get_week_days_long();
584
                $period = get_lang('Day');
585
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) 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 DATE_FORMAT( login_date, '%w' ) ";
590
                $sql_last_x = "SELECT
591
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
592
                                count( login_id ) AS number_of_logins
593
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
594
                               GROUP BY stat_date
595
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
596
                break;
597
        }
598
599
        $content = '';
600
        if ($sql_last_x) {
601
            $res_last_x = Database::query($sql_last_x);
602
            $result_last_x = [];
603
            while ($obj = Database::fetch_object($res_last_x)) {
604
                $stat_date = ('day' === $type) ? $periodCollection[$obj->stat_date] : $obj->stat_date;
605
                $result_last_x[$stat_date] = $obj->number_of_logins;
606
            }
607
            $content .= self::printStats(get_lang('LastLogins').' ('.$period.')', $result_last_x, true);
608
            flush(); //flush web request at this point to see something already while the full data set is loading
609
            $content .= '<br />';
610
        }
611
        $res = Database::query($sql);
612
        $result = [];
613
        while ($obj = Database::fetch_object($res)) {
614
            $stat_date = $obj->stat_date;
615
            switch ($type) {
616
                case 'month':
617
                    $stat_date = explode('-', $stat_date);
618
                    $stat_date[1] = $periodCollection[$stat_date[1] - 1];
619
                    $stat_date = implode(' ', $stat_date);
620
                    break;
621
                case 'day':
622
                    $stat_date = $periodCollection[$stat_date];
623
                    break;
624
            }
625
            $result[$stat_date] = $obj->number_of_logins;
626
        }
627
        $content .= self::printStats(get_lang('AllLogins').' ('.$period.')', $result, true);
628
629
        return $content;
630
    }
631
632
    /**
633
     * Print the number of recent logins.
634
     *
635
     * @param bool  $distinct        whether to only give distinct users stats, or *all* logins
636
     * @param int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
637
     * @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)
638
     *
639
     * @throws Exception
640
     *
641
     * @return string HTML table
642
     */
643
    public static function printRecentLoginStats($distinct = false, $sessionDuration = 0, $periods = [])
644
    {
645
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
646
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
647
        $urlId = api_get_current_access_url_id();
648
        $table_url = '';
649
        $where_url = '';
650
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

650
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
651
            $table_url = ", $access_url_rel_user_table";
652
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
653
        }
654
655
        $now = api_get_utc_datetime();
656
        $field = 'login_id';
657
        if ($distinct) {
658
            $field = 'DISTINCT(login_user_id)';
659
        }
660
661
        if (empty($periods)) {
662
            $periods = [1, 7, 31];
663
        }
664
        $sqlList = [];
665
666
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
667
        foreach ($periods as $day) {
668
            $date = new DateTime($now);
669
            $startDate = $date->format('Y-m-d').' 00:00:00';
670
            $endDate = $date->format('Y-m-d').' 23:59:59';
671
672
            if ($day > 1) {
673
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
674
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
675
            }
676
677
            $localDate = api_get_local_time($startDate, null, null, false, false);
678
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
679
680
            $label = sprintf(get_lang('Last %s days'), $day);
681
            if (1 == $day) {
682
                $label = get_lang('Today');
683
            }
684
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
685
            $sql = "SELECT count($field) AS number
686
                    FROM $table $table_url
687
                    WHERE ";
688
            if (0 == $sessionDuration) {
689
                $sql .= " logout_date != login_date AND ";
690
            } else {
691
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
692
            }
693
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
694
                        $where_url";
695
            $sqlList[$label] = $sql;
696
        }
697
698
        $sql = "SELECT count($field) AS number
699
                FROM $table $table_url ";
700
        if (0 == $sessionDuration) {
701
            $sql .= " WHERE logout_date != login_date $where_url";
702
        } else {
703
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
704
        }
705
        $sqlList[get_lang('Total')] = $sql;
706
        $totalLogin = [];
707
        foreach ($sqlList as $label => $query) {
708
            $res = Database::query($query);
709
            $obj = Database::fetch_object($res);
710
            $totalLogin[$label] = $obj->number;
711
        }
712
713
        if ($distinct) {
714
            $content = self::printStats(get_lang('DistinctUsersLogins'), $totalLogin, false);
715
        } else {
716
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
717
        }
718
719
        return $content;
720
    }
721
722
    /**
723
     * Get the number of recent logins.
724
     *
725
     * @param bool $distinct            Whether to only give distinct users stats, or *all* logins
726
     * @param int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
727
     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
728
     *
729
     * @throws Exception
730
     *
731
     * @return array
732
     */
733
    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
734
    {
735
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
736
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
737
        $urlId = api_get_current_access_url_id();
738
        $table_url = '';
739
        $where_url = '';
740
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

740
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
741
            $table_url = ", $access_url_rel_user_table";
742
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
743
        }
744
745
        $now = api_get_utc_datetime();
746
        $date = new DateTime($now);
747
        $date->sub(new DateInterval('P31D'));
748
        $newDate = $date->format('Y-m-d h:i:s');
749
        $totalLogin = self::buildDatesArray($newDate, $now, true);
750
751
        $field = 'login_id';
752
        if ($distinct) {
753
            $field = 'DISTINCT(login_user_id)';
754
        }
755
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
756
757
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
758
                FROM $table $table_url
759
                WHERE ";
760
        if (0 == $sessionDuration) {
761
            $sql .= " logout_date != login_date AND ";
762
        } else {
763
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
764
        }
765
        $sql .= " login_date >= '$newDate' $where_url
766
                GROUP BY date(login_date)";
767
768
        $res = Database::query($sql);
769
        while ($row = Database::fetch_assoc($res)) {
770
            $monthAndDay = substr($row['login_date'], 5, 5);
771
            $totalLogin[$monthAndDay] = $row['number'];
772
        }
773
774
        return $totalLogin;
775
    }
776
777
    /**
778
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
779
     */
780
    public static function getToolsStats()
781
    {
782
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
783
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
784
        $urlId = api_get_current_access_url_id();
785
786
        $tools = [
787
            'announcement',
788
            'assignment',
789
            'calendar_event',
790
            'chat',
791
            'course_description',
792
            'document',
793
            'dropbox',
794
            'group',
795
            'learnpath',
796
            'link',
797
            'quiz',
798
            'student_publication',
799
            'user',
800
            'forum',
801
        ];
802
        $tool_names = [];
803
        foreach ($tools as $tool) {
804
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
805
        }
806
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

806
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
807
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
808
                    FROM $table t , $access_url_rel_course_table a
809
                    WHERE
810
                        access_tool IN ('".implode("','", $tools)."') AND
811
                        t.c_id = a.c_id AND
812
                        access_url_id='".$urlId."'
813
                        GROUP BY access_tool
814
                    ";
815
        } else {
816
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
817
                    FROM $table
818
                    WHERE access_tool IN ('".implode("','", $tools)."')
819
                    GROUP BY access_tool ";
820
        }
821
822
        $res = Database::query($sql);
823
        $result = [];
824
        while ($obj = Database::fetch_object($res)) {
825
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
826
        }
827
828
        return $result;
829
    }
830
831
    /**
832
     * Show some stats about the accesses to the different course tools.
833
     *
834
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
835
     *
836
     * @return string HTML table
837
     */
838
    public static function printToolStats($result = null)
839
    {
840
        if (empty($result)) {
841
            $result = self::getToolsStats();
842
        }
843
844
        return self::printStats(get_lang('Tools access'), $result, true);
845
    }
846
847
    /**
848
     * Show some stats about the number of courses per language.
849
     */
850
    public static function printCourseByLanguageStats()
851
    {
852
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
853
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
854
        $urlId = api_get_current_access_url_id();
855
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

855
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
856
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
857
                    FROM $table as c, $access_url_rel_course_table as u
858
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'
859
                    GROUP BY course_language
860
                    ORDER BY number_of_courses DESC";
861
        } else {
862
            $sql = "SELECT course_language, count( code ) AS number_of_courses
863
                   FROM $table GROUP BY course_language
864
                   ORDER BY number_of_courses DESC";
865
        }
866
        $res = Database::query($sql);
867
        $result = [];
868
        while ($obj = Database::fetch_object($res)) {
869
            $result[$obj->course_language] = $obj->number_of_courses;
870
        }
871
872
        return $result;
873
    }
874
875
    /**
876
     * Shows the number of users having their picture uploaded in Dokeos.
877
     */
878
    public static function printUserPicturesStats()
879
    {
880
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
881
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
882
        $urlId = api_get_current_access_url_id();
883
        $url_condition = null;
884
        $url_condition2 = null;
885
        $table = null;
886
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

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

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

999
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
1000
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
1001
                   WHERE
1002
                        c_id = a.c_id AND
1003
                        access_url_id='".$urlId."'
1004
                   GROUP BY c_id
1005
                   HAVING c_id <> ''
1006
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1007
        } else {
1008
            $sql = "SELECT * FROM $table t
1009
                   GROUP BY c_id
1010
                   HAVING c_id <> ''
1011
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
1012
        }
1013
        $sql .= ' ORDER BY `'.$columns[$column].'` '.$sql_order[$direction];
1014
        $from = ($page_nr - 1) * $per_page;
1015
        $sql .= ' LIMIT '.$from.','.$per_page;
1016
1017
        $content .= '<p>'.get_lang('Latest access').' &gt;= '.$date_diff.' '.get_lang('days').'</p>';
1018
        $res = Database::query($sql);
1019
        if (Database::num_rows($res) > 0) {
1020
            $courses = [];
1021
            while ($obj = Database::fetch_object($res)) {
1022
                $courseInfo = api_get_course_info_by_id($obj->c_id);
1023
                $course = [];
1024
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
1025
                // Allow sort by date hiding the numerical date
1026
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
1027
                $courses[] = $course;
1028
            }
1029
            $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...
1030
            $parameters['report'] = 'courselastvisit';
1031
            $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...
1032
            $table_header[] = [get_lang("Latest access"), true];
1033
1034
            ob_start();
1035
            Display:: display_sortable_table(
1036
                $table_header,
1037
                $courses,
1038
                ['column' => $column, 'direction' => $direction],
1039
                [],
1040
                $parameters
1041
            );
1042
            $content .= ob_get_contents();
1043
            ob_end_clean();
1044
        } else {
1045
            $content = get_lang('No search results');
1046
        }
1047
1048
        return $content;
1049
    }
1050
1051
    /**
1052
     * Displays the statistics of the messages sent and received by each user in the social network.
1053
     *
1054
     * @param string $messageType Type of message: 'sent' or 'received'
1055
     *
1056
     * @return array Message list
1057
     */
1058
    public static function getMessages($messageType)
1059
    {
1060
        $messageTable = Database::get_main_table(TABLE_MESSAGE);
1061
        $messageRelUserTable = Database::get_main_table(TABLE_MESSAGE_REL_USER);
1062
        $userTable = Database::get_main_table(TABLE_MAIN_USER);
1063
        $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1064
1065
        $urlId = api_get_current_access_url_id();
1066
1067
        switch ($messageType) {
1068
            case 'sent':
1069
                $field = 'm.user_sender_id';
1070
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_SENDER;
1071
                break;
1072
            case 'received':
1073
                $field = 'mru.user_id';
1074
                $joinCondition = "m.id = mru.message_id AND mru.receiver_type = " . MessageRelUser::TYPE_TO;
1075
                break;
1076
        }
1077
1078
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

1078
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
1079
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1080
            FROM $messageTable m
1081
            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...
1082
            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...
1083
            INNER JOIN $accessUrlRelUserTable url ON u.id = url.user_id
1084
            WHERE url.access_url_id = $urlId
1085
            AND u.active <> " . USER_SOFT_DELETED . "
1086
            GROUP BY $field
1087
            ORDER BY count_message DESC";
1088
        } else {
1089
            $sql = "SELECT u.lastname, u.firstname, u.username, COUNT(DISTINCT m.id) AS count_message
1090
            FROM $messageTable m
1091
            INNER JOIN $messageRelUserTable mru ON $joinCondition
1092
            INNER JOIN $userTable u ON $field = u.id
1093
            WHERE u.active <> " . USER_SOFT_DELETED . "
1094
            GROUP BY $field
1095
            ORDER BY count_message DESC";
1096
        }
1097
        $res = Database::query($sql);
1098
        $messages_sent = [];
1099
        while ($messages = Database::fetch_array($res)) {
1100
            if (empty($messages['username'])) {
1101
                $messages['username'] = get_lang('Unknown');
1102
            }
1103
            $users = api_get_person_name(
1104
                    $messages['firstname'],
1105
                    $messages['lastname']
1106
                ) . '<br />(' . $messages['username'] . ')';
1107
            $messages_sent[$users] = $messages['count_message'];
1108
        }
1109
1110
        return $messages_sent;
1111
    }
1112
1113
    /**
1114
     * Count the number of friends for social network users.
1115
     */
1116
    public static function getFriends()
1117
    {
1118
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
1119
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
1120
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1121
        $urlId = api_get_current_access_url_id();
1122
1123
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

1123
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
1124
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1125
                    FROM $access_url_rel_user_table as url, $user_friend_table uf
1126
                    LEFT JOIN $user_table u
1127
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1128
                    WHERE
1129
                        uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."' AND
1130
                        uf.user_id = url.user_id AND
1131
                        access_url_id = '".$urlId."'
1132
                    GROUP BY uf.user_id
1133
                    ORDER BY count_friend DESC ";
1134
        } else {
1135
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
1136
                    FROM $user_friend_table uf
1137
                    LEFT JOIN $user_table u
1138
                    ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
1139
                    WHERE uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."'
1140
                    GROUP BY uf.user_id
1141
                    ORDER BY count_friend DESC ";
1142
        }
1143
        $res = Database::query($sql);
1144
        $list_friends = [];
1145
        while ($friends = Database::fetch_array($res)) {
1146
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1147
            $list_friends[$users] = $friends['count_friend'];
1148
        }
1149
1150
        return $list_friends;
1151
    }
1152
1153
    /**
1154
     * Print the number of users that didn't login for a certain period of time.
1155
     */
1156
    public static function printUsersNotLoggedInStats()
1157
    {
1158
        $totalLogin = [];
1159
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1160
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1161
        $urlId = api_get_current_access_url_id();
1162
        $total = self::countUsers();
1163
        if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

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

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

1558
        if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
1559
            $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1560
1561
            $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1562
            $urlWhere = 'AND au.access_url_id = '.api_get_current_access_url_id();
1563
        }
1564
1565
        $sql = "SELECT u.id,
1566
                    u.firstname,
1567
                    u.lastname,
1568
                    u.username,
1569
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1570
                FROM $tblUser u
1571
                INNER JOIN $tblLogin l
1572
                ON u.id = l.login_user_id
1573
                $urlJoin
1574
                WHERE u.active <> ".USER_SOFT_DELETED." AND l.login_date BETWEEN '$startDate' AND '$endDate'
1575
                $urlWhere
1576
                GROUP BY u.id";
1577
1578
        $stmt = Database::query($sql);
1579
1580
        return Database::store_result($stmt, 'ASSOC');
1581
    }
1582
1583
    /**
1584
     * Gets the number of new users registered between two dates.
1585
     */
1586
    public static function getNewUserRegistrations(string $startDate, string $endDate): array
1587
    {
1588
        $sql = "SELECT DATE_FORMAT(registration_date, '%Y-%m-%d') as reg_date, COUNT(*) as user_count
1589
            FROM user
1590
            WHERE registration_date BETWEEN '$startDate' AND '$endDate'
1591
            GROUP BY reg_date";
1592
1593
        $result = Database::query($sql);
1594
        $data = [];
1595
        while ($row = Database::fetch_array($result)) {
1596
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1597
            $data[] = ['date' => $row['reg_date'], 'count' => $userCount];
1598
        }
1599
1600
        return $data;
1601
    }
1602
1603
    /**
1604
     * Gets the number of users registered by creator (creator_id) between two dates.
1605
     */
1606
    public static function getUserRegistrationsByCreator(string $startDate, string $endDate): array
1607
    {
1608
        $sql = "SELECT u.creator_id, COUNT(u.id) as user_count, c.firstname, c.lastname
1609
                FROM user u
1610
                LEFT JOIN user c ON u.creator_id = c.id
1611
                WHERE u.registration_date BETWEEN '$startDate' AND '$endDate'
1612
                AND u.creator_id IS NOT NULL
1613
                GROUP BY u.creator_id";
1614
1615
        $result = Database::query($sql);
1616
        $data = [];
1617
        while ($row = Database::fetch_array($result)) {
1618
            $userCount = is_numeric($row['user_count']) ? (int) $row['user_count'] : 0;
1619
            $name = trim($row['firstname'] . ' ' . $row['lastname']);
1620
            if (!empty($name)) {
1621
                $data[] = [
1622
                    'name' => $name,
1623
                    'count' => $userCount
1624
                ];
1625
            }
1626
        }
1627
1628
        return $data;
1629
    }
1630
1631
    /**
1632
     * Initializes an array with dates between two given dates, setting each date's value to 0.
1633
     */
1634
    public static function initializeDateRangeArray(string $startDate, string $endDate): array
1635
    {
1636
        $dateRangeArray = [];
1637
        $currentDate = new DateTime($startDate);
1638
        $endDate = new DateTime($endDate);
1639
1640
        // Loop through the date range and initialize each date with 0
1641
        while ($currentDate <= $endDate) {
1642
            $formattedDate = $currentDate->format('Y-m-d');
1643
            $dateRangeArray[$formattedDate] = 0;
1644
            $currentDate->modify('+1 day');
1645
        }
1646
1647
        return $dateRangeArray;
1648
    }
1649
1650
    /**
1651
     * Checks if the difference between two dates is more than one month.
1652
     */
1653
    public static function isMoreThanAMonth(string $dateStart, string $dateEnd): bool
1654
    {
1655
        $startDate = new DateTime($dateStart);
1656
        $endDate = new DateTime($dateEnd);
1657
1658
        $diff = $startDate->diff($endDate);
1659
1660
        if ($diff->y >= 1) {
1661
            return true;
1662
        }
1663
1664
        if ($diff->m > 1) {
1665
            return true;
1666
        }
1667
1668
        if ($diff->m == 1) {
1669
            return $diff->d > 0;
1670
        }
1671
1672
        return false;
1673
    }
1674
1675
    /**
1676
     * Groups registration data by month.
1677
     */
1678
    public static function groupByMonth(array $registrations): array
1679
    {
1680
        $groupedData = [];
1681
1682
        foreach ($registrations as $registration) {
1683
            $monthYear = (new DateTime($registration['date']))->format('Y-m');
1684
            if (isset($groupedData[$monthYear])) {
1685
                $groupedData[$monthYear] += $registration['count'];
1686
            } else {
1687
                $groupedData[$monthYear] = $registration['count'];
1688
            }
1689
        }
1690
1691
        return $groupedData;
1692
    }
1693
1694
    /**
1695
     * Return de number of certificates generated.
1696
     * This function is resource intensive.
1697
     * @throws \Doctrine\DBAL\Exception
1698
     * @throws Exception
1699
     */
1700
    public static function countCertificatesByQuarter(string $dateFrom = null, string $dateUntil = null): int
1701
    {
1702
        $tableGradebookCertificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
1703
        $condition = "";
1704
        if (!empty($dateFrom) && !empty($dateUntil)) {
1705
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1706
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1707
            $condition = "WHERE (created_at BETWEEN '$dateFrom' AND '$dateUntil')";
1708
        } elseif (!empty($dateFrom)) {
1709
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1710
            $condition = "WHERE created_at >= '$dateFrom'";
1711
        } elseif (!empty($dateUntil)) {
1712
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1713
            $condition = "WHERE created_at <= '$dateUntil'";
1714
        }
1715
        $sql = "
1716
            SELECT count(*) AS count
1717
            FROM $tableGradebookCertificate
1718
            $condition
1719
        ";
1720
        $response = Database::query($sql);
1721
        $obj = Database::fetch_object($response);
1722
        return $obj->count;
1723
    }
1724
1725
    /**
1726
     * Get the number of logins by dates.
1727
     * This function is resource intensive.
1728
     * @throws Exception
1729
     */
1730
    public static function getSessionsByDuration(string $dateFrom, string $dateUntil): array
1731
    {
1732
        $results = [
1733
            '0' => 0,
1734
            '5' => 0,
1735
            '10' => 0,
1736
            '15' => 0,
1737
            '30' => 0,
1738
            '60' => 0,
1739
        ];
1740
        if (!empty($dateFrom) && !empty($dateUntil)) {
1741
            $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1742
            $accessUrlRelUserTable = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1743
            $urlId = api_get_current_access_url_id();
1744
            $tableUrl = '';
1745
            $whereUrl = '';
1746
            $dateFrom = api_get_utc_datetime("$dateFrom 00:00:00");
1747
            $dateUntil = api_get_utc_datetime("$dateUntil 23:59:59");
1748
            if (AccessUrlHelper::isMultiple()) {
0 ignored issues
show
Bug Best Practice introduced by
The method Chamilo\CoreBundle\Servi...UrlHelper::isMultiple() is not static, but was called statically. ( Ignorable by Annotation )

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

1748
            if (AccessUrlHelper::/** @scrutinizer ignore-call */ isMultiple()) {
Loading history...
1749
                $tableUrl = ", $accessUrlRelUserTable";
1750
                $whereUrl = " AND login_user_id = user_id AND access_url_id = $urlId";
1751
            }
1752
            $sql = "SELECT login_id, TIMESTAMPDIFF(SECOND, login_date, logout_date) AS duration
1753
            FROM $table $tableUrl
1754
            WHERE login_date >= '$dateFrom'
1755
            AND logout_date <= '$dateUntil'
1756
            $whereUrl
1757
            ";
1758
            $res = Database::query($sql);
1759
            while ($session = Database::fetch_array($res)) {
1760
                if ($session['duration'] > 3600) {
1761
                    $results['60']++;
1762
                } elseif ($session['duration'] > 1800) {
1763
                    $results['30']++;
1764
                } elseif ($session['duration'] > 900) {
1765
                    $results['15']++;
1766
                } elseif ($session['duration'] > 600) {
1767
                    $results['10']++;
1768
                } elseif ($session['duration'] > 300) {
1769
                    $results['5']++;
1770
                } else {
1771
                    $results['0']++;
1772
                }
1773
            }
1774
        }
1775
        return $results;
1776
    }
1777
}
1778