Passed
Push — master ( e36549...10e9e9 )
by Yannick
10:47 queued 01:28
created

Statistics::printUserPicturesStats()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 0
dl 0
loc 25
rs 9.6
c 0
b 0
f 0
1
<?php
2
/* For licensing terms, see /license.txt */
3
4
/**
5
 * This class provides some functions for statistics.
6
 *
7
 * @package chamilo.statistics
8
 */
9
class Statistics
10
{
11
    /**
12
     * Converts a number of bytes in a formatted string.
13
     *
14
     * @param int $size
15
     *
16
     * @return string Formatted file size
17
     */
18
    public static function makeSizeString($size)
19
    {
20
        if ($size < pow(2, 10)) {
21
            return $size." bytes";
22
        }
23
        if ($size >= pow(2, 10) && $size < pow(2, 20)) {
24
            return round($size / pow(2, 10), 0)." KB";
25
        }
26
        if ($size >= pow(2, 20) && $size < pow(2, 30)) {
27
            return round($size / pow(2, 20), 1)." MB";
28
        }
29
        if ($size > pow(2, 30)) {
30
            return round($size / pow(2, 30), 2)." GB";
31
        }
32
    }
33
34
    /**
35
     * Count courses.
36
     *
37
     * @param string $categoryCode Code of a course category.
38
     *                             Default: count all courses.
39
     *
40
     * @return int Number of courses counted
41
     */
42
    public static function countCourses($categoryCode = null)
43
    {
44
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
45
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
46
        $urlId = api_get_current_access_url_id();
47
        if (api_is_multiple_url_enabled()) {
48
            $sql = "SELECT COUNT(*) AS number
49
                    FROM ".$course_table." as c, $access_url_rel_course_table as u
50
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
51
            if (isset($categoryCode)) {
52
                $sql .= " AND category_code = '".Database::escape_string($categoryCode)."'";
53
            }
54
        } else {
55
            $sql = "SELECT COUNT(*) AS number
56
                    FROM $course_table";
57
            if (isset($categoryCode)) {
58
                $sql .= " WHERE category_code = '".Database::escape_string($categoryCode)."'";
59
            }
60
        }
61
62
        $res = Database::query($sql);
63
        $obj = Database::fetch_object($res);
64
65
        return $obj->number;
66
    }
67
68
    /**
69
     * Count courses by visibility.
70
     *
71
     * @param int $visibility visibility (0 = closed, 1 = private, 2 = open, 3 = public) all courses
72
     *
73
     * @return int Number of courses counted
74
     */
75
    public static function countCoursesByVisibility($visibility = null)
76
    {
77
        if (!isset($visibility)) {
78
            return 0;
79
        }
80
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
81
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
82
        $urlId = api_get_current_access_url_id();
83
        if (api_is_multiple_url_enabled()) {
84
            $sql = "SELECT COUNT(*) AS number
85
                    FROM $course_table as c, $access_url_rel_course_table as u
86
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'";
87
            if (isset($visibility)) {
88
                $sql .= " AND visibility = ".intval($visibility);
89
            }
90
        } else {
91
            $sql = "SELECT COUNT(*) AS number FROM $course_table ";
92
            if (isset($visibility)) {
93
                $sql .= " WHERE visibility = ".intval($visibility);
94
            }
95
        }
96
        $res = Database::query($sql);
97
        $obj = Database::fetch_object($res);
98
99
        return $obj->number;
100
    }
101
102
    /**
103
     * Count users.
104
     *
105
     * @param int    $status                user status (COURSEMANAGER or STUDENT) if not setted it'll count all users
106
     * @param string $categoryCode          course category code. Default: count only users without filtering category
107
     * @param bool   $countInvisibleCourses Count invisible courses (todo)
108
     * @param bool   $onlyActive            Count only active users (false to only return currently active users)
109
     *
110
     * @return int Number of users counted
111
     */
112
    public static function countUsers(
113
        $status = null,
114
        $categoryCode = null,
115
        $countInvisibleCourses = true,
116
        $onlyActive = false
117
    ) {
118
        // Database table definitions
119
        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
120
        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
121
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
122
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
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
        if (api_is_multiple_url_enabled()) {
128
            $sql = "SELECT COUNT(DISTINCT(u.user_id)) AS number
129
                    FROM $user_table as u, $access_url_rel_user_table as url
130
                    WHERE
131
                        u.user_id = url.user_id AND
132
                        access_url_id = '".$urlId."'
133
                        $status_filter $active_filter";
134
            if (isset($categoryCode)) {
135
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
136
                        FROM $course_user_table cu, $course_table c, $access_url_rel_user_table as url
137
                        WHERE
138
                            c.id = cu.c_id AND
139
                            c.category_code = '".Database::escape_string($categoryCode)."' AND
140
                            cu.user_id = url.user_id AND
141
                            access_url_id='".$urlId."'
142
                            $status_filter $active_filter";
143
            }
144
        } else {
145
            $sql = "SELECT COUNT(DISTINCT(user_id)) AS number
146
                    FROM $user_table
147
                    WHERE 1=1 $status_filter $active_filter";
148
            if (isset($categoryCode)) {
149
                $status_filter = isset($status) ? ' AND status = '.intval($status) : '';
150
                $sql = "SELECT COUNT(DISTINCT(cu.user_id)) AS number
151
                        FROM $course_user_table cu, $course_table c
152
                        WHERE
153
                            c.id = cu.c_id AND
154
                            c.category_code = '".Database::escape_string($categoryCode)."'
155
                            $status_filter
156
                            $active_filter
157
                        ";
158
            }
159
        }
160
161
        $res = Database::query($sql);
162
        $obj = Database::fetch_object($res);
163
164
        return $obj->number;
165
    }
166
167
    /**
168
     * Count activities from track_e_default_table.
169
     *
170
     * @return int Number of activities counted
171
     */
172
    public static function getNumberOfActivities($courseId = 0, $sessionId = 0)
173
    {
174
        // Database table definitions
175
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
176
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
177
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
178
        $urlId = api_get_current_access_url_id();
179
        if (api_is_multiple_url_enabled()) {
180
            $sql = "SELECT count(default_id) AS total_number_of_items
181
                    FROM $track_e_default, $table_user user, $access_url_rel_user_table url
182
                    WHERE
183
                        default_user_id = user.user_id AND
184
                        user.user_id=url.user_id AND
185
                        access_url_id = '".$urlId."'";
186
        } else {
187
            $sql = "SELECT count(default_id) AS total_number_of_items
188
                    FROM $track_e_default, $table_user user
189
                    WHERE default_user_id = user.user_id ";
190
        }
191
192
        if (!empty($courseId)) {
193
            $courseId = (int) $courseId;
194
            $sql .= " AND c_id = $courseId";
195
            $sql .= api_get_session_condition($sessionId);
196
        }
197
198
        if (isset($_GET['keyword'])) {
199
            $keyword = Database::escape_string(trim($_GET['keyword']));
200
            $sql .= " AND (
201
                        user.username LIKE '%".$keyword."%' OR 
202
                        default_event_type LIKE '%".$keyword."%' OR 
203
                        default_value_type LIKE '%".$keyword."%' OR 
204
                        default_value LIKE '%".$keyword."%') ";
205
        }
206
207
        $res = Database::query($sql);
208
        $obj = Database::fetch_object($res);
209
210
        return $obj->total_number_of_items;
211
    }
