Passed
Pull Request — master (#5678)
by Angel Fernando Quiroz
13:13 queued 05:29
created

Statistics::rescale()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 6
nop 2
dl 0
loc 14
rs 9.9666
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\UserRelUser;
6
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
7
8
/**
9
 * This class provides some functions for statistics.
10
 */
11
class Statistics
12
{
13
    /**
14
     * Converts a number of bytes in a formatted string.
15
     *
16
     * @param int $size
17
     *
18
     * @return string Formatted file size
19
     */
20
    public static function makeSizeString($size)
21
    {
22
        if ($size < pow(2, 10)) {
23
            return $size." bytes";
24
        }
25
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
26
            return round($size / pow(2, 10), 0)." KB";
27
        }
28
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
29
            return round($size / pow(2, 20), 1)." MB";
30
        }
31
        if ($size > pow(2, 30)) {
32
            return round($size / pow(2, 30), 2)." GB";
33
        }
34
    }
35
36
    /**
37
     * Count courses.
38
     *
39
     * @param string $categoryCode Code of a course category.
40
     *                             Default: count all courses.
41
     *
42
     * @return int Number of courses counted
43
     */
44
    public static function countCourses($categoryCode = null)
45
    {
46
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
47
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
48
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
49
        $urlId = api_get_current_access_url_id();
50
51
        $categoryJoin = '';
52
        $categoryCondition = '';
53
54
        if (!empty($categoryCode)) {
55
            //$categoryJoin = " LEFT JOIN $tblCourseCategory course_category ON course.category_id = course_category.id ";
56
            //$categoryCondition = " course_category.code = '".Database::escape_string($categoryCode)."' ";
57
        }
58
59
        $sql = "SELECT COUNT(*) AS number
60
                FROM ".$course_table." as c, $access_url_rel_course_table as u
61
                $categoryJoin
62
                WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
63
        if (isset($categoryCode)) {
64
            $sql .= " AND $categoryCondition";
65
        }
66
67
        $res = Database::query($sql);
68
        $obj = Database::fetch_object($res);
69
70
        return $obj->number;
71
    }
72
73
    /**
74
     * Count courses by visibility.
75
     *
76
     * @param int $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
77
     *
78
     * @return int Number of courses counted
79
     */
80
    public static function countCoursesByVisibility($visibility = null)
81
    {
82
        if (!isset($visibility)) {
83
            return 0;
84
        }
85
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
86
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
87
        $urlId = api_get_current_access_url_id();
88
        $sql = "SELECT COUNT(*) AS number
89
                FROM $course_table as c, $access_url_rel_course_table as u
90
                WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
91
        if (isset($visibility)) {
92
            $sql .= " AND visibility = ".intval($visibility);
93
        }
94
        $res = Database::query($sql);
95
        $obj = Database::fetch_object($res);
96
97
        return $obj->number;
98
    }
99
100
    /**
101
     * Count users.
102
     *
103
     * @param int    $status                user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
104
     * @param string $categoryCode          course category code. Default: count only users without filtering category
105
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
106
     * @param bool   $onlyActive            Count only active users (false to only return currently active users)
107
     *
108
     * @return int Number of users counted
109
     */
110
    public static function countUsers(
111
        $status = null,
112
        $categoryCode = null,
113
        $countInvisibleCourses = true,
114
        $onlyActive = false
115
    ) {
116
        // Database table definitions
117
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
118
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
119
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
120
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
121
        $tblCourseCategory = Database::get_main_table(TABLE_MAIN_CATEGORY);
122
        $tblCourseRelCategory = Database::get_main_table(TABLE_MAIN_COURSE_REL_CATEGORY);
123
        $urlId = api_get_current_access_url_id();
124
        $active_filter = $onlyActive ? ' AND active = 1' : '';
125
        $status_filter = isset($status) ? ' AND status = '.intval($status) : '';
126
127
        $sql = "SELECT COUNT(DISTINCT(u.id)) AS number
128
            FROM $user_table as u, $access_url_rel_user_table as url
129
            WHERE
130
                u.active <> ".USER_SOFT_DELETED." AND
131
                u.id = url.user_id AND
132
                access_url_id = $urlId
133
                $status_filter $active_filter";
134
        if (isset($categoryCode)) {
135
            $categoryCode = Database::escape_string($categoryCode);
136
            $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
137
                FROM $course_user_table cu, $course_table c, $access_url_rel_user_table as url, $tblCourseRelCategory crc, $tblCourseCategory cc
138
                WHERE
139
                    c.id = cu.c_id AND
140
                    cc.code = '$categoryCode' AND
141
                    crc.course_category_id = cc.id AND
142
                    crc.course_id = c.id AND
143
                    cu.user_id = url.user_id AND
144
                    access_url_id = $urlId
145
                    $status_filter $active_filter";
146
        }
147
148
        $res = Database::query($sql);
149
        $obj = Database::fetch_object($res);
150
151
        return $obj->number;
152
    }
153
154
    /**
155
     * @param string $startDate
156
     * @param string $endDate
157
     *
158
     * @return array
159
     */
160
    public static function getCoursesWithActivity($startDate, $endDate)
161
    {
162
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
163
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
164
        $startDate = Database::escape_string($startDate);
165
        $endDate = Database::escape_string($endDate);
166
167
        $urlId = api_get_current_access_url_id();
168
169
        $sql = "SELECT DISTINCT(t.c_id) FROM $table t , $access_url_rel_course_table a
170
            WHERE
171
                t.c_id = a.c_id AND
172
                access_url_id='".$urlId."' AND
173
                access_date BETWEEN '$startDate' AND '$endDate'
174
            ";
175
176
        $result = Database::query($sql);
177
178
        return Database::store_result($result);
179
    }
