3 // THESE CONSTANTS ARE USED FOR THE REPORTING PAGE.
5 define('STATS_REPORT_LOGINS',1); // double impose logins and unqiue logins on a line graph. site course only.
6 define('STATS_REPORT_READS',2); // double impose student reads and teacher reads on a line graph.
7 define('STATS_REPORT_WRITES',3); // double impose student writes and teacher writes on a line graph.
8 define('STATS_REPORT_ACTIVITY',4); // 2+3 added up, teacher vs student.
9 define('STATS_REPORT_ACTIVITYBYROLE',5); // all activity, reads vs writes, seleted by role.
11 // user level stats reports.
12 define('STATS_REPORT_USER_ACTIVITY',7);
13 define('STATS_REPORT_USER_ALLACTIVITY',8);
14 define('STATS_REPORT_USER_LOGINS',9);
15 define('STATS_REPORT_USER_VIEW',10); // this is the report you see on the user profile.
17 // admin only ranking stats reports
18 define('STATS_REPORT_ACTIVE_COURSES',11);
19 define('STATS_REPORT_ACTIVE_COURSES_WEIGHTED',12);
20 define('STATS_REPORT_PARTICIPATORY_COURSES',13);
21 define('STATS_REPORT_PARTICIPATORY_COURSES_RW',14);
23 // start after 0 = show dailies.
24 define('STATS_TIME_LASTWEEK',1);
25 define('STATS_TIME_LAST2WEEKS',2);
26 define('STATS_TIME_LAST3WEEKS',3);
27 define('STATS_TIME_LAST4WEEKS',4);
29 // start after 10 = show weeklies
30 define('STATS_TIME_LAST2MONTHS',12);
32 define('STATS_TIME_LAST3MONTHS',13);
33 define('STATS_TIME_LAST4MONTHS',14);
34 define('STATS_TIME_LAST5MONTHS',15);
35 define('STATS_TIME_LAST6MONTHS',16);
37 // start after 20 = show monthlies
38 define('STATS_TIME_LAST7MONTHS',27);
39 define('STATS_TIME_LAST8MONTHS',28);
40 define('STATS_TIME_LAST9MONTHS',29);
41 define('STATS_TIME_LAST10MONTHS',30);
42 define('STATS_TIME_LAST11MONTHS',31);
43 define('STATS_TIME_LASTYEAR',32);
45 // different modes for what reports to offer
46 define('STATS_MODE_GENERAL',1);
47 define('STATS_MODE_DETAILED',2);
48 define('STATS_MODE_RANKED',3); // admins only - ranks courses
51 * Print daily cron progress
52 * @param string $ident
54 function stats_daily_progress($ident) {
58 if ($ident == 'init') {
59 $init = $start = time();
63 $elapsed = time() - $start;
66 if (debugging('', DEBUG_ALL
)) {
67 mtrace("$ident:$elapsed ", '');
74 * Execute daily statistics gathering
75 * @param int $maxdays maximum number of days to be processed
76 * @return boolean success
78 function stats_cron_daily($maxdays=1) {
83 // read last execution date from db
84 if (!$timestart = get_config(NULL, 'statslastdaily')) {
85 $timestart = stats_get_base_daily(stats_get_start_from('daily'));
86 set_config('statslastdaily', $timestart);
89 $nextmidnight = stats_get_next_day_start($timestart);
91 // are there any days that need to be processed?
92 if ($now < $nextmidnight) {
93 return true; // everything ok and up-to-date
96 $timeout = empty($CFG->statsmaxruntime
) ?
60*60*24 : $CFG->statsmaxruntime
;
98 if (!set_cron_lock('statsrunning', $now +
$timeout)) {
102 // fisrt delete entries that should not be there yet
103 delete_records_select('stats_daily', "timeend > $timestart");
104 delete_records_select('stats_user_daily', "timeend > $timestart");
106 // Read in a few things we'll use later
107 $viewactions = implode(',', stats_get_action_names('view'));
108 $postactions = implode(',', stats_get_action_names('post'));
110 $guest = get_guest();
111 $guestrole = get_guest_role();
113 list($enroljoin, $enrolwhere) = stats_get_enrolled_sql($CFG->statscatdepth
, true);
114 list($enroljoin_na, $enrolwhere_na) = stats_get_enrolled_sql($CFG->statscatdepth
, false);
115 list($fpjoin, $fpwhere) = stats_get_enrolled_sql(0, true);
117 mtrace("Running daily statistics gathering, starting at $timestart:");
120 $failed = false; // failed stats flag
122 while ($now > $nextmidnight) {
123 if ($days >= $maxdays) {
124 mtrace("...stopping early, reached maximum number of $maxdays days - will continue next time.");
125 set_cron_lock('statsrunning', null);
130 @set_time_limit
($timeout - 200);
134 set_cron_lock('statsrunning', time() +
$timeout, true);
139 $timesql = "l.time >= $timestart AND l.time < $nextmidnight";
140 $timesql1 = "l1.time >= $timestart AND l1.time < $nextmidnight";
141 $timesql2 = "l2.time >= $timestart AND l2.time < $nextmidnight";
143 stats_daily_progress('init');
146 /// find out if any logs available for this day
148 FROM {$CFG->prefix}log l
150 $logspresent = get_records_sql($sql, 0, 1);
152 /// process login info first
153 $sql = "INSERT INTO {$CFG->prefix}stats_user_daily (stattype, timeend, courseid, userid, statsreads)
155 SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID
." AS courseid,
156 l.userid, count(l.id) AS statsreads
157 FROM {$CFG->prefix}log l
158 WHERE action = 'login' AND $timesql
159 GROUP BY stattype, timeend, courseid, userid
160 HAVING count(l.id) > 0";
162 if ($logspresent and !execute_sql($sql, false)) {
166 stats_daily_progress('1');
168 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
170 SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID
." as courseid, 0,
171 COALESCE((SELECT SUM(statsreads)
172 FROM {$CFG->prefix}stats_user_daily s1
173 WHERE s1.stattype = 'logins' AND timeend = $nextmidnight), 0) AS stat1,
175 FROM {$CFG->prefix}stats_user_daily s2
176 WHERE s2.stattype = 'logins' AND timeend = $nextmidnight) AS stat2";
178 if ($logspresent and !execute_sql($sql, false)) {
182 stats_daily_progress('2');
185 // Enrolments and active enrolled users
187 // Unfortunately, we do not know how many users were registered
188 // at given times in history :-(
189 // - stat1: enrolled users
190 // - stat2: enrolled users active in this period
191 // - enrolment is defined now as having course:view capability in
192 // course context or above, we look 3 cats upwards only and ignore prevent
193 // and prohibit caps to simplify it
194 // - SITEID is specialcased here, because it's all about default enrolment
195 // in that case, we'll count non-deleted users.
198 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
200 SELECT 'enrolments' AS stattype, $nextmidnight AS timeend,
201 pl.courseid, pl.roleid, COUNT(DISTINCT pl.userid) AS stat1, 0 AS stat2
202 FROM (SELECT DISTINCT ra.roleid, ra.userid, c.id as courseid
203 FROM {$CFG->prefix}role_assignments ra $enroljoin_na
206 GROUP BY stattype, timeend, pl.courseid, pl.roleid, stat2";
208 if (!execute_sql($sql, false)) {
212 stats_daily_progress('3');
214 // using table alias in UPDATE does not work in pg < 8.2
215 $sql = "UPDATE {$CFG->prefix}stats_daily
216 SET stat2 = (SELECT COUNT(DISTINCT ra.userid)
217 FROM {$CFG->prefix}role_assignments ra $enroljoin_na
218 WHERE ra.roleid = {$CFG->prefix}stats_daily.roleid AND
219 c.id = {$CFG->prefix}stats_daily.courseid AND
222 FROM {$CFG->prefix}log l
223 WHERE l.course = {$CFG->prefix}stats_daily.courseid AND
224 l.userid = ra.userid AND $timesql))
225 WHERE {$CFG->prefix}stats_daily.stattype = 'enrolments' AND
226 {$CFG->prefix}stats_daily.timeend = $nextmidnight AND
227 {$CFG->prefix}stats_daily.courseid IN
228 (SELECT DISTINCT l.course
229 FROM {$CFG->prefix}log l
232 if ($logspresent and !execute_sql($sql, false)) {
236 stats_daily_progress('4');
238 /// now get course total enrolments (roleid==0) - except frontpage
239 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
241 SELECT 'enrolments' AS stattype, $nextmidnight AS timeend,
242 c.id, 0 AS nroleid, COUNT(DISTINCT ra.userid) AS stat1, 0 AS stat2
243 FROM {$CFG->prefix}role_assignments ra $enroljoin_na
244 WHERE c.id <> ".SITEID
." AND $enrolwhere_na
245 GROUP BY stattype, timeend, c.id, nroleid, stat2
246 HAVING COUNT(DISTINCT ra.userid) > 0";
248 if ($logspresent and !execute_sql($sql, false)) {
252 stats_daily_progress('5');
254 $sql = "UPDATE {$CFG->prefix}stats_daily
255 SET stat2 = (SELECT COUNT(DISTINCT ra.userid)
256 FROM {$CFG->prefix}role_assignments ra $enroljoin_na
257 WHERE c.id = {$CFG->prefix}stats_daily.courseid AND
260 FROM {$CFG->prefix}log l
261 WHERE l.course = {$CFG->prefix}stats_daily.courseid AND
262 l.userid = ra.userid AND $timesql))
263 WHERE {$CFG->prefix}stats_daily.stattype = 'enrolments' AND
264 {$CFG->prefix}stats_daily.timeend = $nextmidnight AND
265 {$CFG->prefix}stats_daily.roleid = 0 AND
266 {$CFG->prefix}stats_daily.courseid IN
268 FROM {$CFG->prefix}log l
269 WHERE $timesql AND l.course <> ".SITEID
.")";
271 if ($logspresent and !execute_sql($sql, false)) {
275 stats_daily_progress('6');
277 /// frontapge(==site) enrolments total
278 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
280 SELECT 'enrolments', $nextmidnight, ".SITEID
.", 0,
282 FROM {$CFG->prefix}user u
283 WHERE u.deleted = 0) AS stat1,
284 (SELECT COUNT(DISTINCT u.id)
285 FROM {$CFG->prefix}user u
286 JOIN {$CFG->prefix}log l ON l.userid = u.id
287 WHERE u.deleted = 0 AND $timesql) AS stat2";
289 if ($logspresent and !execute_sql($sql, false)) {
293 stats_daily_progress('7');
295 if (empty($CFG->defaultfrontpageroleid
)) { // 1.9 only, so far
296 $defaultfproleid = 0;
298 $defaultfproleid = $CFG->defaultfrontpageroleid
;
301 /// Default frontpage role enrolments are all site users (not deleted)
302 if ($defaultfproleid) {
303 // first remove default frontpage role counts if created by previous query
305 FROM {$CFG->prefix}stats_daily
306 WHERE stattype = 'enrolments' AND courseid = ".SITEID
." AND
307 roleid = $defaultfproleid AND timeend = $nextmidnight";
308 if ($logspresent and !execute_sql($sql, false)) {
312 stats_daily_progress('8');
314 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
316 SELECT 'enrolments', $nextmidnight, ".SITEID
.", $defaultfproleid,
318 FROM {$CFG->prefix}user u
319 WHERE u.deleted = 0) AS stat1,
320 (SELECT COUNT(DISTINCT u.id)
321 FROM {$CFG->prefix}user u
322 JOIN {$CFG->prefix}log l ON l.userid = u.id
323 WHERE u.deleted = 0 AND $timesql) AS stat2";
325 if ($logspresent and !execute_sql($sql, false)) {
329 stats_daily_progress('9');
332 stats_daily_progress('x');
333 stats_daily_progress('x');
338 /// individual user stats (including not-logged-in) in each course, this is slow - reuse this data if possible
339 $sql = "INSERT INTO {$CFG->prefix}stats_user_daily (stattype, timeend, courseid, userid, statsreads, statswrites)
341 SELECT 'activity' AS stattype, $nextmidnight AS timeend, d.courseid, d.userid,
343 FROM {$CFG->prefix}log l
344 WHERE l.userid = d.userid AND
345 l.course = d.courseid AND $timesql AND
346 l.action IN ($viewactions)) AS statsreads,
348 FROM {$CFG->prefix}log l
349 WHERE l.userid = d.userid AND
350 l.course = d.courseid AND $timesql AND
351 l.action IN ($postactions)) AS statswrites
352 FROM (SELECT DISTINCT u.id AS userid, l.course AS courseid
353 FROM {$CFG->prefix}user u, {$CFG->prefix}log l
354 WHERE u.id = l.userid AND $timesql
356 SELECT 0 AS userid, ".SITEID
." AS courseid) d";
357 // can not use group by here because pg can not handle it :-(
359 if ($logspresent and !execute_sql($sql, false)) {
363 stats_daily_progress('10');
366 /// how many view/post actions in each course total
367 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
369 SELECT 'activity' AS stattype, $nextmidnight AS timeend, c.id AS courseid, 0,
371 FROM {$CFG->prefix}log l1
372 WHERE l1.course = c.id AND l1.action IN ($viewactions) AND
375 FROM {$CFG->prefix}log l2
376 WHERE l2.course = c.id AND l2.action IN ($postactions) AND
378 FROM {$CFG->prefix}course c
379 WHERE EXISTS (SELECT 'x'
380 FROM {$CFG->prefix}log l
381 WHERE l.course = c.id and $timesql)";
383 if ($logspresent and !execute_sql($sql, false)) {
387 stats_daily_progress('11');
390 /// how many view actions for each course+role - excluding guests and frontpage
392 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
394 SELECT 'activity' AS stattype, $nextmidnight AS timeend, pl.courseid, pl.roleid,
395 SUM(sud.statsreads) AS stat1, SUM(sud.statswrites) AS stat2
396 FROM {$CFG->prefix}stats_user_daily sud,
397 (SELECT DISTINCT ra.userid, ra.roleid, c.id AS courseid
398 FROM {$CFG->prefix}role_assignments ra $enroljoin
399 WHERE c.id <> ".SITEID
." AND
400 ra.roleid <> $guestrole->id AND ra.userid <> $guest->id AND
403 WHERE sud.userid = pl.userid AND sud.courseid = pl.courseid AND
404 sud.timeend = $nextmidnight AND sud.stattype='activity'
405 GROUP BY stattype, timeend, pl.courseid, pl.roleid
406 HAVING SUM(sud.statsreads) > 0 OR SUM(sud.statswrites) > 0";
408 if ($logspresent and !execute_sql($sql, false)) {
412 stats_daily_progress('12');
414 /// how many view actions from guests only in each course - excluding frontpage
415 /// (guest is anybody with guest role or no role with course:view in course - this may not work properly if category limit too low)
416 /// normal users may enter course with temporary guest acces too
418 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
420 SELECT 'activity' AS stattype, $nextmidnight AS timeend, sud.courseid, $guestrole->id AS nroleid,
421 SUM(sud.statsreads) AS stat1, SUM(sud.statswrites) AS stat2
422 FROM {$CFG->prefix}stats_user_daily sud
423 WHERE sud.timeend = $nextmidnight AND sud.courseid <> ".SITEID
." AND
424 sud.stattype='activity' AND
425 (sud.userid = $guest->id OR sud.userid
426 NOT IN (SELECT ra.userid
427 FROM {$CFG->prefix}role_assignments ra $enroljoin
428 WHERE c.id <> ".SITEID
." AND ra.roleid <> $guestrole->id AND
430 GROUP BY stattype, timeend, sud.courseid, nroleid
431 HAVING SUM(sud.statsreads) > 0 OR SUM(sud.statswrites) > 0";
433 if ($logspresent and !execute_sql($sql, false)) {
437 stats_daily_progress('13');
440 /// how many view actions for each role on frontpage - excluding guests, not-logged-in and default frontpage role
441 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
443 SELECT 'activity' AS stattype, $nextmidnight AS timeend, pl.courseid, pl.roleid,
444 SUM(sud.statsreads) AS stat1, SUM(sud.statswrites) AS stat2
445 FROM {$CFG->prefix}stats_user_daily sud,
446 (SELECT DISTINCT ra.userid, ra.roleid, c.id AS courseid
447 FROM {$CFG->prefix}role_assignments ra $enroljoin
448 WHERE c.id = ".SITEID
." AND
449 ra.roleid <> $defaultfproleid AND
450 ra.roleid <> $guestrole->id AND ra.userid <> $guest->id AND
453 WHERE sud.userid = pl.userid AND sud.courseid = pl.courseid AND
454 sud.timeend = $nextmidnight AND sud.stattype='activity'
455 GROUP BY stattype, timeend, pl.courseid, pl.roleid
456 HAVING SUM(sud.statsreads) > 0 OR SUM(sud.statswrites) > 0";
458 if ($logspresent and !execute_sql($sql, false)) {
462 stats_daily_progress('14');
465 /// how many view actions for default frontpage role on frontpage only
466 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
468 SELECT 'activity' AS stattype, $nextmidnight AS timeend, sud.courseid, $defaultfproleid AS nroleid,
469 SUM(sud.statsreads) AS stat1, SUM(sud.statswrites) AS stat2
470 FROM {$CFG->prefix}stats_user_daily sud
471 WHERE sud.timeend = $nextmidnight AND sud.courseid = ".SITEID
." AND
472 sud.stattype='activity' AND
473 sud.userid <> $guest->id AND sud.userid <> 0 AND sud.userid
474 NOT IN (SELECT ra.userid
475 FROM {$CFG->prefix}role_assignments ra $fpjoin
476 WHERE c.id = ".SITEID
." AND ra.roleid <> $guestrole->id AND
477 ra.roleid <> $defaultfproleid AND $fpwhere)
478 GROUP BY stattype, timeend, sud.courseid, nroleid
479 HAVING SUM(sud.statsreads) > 0 OR SUM(sud.statswrites) > 0";
481 if ($logspresent and !execute_sql($sql, false)) {
485 stats_daily_progress('15');
487 /// how many view actions for guests or not-logged-in on frontpage
488 $sql = "INSERT INTO {$CFG->prefix}stats_daily (stattype, timeend, courseid, roleid, stat1, stat2)
490 SELECT 'activity' AS stattype, $nextmidnight AS timeend, ".SITEID
." AS courseid, $guestrole->id AS nroleid,
491 SUM(pl.statsreads) AS stat1, SUM(pl.statswrites) AS stat2
492 FROM (SELECT sud.statsreads, sud.statswrites
493 FROM {$CFG->prefix}stats_user_daily sud
494 WHERE (sud.userid = $guest->id OR sud.userid = 0) AND
495 sud.timeend = $nextmidnight AND sud.courseid = ".SITEID
." AND
496 sud.stattype='activity'
498 GROUP BY stattype, timeend, courseid, nroleid
499 HAVING SUM(pl.statsreads) > 0 OR SUM(pl.statswrites) > 0";
501 if ($logspresent and !execute_sql($sql, false)) {
505 stats_daily_progress('16');
507 // remember processed days
508 set_config('statslastdaily', $nextmidnight);
509 mtrace(" finished until $nextmidnight: ".userdate($nextmidnight)." (in ".(time()-$daystart)." s)");
511 $timestart = $nextmidnight;
512 $nextmidnight = stats_get_next_day_start($nextmidnight);
515 set_cron_lock('statsrunning', null);
519 mtrace("...error occured, completed $days days of statistics.");
523 mtrace("...completed $days days of statistics.");
530 * Execute weekly statistics gathering
531 * @return boolean success
533 function stats_cron_weekly() {
538 // read last execution date from db
539 if (!$timestart = get_config(NULL, 'statslastweekly')) {
540 $timestart = stats_get_base_daily(stats_get_start_from('weekly'));
541 set_config('statslastweekly', $timestart);
544 $nextstartweek = stats_get_next_week_start($timestart);
546 // are there any weeks that need to be processed?
547 if ($now < $nextstartweek) {
548 return true; // everything ok and up-to-date
551 $timeout = empty($CFG->statsmaxruntime
) ?
60*60*24 : $CFG->statsmaxruntime
;
553 if (!set_cron_lock('statsrunning', $now +
$timeout)) {
557 // fisrt delete entries that should not be there yet
558 delete_records_select('stats_weekly', "timeend > $timestart");
559 delete_records_select('stats_user_weekly', "timeend > $timestart");
561 mtrace("Running weekly statistics gathering, starting at $timestart:");
564 while ($now > $nextstartweek) {
565 @set_time_limit
($timeout - 200);
570 set_cron_lock('statsrunning', time() +
$timeout, true);
573 $logtimesql = "l.time >= $timestart AND l.time < $nextstartweek";
574 $stattimesql = "timeend > $timestart AND timeend <= $nextstartweek";
576 /// process login info first
577 $sql = "INSERT INTO {$CFG->prefix}stats_user_weekly (stattype, timeend, courseid, userid, statsreads)
579 SELECT 'logins' AS stattype, $nextstartweek AS timeend, ".SITEID
." as courseid,
580 l.userid, count(l.id) AS statsreads
581 FROM {$CFG->prefix}log l
582 WHERE action = 'login' AND $logtimesql
583 GROUP BY stattype, timeend, courseid, userid
584 HAVING count(l.id) > 0";
586 execute_sql($sql, false);
588 $sql = "INSERT INTO {$CFG->prefix}stats_weekly (stattype, timeend, courseid, roleid, stat1, stat2)
590 SELECT 'logins' AS stattype, $nextstartweek AS timeend, ".SITEID
." as courseid, 0,
591 COALESCE((SELECT SUM(statsreads)
592 FROM {$CFG->prefix}stats_user_weekly s1
593 WHERE s1.stattype = 'logins' AND timeend = $nextstartweek), 0) AS nstat1,
595 FROM {$CFG->prefix}stats_user_weekly s2
596 WHERE s2.stattype = 'logins' AND timeend = $nextstartweek) AS nstat2";
598 execute_sql($sql, false);
601 /// now enrolments averages
602 $sql = "INSERT INTO {$CFG->prefix}stats_weekly (stattype, timeend, courseid, roleid, stat1, stat2)
604 SELECT 'enrolments' AS nstattype, $nextstartweek AS ntimeend, courseid, roleid,
605 CEIL(AVG(stat1)) AS nstat1, CEIL(AVG(stat2)) AS nstat2
606 FROM {$CFG->prefix}stats_daily sd
607 WHERE stattype = 'enrolments' AND $stattimesql
608 GROUP BY nstattype, ntimeend, courseid, roleid";
610 execute_sql($sql, false);
613 /// activity read/write averages
614 $sql = "INSERT INTO {$CFG->prefix}stats_weekly (stattype, timeend, courseid, roleid, stat1, stat2)
616 SELECT 'activity' AS nstattype, $nextstartweek AS ntimeend, courseid, roleid,
617 SUM(stat1) AS nstat1, SUM(stat2) AS nstat2
618 FROM {$CFG->prefix}stats_daily
619 WHERE stattype = 'activity' AND $stattimesql
620 GROUP BY nstattype, ntimeend, courseid, roleid";
622 execute_sql($sql, false);
625 /// user read/write averages
626 $sql = "INSERT INTO {$CFG->prefix}stats_user_weekly (stattype, timeend, courseid, userid, statsreads, statswrites)
628 SELECT 'activity' AS nstattype, $nextstartweek AS ntimeend, courseid, userid,
629 SUM(statsreads) AS nstatsreads, SUM(statswrites) AS nstatswrites
630 FROM {$CFG->prefix}stats_user_daily
631 WHERE stattype = 'activity' AND $stattimesql
632 GROUP BY nstattype, ntimeend, courseid, userid";
634 execute_sql($sql, false);
636 set_config('statslastweekly', $nextstartweek);
637 mtrace(" finished until $nextstartweek: ".userdate($nextstartweek));
639 $timestart = $nextstartweek;
640 $nextstartweek = stats_get_next_week_start($nextstartweek);
643 set_cron_lock('statsrunning', null);
644 mtrace("...completed $weeks weeks of statistics.");
649 * Execute monthly statistics gathering
650 * @return boolean success
652 function stats_cron_monthly() {
657 // read last execution date from db
658 if (!$timestart = get_config(NULL, 'statslastmonthly')) {
659 $timestart = stats_get_base_monthly(stats_get_start_from('monthly'));
660 set_config('statslastmonthly', $timestart);
663 $nextstartmonth = stats_get_next_month_start($timestart);
665 // are there any months that need to be processed?
666 if ($now < $nextstartmonth) {
667 return true; // everything ok and up-to-date
670 $timeout = empty($CFG->statsmaxruntime
) ?
60*60*24 : $CFG->statsmaxruntime
;
672 if (!set_cron_lock('statsrunning', $now +
$timeout)) {
676 // fisr delete entries that should not be there yet
677 delete_records_select('stats_monthly', "timeend > $timestart");
678 delete_records_select('stats_user_monthly', "timeend > $timestart");
680 $startmonth = stats_get_base_monthly($now);
683 mtrace("Running monthly statistics gathering, starting at $timestart:");
686 while ($now > $nextstartmonth) {
687 @set_time_limit
($timeout - 200);
692 set_cron_lock('statsrunning', time() +
$timeout, true);
695 $logtimesql = "l.time >= $timestart AND l.time < $nextstartmonth";
696 $stattimesql = "timeend > $timestart AND timeend <= $nextstartmonth";
698 /// process login info first
699 $sql = "INSERT INTO {$CFG->prefix}stats_user_monthly (stattype, timeend, courseid, userid, statsreads)
701 SELECT 'logins' AS stattype, $nextstartmonth AS timeend, ".SITEID
." as courseid,
702 l.userid, count(l.id) AS statsreads
703 FROM {$CFG->prefix}log l
704 WHERE action = 'login' AND $logtimesql
705 GROUP BY stattype, timeend, courseid, userid";
707 execute_sql($sql, false);
709 $sql = "INSERT INTO {$CFG->prefix}stats_monthly (stattype, timeend, courseid, roleid, stat1, stat2)
711 SELECT 'logins' AS stattype, $nextstartmonth AS timeend, ".SITEID
." as courseid, 0,
712 COALESCE((SELECT SUM(statsreads)
713 FROM {$CFG->prefix}stats_user_monthly s1
714 WHERE s1.stattype = 'logins' AND timeend = $nextstartmonth), 0) AS nstat1,
716 FROM {$CFG->prefix}stats_user_monthly s2
717 WHERE s2.stattype = 'logins' AND timeend = $nextstartmonth) AS nstat2";
719 execute_sql($sql, false);
722 /// now enrolments averages
723 $sql = "INSERT INTO {$CFG->prefix}stats_monthly (stattype, timeend, courseid, roleid, stat1, stat2)
725 SELECT 'enrolments' AS nstattype, $nextstartmonth AS ntimeend, courseid, roleid,
726 CEIL(AVG(stat1)) AS nstat1, CEIL(AVG(stat2)) AS nstat2
727 FROM {$CFG->prefix}stats_daily sd
728 WHERE stattype = 'enrolments' AND $stattimesql
729 GROUP BY nstattype, ntimeend, courseid, roleid";
731 execute_sql($sql, false);
734 /// activity read/write averages
735 $sql = "INSERT INTO {$CFG->prefix}stats_monthly (stattype, timeend, courseid, roleid, stat1, stat2)
737 SELECT 'activity' AS nstattype, $nextstartmonth AS ntimeend, courseid, roleid,
738 SUM(stat1) AS nstat1, SUM(stat2) AS nstat2
739 FROM {$CFG->prefix}stats_daily
740 WHERE stattype = 'activity' AND $stattimesql
741 GROUP BY nstattype, ntimeend, courseid, roleid";
743 execute_sql($sql, false);
746 /// user read/write averages
747 $sql = "INSERT INTO {$CFG->prefix}stats_user_monthly (stattype, timeend, courseid, userid, statsreads, statswrites)
749 SELECT 'activity' AS nstattype, $nextstartmonth AS ntimeend, courseid, userid,
750 SUM(statsreads) AS nstatsreads, SUM(statswrites) AS nstatswrites
751 FROM {$CFG->prefix}stats_user_daily
752 WHERE stattype = 'activity' AND $stattimesql
753 GROUP BY nstattype, ntimeend, courseid, userid";
755 execute_sql($sql, false);
757 set_config('statslastmonthly', $nextstartmonth);
758 mtrace(" finished until $nextstartmonth: ".userdate($nextstartmonth));
760 $timestart = $nextstartmonth;
761 $nextstartmonth = stats_get_next_month_start($nextstartmonth);
764 set_cron_lock('statsrunning', null);
765 mtrace("...completed $months months of statistics.");
770 * Returns simplified enrolment sql join data
771 * @param int $limit number of max parent course categories
772 * @param bool $includedoanything include also admins
773 * @return array ra join and where string
775 function stats_get_enrolled_sql($limit, $includedoanything) {
778 $adm = $includedoanything ?
" OR rc.capability = 'moodle/site:doanything'" : "";
780 $join = "JOIN {$CFG->prefix}context ctx
781 ON ctx.id = ra.contextid
782 CROSS JOIN {$CFG->prefix}course c
783 JOIN {$CFG->prefix}role_capabilities rc
784 ON rc.roleid = ra.roleid";
785 $where = "((rc.capability = 'moodle/course:view' $adm)
786 AND rc.permission = 1 AND rc.contextid = ".SYSCONTEXTID
."
787 AND (ctx.contextlevel = ".CONTEXT_SYSTEM
."
788 OR (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE
.")";
790 for($i=1; $i<=$limit; $i++
) {
792 $join .= " LEFT OUTER JOIN {$CFG->prefix}course_categories cc1
793 ON cc1.id = c.category";
794 $where .= " OR (cc1.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT
.")";
797 $join .= " LEFT OUTER JOIN {$CFG->prefix}course_categories cc$i
798 ON cc$i.id = cc$j.parent";
799 $where .= " OR (cc$i.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT
.")";
805 return array($join, $where);
809 * Return starting date of stats processing
810 * @param string $str name of table - daily, weekly or monthly
811 * @return int timestamp
813 function stats_get_start_from($str) {
816 // are there any data in stats table? Should not be...
817 if ($timeend = get_field_sql('SELECT timeend FROM '.$CFG->prefix
.'stats_'.$str.' ORDER BY timeend DESC')) {
820 // decide what to do based on our config setting (either all or none or a timestamp)
821 switch ($CFG->statsfirstrun
) {
823 if ($firstlog = get_field_sql('SELECT time FROM '.$CFG->prefix
.'log ORDER BY time ASC')) {
827 if (is_numeric($CFG->statsfirstrun
)) {
828 return time() - $CFG->statsfirstrun
;
830 // not a number? use next instead
832 return strtotime('-3 day', time());
838 * @param int $time timestamp
839 * @return start of day
841 function stats_get_base_daily($time=0) {
847 if ($CFG->timezone
== 99) {
848 $time = strtotime(date('d-M-Y', $time));
851 $offset = get_timezone_offset($CFG->timezone
);
852 $gtime = $time +
$offset;
853 $gtime = intval($gtime / (60*60*24)) * 60*60*24;
854 return $gtime - $offset;
860 * @param int $time timestamp
861 * @return start of week
863 function stats_get_base_weekly($time=0) {
866 $time = stats_get_base_daily($time);
867 $startday = $CFG->calendar_startwday
;
868 if ($CFG->timezone
== 99) {
869 $thisday = date('w', $time);
871 $offset = get_timezone_offset($CFG->timezone
);
872 $gtime = $time +
$offset;
873 $thisday = gmdate('w', $gtime);
875 if ($thisday > $startday) {
876 $time = $time - (($thisday - $startday) * 60*60*24);
877 } else if ($thisday < $startday) {
878 $time = $time - ((7 +
$thisday - $startday) * 60*60*24);
885 * @param int $time timestamp
886 * @return start of month
888 function stats_get_base_monthly($time=0) {
894 if ($CFG->timezone
== 99) {
895 return strtotime(date('1-M-Y', $time));
898 $time = stats_get_base_daily($time);
899 $offset = get_timezone_offset($CFG->timezone
);
900 $gtime = $time +
$offset;
901 $day = gmdate('d', $gtime);
905 return $gtime - (($day-1) * 60*60*24);
911 * @param int $time timestamp
912 * @return start of next day
914 function stats_get_next_day_start($time) {
915 $next = stats_get_base_daily($time);
916 $next = $next +
60*60*26;
917 $next = stats_get_base_daily($next);
918 if ($next <= $time) {
919 //DST trouble - prevent infinite loops
920 $next = $next +
60*60*24;
927 * @param int $time timestamp
928 * @return start of next week
930 function stats_get_next_week_start($time) {
931 $next = stats_get_base_weekly($time);
932 $next = $next +
60*60*24*9;
933 $next = stats_get_base_weekly($next);
934 if ($next <= $time) {
935 //DST trouble - prevent infinite loops
936 $next = $next +
60*60*24*7;
942 * Start of next month
943 * @param int $time timestamp
944 * @return start of next month
946 function stats_get_next_month_start($time) {
947 $next = stats_get_base_monthly($time);
948 $next = $next +
60*60*24*33;
949 $next = stats_get_base_monthly($next);
950 if ($next <= $time) {
951 //DST trouble - prevent infinite loops
952 $next = $next +
60*60*24*31;
958 * Remove old stats data
960 function stats_clean_old() {
961 mtrace("Running stats cleanup tasks...");
962 $deletebefore = stats_get_base_monthly();
964 // delete dailies older than 3 months (to be safe)
965 $deletebefore = strtotime('-3 months', $deletebefore);
966 delete_records_select('stats_daily', "timeend < $deletebefore");
967 delete_records_select('stats_user_daily', "timeend < $deletebefore");
969 // delete weeklies older than 9 months (to be safe)
970 $deletebefore = strtotime('-6 months', $deletebefore);
971 delete_records_select('stats_weekly', "timeend < $deletebefore");
972 delete_records_select('stats_user_weekly', "timeend < $deletebefore");
974 // don't delete monthlies
976 mtrace("...stats cleanup finished");
979 function stats_get_parameters($time,$report,$courseid,$mode,$roleid=0) {
982 $param = new object();
984 if ($time < 10) { // dailies
985 // number of days to go back = 7* time
986 $param->table
= 'daily';
987 $param->timeafter
= strtotime("-".($time*7)." days",stats_get_base_daily());
988 } elseif ($time < 20) { // weeklies
989 // number of weeks to go back = time - 10 * 4 (weeks) + base week
990 $param->table
= 'weekly';
991 $param->timeafter
= strtotime("-".(($time - 10)*4)." weeks",stats_get_base_weekly());
992 } else { // monthlies.
993 // number of months to go back = time - 20 * months + base month
994 $param->table
= 'monthly';
995 $param->timeafter
= strtotime("-".($time - 20)." months",stats_get_base_monthly());
1000 // compatibility - if we're in postgres, cast to real for some reports.
1002 if ($CFG->dbfamily
== 'postgres') {
1007 // ******************** STATS_MODE_GENERAL ******************** //
1008 case STATS_REPORT_LOGINS
:
1009 $param->fields
= 'timeend,sum(stat1) as line1,sum(stat2) as line2';
1010 $param->fieldscomplete
= true;
1011 $param->stattype
= 'logins';
1012 $param->line1
= get_string('statslogins');
1013 $param->line2
= get_string('statsuniquelogins');
1014 if ($courseid == SITEID
) {
1015 $param->extras
= 'GROUP BY timeend';
1019 case STATS_REPORT_READS
:
1020 $param->fields
= sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat1 as line1';
1021 $param->fieldscomplete
= true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1022 $param->aggregategroupby
= 'roleid';
1023 $param->stattype
= 'activity';
1024 $param->crosstab
= true;
1025 $param->extras
= 'GROUP BY timeend,roleid,stat1';
1026 if ($courseid == SITEID
) {
1027 $param->fields
= sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1) as line1';
1028 $param->extras
= 'GROUP BY timeend,roleid';
1032 case STATS_REPORT_WRITES
:
1033 $param->fields
= sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat2 as line1';
1034 $param->fieldscomplete
= true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1035 $param->aggregategroupby
= 'roleid';
1036 $param->stattype
= 'activity';
1037 $param->crosstab
= true;
1038 $param->extras
= 'GROUP BY timeend,roleid,stat2';
1039 if ($courseid == SITEID
) {
1040 $param->fields
= sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat2) as line1';
1041 $param->extras
= 'GROUP BY timeend,roleid';
1045 case STATS_REPORT_ACTIVITY
:
1046 $param->fields
= sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1+stat2) as line1';
1047 $param->fieldscomplete
= true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1048 $param->aggregategroupby
= 'roleid';
1049 $param->stattype
= 'activity';
1050 $param->crosstab
= true;
1051 $param->extras
= 'GROUP BY timeend,roleid';
1052 if ($courseid == SITEID
) {
1053 $param->extras
= 'GROUP BY timeend,roleid';
1057 case STATS_REPORT_ACTIVITYBYROLE
;
1058 $param->fields
= 'stat1 AS line1, stat2 AS line2';
1059 $param->stattype
= 'activity';
1060 $rolename = get_field('role','name','id',$roleid);
1061 $param->line1
= $rolename . get_string('statsreads');
1062 $param->line2
= $rolename . get_string('statswrites');
1063 if ($courseid == SITEID
) {
1064 $param->extras
= 'GROUP BY timeend';
1068 // ******************** STATS_MODE_DETAILED ******************** //
1069 case STATS_REPORT_USER_ACTIVITY
:
1070 $param->fields
= 'statsreads as line1, statswrites as line2';
1071 $param->line1
= get_string('statsuserreads');
1072 $param->line2
= get_string('statsuserwrites');
1073 $param->stattype
= 'activity';
1076 case STATS_REPORT_USER_ALLACTIVITY
:
1077 $param->fields
= 'statsreads+statswrites as line1';
1078 $param->line1
= get_string('statsuseractivity');
1079 $param->stattype
= 'activity';
1082 case STATS_REPORT_USER_LOGINS
:
1083 $param->fields
= 'statsreads as line1';
1084 $param->line1
= get_string('statsuserlogins');
1085 $param->stattype
= 'logins';
1088 case STATS_REPORT_USER_VIEW
:
1089 $param->fields
= 'statsreads as line1, statswrites as line2, statsreads+statswrites as line3';
1090 $param->line1
= get_string('statsuserreads');
1091 $param->line2
= get_string('statsuserwrites');
1092 $param->line3
= get_string('statsuseractivity');
1093 $param->stattype
= 'activity';
1096 // ******************** STATS_MODE_RANKED ******************** //
1097 case STATS_REPORT_ACTIVE_COURSES
:
1098 $param->fields
= 'sum(stat1+stat2) AS line1';
1099 $param->stattype
= 'activity';
1100 $param->orderby
= 'line1 DESC';
1101 $param->line1
= get_string('activity');
1102 $param->graphline
= 'line1';
1105 case STATS_REPORT_ACTIVE_COURSES_WEIGHTED
:
1107 if (!empty($CFG->statsuserthreshold
) && is_numeric($CFG->statsuserthreshold
)) {
1108 $threshold = $CFG->statsuserthreshold
;
1110 $param->fields
= '';
1111 $param->sql
= 'SELECT activity.courseid, activity.all_activity AS line1, enrolments.highest_enrolments AS line2,
1112 activity.all_activity / enrolments.highest_enrolments as line3
1114 SELECT courseid, (stat1+stat2) AS all_activity
1115 FROM '.$CFG->prefix
.'stats_'.$param->table
.'
1116 WHERE stattype=\'activity\' AND timeend >= '.$param->timeafter
.' AND roleid = 0
1120 SELECT courseid, max(stat1) AS highest_enrolments
1121 FROM '.$CFG->prefix
.'stats_'.$param->table
.'
1122 WHERE stattype=\'enrolments\' AND timeend >= '.$param->timeafter
.' AND stat1 > '.$threshold.'
1125 ON (activity.courseid = enrolments.courseid)
1126 ORDER BY line3 DESC';
1127 $param->line1
= get_string('activity');
1128 $param->line2
= get_string('users');
1129 $param->line3
= get_string('activityweighted');
1130 $param->graphline
= 'line3';
1133 case STATS_REPORT_PARTICIPATORY_COURSES
:
1135 if (!empty($CFG->statsuserthreshold
) && is_numeric($CFG->statsuserthreshold
)) {
1136 $threshold = $CFG->statsuserthreshold
;
1138 $param->fields
= '';
1139 $param->sql
= 'SELECT courseid, ceil(avg(all_enrolments)) as line1,
1140 ceil(avg(active_enrolments)) as line2, avg(proportion_active) AS line3
1142 SELECT courseid, timeend, stat2 as active_enrolments,
1143 stat1 as all_enrolments, stat2'.$real.'/stat1'.$real.' as proportion_active
1144 FROM '.$CFG->prefix
.'stats_'.$param->table
.'
1145 WHERE stattype=\'enrolments\' AND roleid = 0 AND stat1 > '.$threshold.'
1147 WHERE timeend >= '.$param->timeafter
.'
1149 ORDER BY line3 DESC';
1151 $param->line1
= get_string('users');
1152 $param->line2
= get_string('activeusers');
1153 $param->line3
= get_string('participationratio');
1154 $param->graphline
= 'line3';
1157 case STATS_REPORT_PARTICIPATORY_COURSES_RW
:
1158 $param->fields
= '';
1159 $param->sql
= 'SELECT courseid, sum(views) AS line1, sum(posts) AS line2,
1160 avg(proportion_active) AS line3
1162 SELECT courseid, timeend, stat1 as views, stat2 AS posts,
1163 stat2'.$real.'/stat1'.$real.' as proportion_active
1164 FROM '.$CFG->prefix
.'stats_'.$param->table
.'
1165 WHERE stattype=\'activity\' AND roleid = 0 AND stat1 > 0
1167 WHERE timeend >= '.$param->timeafter
.'
1169 ORDER BY line3 DESC';
1170 $param->line1
= get_string('views');
1171 $param->line2
= get_string('posts');
1172 $param->line3
= get_string('participationratio');
1173 $param->graphline
= 'line3';
1178 if ($courseid == SITEID && $mode != STATS_MODE_RANKED) { // just aggregate all courses.
1179 $param->fields = preg_replace('/(?:sum)([a-zA-Z0-9+_]*)\W+as\W+([a-zA-Z0-9_]*)/i','sum($1) as $2',$param->fields);
1180 $param->extras = ' GROUP BY timeend'.((!empty($param->aggregategroupby)) ? ','.$param->aggregategroupby : '');
1183 //TODO must add the SITEID reports to the rest of the reports.
1187 function stats_get_view_actions() {
1188 return array('view','view all','history');
1191 function stats_get_post_actions() {
1192 return array('add','delete','edit','add mod','delete mod','edit section'.'enrol','loginas','new','unenrol','update','update mod');
1195 function stats_get_action_names($str) {
1198 $mods = get_records('modules');
1199 $function = 'stats_get_'.$str.'_actions';
1200 $actions = $function();
1201 foreach ($mods as $mod) {
1202 $file = $CFG->dirroot
.'/mod/'.$mod->name
.'/lib.php';
1203 if (!is_readable($file)) {
1206 require_once($file);
1207 $function = $mod->name
.'_get_'.$str.'_actions';
1208 if (function_exists($function)) {
1209 $actions = array_merge($actions,$function());
1213 // The array_values() forces a stack-like array
1214 // so we can later loop over safely...
1215 $actions = array_values(array_unique($actions));
1216 $c = count($actions);
1217 for ($n=0;$n<$c;$n++
) {
1218 $actions[$n] = "'" . $actions[$n] . "'"; // quote them for SQL
1223 function stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth) {
1225 $now = stats_get_base_daily(time());
1226 // it's really important that it's TIMEEND in the table. ie, tuesday 00:00:00 is monday night.
1227 // so we need to take a day off here (essentially add a day to $now
1230 $timeoptions = array();
1232 if ($now - (60*60*24*7) >= $earliestday) {
1233 $timeoptions[STATS_TIME_LASTWEEK
] = get_string('numweeks','moodle',1);
1235 if ($now - (60*60*24*14) >= $earliestday) {
1236 $timeoptions[STATS_TIME_LAST2WEEKS
] = get_string('numweeks','moodle',2);
1238 if ($now - (60*60*24*21) >= $earliestday) {
1239 $timeoptions[STATS_TIME_LAST3WEEKS
] = get_string('numweeks','moodle',3);
1241 if ($now - (60*60*24*28) >= $earliestday) {
1242 $timeoptions[STATS_TIME_LAST4WEEKS
] = get_string('numweeks','moodle',4);// show dailies up to (including) here.
1244 if ($lastweekend - (60*60*24*56) >= $earliestweek) {
1245 $timeoptions[STATS_TIME_LAST2MONTHS
] = get_string('nummonths','moodle',2);
1247 if ($lastweekend - (60*60*24*84) >= $earliestweek) {
1248 $timeoptions[STATS_TIME_LAST3MONTHS
] = get_string('nummonths','moodle',3);
1250 if ($lastweekend - (60*60*24*112) >= $earliestweek) {
1251 $timeoptions[STATS_TIME_LAST4MONTHS
] = get_string('nummonths','moodle',4);
1253 if ($lastweekend - (60*60*24*140) >= $earliestweek) {
1254 $timeoptions[STATS_TIME_LAST5MONTHS
] = get_string('nummonths','moodle',5);
1256 if ($lastweekend - (60*60*24*168) >= $earliestweek) {
1257 $timeoptions[STATS_TIME_LAST6MONTHS
] = get_string('nummonths','moodle',6); // show weeklies up to (including) here
1259 if (strtotime('-7 months',$lastmonthend) >= $earliestmonth) {
1260 $timeoptions[STATS_TIME_LAST7MONTHS
] = get_string('nummonths','moodle',7);
1262 if (strtotime('-8 months',$lastmonthend) >= $earliestmonth) {
1263 $timeoptions[STATS_TIME_LAST8MONTHS
] = get_string('nummonths','moodle',8);
1265 if (strtotime('-9 months',$lastmonthend) >= $earliestmonth) {
1266 $timeoptions[STATS_TIME_LAST9MONTHS
] = get_string('nummonths','moodle',9);
1268 if (strtotime('-10 months',$lastmonthend) >= $earliestmonth) {
1269 $timeoptions[STATS_TIME_LAST10MONTHS
] = get_string('nummonths','moodle',10);
1271 if (strtotime('-11 months',$lastmonthend) >= $earliestmonth) {
1272 $timeoptions[STATS_TIME_LAST11MONTHS
] = get_string('nummonths','moodle',11);
1274 if (strtotime('-1 year',$lastmonthend) >= $earliestmonth) {
1275 $timeoptions[STATS_TIME_LASTYEAR
] = get_string('lastyear');
1278 return $timeoptions;
1281 function stats_get_report_options($courseid,$mode) {
1284 $reportoptions = array();
1287 case STATS_MODE_GENERAL
:
1288 $reportoptions[STATS_REPORT_ACTIVITY
] = get_string('statsreport'.STATS_REPORT_ACTIVITY
);
1289 if ($courseid != SITEID
&& $context = get_context_instance(CONTEXT_COURSE
, $courseid)) {
1290 $sql = 'SELECT r.id,r.name FROM '.$CFG->prefix
.'role r JOIN '.$CFG->prefix
.'stats_daily s ON s.roleid = r.id WHERE s.courseid = '.$courseid;
1291 if ($roles = get_records_sql($sql)) {
1292 foreach ($roles as $role) {
1293 $reportoptions[STATS_REPORT_ACTIVITYBYROLE
.$role->id
] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE
). ' '.$role->name
;
1297 $reportoptions[STATS_REPORT_READS
] = get_string('statsreport'.STATS_REPORT_READS
);
1298 $reportoptions[STATS_REPORT_WRITES
] = get_string('statsreport'.STATS_REPORT_WRITES
);
1299 if ($courseid == SITEID
) {
1300 $reportoptions[STATS_REPORT_LOGINS
] = get_string('statsreport'.STATS_REPORT_LOGINS
);
1304 case STATS_MODE_DETAILED
:
1305 $reportoptions[STATS_REPORT_USER_ACTIVITY
] = get_string('statsreport'.STATS_REPORT_USER_ACTIVITY
);
1306 $reportoptions[STATS_REPORT_USER_ALLACTIVITY
] = get_string('statsreport'.STATS_REPORT_USER_ALLACTIVITY
);
1307 if (has_capability('moodle/site:viewreports', get_context_instance(CONTEXT_SYSTEM
))) {
1309 $reportoptions[STATS_REPORT_USER_LOGINS
] = get_string('statsreport'.STATS_REPORT_USER_LOGINS
);
1312 case STATS_MODE_RANKED
:
1313 if (has_capability('moodle/site:viewreports', get_context_instance(CONTEXT_SYSTEM
))) {
1314 $reportoptions[STATS_REPORT_ACTIVE_COURSES
] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES
);
1315 $reportoptions[STATS_REPORT_ACTIVE_COURSES_WEIGHTED
] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES_WEIGHTED
);
1316 $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES
] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES
);
1317 $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES_RW
] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES_RW
);
1322 return $reportoptions;
1325 function stats_fix_zeros($stats,$timeafter,$timestr,$line2=true,$line3=false) {
1327 if (empty($stats)) {
1331 $timestr = str_replace('user_','',$timestr); // just in case.
1332 $fun = 'stats_get_base_'.$timestr;
1337 // add something to timeafter since it is our absolute base
1338 $actualtimes = array();
1339 foreach ($stats as $statid=>$s) {
1340 //normalize the times in stats - those might have been created in different timezone, DST etc.
1341 $s->timeend
= $fun($s->timeend +
60*60*5);
1342 $stats[$statid] = $s;
1344 $actualtimes[] = $s->timeend
;
1347 $timeafter = array_pop(array_values($actualtimes));
1349 while ($timeafter < $now) {
1350 $times[] = $timeafter;
1351 if ($timestr == 'daily') {
1352 $timeafter = stats_get_next_day_start($timeafter);
1353 } else if ($timestr == 'weekly') {
1354 $timeafter = stats_get_next_week_start($timeafter);
1355 } else if ($timestr == 'monthly') {
1356 $timeafter = stats_get_next_month_start($timeafter);
1358 return $stats; // this will put us in a never ending loop.
1362 foreach ($times as $count => $time) {
1363 if (!in_array($time,$actualtimes) && $count != count($times) -1) {
1364 $newobj = new StdClass
;
1365 $newobj->timeend
= $time;
1367 $newobj->roleid
= 0;
1369 if (!empty($line2)) {
1372 if (!empty($line3)) {
1375 $newobj->zerofixed
= true;
1380 usort($stats,"stats_compare_times");
1385 // helper function to sort arrays by $obj->timeend
1386 function stats_compare_times($a,$b) {
1387 if ($a->timeend
== $b->timeend
) {
1390 return ($a->timeend
> $b->timeend
) ?
-1 : 1;
1393 function stats_check_uptodate($courseid=0) {
1396 if (empty($courseid)) {
1400 $latestday = stats_get_start_from('daily');
1402 if ((time() - 60*60*24*2) < $latestday) { // we're ok
1407 $a->daysdone
= get_field_sql("SELECT count(distinct(timeend)) from {$CFG->prefix}stats_daily");
1409 // how many days between the last day and now?
1410 $a->dayspending
= ceil((stats_get_base_daily() - $latestday)/(60*60*24));
1412 if ($a->dayspending
== 0 && $a->daysdone
!= 0) {
1413 return NULL; // we've only just started...
1416 //return error as string
1417 return get_string('statscatchupmode','error',$a);
1421 * Calculate missing course totals in stats
1423 function stats_upgrade_totals() {
1426 if (empty($CFG->statsrolesupgraded
)) {
1427 // stats not yet upgraded to cope with roles...
1431 $types = array('daily', 'weekly', 'monthly');
1434 $y30 = 60*60*24*365*30; // 30 years ago :-O
1435 $y20 = 60*60*24*365*20; // 20 years ago :-O
1436 $limit = $now - $y20;
1438 foreach ($types as $i => $type) {
1439 $type2 = $types[($i+
1) %
count($types)];
1441 // delete previous incomplete data
1442 $sql = "DELETE FROM {$CFG->prefix}stats_$type2
1443 WHERE timeend < $limit";
1446 // clear the totals if already exist
1447 $sql = "DELETE FROM {$CFG->prefix}stats_$type
1448 WHERE (stattype = 'enrolments' OR stattype = 'activity') AND
1452 $sql = "INSERT INTO {$CFG->prefix}stats_$type2 (stattype, timeend, courseid, roleid, stat1, stat2)
1454 SELECT stattype, (timeend - $y30), courseid, 0, SUM(stat1), SUM(stat2)
1455 FROM {$CFG->prefix}stats_$type
1456 WHERE (stattype = 'enrolments' OR stattype = 'activity') AND
1458 GROUP BY stattype, timeend, courseid";
1461 $sql = "INSERT INTO {$CFG->prefix}stats_$type (stattype, timeend, courseid, roleid, stat1, stat2)
1463 SELECT stattype, (timeend + $y30), courseid, roleid, stat1, stat2
1464 FROM {$CFG->prefix}stats_$type2
1465 WHERE (stattype = 'enrolments' OR stattype = 'activity') AND
1466 roleid = 0 AND timeend < $y20";
1469 $sql = "DELETE FROM {$CFG->prefix}stats_$type2
1470 WHERE timeend < $limit";
1476 function stats_upgrade_for_roles_wrapper() {
1478 if (!empty($CFG->statsrolesupgraded
)) {
1482 $result = begin_sql();
1484 $result = $result && stats_upgrade_user_table_for_roles('daily');
1485 $result = $result && stats_upgrade_user_table_for_roles('weekly');
1486 $result = $result && stats_upgrade_user_table_for_roles('monthly');
1488 $result = $result && stats_upgrade_table_for_roles('daily');
1489 $result = $result && stats_upgrade_table_for_roles('weekly');
1490 $result = $result && stats_upgrade_table_for_roles('monthly');
1493 $result = $result && commit_sql();
1495 if (!empty($result)) {
1496 set_config('statsrolesupgraded',time());
1499 // finally upgade totals, no big deal if it fails
1500 stats_upgrade_totals();
1506 * Upgrades a prefix_stats_user_* table for the new role based permission
1509 * @param string $period daily, weekly or monthly: the stat period to upgrade
1510 * @return boolean @todo maybe something else (error message) depending on
1511 * how this will be called.
1513 function stats_upgrade_user_table_for_roles($period) {
1515 static $teacher_role_id, $student_role_id;
1517 if (!in_array($period, array('daily', 'weekly', 'monthly'))) {
1518 error_log('stats upgrade: invalid period: ' . $period);
1522 if (!$teacher_role_id) {
1523 $role = get_roles_with_capability('moodle/legacy:editingteacher', CAP_ALLOW
);
1524 $role = array_keys($role);
1525 $teacher_role_id = $role[0];
1526 $role = get_roles_with_capability('moodle/legacy:student', CAP_ALLOW
);
1527 $role = array_keys($role);
1528 $student_role_id = $role[0];
1531 if (empty($teacher_role_id) ||
empty($student_role_id)) {
1532 error_log("Couldn't find legacy roles for teacher or student");
1538 $status = $status && execute_sql("UPDATE {$CFG->prefix}stats_user_{$period}
1539 SET roleid = $teacher_role_id
1541 $status = $status && execute_sql("UPDATE {$CFG->prefix}stats_user_{$period}
1542 SET roleid = $student_role_id
1549 * Upgrades a prefix_stats_* table for the new role based permission system.
1551 * @param string $period daily, weekly or monthly: the stat period to upgrade
1552 * @return boolean @todo depends on how this will be called
1554 function stats_upgrade_table_for_roles ($period) {
1556 static $teacher_role_id, $student_role_id;
1558 if (!in_array($period, array('daily', 'weekly', 'monthly'))) {
1562 if (!$teacher_role_id) {
1563 $role = get_roles_with_capability('moodle/legacy:editingteacher', CAP_ALLOW
);
1564 $role = array_keys($role);
1565 $teacher_role_id = $role[0];
1566 $role = get_roles_with_capability('moodle/legacy:student', CAP_ALLOW
);
1567 $role = array_keys($role);
1568 $student_role_id = $role[0];
1571 if (empty($teacher_role_id) ||
empty($student_role_id)) {
1572 error_log("Couldn't find legacy roles for teacher or student");
1576 execute_sql("CREATE TABLE {$CFG->prefix}stats_{$period}_tmp AS
1577 SELECT * FROM {$CFG->prefix}stats_{$period}");
1579 $table = new XMLDBTable('stats_' . $period);
1580 if (!drop_table($table)) {
1584 // Create a new stats table
1585 // @todo this definition I have made blindly by looking at how definitions are
1586 // made, it needs work to make sure it works properly
1587 require_once("$CFG->libdir/xmldb/classes/XMLDBTable.class.php");
1589 $table = new XMLDBTable('stats_' . $period);
1590 $table->addFieldInfo('id', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1591 XMLDB_NOTNULL
, XMLDB_SEQUENCE
, null, null, null);
1593 $table->addFieldInfo('courseid', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1594 XMLDB_NOTNULL
, null, null, null, null);
1596 $table->addFieldInfo('roleid', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1597 XMLDB_NOTNULL
, null, null, null, null);
1598 $table->addFieldInfo('timeend', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1599 XMLDB_NOTNULL
, null, null, null, null);
1600 $table->addFieldInfo('stattype', XMLDB_TYPE_CHAR
, '20', null, XMLDB_NOTNULL
,
1601 null, XMLDB_ENUM
, array('enrolments', 'activity', 'logins'), 'activity');
1602 $table->addFieldInfo('stat1', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1603 XMLDB_NOTNULL
, null, null, null, null);
1604 $table->addFieldInfo('stat2', XMLDB_TYPE_INTEGER
, '10', XMLDB_UNSIGNED
,
1605 XMLDB_NOTNULL
, null, null, null, null);
1607 /// Adding keys to table stats_daily
1608 $table->addKeyInfo('primary', XMLDB_KEY_PRIMARY
, array('id'));
1610 /// Adding indexes to table stats_daily
1611 $table->addIndexInfo('courseid', XMLDB_INDEX_NOTUNIQUE
, array('courseid'));
1612 $table->addIndexInfo('timeend', XMLDB_INDEX_NOTUNIQUE
, array('timeend'));
1613 $table->addIndexInfo('roleid', XMLDB_INDEX_NOTUNIQUE
, array('roleid'));
1615 if (!create_table($table)) {
1620 // Now insert the data from the temporary table into the new one
1623 // Student enrolments
1624 execute_sql("INSERT INTO {$CFG->prefix}stats_{$period}
1625 (courseid, roleid, timeend, stattype, stat1, stat2)
1626 SELECT courseid, $student_role_id, timeend, 'enrolments', students, activestudents
1627 FROM {$CFG->prefix}stats_{$period}_tmp");
1629 // Teacher enrolments
1630 execute_sql("INSERT INTO {$CFG->prefix}stats_{$period}
1631 (courseid, roleid, timeend, stattype, stat1, stat2)
1632 SELECT courseid, $teacher_role_id, timeend, 'enrolments', teachers, activeteachers
1633 FROM {$CFG->prefix}stats_{$period}_tmp");
1636 execute_sql("INSERT INTO {$CFG->prefix}stats_{$period}
1637 (courseid, roleid, timeend, stattype, stat1, stat2)
1638 SELECT courseid, $student_role_id, timeend, 'activity', studentreads, studentwrites
1639 FROM {$CFG->prefix}stats_{$period}_tmp");
1642 execute_sql("INSERT INTO {$CFG->prefix}stats_{$period}
1643 (courseid, roleid, timeend, stattype, stat1, stat2)
1644 SELECT courseid, $teacher_role_id, timeend, 'activity', teacherreads, teacherwrites
1645 FROM {$CFG->prefix}stats_{$period}_tmp");
1648 execute_sql("INSERT INTO {$CFG->prefix}stats_{$period}
1649 (courseid, roleid, timeend, stattype, stat1, stat2)
1650 SELECT courseid, 0, timeend, 'logins', logins, uniquelogins
1651 FROM {$CFG->prefix}stats_{$period}_tmp WHERE courseid = ".SITEID
);
1653 // Drop the temporary table
1654 $table = new XMLDBTable('stats_' . $period . '_tmp');
1655 if (!drop_table($table)) {