212
213
    /**
214
     * Get activities data to display.
215
     *
216
     * @param int    $from
217
     * @param int    $numberOfItems
218
     * @param int    $column
219
     * @param string $direction
220
     * @param int    $courseId
221
     * @param int    $sessionId
222
     *
223
     * @return array
224
     */
225
    public static function getActivitiesData(
226
        $from,
227
        $numberOfItems,
228
        $column,
229
        $direction,
230
        $courseId = 0,
231
        $sessionId = 0
232
    ) {
233
        $track_e_default = Database::get_main_table(TABLE_STATISTIC_TRACK_E_DEFAULT);
234
        $table_user = Database::get_main_table(TABLE_MAIN_USER);
235
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
236
        $urlId = api_get_current_access_url_id();
237
        $column = intval($column);
238
        $from = intval($from);
239
        $numberOfItems = intval($numberOfItems);
240
        $direction = strtoupper($direction);
241
242
        if (!in_array($direction, ['ASC', 'DESC'])) {
243
            $direction = 'DESC';
244
        }
245
246
        if (api_is_multiple_url_enabled()) {
247
            $sql = "SELECT
248
                        default_event_type  as col0,
249
                        default_value_type    as col1,
250
                        default_value        as col2,
251
                        c_id         as col3,
252
                        session_id as col4,
253
                        user.username         as col5,
254
                        user.user_id         as col6,
255
                        default_date         as col7
256
                    FROM $track_e_default as track_default,
257
                    $table_user as user,
258
                    $access_url_rel_user_table as url
259
                    WHERE
260
                        track_default.default_user_id = user.user_id AND
261
                        url.user_id = user.user_id AND
262
                        access_url_id= $urlId ";
263
        } else {
264
            $sql = "SELECT
265
                       default_event_type  as col0,
266
                       default_value_type    as col1,
267
                       default_value        as col2,
268
                       c_id         as col3,
269
                       session_id as col4,
270
                       user.username         as col5,
271
                       user.user_id         as col6,
272
                       default_date         as col7
273
                   FROM $track_e_default track_default, $table_user user
274
                   WHERE track_default.default_user_id = user.user_id ";
275
        }
276
277
        if (!empty($_GET['keyword'])) {
278
            $keyword = Database::escape_string(trim($_GET['keyword']));
279
            $sql .= " AND (user.username LIKE '%".$keyword."%' OR
280
                        default_event_type LIKE '%".$keyword."%' OR
281
                        default_value_type LIKE '%".$keyword."%' OR
282
                        default_value LIKE '%".$keyword."%') ";
283
        }
284
285
        if (!empty($courseId)) {
286
            $courseId = (int) $courseId;
287
            $sql .= " AND c_id = $courseId";
288
            $sql .= api_get_session_condition($sessionId);
289
        }
290
291
        if (!empty($column) && !empty($direction)) {
292
            $sql .= " ORDER BY col$column $direction";
293
        } else {
294
            $sql .= " ORDER BY col7 DESC ";
295
        }
296
        $sql .= " LIMIT $from, $numberOfItems ";
297
298
        $res = Database::query($sql);
299
        $activities = [];