180
181
    /**
182
     * Count activities from track_e_default_table.
183
     *
184
     * @return int Number of activities counted
185
     */
186
    public static function getNumberOfActivities($courseId = 0, $sessionId = 0)
187
    {
188
        // Database table definitions
189
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
190
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
191
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
192
        $urlId = api_get_current_access_url_id();
193
        $sql = "SELECT count(default_id) AS total_number_of_items
194
            FROM $track_e_default, $table_user user, $access_url_rel_user_table url
195
            WHERE user.active <> ".USER_SOFT_DELETED." AND
196
                default_user_id = user.id AND
197
                user.id=url.user_id AND
198
                access_url_id = '".$urlId."'";
199
200
        if (!empty($courseId)) {
201
            $courseId = (int) $courseId;
202
            $sql .= " AND c_id = $courseId";
203
            $sql .= api_get_session_condition($sessionId);
204
        }
205
206
        if (isset($_GET['keyword'])) {
207
            $keyword = Database::escape_string(trim($_GET['keyword']));
208
            $sql .= " AND (
209
                        user.username LIKE '%".$keyword."%' OR
210
                        default_event_type LIKE '%".$keyword."%' OR
211
                        default_value_type LIKE '%".$keyword."%' OR
212
                        default_value LIKE '%".$keyword."%') ";
213
        }
214
        $res = Database::query($sql);
215
        $obj = Database::fetch_object($res);
216
217
        return $obj->total_number_of_items;
218
    }
219
220
    /**
221
     * Get activities data to display.
222
     *
223
     * @param int    $from
224
     * @param int    $numberOfItems
225
     * @param int    $column
226
     * @param string $direction
227
     * @param int    $courseId
228
     * @param int    $sessionId
229
     *
230
     * @return array
231
     */
232
    public static function getActivitiesData(
233
        $from,
234
        $numberOfItems,
235
        $column,
236
        $direction,
237
        $courseId = 0,
238
        $sessionId = 0
239
    ) {
240
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
241
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
242
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
243
        $urlId = api_get_current_access_url_id();
244
        $column = (int) $column;
245
        $from = (int) $from;
246
        $numberOfItems = (int) $numberOfItems;
247
        $direction = strtoupper($direction);
248
249
        if (!in_array($direction, ['ASC', 'DESC'])) {
250
            $direction = 'DESC';
251
        }
252
253
        $sql = "SELECT
254
                default_event_type  as col0,
255
                default_value_type    as col1,
256
                default_value        as col2,
257
                c_id         as col3,
258
                session_id as col4,
259
                user.username         as col5,
260
                user.id         as col6,
261
                default_date         as col7
262
            FROM $track_e_default as track_default,
263
            $table_user as user,
264
            $access_url_rel_user_table as url
265
            WHERE
266
                user.active <> -1 AND
267
                track_default.default_user_id = user.id AND
268
                url.user_id = user.id AND
269
                access_url_id= $urlId ";
270
271
        if (!empty($_GET['keyword'])) {
272
            $keyword = Database::escape_string(trim($_GET['keyword']));
273
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
274
                        default_event_type LIKE '%".$keyword."%' OR
275
                        default_value_type LIKE '%".$keyword."%' OR
276
                        default_value LIKE '%".$keyword."%') ";
277
        }
278
279
        if (!empty($courseId)) {
280
            $courseId = (int) $courseId;
281
            $sql .= " AND c_id = $courseId";
282
            $sql .= api_get_session_condition($sessionId);
283
        }
284
285
        if (!empty($column) && !empty($direction)) {
286
            $sql .= " ORDER BY col$column $direction";
287
        } else {
288
            $sql .= " ORDER BY col7 DESC ";
289
        }
290
        $sql .= " LIMIT $from, $numberOfItems ";
291
292
        $res = Database::query($sql);
293
        $activities = [];
294
        while ($row = Database::fetch_row($res)) {
295
            if (false === strpos($row[1], '_object') &&
296
                false === strpos($row[1], '_array')
297
            ) {
298
                $row[2] = $row[2];
299
            } else {
300
                if (!empty($row[2])) {
301
                    $originalData = str_replace('\\', '', $row[2]);
302
                    $row[2] = UnserializeApi::unserialize('not_allowed_classes', $originalData);
303
                    if (is_array($row[2]) && !empty($row[2])) {
304
                        $row[2] = implode_with_key(', ', $row[2]);
305
                    } else {
306
                        $row[2] = $originalData;
307
                    }
308
                }
309
            }
310
311
            if (!empty($row['default_date'])) {
312
                $row['default_date'] = api_get_local_time($row['default_date']);
313
            } else {
314
                $row['default_date'] = '-';
315
            }
316
317
            if (!empty($row[7])) {
318
                $row[7] = api_get_local_time($row[7]);
319
            } else {
320
                $row[7] = '-';
321
            }
322
323
            if (!empty($row[5])) {
324
                // Course
325
                if (!empty($row[3])) {
326
                    $row[3] = Display::url(
327
                        $row[3],
328
                        api_get_path(WEB_CODE_PATH).'admin/course_edit.php?id='.$row[3]
329
                    );
330
                } else {
331
                    $row[3] = '-';
332
                }
333
334
                // session
335
                if (!empty($row[4])) {
336
                    $row[4] = Display::url(
337
                        $row[4],
338
                        api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$row[4]
339
                    );
340
                } else {
341
                    $row[4] = '-';
342
                }
343
344
                // User id.
345
                $row[5] = Display::url(
346
                    $row[5],
347
                    api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$row[6],
348
                    ['class' => 'ajax']
349
                );
350
351
                $row[6] = Tracking::get_ip_from_user_event(
352
                    $row[6],
353
                    $row[7],
354
                    true
355
                );
356
                if (empty($row[6])) {
357
                    $row[6] = get_lang('Unknown');
358
                }
359
            }
360
            $activities[] = $row;
361
        }
