statistics.lib.php 51 KB

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