300
        while ($row = Database::fetch_row($res)) {
301
            if (strpos($row[1], '_object') === false &&
302
                strpos($row[1], '_array') === false
303
            ) {
304
                $row[2] = $row[2];
305
            } else {
306
                if (!empty($row[2])) {
307
                    $originalData = str_replace('\\', '', $row[2]);
308
                    $row[2] = unserialize($originalData);
309
                    if (is_array($row[2]) && !empty($row[2])) {
310
                        $row[2] = implode_with_key(', ', $row[2]);
311
                    } else {
312
                        $row[2] = $originalData;
313
                    }
314
                }
315
            }
316
317
            if (!empty($row['default_date'])) {
318
                $row['default_date'] = api_get_local_time($row['default_date']);
319
            } else {
320
                $row['default_date'] = '-';
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
     * Get all course categories.
368
     *
369
     * @return array All course categories (code => name)
370
     */
371
    public static function getCourseCategories()
372
    {
373
        $categoryTable = Database::get_main_table(TABLE_MAIN_CATEGORY);
374
        $sql = "SELECT code, name 
375
                FROM $categoryTable
376
                ORDER BY tree_pos";
377
        $res = Database::query($sql);
378
        $categories = [];
379
        while ($category = Database::fetch_object($res)) {
380
            $categories[$category->code] = $category->name;
381
        }
382
383
        return $categories;
384
    }
385
386
    /**
387
     * Rescale data.
388
     *
389
     * @param array $data The data that should be rescaled
390
     * @param int   $max  The maximum value in the rescaled data (default = 500);
391
     *
392
     * @return array The rescaled data, same key as $data
393
     */
394
    public static function rescale($data, $max = 500)
395
    {
396
        $data_max = 1;
397
        foreach ($data as $index => $value) {
398
            $data_max = ($data_max < $value ? $value : $data_max);
399
        }
400
        reset($data);
401
        $result = [];
402
        $delta = $max / $data_max;
403
        foreach ($data as $index => $value) {
404
            $result[$index] = (int) round($value * $delta);
405
        }
406
407
        return $result;
408
    }
409
410
    /**
411
     * Show statistics.
412
     *
413
     * @param string $title      The title
414
     * @param array  $stats
415
     * @param bool   $showTotal
416
     * @param bool   $isFileSize
417
     */
418
    public static function printStats(
419
        $title,
420
        $stats,
421
        $showTotal = true,
422
        $isFileSize = false
423
    ) {
424
        $total = 0;
425
        $data = self::rescale($stats);
426
        echo '<table class="data_table" cellspacing="0" cellpadding="3">
427
                <tr><th colspan="'.($showTotal ? '4' : '3').'">'.$title.'</th></tr>';
428
        $i = 0;
429
        foreach ($stats as $subtitle => $number) {
430
            $total += $number;
431
        }
432
433
        foreach ($stats as $subtitle => $number) {
434
            if (!$isFileSize) {
435
                $number_label = number_format($number, 0, ',', '.');
436
            } else {
437
                $number_label = self::makeSizeString($number);
438
            }
439
            $percentage = ($total > 0 ? number_format(100 * $number / $total, 1, ',', '.') : '0');
440
441
            echo '<tr class="row_'.($i % 2 == 0 ? 'odd' : 'even').'">
442
                    <td width="150">'.$subtitle.'</td>
443
                    <td width="550">'.Display::bar_progress($percentage, false).'</td>
444
                    <td align="right">'.$number_label.'</td>';
445
            if ($showTotal) {
446
                echo '<td align="right"> '.$percentage.'%</td>';
447
            }
448
            echo '</tr>';
449
            $i++;
450
        }
451
        if ($showTotal) {
452
            if (!$isFileSize) {
453
                $total_label = number_format($total, 0, ',', '.');
454
            } else {
455
                $total_label = self::makeSizeString($total);
456
            }
457
            echo '<tr><th colspan="4" align="right">'.get_lang('Total').': '.$total_label.'</td></tr>';
458
        }
459
        echo '</table>';
460
    }
461
462
    /**
463
     * Show some stats about the number of logins.
464
     *
465
     * @param string $type month, hour or day
466
     */
467
    public static function printLoginStats($type)
468
    {
469
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
470
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
471
        $urlId = api_get_current_access_url_id();
472
473
        $table_url = null;
474
        $where_url = null;
475
        $now = api_get_utc_datetime();
476
        $where_url_last = ' WHERE login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
477
        if (api_is_multiple_url_enabled()) {
478
            $table_url = ", $access_url_rel_user_table";
479
            $where_url = " WHERE login_user_id=user_id AND access_url_id='".$urlId."'";
480
            $where_url_last = ' AND login_date > DATE_SUB("'.$now.'",INTERVAL 1 %s)';
481
        }
482
483
        $period = get_lang('PeriodMonth');
484
        $periodCollection = api_get_months_long();
485
        $sql = "SELECT 
486
                DATE_FORMAT( login_date, '%Y-%m' ) AS stat_date , 
487
                count( login_id ) AS number_of_logins
488
                FROM $table $table_url $where_url
489
                GROUP BY stat_date
490
                ORDER BY login_date DESC";
491
        $sql_last_x = null;
492
493
        switch ($type) {
494
            case 'hour':
495
                $period = get_lang('PeriodHour');
496
                $sql = "SELECT 
497
                          DATE_FORMAT( login_date, '%H') AS stat_date, 
498
                          count( login_id ) AS number_of_logins 
499
                        FROM $table $table_url $where_url 
500
                        GROUP BY stat_date 
501
                        ORDER BY stat_date ";
502
                $sql_last_x = "SELECT 
503
                                DATE_FORMAT( login_date, '%H' ) AS stat_date, 
504
                                count( login_id ) AS number_of_logins 
505
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'DAY')." 
506
                               GROUP BY stat_date 
507
                               ORDER BY stat_date ";
508
                break;
509
            case 'day':
510
                $periodCollection = api_get_week_days_long();
511
                $period = get_lang('PeriodDay');
512
                $sql = "SELECT DATE_FORMAT( login_date, '%w' ) AS stat_date , 
513
                        count( login_id ) AS number_of_logins 
514
                        FROM  $table $table_url $where_url 
515
                        GROUP BY stat_date 
516
                        ORDER BY DATE_FORMAT( login_date, '%w' ) ";
517
                $sql_last_x = "SELECT 
518
                                DATE_FORMAT( login_date, '%w' ) AS stat_date, 
519
                                count( login_id ) AS number_of_logins 
520
                               FROM $table $table_url $where_url ".sprintf($where_url_last, 'WEEK')." 
521
                               GROUP BY stat_date 
522
                               ORDER BY DATE_FORMAT( login_date, '%w' ) ";
523
                break;
524
        }
525
526
        if ($sql_last_x) {
527
            $res_last_x = Database::query($sql_last_x);
528
            $result_last_x = [];
529
            while ($obj = Database::fetch_object($res_last_x)) {
530
                $stat_date = ($type === 'day') ? $periodCollection[$obj->stat_date] : $obj->stat_date;
531
                $result_last_x[$stat_date] = $obj->number_of_logins;
532
            }
533
            self::printStats(get_lang('LastLogins').' ('.$period.')', $result_last_x, true);
534
            flush(); //flush web request at this point to see something already while the full data set is loading
535
            echo '<br />';
536
        }
537
        $res = Database::query($sql);
538
        $result = [];
539
        while ($obj = Database::fetch_object($res)) {
540
            $stat_date = $obj->stat_date;
541
            switch ($type) {
542
                case 'month':
543
                    $stat_date = explode('-', $stat_date);
544
                    $stat_date[1] = $periodCollection[$stat_date[1] - 1];
545
                    $stat_date = implode(' ', $stat_date);
546
                    break;
547
                case 'day':
548
                    $stat_date = $periodCollection[$stat_date];
549
                    break;
550
            }
551
            $result[$stat_date] = $obj->number_of_logins;
552
        }
553
        self::printStats(get_lang('AllLogins').' ('.$period.')', $result, true);
554
    }
555
556
    /**
557
     * Print the number of recent logins.
558
     *
559
     * @param bool $distinct        whether to only give distinct users stats, or *all* logins
560
     * @param int  $sessionDuration
561
     */
562
    public static function printRecentLoginStats($distinct = false, $sessionDuration = 0)
563
    {
564
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
565
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
566
        $urlId = api_get_current_access_url_id();
567
        $table_url = '';
568
        $where_url = '';
569
        if (api_is_multiple_url_enabled()) {
570
            $table_url = ", $access_url_rel_user_table";
571
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
572
        }
573
574
        $now = api_get_utc_datetime();
575
        $field = 'login_id';
576
        if ($distinct) {
577
            $field = 'DISTINCT(login_user_id)';
578
        }
579
580
        $days = [1, 7, 15, 31];
581
        $sqlList = [];
582
583
        $sessionDuration = (int) $sessionDuration;
584
        foreach ($days as $day) {
585
            $date = new DateTime($now);
586
            $startDate = $date->format('Y-m-d').' 00:00:00';
587
            $endDate = $date->format('Y-m-d').' 23:59:59';
588
589
            if ($day > 1) {
590
                $startDate = $date->sub(new DateInterval('P'.$day.'D'));
591
                $startDate = $startDate->format('Y-m-d').' 00:00:00';
592
            }
593
594
            $localDate = api_get_local_time($startDate, null, null, false, false);
595
            $localEndDate = api_get_local_time($endDate, null, null, false, false);
596
597
            $label = sprintf(get_lang('LastXDays'), $day);
598
            if ($day == 1) {
599
                $label = get_lang('Today');
600
            }
601
            $label .= " <br /> $localDate - $localEndDate";
602
            $sql = "SELECT count($field) AS number 
603
                    FROM $table $table_url 
604
                    WHERE 
605
                        UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND
606
                        login_date BETWEEN '$startDate' AND '$endDate'  
607
                        $where_url";
608
            $sqlList[$label] = $sql;
609
        }
610
611
        $sql = "SELECT count($field) AS number 
612
                FROM $table $table_url                
613
                WHERE UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration $where_url
614
               ";
615
        $sqlList[get_lang('Total')] = $sql;
616
        $totalLogin = [];
617
        foreach ($sqlList as $label => $query) {
618
            $res = Database::query($query);
619
            $obj = Database::fetch_object($res);
620
            $totalLogin[$label] = $obj->number;
621
        }
622
        if ($distinct) {
623
            self::printStats(get_lang('DistinctUsersLogins'), $totalLogin, false);
624
        } else {
625
            self::printStats(get_lang('Logins'), $totalLogin, false);
626
        }
627
    }
628
629
    /**
630
     * get the number of recent logins.
631
     *
632
     * @param bool $distinct        Whether to only give distinct users stats, or *all* logins
633
     * @param int  $sessionDuration
634
     * @param bool $completeMissingDays Whether to fill the daily gaps (if any) when getting a list of logins
635
     *
636
     * @return array
637
     */
638
    public static function getRecentLoginStats($distinct = false, $sessionDuration = 0, $completeMissingDays = true)
639
    {
640
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
641
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
642
        $urlId = api_get_current_access_url_id();
643
        $table_url = '';
644
        $where_url = '';
645
        if (api_is_multiple_url_enabled()) {
646
            $table_url = ", $access_url_rel_user_table";
647
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
648
        }
649
650
        $now = api_get_utc_datetime();
651
        $date = new DateTime($now);
652
        $date->sub(new DateInterval('P15D'));
653
        $newDate = $date->format('Y-m-d h:i:s');
654
        $totalLogin = self::buildDatesArray($newDate, $now, true);
655
656
        $field = 'login_id';
657
        if ($distinct) {
658
            $field = 'DISTINCT(login_user_id)';
659
        }
660
        $sessionDuration = (int) $sessionDuration;
661
662
        $sql = "SELECT count($field) AS number, date(login_date) as login_date
663
                FROM $table $table_url 
664
                WHERE 
665
                UNIX_TIMESTAMP(logout_date) - UNIX_TIMESTAMP(login_date) > $sessionDuration AND
666
                login_date >= '$newDate' $where_url 
667
                GROUP BY date(login_date)";
668
669
        $res = Database::query($sql);
670
        while ($row = Database::fetch_array($res, 'ASSOC')) {
671
            $monthAndDay = substr($row['login_date'], 5, 5);
672
            $totalLogin[$monthAndDay] = $row['number'];
673
        }
674
675
        return $totalLogin;
676
    }
677
678
    /**
679
     * Get course tools usage statistics for the whole platform (by URL if multi-url).
680
     */
681
    public static function getToolsStats()
682
    {
683
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ACCESS);
684
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
685
        $urlId = api_get_current_access_url_id();
686
687
        $tools = [
688
            'announcement',
689
            'assignment',
690
            'calendar_event',
691
            'chat',
692
            'course_description',
693
            'document',
694
            'dropbox',
695
            'group',
696
            'learnpath',
697
            'link',
698
            'quiz',
699
            'student_publication',
700
            'user',
701
            'forum',
702
        ];
703
        $tool_names = [];
704
        foreach ($tools as $tool) {
705
            $tool_names[$tool] = get_lang(ucfirst($tool), '');
706
        }
707
        if (api_is_multiple_url_enabled()) {
708
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
709
                    FROM $table t , $access_url_rel_course_table a
710
                    WHERE
711
                        access_tool IN ('".implode("','", $tools)."') AND
712
                        t.c_id = a.c_id AND
713
                        access_url_id='".$urlId."'
714
                        GROUP BY access_tool
715
                    ";
716
        } else {
717
            $sql = "SELECT access_tool, count( access_id ) AS number_of_logins
718
                    FROM $table
719
                    WHERE access_tool IN ('".implode("','", $tools)."')
720
                    GROUP BY access_tool ";
721
        }
722
723
        $res = Database::query($sql);
724
        $result = [];
725
        while ($obj = Database::fetch_object($res)) {
726
            $result[$tool_names[$obj->access_tool]] = $obj->number_of_logins;
727
        }
728
729
        return $result;
730
    }