362
363
        return $activities;
364
    }
365
366
    /**
367
     * Rescale data.
368
     *
369
     * @param array $data The data that should be rescaled
370
     * @param int   $max  The maximum value in the rescaled data (default = 500);
371
     *
372
     * @return array The rescaled data, same key as $data
373
     */
374
    public static function rescale($data, $max = 500)
375
    {
376
        $data_max = 1;
377
        foreach ($data as $index => $value) {
378
            $data_max = ($data_max < $value ? $value : $data_max);
379
        }
380
        reset($data);
381
        $result = [];
382
        $delta = $max / $data_max;
383
        foreach ($data as $index => $value) {
384
            $result[$index] = (int) round($value * $delta);
385
        }
386
387
        return $result;
388
    }
389
390
    /**
391
     * Show statistics.
392
     *
393
     * @param string $title      The title
394
     * @param array  $stats
395
     * @param bool   $showTotal
396
     * @param bool   $isFileSize
397
     *
398
     * @return string HTML table
399
     */
400
    public static function printStats(
401
        $title,
402
        $stats,
403
        $showTotal = true,
404
        $isFileSize = false
405
    ) {
406
        $total = 0;
407
        $content = '<table class="table table-hover table-striped data_table" cellspacing="0" cellpadding="3" width="90%">
408
            <thead><tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr></thead><tbody>';
409
        $i = 0;
410
        foreach ($stats as $subtitle => $number) {
411
            $total += $number;
412
        }
413
414
        foreach ($stats as $subtitle => $number) {
415
            if (!$isFileSize) {
416
                $number_label = number_format($number, 0, ',', '.');
417
            } else {
418
                $number_label = self::makeSizeString($number);
419
            }
420
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
421
422
            $content .= '<tr class="row_'.(0 == $i % 2 ? 'odd' : 'even').'">
423
                    <td width="25%" style="vertical-align:top;">'.$subtitle.'</td>
424
                    <td width="60%">'.Display::bar_progress($percentage, false).'</td>
425
                    <td width="5%" align="right" style="vertical-align:top;">'.$number_label.'</td>';
426
            if ($showTotal) {
427
                $content .= '<td width="5%" align="right"> '.$percentage.'%</td>';
428
            }
429
            $content .= '</tr>';
430
            $i++;
431
        }
432
        $content .= '</tbody>';
433
        if ($showTotal) {
434
            if (!$isFileSize) {
435
                $total_label = number_format($total, 0, ',', '.');
436
            } else {
437
                $total_label = self::makeSizeString($total);
438
            }
439
            $content .= '
440
                <tfoot><tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr></tfoot>
441
            ';
442
        }
443
        $content .= '</table>';
444
445
        return $content;
446
    }
447
448
    /**
449
     * Show some stats about the number of logins.
450
     *
451
     * @param string $type month, hour or day
452
     */
453
    public static function printLoginStats($type)
454
    {
455
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
456
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
457
        $urlId = api_get_current_access_url_id();
458
459
        $now = api_get_utc_datetime();
460
        $table_url = ", $access_url_rel_user_table";
461
        $where_url = " WHERE login_user_id=user_id AND access_url_id='".$urlId."'";
462
        $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
463
464
        $period = get_lang('Month');
465
        $periodCollection = api_get_months_long();
466
        $sql = "SELECT
467
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date ,
468
                count( login_id ) AS number_of_logins
469
                FROM $table $table_url $where_url
470
                GROUP BY stat_date
471
                ORDER BY login_date DESC";
472
        $sql_last_x = null;
473
474
        switch ($type) {
475
            case 'hour':
476
                $period = get_lang('Hour');
477
                $sql = "SELECT
478
                          DATE_FORMAT( login_date, '%H') AS stat_date,
479
                          count( login_id ) AS number_of_logins
480
                        FROM $table $table_url $where_url
481
                        GROUP BY stat_date
482
                        ORDER BY stat_date ";
483
                $sql_last_x = "SELECT
484
                                DATE_FORMAT( login_date, '%H' ) AS stat_date,
485
                                count( login_id ) AS number_of_logins
486
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')."
487
                               GROUP BY stat_date
488
                               ORDER BY stat_date ";
489
                break;
490
            case 'day':
491
                $periodCollection = api_get_week_days_long();
492
                $period = get_lang('Day');
493
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date ,
494
                        count( login_id ) AS number_of_logins
495
                        FROM  $table $table_url $where_url
496
                        GROUP BY stat_date
497
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
498
                $sql_last_x = "SELECT
499
                                DATE_FORMAT( login_date, '%w' ) AS stat_date,
500
                                count( login_id ) AS number_of_logins
501
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')."
502
                               GROUP BY stat_date
503
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
504
                break;
505
        }
506
507
        $content = '';
508
        if ($sql_last_x) {
509
            $res_last_x = Database::query($sql_last_x);
510
            $result_last_x = [];
511
            while ($obj = Database::fetch_object($res_last_x)) {
512
                $stat_date = ('day' === $type) ? $periodCollection[$obj->stat_date] : $obj->stat_date;
513
                $result_last_x[$stat_date] = $obj->number_of_logins;
514
            }
515
            $content .= self::printStats(get_lang('LastLogins').' ('.$period.')', $result_last_x, true);
516
            flush(); //flush web request at this point to see something already while the full data set is loading
517
            $content .= '<br />';
518
        }
519
        $res = Database::query($sql);
520
        $result = [];
521
        while ($obj = Database::fetch_object($res)) {
522
            $stat_date = $obj->stat_date;
523
            switch ($type) {
524
                case 'month':
525
                    $stat_date = explode('-', $stat_date);
526
                    $stat_date[1] = $periodCollection[$stat_date[1] - 1];
527
                    $stat_date = implode(' ', $stat_date);
528
                    break;
529
                case 'day':
530
                    $stat_date = $periodCollection[$stat_date];
531
                    break;
532
            }
533
            $result[$stat_date] = $obj->number_of_logins;
534
        }
535
        $content .= self::printStats(get_lang('AllLogins').' ('.$period.')', $result, true);
536
537
        return $content;
538
    }
