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