731
732
    /**
733
     * Show some stats about the accesses to the different course tools.
734
     *
735
     * @param array $result If defined, this serves as data. Otherwise, will get the data from getToolsStats()
736
     */
737
    public static function printToolStats($result = null)
738
    {
739
        if (empty($result)) {
740
            $result = self::getToolsStats();
741
        }
742
        self::printStats(get_lang('PlatformToolAccess'), $result, true);
743
    }
744
745
    /**
746
     * Show some stats about the number of courses per language.
747
     */
748
    public static function printCourseByLanguageStats()
749
    {
750
        $table = Database::get_main_table(TABLE_MAIN_COURSE);
751
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
752
        $urlId = api_get_current_access_url_id();
753
        if (api_is_multiple_url_enabled()) {
754
            $sql = "SELECT course_language, count( c.code ) AS number_of_courses
755
                    FROM $table as c, $access_url_rel_course_table as u
756
                    WHERE u.c_id = c.id AND access_url_id='".$urlId."'
757
                    GROUP BY course_language
758
                    ORDER BY number_of_courses DESC";
759
        } else {
760
            $sql = "SELECT course_language, count( code ) AS number_of_courses
761
                   FROM $table GROUP BY course_language
762
                   ORDER BY number_of_courses DESC";
763
        }
764
        $res = Database::query($sql);
765
        $result = [];
766
        while ($obj = Database::fetch_object($res)) {
767
            $result[$obj->course_language] = $obj->number_of_courses;
768
        }
769
        return $result;
770
    }