539
540
    /**
541
     * Print the number of recent logins.
542
     *
543
     * @param bool  $distinct        whether to only give distinct users stats, or *all* logins
544
     * @param int   $sessionDuration Number of minutes a session must have lasted at a minimum to be taken into account
545
     * @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)
546
     *
547
     * @throws Exception
548
     *
549
     * @return string HTML table
550
     */
551
    public static function printRecentLoginStats($distinct = false, $sessionDuration = 0, $periods = [])
552
    {
553
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
554
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
555
        $urlId = api_get_current_access_url_id();
556
        $table_url = ", $access_url_rel_user_table";
557
        $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
558
559
        $now = api_get_utc_datetime();
560
        $field = 'login_id';
561
        if ($distinct) {
562
            $field = 'DISTINCT(login_user_id)';
563
        }
564
565
        if (empty($periods)) {
566
            $periods = [1, 7, 31];
567
        }
568
        $sqlList = [];
569
570
        $sessionDuration = (int) $sessionDuration * 60; // convert from minutes to seconds
571
        foreach ($periods as $day) {
572
            $date = new DateTime($now);
573
            $startDate = $date->format('Y-m-d').' 00:00:00';
574
            $endDate = $date->format('Y-m-d').' 23:59:59';
575
576
            if ($day > 1) {
577
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
578
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
579
            }
580
581
            $localDate = api_get_local_time($startDate, null, null, false, false);
582
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
583
584
            $label = sprintf(get_lang('Last %s days'), $day);
585
            if (1 == $day) {
586
                $label = get_lang('Today');
587
            }
588
            $label .= " <span class=\"muted right\" style=\"float: right; margin-right: 5px;\">[$localDate - $localEndDate]</span>";
589
            $sql = "SELECT count($field) AS number
590
                    FROM $table $table_url
591
                    WHERE ";
592
            if (0 == $sessionDuration) {
593
                $sql .= " logout_date != login_date AND ";
594
            } else {
595
                $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
596
            }
597
            $sql .= "login_date BETWEEN '$startDate' AND '$endDate'
598
                        $where_url";
599
            $sqlList[$label] = $sql;
600
        }
601
602
        $sql = "SELECT count($field) AS number
603
                FROM $table $table_url ";
604
        if (0 == $sessionDuration) {
605
            $sql .= " WHERE logout_date != login_date $where_url";
606
        } else {
607
            $sql .= " WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url";
608
        }
609
        $sqlList[get_lang('Total')] = $sql;
610
        $totalLogin = [];
611
        foreach ($sqlList as $label => $query) {
612
            $res = Database::query($query);
613
            $obj = Database::fetch_object($res);
614
            $totalLogin[$label] = $obj->number;
615
        }
616
617
        if ($distinct) {
618
            $content = self::printStats(get_lang('DistinctUsersLogins'), $totalLogin, false);
619
        } else {
620
            $content = self::printStats(get_lang('Logins'), $totalLogin, false);
621
        }
622
623
        return $content;
624
    }
625
626
    /**
627
     * Get the number of recent logins.
628
     *
629
     * @param bool $distinct            Whether to only give distinct users stats, or *all* logins
630
     * @param int  $sessionDuration     Number of minutes a session must have lasted at a minimum to be taken into account
631
     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
632
     *
633
     * @throws Exception
634
     *
635
     * @return array
636
     */
637
    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
638
    {
639
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
640
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
641
        $urlId = api_get_current_access_url_id();
642
        $table_url = ", $access_url_rel_user_table";
643
        $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
644
645
        $now = api_get_utc_datetime();
646
        $date = new DateTime($now);
647
        $date->sub(new DateInterval('P31D'));
648
        $newDate = $date->format('Y-m-d h:i:s');
649
        $totalLogin = self::buildDatesArray($newDate, $now, true);
650
651
        $field = 'login_id';
652
        if ($distinct) {
653
            $field = 'DISTINCT(login_user_id)';
654
        }
655
        $sessionDuration = (int) $sessionDuration * 60; //Convert from minutes to seconds
656
657
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
658
                FROM $table $table_url
659
                WHERE ";
660
        if (0 == $sessionDuration) {
661
            $sql .= " logout_date != login_date AND ";
662
        } else {
663
            $sql .= " UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND ";
664
        }
665
        $sql .= " login_date >= '$newDate' $where_url
666
                GROUP BY date(login_date)";
667
668
        $res = Database::query($sql);
669
        while ($row = Database::fetch_assoc($res)) {
670
            $monthAndDay = substr($row['login_date'], 5, 5);
671
            $totalLogin[$monthAndDay] = $row['number'];
672
        }
673
674
        return $totalLogin;
675
    }
676
677
    /**
678
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
679
     */
680
    public static function getToolsStats()
681
    {
682
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
683
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
684
        $urlId = api_get_current_access_url_id();
685
686
        $tools = [
687
            'announcement',
688
            'assignment',
689
            'calendar_event',
690
            'chat',
691
            'course_description',
692
            'document',
693
            'dropbox',
694
            'group',
695
            'learnpath',
696
            'link',
697
            'quiz',
698
            'student_publication',
699
            'user',
700
            'forum',
701
        ];
702
        $tool_names = [];
703
        foreach ($tools as $tool) {
704
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
705
        }
706
        $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
707
            FROM $table t , $access_url_rel_course_table a
708
            WHERE
709
                access_tool IN ('".implode("','", $tools)."') AND
710
                t.c_id = a.c_id AND
711
                access_url_id='".$urlId."'
712
                GROUP BY access_tool
713
            ";
714
715
        $res = Database::query($sql);
716
        $result = [];
717
        while ($obj = Database::fetch_object($res)) {
718
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
719
        }
720
721
        return $result;
722
    }
723
724
    /**
725
     * Show some stats about the accesses to the different course tools.
726
     *
727
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
728
     *
729
     * @return string HTML table
730
     */
731
    public static function printToolStats($result = null)
732
    {
733
        if (empty($result)) {
734
            $result = self::getToolsStats();
735
        }
736
737
        return self::printStats(get_lang('Tools access'), $result, true);
738
    }
739
740
    /**
741
     * Show some stats about the number of courses per language.
742
     */
743
    public static function printCourseByLanguageStats()
744
    {
745
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
746
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
747
        $urlId = api_get_current_access_url_id();
748
        $sql = "SELECT course_language, count( c.code ) AS number_of_courses
749
            FROM $table as c, $access_url_rel_course_table as u
750
            WHERE u.c_id = c.id AND access_url_id='".$urlId."'
751
            GROUP BY course_language
752
            ORDER BY number_of_courses DESC";
753
        $res = Database::query($sql);
754
        $result = [];
755
        while ($obj = Database::fetch_object($res)) {
756
            $result[$obj->course_language] = $obj->number_of_courses;
757
        }
758
759
        return $result;
760
    }
761
762
    /**
763
     * Shows the number of users having their picture uploaded in Dokeos.
764
     */
765
    public static function printUserPicturesStats()
766
    {
767
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
768
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
769
        $urlId = api_get_current_access_url_id();
770
        $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.id AND access_url_id='".$urlId."'";
771
        $url_condition2 = " AND url.user_id=u.id AND access_url_id='".$urlId."'";
772
        $table = ", $access_url_rel_user_table as url ";
773
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
774
        $res = Database::query($sql);
775
        $count1 = Database::fetch_object($res);
776
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table ".
777
               "WHERE LENGTH(picture_uri) > 0 $url_condition2";
778
779
        $sql .= !str_contains($sql, 'WHERE') ? ' WHERE u.active <> '.USER_SOFT_DELETED : ' AND u.active <> '.USER_SOFT_DELETED;
780
781
        $res = Database::query($sql);
782
        $count2 = Database::fetch_object($res);
783
        // #users without picture
784
        $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...
785
        $result[get_lang('Yes')] = $count2->n; // #users with picture
786
787
        return self::printStats(get_lang('Number of users').' ('.get_lang('Picture').')', $result, true);
788
    }
789
790
    /**
791
     * Important activities.
792
     */
793
    public static function printActivitiesStats()
794
    {
795
        $content = '<h4>'.get_lang('Important activities').'</h4>';
796
        // Create a search-box
797
        $form = new FormValidator(
798
            'search_simple',
799
            'get',
800
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
801
            '',
802
            'width=200px',
803
            false
804
        );
805
        $renderer = &$form->defaultRenderer();
806
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
807
        $form->addHidden('report', 'activities');
808
        $form->addHidden('activities_direction', 'DESC');
809
        $form->addHidden('activities_column', '4');
810
        $form->addElement('text', 'keyword', get_lang('Keyword'));
811
        $form->addButtonSearch(get_lang('Search'), 'submit');
812
        $content .= '<div class="actions">';
813
        $content .= $form->returnForm();
814
        $content .= '</div>';
815
816
        $table = new SortableTable(
817
            'activities',
818
            ['Statistics', 'getNumberOfActivities'],
819
            ['Statistics', 'getActivitiesData'],
820
            7,
821
            50,
822
            'DESC'
823
        );
824
        $parameters = [];
825
826
        $parameters['report'] = 'activities';
827
        if (isset($_GET['keyword'])) {
828
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
829
        }
830
831
        $table->set_additional_parameters($parameters);
832
        $table->set_header(0, get_lang('Event type'));
833
        $table->set_header(1, get_lang('Data type'));
834
        $table->set_header(2, get_lang('Value'));
835
        $table->set_header(3, get_lang('Course'));
836
        $table->set_header(4, get_lang('Session'));
837
        $table->set_header(5, get_lang('Username'));
838
        $table->set_header(6, get_lang('IP address'));
839
        $table->set_header(7, get_lang('Date'));
840
        $content .= $table->return_table();
841
842
        return $content;
843
    }
844
845
    /**
846
     * Shows statistics about the time of last visit to each course.
847
     */
848
    public static function printCourseLastVisit()
849
    {
850
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
851
        $urlId = api_get_current_access_url_id();
852
853
        $columns[0] = 't.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...
854
        $columns[1] = 'access_date';
855
        $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...
856
        $sql_order[SORT_DESC] = 'DESC';
857
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
858
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
859
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
860
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
861
862
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
863
            $direction = SORT_ASC;
864
        }
865
        $form = new FormValidator('courselastvisit', 'get');
866
        $form->addElement('hidden', 'report', 'courselastvisit');
867
        $form->addText('date_diff', get_lang('days'), true);
868
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
869
        $form->addButtonSearch(get_lang('Search'), 'submit');
870
        if (!isset($_GET['date_diff'])) {
871
            $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...
872
        } else {
873
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
874
        }
875
        $form->setDefaults($defaults);
876
        $content = $form->returnForm();
877
878
        $values = $form->exportValues();
879
        $date_diff = $values['date_diff'];