771
772
    /**
773
     * Shows the number of users having their picture uploaded in Dokeos.
774
     */
775
    public static function printUserPicturesStats()
776
    {
777
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
778
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
779
        $urlId = api_get_current_access_url_id();
780
        $url_condition = null;
781
        $url_condition2 = null;
782
        $table = null;
783
        if (api_is_multiple_url_enabled()) {
784
            $url_condition = ", $access_url_rel_user_table as url WHERE url.user_id=u.user_id AND access_url_id='".$urlId."'";
785
            $url_condition2 = " AND url.user_id=u.user_id AND access_url_id='".$urlId."'";
786
            $table = ", $access_url_rel_user_table as url ";
787
        }
788
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u ".$url_condition;
789
        $res = Database::query($sql);
790
        $count1 = Database::fetch_object($res);
791
        $sql = "SELECT COUNT(*) AS n FROM $user_table as u $table ".
792
               "WHERE LENGTH(picture_uri) > 0 $url_condition2";
793
        $res = Database::query($sql);
794
        $count2 = Database::fetch_object($res);
795
        // #users without picture
796
        $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...
797
        $result[get_lang('Yes')] = $count2->n; // #users with picture
798
799
        self::printStats(get_lang('CountUsers').' ('.get_lang('UserPicture').')', $result, true);
800
    }