880
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
881
        $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
882
           WHERE
883
                t.c_id = a.c_id AND
884
                access_url_id='".$urlId."'
885
           GROUP BY t.c_id
886
           HAVING t.c_id <> ''
887
           AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
888
        $sql .= ' ORDER BY `'.$columns[$column].'` '.$sql_order[$direction];
889
        $from = ($page_nr - 1) * $per_page;
890
        $sql .= ' LIMIT '.$from.','.$per_page;
891
892
        $content .= '<p>'.get_lang('Latest access').' &gt;= '.$date_diff.' '.get_lang('days').'</p>';
893
        $res = Database::query($sql);
894
        if (Database::num_rows($res) > 0) {
895
            $courses = [];
896
            while ($obj = Database::fetch_object($res)) {
897
                $courseInfo = api_get_course_info_by_id($obj->c_id);
898
                $course = [];
899
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
900
                // Allow sort by date hiding the numerical date
901
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
902
                $courses[] = $course;
903
            }
904
            $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...
905
            $parameters['report'] = 'courselastvisit';
906
            $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...
907
            $table_header[] = [get_lang("Latest access"), true];
908
909
            ob_start();
910
            Display:: display_sortable_table(
911
                $table_header,
912
                $courses,
913
                ['column' => $column, 'direction' => $direction],
914
                [],
915
                $parameters
916
            );
917
            $content .= ob_get_contents();
918
            ob_end_clean();
919
        } else {
920
            $content = get_lang('No search results');
921
        }
922
923
        return $content;
924
    }
925
926
    /**
927
     * Displays the statistics of the messages sent and received by each user in the social network.
928
     *
929
     * @param string $messageType Type of message: 'sent' or 'received'
930
     *
931
     * @return array Message list
932
     */
933
    public static function getMessages($messageType)
934
    {
935
        $message_table = Database::get_main_table(TABLE_MESSAGE);
936
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
937
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
938
939
        $urlId = api_get_current_access_url_id();
940
941
        switch ($messageType) {
942
            case 'sent':
943
                $field = 'user_sender_id';
944
                break;
945
            case 'received':
946
                $field = 'user_receiver_id';
947
                break;
948
        }
949
950
        $sql = "SELECT lastname, firstname, username, COUNT($field) AS count_message
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...
951
            FROM $access_url_rel_user_table as url, $message_table m
952
            LEFT JOIN $user_table u ON m.$field = u.id AND u.active <> ".USER_SOFT_DELETED."
953
            WHERE  url.user_id = m.$field AND  access_url_id='".$urlId."'
954
            GROUP BY m.$field
955
            ORDER BY count_message DESC ";
956
        $res = Database::query($sql);
957
        $messages_sent = [];
958
        while ($messages = Database::fetch_array($res)) {
959
            if (empty($messages['username'])) {
960
                $messages['username'] = get_lang('Unknown');
961
            }
962
            $users = api_get_person_name(
963
                $messages['firstname'],
964
                $messages['lastname']
965
            ).'<br />('.$messages['username'].')';
966
            $messages_sent[$users] = $messages['count_message'];
967
        }
968
969
        return $messages_sent;
970
    }
971
972
    /**
973
     * Count the number of friends for social network users.
974
     */
975
    public static function getFriends()
976
    {
977
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
978
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
979
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
980
        $urlId = api_get_current_access_url_id();
981
982
        $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend
983
            FROM $access_url_rel_user_table as url, $user_friend_table uf
984
            LEFT JOIN $user_table u
985
            ON (uf.user_id = u.id) AND u.active <> ".USER_SOFT_DELETED."
986
            WHERE
987
                uf.relation_type <> '".UserRelUser::USER_RELATION_TYPE_RRHH."' AND
988
                uf.user_id = url.user_id AND
989
                access_url_id = '".$urlId."'
990
            GROUP BY uf.user_id
991
            ORDER BY count_friend DESC ";
992
        $res = Database::query($sql);
993
        $list_friends = [];
994
        while ($friends = Database::fetch_array($res)) {
995
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
996
            $list_friends[$users] = $friends['count_friend'];
997
        }
998
999
        return $list_friends;
1000
    }
1001
1002
    /**
1003
     * Print the number of users that didn't login for a certain period of time.
1004
     */
1005
    public static function printUsersNotLoggedInStats()
1006
    {
1007
        $totalLogin = [];
1008
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1009
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1010
        $urlId = api_get_current_access_url_id();
1011
        $total = self::countUsers();
1012
        $table_url = ", $access_url_rel_user_table";
1013
        $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
1014
        $now = api_get_utc_datetime();
1015
        $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...
1016
            "SELECT count(distinct(login_user_id)) AS number ".
1017
            " FROM $table $table_url ".
1018
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1019
        $sql[get_lang('In the last 7 days')] =
1020
            "SELECT count(distinct(login_user_id)) AS number ".
1021
            " FROM $table $table_url ".
1022
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1023
        $sql[get_lang('In the last 31 days')] =
1024
            "SELECT count(distinct(login_user_id)) AS number ".
1025
            " FROM $table $table_url ".
1026
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1027
        $sql[sprintf(get_lang('Last %i months'), 6)] =
1028
            "SELECT count(distinct(login_user_id)) AS number ".
1029
            " FROM $table $table_url ".
1030
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1031
        $sql[get_lang('Never connected')] =
1032
            "SELECT count(distinct(login_user_id)) AS number ".
1033
            " FROM $table $table_url WHERE 1=1 $where_url";
1034
        foreach ($sql as $index => $query) {
1035
            $res = Database::query($query);
1036
            $obj = Database::fetch_object($res);
1037
            $r = $total - $obj->number;
1038
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1039
        }
1040
1041
        return self::printStats(
1042
            get_lang('Not logged in for some time'),
1043
            $totalLogin,
1044
            false
1045
        );
1046
    }
1047
1048
    /**
1049
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1050
     * within the provided range (including limits). Dates are assumed to be
1051
     * given in UTC.
1052
     *
1053
     * @param string $startDate  Start date, in Y-m-d or Y-m-d h:i:s format
1054
     * @param string $endDate    End date, in Y-m-d or Y-m-d h:i:s format
1055
     * @param bool   $removeYear Whether to remove the year in the results (for easier reading)
1056
     *
1057
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1058
     */
1059
    public static function buildDatesArray($startDate, $endDate, $removeYear = false)
1060
    {
1061
        if (strlen($startDate) > 10) {
1062
            $startDate = substr($startDate, 0, 10);
1063
        }
1064
        if (strlen($endDate) > 10) {
1065
            $endDate = substr($endDate, 0, 10);
1066
        }
1067
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1068
            return false;
1069
        }
1070
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1071
            return false;
1072
        }
1073
        $startTimestamp = strtotime($startDate);
1074
        $endTimestamp = strtotime($endDate);
1075
        $list = [];
1076
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1077
            $datetime = api_get_utc_datetime($time);
1078
            if ($removeYear) {
1079
                $datetime = substr($datetime, 5, 5);
1080
            } else {
1081
                $dateTime = substr($datetime, 0, 10);
1082
            }
1083
            $list[$datetime] = 0;
1084
        }
1085
1086
        return $list;
1087
    }
1088
1089
    /**
1090
     * Prepare the JS code to load a chart.
1091
     *
1092
     * @param string $url     URL for AJAX data generator
1093
     * @param string $type    bar, line, pie, etc
1094
     * @param string $options Additional options to the chart (see chart-specific library)
1095
     * @param string A JS code for loading the chart together with a call to AJAX data generator
1096
     */
1097
    public static function getJSChartTemplate($url, $type = 'pie', $options = '', $elementId = 'canvas')
1098
    {
1099
        $chartCode = '
1100
        <script>
1101
        $(function() {
1102
            $.ajax({
1103
                url: "'.$url.'",
1104
                type: "POST",
1105
                success: function(data) {
1106
                    Chart.defaults.global.responsive = true;
1107
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1108
                    var chart = new Chart(ctx, {
1109
                        type: "'.$type.'",
1110
                        data: data,
1111
                        options: {'.$options.'}
1112
                    });
1113
                    var title = chart.options.title.text;
1114
                    $("#'.$elementId.'_title").html(title);
1115
                    $("#'.$elementId.'_table").html(data.table);
1116
                }
1117
            });
1118
        });
1119
        </script>';
1120
1121
        return $chartCode;
1122
    }
1123
1124
    public static function getJSChartTemplateWithData($data, $type = 'pie', $options = '', $elementId = 'canvas')
1125
    {
1126
        $data = json_encode($data);
1127
        $chartCode = '
1128
        <script>
1129
            $(function() {
1130
                Chart.defaults.global.responsive = true;
1131
                var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1132
                var chart = new Chart(ctx, {
1133
                    type: "'.$type.'",
1134
                    data: '.$data.',
1135
                    options: {'.$options.'}
1136
                });
1137
            });
1138
        </script>';
1139
1140
        return $chartCode;
1141
    }
1142
1143
    public static function buildJsChartData($all, $chartName)
1144
    {
1145
        $list = [];
1146
        $palette = ChamiloApi::getColorPalette(true, true);
1147
        foreach ($all as $tick => $tock) {
1148
            $list['labels'][] = $tick;
1149
        }
1150
1151
        $list['datasets'][0]['label'] = $chartName;
1152
        $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
1153
1154
        $i = 0;
1155
        foreach ($all as $tick => $tock) {
1156
            $j = $i % count($palette);
1157
            $list['datasets'][0]['data'][] = $tock;
1158
            $list['datasets'][0]['backgroundColor'][] = $palette[$j];
1159
            $i++;
1160
        }
1161
1162
        $scoreDisplay = ScoreDisplay::instance();
1163
        $table = new HTML_Table(['class' => 'data_table']);
1164
        $headers = [
1165
            get_lang('Name'),
1166
            get_lang('Count'),
1167
            get_lang('Percentage'),
1168
        ];
1169
        $row = 0;
1170
        $column = 0;
1171
        foreach ($headers as $header) {
1172
            $table->setHeaderContents($row, $column, $header);
1173
            $column++;
1174
        }
1175
1176
        $total = 0;
1177
        foreach ($all as $name => $value) {
1178
            $total += $value;
1179
        }
1180
        $row++;
1181
        foreach ($all as $name => $value) {
1182
            $table->setCellContents($row, 0, $name);
1183
            $table->setCellContents($row, 1, $value);
1184
            $table->setCellContents($row, 2, $scoreDisplay->display_score([$value, $total], SCORE_PERCENT));
1185
            $row++;
1186
        }
1187
        $table = Display::page_subheader2($chartName).$table->toHtml();
1188
1189
        return ['chart' => $list, 'table' => $table];
1190
    }
1191
1192
    /**
1193
     * Display the Logins By Date report and allow export its result to XLS.
1194
     */
1195
    public static function printLoginsByDate()