801
802
    /**
803
     * Important activities.
804
     */
805
    public static function printActivitiesStats()
806
    {
807
        echo '<h4>'.get_lang('ImportantActivities').'</h4>';
808
        // Create a search-box
809
        $form = new FormValidator(
810
            'search_simple',
811
            'get',
812
            api_get_path(WEB_CODE_PATH).'admin/statistics/index.php',
813
            '',
814
            'width=200px',
815
            false
816
        );
817
        $renderer = &$form->defaultRenderer();
818
        $renderer->setCustomElementTemplate('<span>{element}</span> ');
819
        $form->addHidden('report', 'activities');
820
        $form->addHidden('activities_direction', 'DESC');
821
        $form->addHidden('activities_column', '4');
822
        $form->addElement('text', 'keyword', get_lang('Keyword'));
823
        $form->addButtonSearch(get_lang('Search'), 'submit');
824
        echo '<div class="actions">';
825
        $form->display();
826
        echo '</div>';
827
828
        $table = new SortableTable(
829
            'activities',
830
            ['Statistics', 'getNumberOfActivities'],
831
            ['Statistics', 'getActivitiesData'],
832
            7,
833
            50,
834
            'DESC'
835
        );
836
        $parameters = [];
837
838
        $parameters['report'] = 'activities';
839
        if (isset($_GET['keyword'])) {
840
            $parameters['keyword'] = Security::remove_XSS($_GET['keyword']);
841
        }
842
843
        $table->set_additional_parameters($parameters);
844
        $table->set_header(0, get_lang('EventType'));
845
        $table->set_header(1, get_lang('DataType'));
846
        $table->set_header(2, get_lang('Value'));
847
        $table->set_header(3, get_lang('Course'));
848
        $table->set_header(4, get_lang('Session'));
849
        $table->set_header(5, get_lang('UserName'));
850
        $table->set_header(6, get_lang('IPAddress'));
851
        $table->set_header(7, get_lang('Date'));
852
        $table->display();
853
    }
854
855
    /**
856
     * Shows statistics about the time of last visit to each course.
857
     */
858
    public static function printCourseLastVisit()
859
    {
860
        $access_url_rel_course_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_COURSE);
861
        $urlId = api_get_current_access_url_id();
862
863
        $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...
864
        $columns[1] = 'access_date';
865
        $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...
866
        $sql_order[SORT_DESC] = 'DESC';
867
        $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10;
868
        $page_nr = isset($_GET['page_nr']) ? intval($_GET['page_nr']) : 1;
869
        $column = isset($_GET['column']) ? intval($_GET['column']) : 0;
870
        $direction = isset($_GET['direction']) ? $_GET['direction'] : SORT_ASC;
871
872
        if (!in_array($direction, [SORT_ASC, SORT_DESC])) {
873
            $direction = SORT_ASC;
874
        }
875
        $form = new FormValidator('courselastvisit', 'get');
876
        $form->addElement('hidden', 'report', 'courselastvisit');
877
        $form->addText('date_diff', get_lang('Days'), true);
878
        $form->addRule('date_diff', 'InvalidNumber', 'numeric');
879
        $form->addButtonSearch(get_lang('Search'), 'submit');
880
        if (!isset($_GET['date_diff'])) {
881
            $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...
882
        } else {
883
            $defaults['date_diff'] = Security::remove_XSS($_GET['date_diff']);
884
        }
885
        $form->setDefaults($defaults);
886
        $form->display();
887
        $values = $form->exportValues();
888
        $date_diff = $values['date_diff'];
889
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LASTACCESS);
890
        if (api_is_multiple_url_enabled()) {
891
            $sql = "SELECT * FROM $table t , $access_url_rel_course_table a
892
                   WHERE
893
                        t.c_id = a.c_id AND
894
                        access_url_id='".$urlId."'
895
                   GROUP BY t.c_id
896
                   HAVING t.c_id <> ''
897
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
898
        } else {
899
            $sql = "SELECT * FROM $table t
900
                   GROUP BY t.c_id
901
                   HAVING t.c_id <> ''
902
                   AND DATEDIFF( '".api_get_utc_datetime()."' , access_date ) <= ".$date_diff;
903
        }