1196
    {
1197
        if (isset($_GET['export']) && 'xls' === $_GET['export']) {
1198
            $result = self::getLoginsByDate($_GET['start'], $_GET['end']);
1199
            $data = [[get_lang('Username'), get_lang('First name'), get_lang('Last name'), get_lang('Total time')]];
1200
1201
            foreach ($result as $i => $item) {
1202
                $data[] = [
1203
                    $item['username'],
1204
                    $item['firstname'],
1205
                    $item['lastname'],
1206
                    api_time_to_hms($item['time_count']),
1207
                ];
1208
            }
1209
1210
            Export::arrayToXls($data);
1211
            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...
1212
        }
1213
1214
        $content = Display::page_header(get_lang('Logins by date'));
1215
1216
        $actions = '';
1217
        $form = new FormValidator('frm_logins_by_date', 'get');
1218
        $form->addDateRangePicker(
1219
            'daterange',
1220
            get_lang('Date range'),
1221
            true,
1222
            ['format' => 'YYYY-MM-DD', 'timePicker' => 'false', 'validate_format' => 'Y-m-d']
1223
        );
1224
        $form->addHidden('report', 'logins_by_date');
1225
        $form->addButtonFilter(get_lang('Search'));
1226
1227
        if ($form->validate()) {
1228
            $values = $form->exportValues();
1229
1230
            $result = self::getLoginsByDate($values['daterange_start'], $values['daterange_end']);
1231
1232
            if (!empty($result)) {
1233
                $actions = Display::url(
1234
                    Display::getMdiIcon(ActionIcon::EXPORT_SPREADSHEET, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('ExportToXls')),
1235
                    api_get_self().'?'.http_build_query(
1236
                        [
1237
                            'report' => 'logins_by_date',
1238
                            'export' => 'xls',
1239
                            'start' => Security::remove_XSS($values['daterange_start']),
1240
                            'end' => Security::remove_XSS($values['daterange_end']),
1241
                        ]
1242
                    )
1243
                );
1244
            }
1245
1246
            $table = new HTML_Table(['class' => 'data_table']);
1247
            $table->setHeaderContents(0, 0, get_lang('Username'));
1248
            $table->setHeaderContents(0, 1, get_lang('First name'));
1249
            $table->setHeaderContents(0, 2, get_lang('Last name'));
1250
            $table->setHeaderContents(0, 3, get_lang('Total time'));
1251
1252
            foreach ($result as $i => $item) {
1253
                $table->setCellContents($i + 1, 0, $item['username']);
1254
                $table->setCellContents($i + 1, 1, $item['firstname']);
1255
                $table->setCellContents($i + 1, 2, $item['lastname']);
1256
                $table->setCellContents($i + 1, 3, api_time_to_hms($item['time_count']));
1257
            }
1258
1259
            $table->setColAttributes(0, ['class' => 'text-center']);
1260
            $table->setColAttributes(3, ['class' => 'text-center']);
1261
            $content = $table->toHtml();
1262
        }
1263
1264
        $content .= $form->returnForm();
1265
1266
        if (!empty($actions)) {
1267
            $content .= Display::toolbarAction('logins_by_date_toolbar', [$actions]);
1268
        }
1269
1270
        return $content;
1271
    }
1272
1273
    public static function getBossTable($bossId)
1274
    {
1275
        $students = UserManager::getUsersFollowedByStudentBoss(
1276
            $bossId,
1277
            0,
1278
            false,
1279
            false,
1280
            false,
1281
            null,
1282
            null,
1283
            null,
1284
            null,
1285
            1
1286
        );
1287
1288
        if (!empty($students)) {
1289
            $table = new HTML_Table(['class' => 'table table-responsive', 'id' => 'table_'.$bossId]);
1290
            $headers = [
1291
                get_lang('Name'),
1292
                //get_lang('LastName'),
1293
            ];
1294
            $row = 0;
1295
            $column = 0;
1296
            foreach ($headers as $header) {
1297
                $table->setHeaderContents($row, $column, $header);
1298
                $column++;
1299
            }
1300
            $row++;
1301
            foreach ($students as $student) {
1302
                $column = 0;
1303
                $content = api_get_person_name($student['firstname'], $student['lastname']).'';
1304
                $content = '<div style="width: 200px; overflow-wrap: break-word;">'.$content.'</div>';
1305
                $table->setCellContents(
1306
                    $row,
1307
                    $column++,
1308
                    $content
1309
                );
1310
                $row++;
1311
            }
1312
1313
            return $table->toHtml();
1314
        }
1315
1316
        return '<table id="table_'.$bossId.'"></table>';
1317
    }
1318
1319
    /**
1320
     * @param string $startDate
1321
     * @param string $endDate
1322
     *
1323
     * @return array
1324
     */
1325
    private static function getLoginsByDate($startDate, $endDate)
1326
    {
1327
        $startDate = api_get_utc_datetime("$startDate 00:00:00");
1328
        $endDate = api_get_utc_datetime("$endDate 23:59:59");
1329
1330
        if (empty($startDate) || empty($endDate)) {
1331
            return [];
1332
        }
1333
1334
        $tblUser = Database::get_main_table(TABLE_MAIN_USER);
1335
        $tblLogin = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1336
        $tblUrlUser = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1337
1338
        $urlJoin = "INNER JOIN $tblUrlUser au ON u.id = au.user_id";
1339
        $urlWhere = 'AND au.access_url_id = '.api_get_current_access_url_id();
1340
1341
        $sql = "SELECT u.id,
1342
                    u.firstname,
1343
                    u.lastname,
1344
                    u.username,
1345
                    SUM(TIMESTAMPDIFF(SECOND, l.login_date, l.logout_date)) AS time_count
1346
                FROM $tblUser u
1347
                INNER JOIN $tblLogin l
1348
                ON u.id = l.login_user_id
1349
                $urlJoin
1350
                WHERE u.active <> ".USER_SOFT_DELETED." AND l.login_date BETWEEN '$startDate' AND '$endDate'
1351
                $urlWhere
1352
                GROUP BY u.id";
1353
1354
        $stmt = Database::query($sql);
1355
1356
        return Database::store_result($stmt, 'ASSOC');
1357
    }
1358
}
1359