904
        $sql .= ' ORDER BY '.$columns[$column].' '.$sql_order[$direction];
905
        $from = ($page_nr - 1) * $per_page;
906
        $sql .= ' LIMIT '.$from.','.$per_page;
907
908
        echo '<p>'.get_lang('LastAccess').' &gt;= '.$date_diff.' '.get_lang('Days').'</p>';
909
        $res = Database::query($sql);
910
        if (Database::num_rows($res) > 0) {
911
            $courses = [];
912
            while ($obj = Database::fetch_object($res)) {
913
                $courseInfo = api_get_course_info_by_id($obj->c_id);
914
                $course = [];
915
                $course[] = '<a href="'.api_get_path(WEB_COURSE_PATH).$courseInfo['code'].'">'.$courseInfo['code'].' <a>';
916
                // Allow sort by date hiding the numerical date
917
                $course[] = '<span style="display:none;">'.$obj->access_date.'</span>'.api_convert_and_format_date($obj->access_date);
918
                $courses[] = $course;
919
            }
920
            $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...
921
            $parameters['report'] = 'courselastvisit';
922
            $table_header[] = [get_lang("CourseCode"), 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...
923
            $table_header[] = [get_lang("LastAccess"), true];
924
            Display:: display_sortable_table(
925
                $table_header,
926
                $courses,
927
                ['column' => $column, 'direction' => $direction],
928
                [],
929
                $parameters
930
            );
931
        } else {
932
            echo get_lang('NoSearchResults');
933
        }
934
    }
935
936
    /**
937
     * Displays the statistics of the messages sent and received by each user in the social network.
938
     *
939
     * @param string $messageType Type of message: 'sent' or 'received'
940
     *
941
     * @return array Message list
942
     */
943
    public static function getMessages($messageType)
944
    {
945
        $message_table = Database::get_main_table(TABLE_MESSAGE);
946
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
947
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
948
949
        $urlId = api_get_current_access_url_id();
950
951
        switch ($messageType) {
952
            case 'sent':
953
                $field = 'user_sender_id';
954
                break;
955
            case 'received':
956
                $field = 'user_receiver_id';
957
                break;
958
        }
959
960
        if (api_is_multiple_url_enabled()) {
961
            $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...
962
                FROM $access_url_rel_user_table as url, $message_table m 
963
                LEFT JOIN $user_table u ON m.$field = u.user_id 
964
                WHERE  url.user_id = m.$field AND  access_url_id='".$urlId."' 
965
                GROUP BY m.$field 
966
                ORDER BY count_message DESC ";
967
        } else {
968
            $sql = "SELECT lastname, firstname, username, COUNT($field) AS count_message 
969
                FROM $message_table m 
970
                LEFT JOIN $user_table u ON m.$field = u.user_id 
971
                GROUP BY m.$field ORDER BY count_message DESC ";
972
        }
973
        $res = Database::query($sql);
974
        $messages_sent = [];
975
        while ($messages = Database::fetch_array($res)) {
976
            if (empty($messages['username'])) {
977
                $messages['username'] = get_lang('Unknown');
978
            }
979
            $users = api_get_person_name(
980
                $messages['firstname'],
981
                $messages['lastname']
982
            ).'<br />('.$messages['username'].')';
983
            $messages_sent[$users] = $messages['count_message'];
984
        }
985
986
        return $messages_sent;
987
    }
988
989
    /**
990
     * Count the number of friends for social network users.
991
     */
992
    public static function getFriends()
993
    {
994
        $user_friend_table = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
995
        $user_table = Database::get_main_table(TABLE_MAIN_USER);
996
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
997
        $urlId = api_get_current_access_url_id();
998
999
        if (api_is_multiple_url_enabled()) {
1000
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend 
1001
                    FROM $access_url_rel_user_table as url, $user_friend_table uf 
1002
                    LEFT JOIN $user_table u 
1003
                    ON (uf.user_id = u.user_id) 
1004
                    WHERE 
1005
                        uf.relation_type <> '".USER_RELATION_TYPE_RRHH."' AND 
1006
                        uf.user_id = url.user_id AND  
1007
                        access_url_id = '".$urlId."' 
1008
                    GROUP BY uf.user_id 
1009
                    ORDER BY count_friend DESC ";
1010
        } else {
1011
            $sql = "SELECT lastname, firstname, username, COUNT(friend_user_id) AS count_friend 
1012
                    FROM $user_friend_table uf 
1013
                    LEFT JOIN $user_table u 
1014
                    ON (uf.user_id = u.user_id) 
1015
                    WHERE uf.relation_type <> '".USER_RELATION_TYPE_RRHH."' 
1016
                    GROUP BY uf.user_id 
1017
                    ORDER BY count_friend DESC ";
1018
        }
1019
        $res = Database::query($sql);
1020
        $list_friends = [];
1021
        while ($friends = Database::fetch_array($res)) {
1022
            $users = api_get_person_name($friends['firstname'], $friends['lastname']).'<br />('.$friends['username'].')';
1023
            $list_friends[$users] = $friends['count_friend'];
1024
        }
1025
1026
        return $list_friends;
1027
    }
1028
1029
    /**
1030
     * Print the number of users that didn't login for a certain period of time.
1031
     */
1032
    public static function printUsersNotLoggedInStats()
1033
    {
1034
        $totalLogin = [];
1035
        $table = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
1036
        $access_url_rel_user_table = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
1037
        $urlId = api_get_current_access_url_id();
1038
        $total = self::countUsers();
1039
        if (api_is_multiple_url_enabled()) {
1040
            $table_url = ", $access_url_rel_user_table";
1041
            $where_url = " AND login_user_id=user_id AND access_url_id='".$urlId."'";
1042
        } else {
1043
            $table_url = '';
1044
            $where_url = '';
1045
        }
1046
        $now = api_get_utc_datetime();
1047
        $sql[get_lang('ThisDay')] =
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...
1048
            "SELECT count(distinct(login_user_id)) AS number ".
1049
            " FROM $table $table_url ".
1050
            " WHERE DATE_ADD(login_date, INTERVAL 1 DAY) >= '$now' $where_url";
1051
        $sql[get_lang('Last7days')] =
1052
            "SELECT count(distinct(login_user_id)) AS number ".
1053
            " FROM $table $table_url ".
1054
            " WHERE DATE_ADD(login_date, INTERVAL 7 DAY) >= '$now' $where_url";
1055
        $sql[get_lang('Last31days')] =
1056
            "SELECT count(distinct(login_user_id)) AS number ".
1057
            " FROM $table $table_url ".
1058
            " WHERE DATE_ADD(login_date, INTERVAL 31 DAY) >= '$now' $where_url";
1059
        $sql[sprintf(get_lang('LastXMonths'), 6)] =
1060
            "SELECT count(distinct(login_user_id)) AS number ".
1061
            " FROM $table $table_url ".
1062
            " WHERE DATE_ADD(login_date, INTERVAL 6 MONTH) >= '$now' $where_url";
1063
        $sql[get_lang('NeverConnected')] =
1064
            "SELECT count(distinct(login_user_id)) AS number ".
1065
            " FROM $table $table_url WHERE 1=1 $where_url";
1066
        foreach ($sql as $index => $query) {
1067
            $res = Database::query($query);
1068
            $obj = Database::fetch_object($res);
1069
            $r = $total - $obj->number;
1070
            $totalLogin[$index] = $r < 0 ? 0 : $r;
1071
        }
1072
        self::printStats(
1073
            get_lang('StatsUsersDidNotLoginInLastPeriods'),
1074
            $totalLogin,
1075
            false
1076
        );
1077
    }
1078
    /**
1079
     * Returns an array with indexes as the 'yyyy-mm-dd' format of each date
1080
     * within the provided range (including limits). Dates are assumed to be
1081
     * given in UTC
1082
     * @param string $startDate Start date, in Y-m-d or Y-m-d h:i:s format
1083
     * @param string $endDate End date, in Y-m-d or Y-m-d h:i:s format
1084
     * @param bool   $removeYear Whether to remove the year in the results (for easier reading)
1085
     * @return array|bool False on error in the params, array of [date1 => 0, date2 => 0, ...] otherwise
1086
     */
1087
    public static function buildDatesArray($startDate, $endDate, $removeYear = false)
1088
    {
1089
        if (strlen($startDate) > 10) {
1090
            $startDate = substr($startDate, 0, 10);
1091
        }
1092
        if (strlen($endDate) > 10) {
1093
            $endDate = substr($endDate, 0, 10);
1094
        }
1095
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1096
            return false;
1097
        }
1098
        if (!preg_match('/\d\d\d\d-\d\d-\d\d/', $startDate)) {
1099
            return false;
1100
        }
1101
        $startTimestamp = strtotime($startDate);
1102
        $endTimestamp = strtotime($endDate);
1103
        $list = [];
1104
        for ($time = $startTimestamp; $time < $endTimestamp; $time += 86400) {
1105
            $datetime = api_get_utc_datetime($time);
1106
            if ($removeYear) {
1107
                $datetime = substr($datetime, 5, 5);
1108
            } else {
1109
                $dateTime = substr($datetime, 0, 10);
1110
            }
1111
            $list[$datetime] = 0;
1112
        }
1113
        return $list;
1114
    }
1115
    /**
1116
     * Prepare the JS code to load a chart
1117
     * @param string $url URL for AJAX data generator
1118
     * @param string $type bar, line, pie, etc
1119
     * @param string $options Additional options to the chart (see chart-specific library)
1120
     * @param string A JS code for loading the chart together with a call to AJAX data generator
1121
     */
1122
    public static function getJSChartTemplate($url, $type = 'pie', $options = '', $elementId = 'canvas')
1123
    { 
1124
        $chartCode = '
1125
        <script>
1126
        $(document).ready(function() {
1127
            $.ajax({
1128
                url: "'.$url.'",
1129
                type: "POST",
1130
                success: function(data) {
1131
                    Chart.defaults.global.responsive = true;
1132
                    var ctx = document.getElementById("'.$elementId.'").getContext("2d");
1133
                    var myLoginChart = new Chart(ctx, {
1134
                        type: "'.$type.'",
1135
                        data: data,
1136
                        options: {'.$options.'}
1137
                    });
1138
                }
1139
            });
1140
        });
1141
        </script>';
1142
        return $chartCode;
1143
    }
1144
}
1145