MDL-22800 stats code using new enrol tables, I did not test it much because I do...
[moodle.git] / lib / statslib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package   moodlecore
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 /** THESE CONSTANTS ARE USED FOR THE REPORTING PAGE. */
26 define('STATS_REPORT_LOGINS',1); // double impose logins and unique logins on a line graph. site course only.
27 define('STATS_REPORT_READS',2); // double impose student reads and teacher reads on a line graph.
28 define('STATS_REPORT_WRITES',3); // double impose student writes and teacher writes on a line graph.
29 define('STATS_REPORT_ACTIVITY',4); // 2+3 added up, teacher vs student.
30 define('STATS_REPORT_ACTIVITYBYROLE',5); // all activity, reads vs writes, selected by role.
32 // user level stats reports.
33 define('STATS_REPORT_USER_ACTIVITY',7);
34 define('STATS_REPORT_USER_ALLACTIVITY',8);
35 define('STATS_REPORT_USER_LOGINS',9);
36 define('STATS_REPORT_USER_VIEW',10);  // this is the report you see on the user profile.
38 // admin only ranking stats reports
39 define('STATS_REPORT_ACTIVE_COURSES',11);
40 define('STATS_REPORT_ACTIVE_COURSES_WEIGHTED',12);
41 define('STATS_REPORT_PARTICIPATORY_COURSES',13);
42 define('STATS_REPORT_PARTICIPATORY_COURSES_RW',14);
44 // start after 0 = show dailies.
45 define('STATS_TIME_LASTWEEK',1);
46 define('STATS_TIME_LAST2WEEKS',2);
47 define('STATS_TIME_LAST3WEEKS',3);
48 define('STATS_TIME_LAST4WEEKS',4);
50 // start after 10 = show weeklies
51 define('STATS_TIME_LAST2MONTHS',12);
53 define('STATS_TIME_LAST3MONTHS',13);
54 define('STATS_TIME_LAST4MONTHS',14);
55 define('STATS_TIME_LAST5MONTHS',15);
56 define('STATS_TIME_LAST6MONTHS',16);
58 // start after 20 = show monthlies
59 define('STATS_TIME_LAST7MONTHS',27);
60 define('STATS_TIME_LAST8MONTHS',28);
61 define('STATS_TIME_LAST9MONTHS',29);
62 define('STATS_TIME_LAST10MONTHS',30);
63 define('STATS_TIME_LAST11MONTHS',31);
64 define('STATS_TIME_LASTYEAR',32);
66 // different modes for what reports to offer
67 define('STATS_MODE_GENERAL',1);
68 define('STATS_MODE_DETAILED',2);
69 define('STATS_MODE_RANKED',3); // admins only - ranks courses
71 /**
72  * Print daily cron progress
73  * @param string $ident
74  */
75 function stats_daily_progress($ident) {
76     static $start = 0;
77     static $init  = 0;
79     if ($ident == 'init') {
80         $init = $start = time();
81         return;
82     }
84     $elapsed = time() - $start;
85     $start   = time();
87     if (debugging('', DEBUG_ALL)) {
88         mtrace("$ident:$elapsed ", '');
89     } else {
90         mtrace('.', '');
91     }
92 }
94 /**
95  * Execute daily statistics gathering
96  * @param int $maxdays maximum number of days to be processed
97  * @return boolean success
98  */
99 function stats_cron_daily($maxdays=1) {
100     global $CFG, $DB;
102     $now = time();
104     $fpcontext = get_context_instance(CONTEXT_COURSE, SITEID, MUST_EXIST);
106     // read last execution date from db
107     if (!$timestart = get_config(NULL, 'statslastdaily')) {
108         $timestart = stats_get_base_daily(stats_get_start_from('daily'));
109         set_config('statslastdaily', $timestart);
110     }
112     // calculate scheduled time
113     $scheduledtime = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
115     // Note: This will work fine for sites running cron each 4 hours or less (hopefully, 99.99% of sites). MDL-16709
116     // check to make sure we're due to run, at least 20 hours after last run
117     if (isset($CFG->statslastexecution) and ((time() - 20*60*60) < $CFG->statslastexecution)) {
118         mtrace("...preventing stats to run, last execution was less than 20 hours ago.");
119         return false;
120     // also check that we are a max of 4 hours after scheduled time, stats won't run after that
121     } else if (time() > $scheduledtime + 4*60*60) {
122         mtrace("...preventing stats to run, more than 4 hours since scheduled time.");
123         return false;
124     } else {
125         set_config('statslastexecution', time()); /// Grab this execution as last one
126     }
128     $nextmidnight = stats_get_next_day_start($timestart);
130     // are there any days that need to be processed?
131     if ($now < $nextmidnight) {
132         return true; // everything ok and up-to-date
133     }
136     $timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
138     if (!set_cron_lock('statsrunning', $now + $timeout)) {
139         return false;
140     }
142     // first delete entries that should not be there yet
143     $DB->delete_records_select('stats_daily',      "timeend > $timestart");
144     $DB->delete_records_select('stats_user_daily', "timeend > $timestart");
146     // Read in a few things we'll use later
147     $viewactions = stats_get_action_names('view');
148     $postactions = stats_get_action_names('post');
150     $guest           = (int)$CFG->siteguest;
151     $guestrole       = (int)$CFG->guestroleid;
152     $defaultfproleid = (int)$CFG->defaultfrontpageroleid;
154     mtrace("Running daily statistics gathering, starting at $timestart:");
156     $days = 0;
157     $failed = false; // failed stats flag
159     while ($now > $nextmidnight) {
160         if ($days >= $maxdays) {
161             mtrace("...stopping early, reached maximum number of $maxdays days - will continue next time.");
162             set_cron_lock('statsrunning', null);
163             return false;
164         }
166         $days++;
167         @set_time_limit($timeout - 200);
169         if ($days > 1) {
170             // move the lock
171             set_cron_lock('statsrunning', time() + $timeout, true);
172         }
174         $daystart = time();
176         $timesql  = "l.time >= $timestart  AND l.time  < $nextmidnight";
177         $timesql1 = "l1.time >= $timestart AND l1.time < $nextmidnight";
178         $timesql2 = "l2.time >= $timestart AND l2.time < $nextmidnight";
180         stats_daily_progress('init');
183     /// find out if any logs available for this day
184         $sql = "SELECT 'x'
185                   FROM {log} l
186                  WHERE $timesql";
187         $logspresent = $DB->get_records_sql($sql, null, 0, 1);
189     /// process login info first
190         $sql = "INSERT INTO {stats_user_daily} (stattype, timeend, courseid, userid, statsreads)
192                 SELECT 'logins', timeend, courseid, userid, count(statsreads)
193                  FROM (
194                           SELECT $nextmidnight AS timeend, ".SITEID." AS courseid, l.userid, l.id AS statsreads
195                             FROM {log} l
196                            WHERE action = 'login' AND $timesql
197                        ) inline_view
198               GROUP BY timeend, courseid, userid
199                 HAVING count(statsreads) > 0";
201         if ($logspresent and !$DB->execute($sql)) {
202             $failed = true;
203             break;
204         }
205         stats_daily_progress('1');
207         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
209                 SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID." as courseid, 0,
210                        COALESCE((SELECT SUM(statsreads)
211                                        FROM {stats_user_daily} s1
212                                       WHERE s1.stattype = 'logins' AND timeend = $nextmidnight), 0) AS stat1,
213                        (SELECT COUNT('x')
214                           FROM {stats_user_daily} s2
215                          WHERE s2.stattype = 'logins' AND timeend = $nextmidnight) AS stat2" .
216                 $DB->sql_null_from_clause();
218         if ($logspresent and !$DB->execute($sql)) {
219             $failed = true;
220             break;
221         }
222         stats_daily_progress('2');
225         // Enrolments and active enrolled users
226         //
227         // Unfortunately, we do not know how many users were registered
228         // at given times in history :-(
229         // - stat1: enrolled users
230         // - stat2: enrolled users active in this period
231         // - SITEID is special case here, because it's all about default enrolment
232         //   in that case, we'll count non-deleted users.
233         //
235         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
237                 SELECT 'enrolments', timeend, courseid, roleid, COUNT(DISTINCT userid), 0
238                   FROM (
239                            SELECT $nextmidnight AS timeend, e.courseid, ra.roleid, ue.userid
240                              FROM {role_assignments} ra
241                              JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
242                              JOIN {enrol} e ON e.courseid = c.instanceid
243                              JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
244                         ) inline_view
245               GROUP BY timeend, courseid, roleid";
247         if (!$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
248             $failed = true;
249             break;
250         }
251         stats_daily_progress('3');
253         // using table alias in UPDATE does not work in pg < 8.2
254         $sql = "UPDATE {stats_daily}
255                    SET stat2 = (SELECT COUNT(DISTINCT ra.userid)
256                                   FROM {role_assignments} ra
257                                   JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
258                                   JOIN {enrol} e ON e.courseid = c.instanceid
259                                   JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
260                                   WHERE ra.roleid = {stats_daily}.roleid AND
261                                        e.courseid = {stats_daily}.courseid AND
262                                        EXISTS (SELECT 'x'
263                                                  FROM {log} l
264                                                 WHERE l.course = {stats_daily}.courseid AND
265                                                       l.userid = ra.userid AND $timesql))
266                  WHERE {stats_daily}.stattype = 'enrolments' AND
267                        {stats_daily}.timeend = $nextmidnight AND
268                        {stats_daily}.courseid IN
269                           (SELECT DISTINCT l.course
270                              FROM {log} l
271                             WHERE $timesql)";
273         if (!$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
274             $failed = true;
275             break;
276         }
277         stats_daily_progress('4');
279     /// now get course total enrolments (roleid==0) - except frontpage
280         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
282                 SELECT 'enrolments', timeend, id, nroleid, COUNT(DISTINCT userid), 0
283                   FROM (
284                            SELECT $nextmidnight AS timeend, e.courseid AS id, 0 AS nroleid, ue.userid
285                              FROM {enrol} e
286                              JOIN {user_enrolments} ue ON ue.enrolid = e.id
287                        ) inline_view
288               GROUP BY timeend, id, nroleid
289                 HAVING COUNT(DISTINCT userid) > 0";
291         if ($logspresent and !$DB->execute($sql)) {
292             $failed = true;
293             break;
294         }
295         stats_daily_progress('5');
297         $sql = "UPDATE {stats_daily}
298                    SET stat2 = (SELECT COUNT(DISTINCT ue.userid)
299                                   FROM {enrol} e
300                                   JOIN {user_enrolments} ue ON ue.enrolid = e.id
301                                  WHERE e.courseid = {stats_daily}.courseid AND
302                                        EXISTS (SELECT 'x'
303                                                  FROM {log} l
304                                                 WHERE l.course = {stats_daily}.courseid AND
305                                                       l.userid = ue.userid AND $timesql))
306                  WHERE {stats_daily}.stattype = 'enrolments' AND
307                        {stats_daily}.timeend = $nextmidnight AND
308                        {stats_daily}.roleid = 0 AND
309                        {stats_daily}.courseid IN
310                           (SELECT l.course
311                              FROM {log} l
312                             WHERE $timesql AND l.course <> ".SITEID.")";
314         if ($logspresent and !$DB->execute($sql, array())) {
315             $failed = true;
316             break;
317         }
318         stats_daily_progress('6');
320     /// frontapge(==site) enrolments total
321         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
323                 SELECT 'enrolments', $nextmidnight, ".SITEID.", 0,
324                        (SELECT COUNT('x')
325                           FROM {user} u
326                          WHERE u.deleted = 0) AS stat1,
327                        (SELECT COUNT(DISTINCT u.id)
328                           FROM {user} u
329                                JOIN {log} l ON l.userid = u.id
330                          WHERE u.deleted = 0 AND $timesql) AS stat2" .
331                 $DB->sql_null_from_clause();
333         if ($logspresent and !$DB->execute($sql)) {
334             $failed = true;
335             break;
336         }
337         stats_daily_progress('7');
339     /// Default frontpage role enrolments are all site users (not deleted)
340         if ($defaultfproleid) {
341             // first remove default frontpage role counts if created by previous query
342             $sql = "DELETE
343                       FROM {stats_daily}
344                      WHERE stattype = 'enrolments' AND courseid = ".SITEID." AND
345                            roleid = $defaultfproleid AND timeend = $nextmidnight";
346             if ($logspresent and !$DB->execute($sql)) {
347                 $failed = true;
348                 break;
349             }
350             stats_daily_progress('8');
352             $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
354                     SELECT 'enrolments', $nextmidnight, ".SITEID.", $defaultfproleid,
355                            (SELECT COUNT('x')
356                               FROM {user} u
357                              WHERE u.deleted = 0) AS stat1,
358                            (SELECT COUNT(DISTINCT u.id)
359                               FROM {user} u
360                                    JOIN {log} l ON l.userid = u.id
361                              WHERE u.deleted = 0 AND $timesql) AS stat2" .
362                     $DB->sql_null_from_clause();;
364             if ($logspresent and !$DB->execute($sql)) {
365                 $failed = true;
366                 break;
367             }
368             stats_daily_progress('9');
370         } else {
371             stats_daily_progress('x');
372             stats_daily_progress('x');
373         }
377     /// individual user stats (including not-logged-in) in each course, this is slow - reuse this data if possible
378         list($viewactionssql, $params1) = $DB->get_in_or_equal($viewactions, SQL_PARAMS_NAMED, 'view000');
379         list($postactionssql, $params2) = $DB->get_in_or_equal($postactions, SQL_PARAMS_NAMED, 'post000');
380         $sql = "INSERT INTO {stats_user_daily} (stattype, timeend, courseid, userid, statsreads, statswrites)
382                 SELECT 'activity' AS stattype, $nextmidnight AS timeend, d.courseid, d.userid,
383                        (SELECT COUNT('x')
384                           FROM {log} l
385                          WHERE l.userid = d.userid AND
386                                l.course = d.courseid AND $timesql AND
387                                l.action $viewactionssql) AS statsreads,
388                        (SELECT COUNT('x')
389                           FROM {log} l
390                          WHERE l.userid = d.userid AND
391                                l.course = d.courseid AND $timesql AND
392                                l.action $postactionssql) AS statswrites
393                   FROM (SELECT DISTINCT u.id AS userid, l.course AS courseid
394                           FROM {user} u, {log} l
395                          WHERE u.id = l.userid AND $timesql
396                        UNION
397                         SELECT 0 AS userid, ".SITEID." AS courseid" . $DB->sql_null_from_clause() . ") d";
398                         // can not use group by here because pg can not handle it :-(
400         if ($logspresent and !$DB->execute($sql, array_merge($params1, $params2))) {
401             $failed = true;
402             break;
403         }
404         stats_daily_progress('10');
407     /// how many view/post actions in each course total
408         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
410                 SELECT 'activity' AS stattype, $nextmidnight AS timeend, c.id AS courseid, 0,
411                        (SELECT COUNT('x')
412                           FROM {log} l1
413                          WHERE l1.course = c.id AND l1.action $viewactionssql AND
414                                $timesql1) AS stat1,
415                        (SELECT COUNT('x')
416                           FROM {log} l2
417                          WHERE l2.course = c.id AND l2.action $postactionssql AND
418                                $timesql2) AS stat2
419                   FROM {course} c
420                  WHERE EXISTS (SELECT 'x'
421                                  FROM {log} l
422                                 WHERE l.course = c.id and $timesql)";
424         if ($logspresent and !$DB->execute($sql, array_merge($params1, $params2))) {
425             $failed = true;
426             break;
427         }
428         stats_daily_progress('11');
431     /// how many view actions for each course+role - excluding guests and frontpage
433         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
435                 SELECT 'activity', timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
436                   FROM (
437                            SELECT $nextmidnight AS timeend, pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
438                              FROM {stats_user_daily} sud,
439                                       (SELECT DISTINCT ra.userid, ra.roleid, e.courseid
440                                          FROM {role_assignments} ra
441                                          JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
442                                          JOIN {enrol} e ON e.courseid = c.instanceid
443                                          JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
444                                         WHERE ra.roleid <> $guestrole AND
445                                               ra.userid <> $guest
446                                       ) pl
447                             WHERE sud.userid = pl.userid AND
448                                   sud.courseid = pl.courseid AND
449                                   sud.timeend = $nextmidnight AND
450                                   sud.stattype='activity'
451                        ) inline_view
452               GROUP BY timeend, courseid, roleid
453                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
455         if ($logspresent and !$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
456             $failed = true;
457             break;
458         }
459         stats_daily_progress('12');
461     /// how many view actions from guests only in each course - excluding frontpage
462     /// normal users may enter course with temporary guest access too
464         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
466                 SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
467                   FROM (
468                            SELECT $nextmidnight AS timeend, sud.courseid, $guestrole AS nroleid, sud.statsreads, sud.statswrites
469                              FROM {stats_user_daily} sud
470                             WHERE sud.timeend = $nextmidnight AND sud.courseid <> ".SITEID." AND
471                                   sud.stattype='activity' AND
472                                   (sud.userid = $guest OR sud.userid
473                                     NOT IN (SELECT ue.userid
474                                               FROM {user_enrolments} ue
475                                               JOIN {enrol} e ON ue.enrolid = e.id
476                                              WHERE e.courseid = sud.courseid))
477                        ) inline_view
478               GROUP BY timeend, courseid, nroleid
479                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
481         if ($logspresent and !$DB->execute($sql, array())) {
482             $failed = true;
483             break;
484         }
485         stats_daily_progress('13');
488     /// how many view actions for each role on frontpage - excluding guests, not-logged-in and default frontpage role
489         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
491                 SELECT 'activity', timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
492                   FROM (
493                            SELECT $nextmidnight AS timeend, pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
494                              FROM {stats_user_daily} sud,
495                                       (SELECT DISTINCT ra.userid, ra.roleid, c.instanceid AS courseid
496                                          FROM {role_assignments} ra
497                                          JOIN {context} c ON c.id = ra.contextid
498                                         WHERE ra.contextid = :fpcontext AND
499                                               ra.roleid <> $defaultfproleid AND
500                                               ra.roleid <> $guestrole AND
501                                               ra.userid <> $guest
502                                       ) pl
503                             WHERE sud.userid = pl.userid AND
504                                   sud.courseid = pl.courseid AND
505                                   sud.timeend = $nextmidnight AND
506                                   sud.stattype='activity'
507                        ) inline_view
508               GROUP BY timeend, courseid, roleid
509                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
511         if ($logspresent and !$DB->execute($sql, array('fpcontext'=>$fpcontext->id))) {
512             $failed = true;
513             break;
514         }
515         stats_daily_progress('14');
518     /// how many view actions for default frontpage role on frontpage only
519         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
521                 SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
522                   FROM (
523                            SELECT sud.timeend AS timeend, sud.courseid, $defaultfproleid AS nroleid, sud.statsreads, sud.statswrites
524                              FROM {stats_user_daily} sud
525                             WHERE sud.timeend = :nextm AND sud.courseid = :siteid AND
526                                   sud.stattype='activity' AND
527                                   sud.userid <> $guest AND sud.userid <> 0 AND sud.userid
528                                   NOT IN (SELECT ra.userid
529                                             FROM {role_assignments} ra
530                                            WHERE ra.roleid <> $guestrole AND
531                                                  ra.roleid <> $defaultfproleid AND ra.contextid = :fpcontext)
532                        ) inline_view
533               GROUP BY timeend, courseid, nroleid
534                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
536         if ($logspresent and !$DB->execute($sql, array('fpcontext'=>$fpcontext->id, 'siteid'=>SITEID, 'nextm'=>$nextmidnight))) {
537             $failed = true;
538             break;
539         }
540         stats_daily_progress('15');
542     /// how many view actions for guests or not-logged-in on frontpage
543         $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
545                 SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
546                   FROM (
547                            SELECT $nextmidnight AS timeend, ".SITEID." AS courseid, $guestrole AS nroleid, pl.statsreads, pl.statswrites
548                              FROM (
549                                       SELECT sud.statsreads, sud.statswrites
550                                         FROM {stats_user_daily} sud
551                                        WHERE (sud.userid = $guest OR sud.userid = 0) AND
552                                              sud.timeend = $nextmidnight AND sud.courseid = ".SITEID." AND
553                                              sud.stattype='activity'
554                                   ) pl
555                        ) inline_view
556               GROUP BY timeend, courseid, nroleid
557                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
559         if ($logspresent and !$DB->execute($sql)) {
560             $failed = true;
561             break;
562         }
563         stats_daily_progress('16');
565         // remember processed days
566         set_config('statslastdaily', $nextmidnight);
567         mtrace("  finished until $nextmidnight: ".userdate($nextmidnight)." (in ".(time()-$daystart)." s)");
569         $timestart    = $nextmidnight;
570         $nextmidnight = stats_get_next_day_start($nextmidnight);
571     }
573     set_cron_lock('statsrunning', null);
575     if ($failed) {
576         $days--;
577         mtrace("...error occurred, completed $days days of statistics.");
578         return false;
580     } else {
581         mtrace("...completed $days days of statistics.");
582         return true;
583     }
587 /**
588  * Execute weekly statistics gathering
589  * @return boolean success
590  */
591 function stats_cron_weekly() {
592     global $CFG, $DB;
594     $now = time();
596     // read last execution date from db
597     if (!$timestart = get_config(NULL, 'statslastweekly')) {
598         $timestart = stats_get_base_daily(stats_get_start_from('weekly'));
599         set_config('statslastweekly', $timestart);
600     }
602     $nextstartweek = stats_get_next_week_start($timestart);
604     // are there any weeks that need to be processed?
605     if ($now < $nextstartweek) {
606         return true; // everything ok and up-to-date
607     }
609     $timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
611     if (!set_cron_lock('statsrunning', $now + $timeout)) {
612         return false;
613     }
615     // fisrt delete entries that should not be there yet
616     $DB->delete_records_select('stats_weekly',      "timeend > $timestart");
617     $DB->delete_records_select('stats_user_weekly', "timeend > $timestart");
619     mtrace("Running weekly statistics gathering, starting at $timestart:");
621     $weeks = 0;
622     while ($now > $nextstartweek) {
623         @set_time_limit($timeout - 200);
624         $weeks++;
626         if ($weeks > 1) {
627             // move the lock
628             set_cron_lock('statsrunning', time() + $timeout, true);
629         }
631         $logtimesql  = "l.time >= $timestart AND l.time < $nextstartweek";
632         $stattimesql = "timeend > $timestart AND timeend <= $nextstartweek";
634     /// process login info first
635         $sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads)
637                 SELECT 'logins', timeend, courseid, userid, COUNT(statsreads)
638                   FROM (
639                            SELECT $nextstartweek AS timeend, ".SITEID." as courseid, l.userid, l.id AS statsreads
640                              FROM {log} l
641                             WHERE action = 'login' AND $logtimesql
642                        ) inline_view
643               GROUP BY timeend, courseid, userid
644                 HAVING count(statsreads) > 0";
646         $DB->execute($sql);
648         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
650                 SELECT 'logins' AS stattype, $nextstartweek AS timeend, ".SITEID." as courseid, 0,
651                        COALESCE((SELECT SUM(statsreads)
652                                    FROM {stats_user_weekly} s1
653                                   WHERE s1.stattype = 'logins' AND timeend = $nextstartweek), 0) AS nstat1,
654                        (SELECT COUNT('x')
655                           FROM {stats_user_weekly} s2
656                          WHERE s2.stattype = 'logins' AND timeend = $nextstartweek) AS nstat2" .
657                 $DB->sql_null_from_clause();
659         $DB->execute($sql);
662     /// now enrolments averages
663         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
665                 SELECT 'enrolments', ntimeend, courseid, roleid, " . $DB->sql_ceil('AVG(stat1)') . ", " . $DB->sql_ceil('AVG(stat2)') . "
666                   FROM (
667                            SELECT $nextstartweek AS ntimeend, courseid, roleid, stat1, stat2
668                              FROM {stats_daily} sd
669                             WHERE stattype = 'enrolments' AND $stattimesql
670                        ) inline_view
671               GROUP BY ntimeend, courseid, roleid";
673         $DB->execute($sql);
676     /// activity read/write averages
677         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
679                 SELECT 'activity', ntimeend, courseid, roleid, SUM(stat1), SUM(stat2)
680                   FROM (
681                            SELECT $nextstartweek AS ntimeend, courseid, roleid, stat1, stat2
682                              FROM {stats_daily}
683                             WHERE stattype = 'activity' AND $stattimesql
684                        ) inline_view
685               GROUP BY ntimeend, courseid, roleid";
687         $DB->execute($sql);
690     /// user read/write averages
691         $sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads, statswrites)
693                 SELECT 'activity', ntimeend, courseid, userid, SUM(statsreads), SUM(statswrites)
694                   FROM (
695                            SELECT $nextstartweek AS ntimeend, courseid, userid, statsreads, statswrites
696                              FROM {stats_user_daily}
697                             WHERE stattype = 'activity' AND $stattimesql
698                        ) inline_view
699               GROUP BY ntimeend, courseid, userid";
701         $DB->execute($sql);
703         set_config('statslastweekly', $nextstartweek);
704         mtrace(" finished until $nextstartweek: ".userdate($nextstartweek));
706         $timestart     = $nextstartweek;
707         $nextstartweek = stats_get_next_week_start($nextstartweek);
708     }
710     set_cron_lock('statsrunning', null);
711     mtrace("...completed $weeks weeks of statistics.");
712     return true;
715 /**
716  * Execute monthly statistics gathering
717  * @return boolean success
718  */
719 function stats_cron_monthly() {
720     global $CFG, $DB;
722     $now = time();
724     // read last execution date from db
725     if (!$timestart = get_config(NULL, 'statslastmonthly')) {
726         $timestart = stats_get_base_monthly(stats_get_start_from('monthly'));
727         set_config('statslastmonthly', $timestart);
728     }
730     $nextstartmonth = stats_get_next_month_start($timestart);
732     // are there any months that need to be processed?
733     if ($now < $nextstartmonth) {
734         return true; // everything ok and up-to-date
735     }
737     $timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
739     if (!set_cron_lock('statsrunning', $now + $timeout)) {
740         return false;
741     }
743     // fisr delete entries that should not be there yet
744     $DB->delete_records_select('stats_monthly', "timeend > $timestart");
745     $DB->delete_records_select('stats_user_monthly', "timeend > $timestart");
747     $startmonth = stats_get_base_monthly($now);
750     mtrace("Running monthly statistics gathering, starting at $timestart:");
752     $months = 0;
753     while ($now > $nextstartmonth) {
754         @set_time_limit($timeout - 200);
755         $months++;
757         if ($months > 1) {
758             // move the lock
759             set_cron_lock('statsrunning', time() + $timeout, true);
760         }
762         $logtimesql  = "l.time >= $timestart AND l.time < $nextstartmonth";
763         $stattimesql = "timeend > $timestart AND timeend <= $nextstartmonth";
765     /// process login info first
766         $sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads)
768                 SELECT 'logins', timeend, courseid, userid, COUNT(statsreads)
769                   FROM (
770                            SELECT $nextstartmonth AS timeend, ".SITEID." as courseid, l.userid, l.id AS statsreads
771                              FROM {log} l
772                             WHERE action = 'login' AND $logtimesql
773                        ) inline_view
774               GROUP BY timeend, courseid, userid";
776         $DB->execute($sql);
778         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
780                 SELECT 'logins' AS stattype, $nextstartmonth AS timeend, ".SITEID." as courseid, 0,
781                        COALESCE((SELECT SUM(statsreads)
782                                    FROM {stats_user_monthly} s1
783                                   WHERE s1.stattype = 'logins' AND timeend = $nextstartmonth), 0) AS nstat1,
784                        (SELECT COUNT('x')
785                           FROM {stats_user_monthly} s2
786                          WHERE s2.stattype = 'logins' AND timeend = $nextstartmonth) AS nstat2" .
787                 $DB->sql_null_from_clause();
789         $DB->execute($sql);
792     /// now enrolments averages
793         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
795                 SELECT 'enrolments', ntimeend, courseid, roleid, " . $DB->sql_ceil('AVG(stat1)') . ", " . $DB->sql_ceil('AVG(stat2)') . "
796                   FROM (
797                            SELECT $nextstartmonth AS ntimeend, courseid, roleid, stat1, stat2
798                              FROM {stats_daily} sd
799                             WHERE stattype = 'enrolments' AND $stattimesql
800                        ) inline_view
801               GROUP BY ntimeend, courseid, roleid";
803         $DB->execute($sql);
806     /// activity read/write averages
807         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
809                 SELECT 'activity', ntimeend, courseid, roleid, SUM(stat1), SUM(stat2)
810                   FROM (
811                            SELECT $nextstartmonth AS ntimeend, courseid, roleid, stat1, stat2
812                              FROM {stats_daily}
813                             WHERE stattype = 'activity' AND $stattimesql
814                        ) inline_view
815               GROUP BY ntimeend, courseid, roleid";
817         $DB->execute($sql);
820     /// user read/write averages
821         $sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads, statswrites)
823                 SELECT 'activity', ntimeend, courseid, userid, SUM(statsreads), SUM(statswrites)
824                   FROM (
825                            SELECT $nextstartmonth AS ntimeend, courseid, userid, statsreads, statswrites
826                              FROM {stats_user_daily}
827                             WHERE stattype = 'activity' AND $stattimesql
828                        ) inline_view
829               GROUP BY ntimeend, courseid, userid";
831         $DB->execute($sql);
833         set_config('statslastmonthly', $nextstartmonth);
834         mtrace(" finished until $nextstartmonth: ".userdate($nextstartmonth));
836         $timestart      = $nextstartmonth;
837         $nextstartmonth = stats_get_next_month_start($nextstartmonth);
838     }
840     set_cron_lock('statsrunning', null);
841     mtrace("...completed $months months of statistics.");
842     return true;
845 /**
846  * Return starting date of stats processing
847  * @param string $str name of table - daily, weekly or monthly
848  * @return int timestamp
849  */
850 function stats_get_start_from($str) {
851     global $CFG, $DB;
853     // are there any data in stats table? Should not be...
854     if ($timeend = $DB->get_field_sql('SELECT timeend FROM {stats_'.$str.'} ORDER BY timeend DESC')) {
855         return $timeend;
856     }
857     // decide what to do based on our config setting (either all or none or a timestamp)
858     switch ($CFG->statsfirstrun) {
859         case 'all':
860             if ($firstlog = $DB->get_field_sql('SELECT time FROM {log} ORDER BY time ASC')) {
861                 return $firstlog;
862             }
863         default:
864             if (is_numeric($CFG->statsfirstrun)) {
865                 return time() - $CFG->statsfirstrun;
866             }
867             // not a number? use next instead
868         case 'none':
869             return strtotime('-3 day', time());
870     }
873 /**
874  * Start of day
875  * @param int $time timestamp
876  * @return start of day
877  */
878 function stats_get_base_daily($time=0) {
879     global $CFG;
881     if (empty($time)) {
882         $time = time();
883     }
884     if ($CFG->timezone == 99) {
885         $time = strtotime(date('d-M-Y', $time));
886         return $time;
887     } else {
888         $offset = get_timezone_offset($CFG->timezone);
889         $gtime = $time + $offset;
890         $gtime = intval($gtime / (60*60*24)) * 60*60*24;
891         return $gtime - $offset;
892     }
895 /**
896  * Start of week
897  * @param int $time timestamp
898  * @return start of week
899  */
900 function stats_get_base_weekly($time=0) {
901     global $CFG;
903     $time = stats_get_base_daily($time);
904     $startday = $CFG->calendar_startwday;
905     if ($CFG->timezone == 99) {
906         $thisday = date('w', $time);
907     } else {
908         $offset = get_timezone_offset($CFG->timezone);
909         $gtime = $time + $offset;
910         $thisday = gmdate('w', $gtime);
911     }
912     if ($thisday > $startday) {
913         $time = $time - (($thisday - $startday) * 60*60*24);
914     } else if ($thisday < $startday) {
915         $time = $time - ((7 + $thisday - $startday) * 60*60*24);
916     }
917     return $time;
920 /**
921  * Start of month
922  * @param int $time timestamp
923  * @return start of month
924  */
925 function stats_get_base_monthly($time=0) {
926     global $CFG;
928     if (empty($time)) {
929         $time = time();
930     }
931     if ($CFG->timezone == 99) {
932         return strtotime(date('1-M-Y', $time));
934     } else {
935         $time = stats_get_base_daily($time);
936         $offset = get_timezone_offset($CFG->timezone);
937         $gtime = $time + $offset;
938         $day = gmdate('d', $gtime);
939         if ($day == 1) {
940             return $time;
941         }
942         return $gtime - (($day-1) * 60*60*24);
943     }
946 /**
947  * Start of next day
948  * @param int $time timestamp
949  * @return start of next day
950  */
951 function stats_get_next_day_start($time) {
952     $next = stats_get_base_daily($time);
953     $next = $next + 60*60*26;
954     $next = stats_get_base_daily($next);
955     if ($next <= $time) {
956         //DST trouble - prevent infinite loops
957         $next = $next + 60*60*24;
958     }
959     return $next;
962 /**
963  * Start of next week
964  * @param int $time timestamp
965  * @return start of next week
966  */
967 function stats_get_next_week_start($time) {
968     $next = stats_get_base_weekly($time);
969     $next = $next + 60*60*24*9;
970     $next = stats_get_base_weekly($next);
971     if ($next <= $time) {
972         //DST trouble - prevent infinite loops
973         $next = $next + 60*60*24*7;
974     }
975     return $next;
978 /**
979  * Start of next month
980  * @param int $time timestamp
981  * @return start of next month
982  */
983 function stats_get_next_month_start($time) {
984     $next = stats_get_base_monthly($time);
985     $next = $next + 60*60*24*33;
986     $next = stats_get_base_monthly($next);
987     if ($next <= $time) {
988         //DST trouble - prevent infinite loops
989         $next = $next + 60*60*24*31;
990     }
991     return $next;
994 /**
995  * Remove old stats data
996  */
997 function stats_clean_old() {
998     global $DB;
999     mtrace("Running stats cleanup tasks...");
1000     $deletebefore =  stats_get_base_monthly();
1002     // delete dailies older than 3 months (to be safe)
1003     $deletebefore = strtotime('-3 months', $deletebefore);
1004     $DB->delete_records_select('stats_daily',      "timeend < $deletebefore");
1005     $DB->delete_records_select('stats_user_daily', "timeend < $deletebefore");
1007     // delete weeklies older than 9  months (to be safe)
1008     $deletebefore = strtotime('-6 months', $deletebefore);
1009     $DB->delete_records_select('stats_weekly',      "timeend < $deletebefore");
1010     $DB->delete_records_select('stats_user_weekly', "timeend < $deletebefore");
1012     // don't delete monthlies
1014     mtrace("...stats cleanup finished");
1017 function stats_get_parameters($time,$report,$courseid,$mode,$roleid=0) {
1018     global $CFG, $DB;
1020     $param = new object();
1021     $params->params = array();
1023     if ($time < 10) { // dailies
1024         // number of days to go back = 7* time
1025         $param->table = 'daily';
1026         $param->timeafter = strtotime("-".($time*7)." days",stats_get_base_daily());
1027     } elseif ($time < 20) { // weeklies
1028         // number of weeks to go back = time - 10 * 4 (weeks) + base week
1029         $param->table = 'weekly';
1030         $param->timeafter = strtotime("-".(($time - 10)*4)." weeks",stats_get_base_weekly());
1031     } else { // monthlies.
1032         // number of months to go back = time - 20 * months + base month
1033         $param->table = 'monthly';
1034         $param->timeafter = strtotime("-".($time - 20)." months",stats_get_base_monthly());
1035     }
1037     $param->extras = '';
1039     switch ($report) {
1040     // ******************** STATS_MODE_GENERAL ******************** //
1041     case STATS_REPORT_LOGINS:
1042         $param->fields = 'timeend,sum(stat1) as line1,sum(stat2) as line2';
1043         $param->fieldscomplete = true;
1044         $param->stattype = 'logins';
1045         $param->line1 = get_string('statslogins');
1046         $param->line2 = get_string('statsuniquelogins');
1047         if ($courseid == SITEID) {
1048             $param->extras = 'GROUP BY timeend';
1049         }
1050         break;
1052     case STATS_REPORT_READS:
1053         $param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat1 as line1';
1054         $param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1055         $param->aggregategroupby = 'roleid';
1056         $param->stattype = 'activity';
1057         $param->crosstab = true;
1058         $param->extras = 'GROUP BY timeend,roleid,stat1';
1059         if ($courseid == SITEID) {
1060             $param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1) as line1';
1061             $param->extras = 'GROUP BY timeend,roleid';
1062         }
1063         break;
1065     case STATS_REPORT_WRITES:
1066         $param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat2 as line1';
1067         $param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1068         $param->aggregategroupby = 'roleid';
1069         $param->stattype = 'activity';
1070         $param->crosstab = true;
1071         $param->extras = 'GROUP BY timeend,roleid,stat2';
1072         if ($courseid == SITEID) {
1073             $param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat2) as line1';
1074             $param->extras = 'GROUP BY timeend,roleid';
1075         }
1076         break;
1078     case STATS_REPORT_ACTIVITY:
1079         $param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1+stat2) as line1';
1080         $param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
1081         $param->aggregategroupby = 'roleid';
1082         $param->stattype = 'activity';
1083         $param->crosstab = true;
1084         $param->extras = 'GROUP BY timeend,roleid';
1085         if ($courseid == SITEID) {
1086             $param->extras = 'GROUP BY timeend,roleid';
1087         }
1088         break;
1090     case STATS_REPORT_ACTIVITYBYROLE;
1091         $param->fields = 'stat1 AS line1, stat2 AS line2';
1092         $param->stattype = 'activity';
1093         $rolename = $DB->get_field('role','name', array('id'=>$roleid));
1094         $param->line1 = $rolename . get_string('statsreads');
1095         $param->line2 = $rolename . get_string('statswrites');
1096         if ($courseid == SITEID) {
1097             $param->extras = 'GROUP BY timeend';
1098         }
1099         break;
1101     // ******************** STATS_MODE_DETAILED ******************** //
1102     case STATS_REPORT_USER_ACTIVITY:
1103         $param->fields = 'statsreads as line1, statswrites as line2';
1104         $param->line1 = get_string('statsuserreads');
1105         $param->line2 = get_string('statsuserwrites');
1106         $param->stattype = 'activity';
1107         break;
1109     case STATS_REPORT_USER_ALLACTIVITY:
1110         $param->fields = 'statsreads+statswrites as line1';
1111         $param->line1 = get_string('statsuseractivity');
1112         $param->stattype = 'activity';
1113         break;
1115     case STATS_REPORT_USER_LOGINS:
1116         $param->fields = 'statsreads as line1';
1117         $param->line1 = get_string('statsuserlogins');
1118         $param->stattype = 'logins';
1119         break;
1121     case STATS_REPORT_USER_VIEW:
1122         $param->fields = 'statsreads as line1, statswrites as line2, statsreads+statswrites as line3';
1123         $param->line1 = get_string('statsuserreads');
1124         $param->line2 = get_string('statsuserwrites');
1125         $param->line3 = get_string('statsuseractivity');
1126         $param->stattype = 'activity';
1127         break;
1129     // ******************** STATS_MODE_RANKED ******************** //
1130     case STATS_REPORT_ACTIVE_COURSES:
1131         $param->fields = 'sum(stat1+stat2) AS line1';
1132         $param->stattype = 'activity';
1133         $param->orderby = 'line1 DESC';
1134         $param->line1 = get_string('activity');
1135         $param->graphline = 'line1';
1136         break;
1138     case STATS_REPORT_ACTIVE_COURSES_WEIGHTED:
1139         $threshold = 0;
1140         if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
1141             $threshold = $CFG->statsuserthreshold;
1142         }
1143         $param->fields = '';
1144         $param->sql = 'SELECT activity.courseid, activity.all_activity AS line1, enrolments.highest_enrolments AS line2,
1145                         activity.all_activity / enrolments.highest_enrolments as line3
1146                        FROM (
1147                             SELECT courseid, sum(stat1+stat2) AS all_activity
1148                               FROM {stats_'.$param->table.'}
1149                              WHERE stattype=\'activity\' AND timeend >= '.(int)$param->timeafter.' AND roleid = 0 GROUP BY courseid
1150                        ) activity
1151                        INNER JOIN
1152                             (
1153                             SELECT courseid, max(stat1) AS highest_enrolments
1154                               FROM {stats_'.$param->table.'}
1155                              WHERE stattype=\'enrolments\' AND timeend >= '.(int)$param->timeafter.' AND stat1 > '.(int)$threshold.'
1156                           GROUP BY courseid
1157                       ) enrolments
1158                       ON (activity.courseid = enrolments.courseid)
1159                       ORDER BY line3 DESC';
1160         $param->line1 = get_string('activity');
1161         $param->line2 = get_string('users');
1162         $param->line3 = get_string('activityweighted');
1163         $param->graphline = 'line3';
1164         break;
1166     case STATS_REPORT_PARTICIPATORY_COURSES:
1167         $threshold = 0;
1168         if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
1169             $threshold = $CFG->statsuserthreshold;
1170         }
1171         $param->fields = '';
1172         $param->sql = 'SELECT courseid, ' . $DB->sql_ceil('avg(all_enrolments)') . ' as line1, ' .
1173                          $DB->sql_ceil('avg(active_enrolments)') . ' as line2, avg(proportion_active) AS line3
1174                        FROM (
1175                            SELECT courseid, timeend, stat2 as active_enrolments,
1176                                   stat1 as all_enrolments, '.$DB->sql_cast_char2real('stat2').'/'.$DB->sql_cast_char2real('stat1').' AS proportion_active
1177                              FROM {stats_'.$param->table.'}
1178                             WHERE stattype=\'enrolments\' AND roleid = 0 AND stat1 > '.(int)$threshold.'
1179                        ) aq
1180                        WHERE timeend >= '.(int)$param->timeafter.'
1181                        GROUP BY courseid
1182                        ORDER BY line3 DESC';
1184         $param->line1 = get_string('users');
1185         $param->line2 = get_string('activeusers');
1186         $param->line3 = get_string('participationratio');
1187         $param->graphline = 'line3';
1188         break;
1190     case STATS_REPORT_PARTICIPATORY_COURSES_RW:
1191         $param->fields = '';
1192         $param->sql =  'SELECT courseid, sum(views) AS line1, sum(posts) AS line2,
1193                            avg(proportion_active) AS line3
1194                          FROM (
1195                            SELECT courseid, timeend, stat1 as views, stat2 AS posts,
1196                                   '.$DB->sql_cast_char2real('stat2').'/'.$DB->sql_cast_char2real('stat1').' as proportion_active
1197                              FROM {stats_'.$param->table.'}
1198                             WHERE stattype=\'activity\' AND roleid = 0 AND stat1 > 0
1199                        ) aq
1200                        WHERE timeend >= '.(int)$param->timeafter.'
1201                        GROUP BY courseid
1202                        ORDER BY line3 DESC';
1203         $param->line1 = get_string('views');
1204         $param->line2 = get_string('posts');
1205         $param->line3 = get_string('participationratio');
1206         $param->graphline = 'line3';
1207         break;
1208     }
1210     /*
1211     if ($courseid == SITEID && $mode != STATS_MODE_RANKED) { // just aggregate all courses.
1212         $param->fields = preg_replace('/(?:sum)([a-zA-Z0-9+_]*)\W+as\W+([a-zA-Z0-9_]*)/i','sum($1) as $2',$param->fields);
1213         $param->extras = ' GROUP BY timeend'.((!empty($param->aggregategroupby)) ? ','.$param->aggregategroupby : '');
1214     }
1215     */
1216     //TODO must add the SITEID reports to the rest of the reports.
1217     return $param;
1220 function stats_get_view_actions() {
1221     return array('view','view all','history');
1224 function stats_get_post_actions() {
1225     return array('add','delete','edit','add mod','delete mod','edit section'.'enrol','loginas','new','unenrol','update','update mod');
1228 function stats_get_action_names($str) {
1229     global $CFG, $DB;
1231     $mods = $DB->get_records('modules');
1232     $function = 'stats_get_'.$str.'_actions';
1233     $actions = $function();
1234     foreach ($mods as $mod) {
1235         $file = $CFG->dirroot.'/mod/'.$mod->name.'/lib.php';
1236         if (!is_readable($file)) {
1237             continue;
1238         }
1239         require_once($file);
1240         $function = $mod->name.'_get_'.$str.'_actions';
1241         if (function_exists($function)) {
1242             $mod_actions = $function();
1243             if (is_array($mod_actions)) {
1244                 $actions = array_merge($actions, $mod_actions);
1245             }
1246         }
1247     }
1249     // The array_values() forces a stack-like array
1250     // so we can later loop over safely...
1251     $actions =  array_values(array_unique($actions));
1252     $c = count($actions);
1253     for ($n=0;$n<$c;$n++) {
1254         $actions[$n] = $actions[$n];
1255     }
1256     return $actions;
1259 function stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth) {
1261     $now = stats_get_base_daily(time());
1262     // it's really important that it's TIMEEND in the table. ie, tuesday 00:00:00 is monday night.
1263     // so we need to take a day off here (essentially add a day to $now
1264     $now += 60*60*24;
1266     $timeoptions = array();
1268     if ($now - (60*60*24*7) >= $earliestday) {
1269         $timeoptions[STATS_TIME_LASTWEEK] = get_string('numweeks','moodle',1);
1270     }
1271     if ($now - (60*60*24*14) >= $earliestday) {
1272         $timeoptions[STATS_TIME_LAST2WEEKS] = get_string('numweeks','moodle',2);
1273     }
1274     if ($now - (60*60*24*21) >= $earliestday) {
1275         $timeoptions[STATS_TIME_LAST3WEEKS] = get_string('numweeks','moodle',3);
1276     }
1277     if ($now - (60*60*24*28) >= $earliestday) {
1278         $timeoptions[STATS_TIME_LAST4WEEKS] = get_string('numweeks','moodle',4);// show dailies up to (including) here.
1279     }
1280     if ($lastweekend - (60*60*24*56) >= $earliestweek) {
1281         $timeoptions[STATS_TIME_LAST2MONTHS] = get_string('nummonths','moodle',2);
1282     }
1283     if ($lastweekend - (60*60*24*84) >= $earliestweek) {
1284         $timeoptions[STATS_TIME_LAST3MONTHS] = get_string('nummonths','moodle',3);
1285     }
1286     if ($lastweekend - (60*60*24*112) >= $earliestweek) {
1287         $timeoptions[STATS_TIME_LAST4MONTHS] = get_string('nummonths','moodle',4);
1288     }
1289     if ($lastweekend - (60*60*24*140) >= $earliestweek) {
1290         $timeoptions[STATS_TIME_LAST5MONTHS] = get_string('nummonths','moodle',5);
1291     }
1292     if ($lastweekend - (60*60*24*168) >= $earliestweek) {
1293         $timeoptions[STATS_TIME_LAST6MONTHS] = get_string('nummonths','moodle',6); // show weeklies up to (including) here
1294     }
1295     if (strtotime('-7 months',$lastmonthend) >= $earliestmonth) {
1296         $timeoptions[STATS_TIME_LAST7MONTHS] = get_string('nummonths','moodle',7);
1297     }
1298     if (strtotime('-8 months',$lastmonthend) >= $earliestmonth) {
1299         $timeoptions[STATS_TIME_LAST8MONTHS] = get_string('nummonths','moodle',8);
1300     }
1301     if (strtotime('-9 months',$lastmonthend) >= $earliestmonth) {
1302         $timeoptions[STATS_TIME_LAST9MONTHS] = get_string('nummonths','moodle',9);
1303     }
1304     if (strtotime('-10 months',$lastmonthend) >= $earliestmonth) {
1305         $timeoptions[STATS_TIME_LAST10MONTHS] = get_string('nummonths','moodle',10);
1306     }
1307     if (strtotime('-11 months',$lastmonthend) >= $earliestmonth) {
1308         $timeoptions[STATS_TIME_LAST11MONTHS] = get_string('nummonths','moodle',11);
1309     }
1310     if (strtotime('-1 year',$lastmonthend) >= $earliestmonth) {
1311         $timeoptions[STATS_TIME_LASTYEAR] = get_string('lastyear');
1312     }
1314     $years = (int)date('y', $now) - (int)date('y', $earliestmonth);
1315     if ($years > 1) {
1316         for($i = 2; $i <= $years; $i++) {
1317             $timeoptions[$i*12+20] = get_string('numyears', 'moodle', $i);
1318         }
1319     }
1321     return $timeoptions;
1324 function stats_get_report_options($courseid,$mode) {
1325     global $CFG, $DB;
1327     $reportoptions = array();
1329     switch ($mode) {
1330     case STATS_MODE_GENERAL:
1331         $reportoptions[STATS_REPORT_ACTIVITY] = get_string('statsreport'.STATS_REPORT_ACTIVITY);
1332         if ($courseid != SITEID && $context = get_context_instance(CONTEXT_COURSE, $courseid)) {
1333             $sql = 'SELECT r.id,r.name FROM {role} r JOIN {stats_daily} s ON s.roleid = r.id WHERE s.courseid = '.$courseid;
1334             if ($roles = $DB->get_records_sql($sql)) {
1335                 foreach ($roles as $role) {
1336                     $reportoptions[STATS_REPORT_ACTIVITYBYROLE.$role->id] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE). ' '.$role->name;
1337                 }
1338             }
1339         }
1340         $reportoptions[STATS_REPORT_READS] = get_string('statsreport'.STATS_REPORT_READS);
1341         $reportoptions[STATS_REPORT_WRITES] = get_string('statsreport'.STATS_REPORT_WRITES);
1342         if ($courseid == SITEID) {
1343             $reportoptions[STATS_REPORT_LOGINS] = get_string('statsreport'.STATS_REPORT_LOGINS);
1344         }
1346         break;
1347     case STATS_MODE_DETAILED:
1348         $reportoptions[STATS_REPORT_USER_ACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ACTIVITY);
1349         $reportoptions[STATS_REPORT_USER_ALLACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ALLACTIVITY);
1350         if (has_capability('coursereport/stats:view', get_context_instance(CONTEXT_SYSTEM))) {
1351             $site = get_site();
1352             $reportoptions[STATS_REPORT_USER_LOGINS] = get_string('statsreport'.STATS_REPORT_USER_LOGINS);
1353         }
1354         break;
1355     case STATS_MODE_RANKED:
1356         if (has_capability('coursereport/stats:view', get_context_instance(CONTEXT_SYSTEM))) {
1357             $reportoptions[STATS_REPORT_ACTIVE_COURSES] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES);
1358             $reportoptions[STATS_REPORT_ACTIVE_COURSES_WEIGHTED] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES_WEIGHTED);
1359             $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES);
1360             $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES_RW] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES_RW);
1361         }
1362      break;
1363     }
1365     return $reportoptions;
1368 function stats_fix_zeros($stats,$timeafter,$timestr,$line2=true,$line3=false) {
1370     if (empty($stats)) {
1371         return;
1372     }
1374     $timestr = str_replace('user_','',$timestr); // just in case.
1375     $fun = 'stats_get_base_'.$timestr;
1377     $now = $fun();
1379     $times = array();
1380     // add something to timeafter since it is our absolute base
1381     $actualtimes = array();
1382     foreach ($stats as $statid=>$s) {
1383         //normalize the times in stats - those might have been created in different timezone, DST etc.
1384         $s->timeend = $fun($s->timeend + 60*60*5);
1385         $stats[$statid] = $s;
1387         $actualtimes[] = $s->timeend;
1388     }
1390     $timeafter = array_pop(array_values($actualtimes));
1392     while ($timeafter < $now) {
1393         $times[] = $timeafter;
1394         if ($timestr == 'daily') {
1395             $timeafter = stats_get_next_day_start($timeafter);
1396         } else if ($timestr == 'weekly') {
1397             $timeafter = stats_get_next_week_start($timeafter);
1398         } else if ($timestr == 'monthly') {
1399             $timeafter = stats_get_next_month_start($timeafter);
1400         } else {
1401             return $stats; // this will put us in a never ending loop.
1402         }
1403     }
1405     foreach ($times as $count => $time) {
1406         if (!in_array($time,$actualtimes) && $count != count($times) -1) {
1407             $newobj = new StdClass;
1408             $newobj->timeend = $time;
1409             $newobj->id = 0;
1410             $newobj->roleid = 0;
1411             $newobj->line1 = 0;
1412             if (!empty($line2)) {
1413                 $newobj->line2 = 0;
1414             }
1415             if (!empty($line3)) {
1416                 $newobj->line3 = 0;
1417             }
1418             $newobj->zerofixed = true;
1419             $stats[] = $newobj;
1420         }
1421     }
1423     usort($stats,"stats_compare_times");
1424     return $stats;
1428 // helper function to sort arrays by $obj->timeend
1429 function stats_compare_times($a,$b) {
1430    if ($a->timeend == $b->timeend) {
1431        return 0;
1432    }
1433    return ($a->timeend > $b->timeend) ? -1 : 1;
1436 function stats_check_uptodate($courseid=0) {
1437     global $CFG, $DB;
1439     if (empty($courseid)) {
1440         $courseid = SITEID;
1441     }
1443     $latestday = stats_get_start_from('daily');
1445     if ((time() - 60*60*24*2) < $latestday) { // we're ok
1446         return NULL;
1447     }
1449     $a = new object();
1450     $a->daysdone = $DB->get_field_sql("SELECT COUNT(DISTINCT(timeend)) FROM {stats_daily}");
1452     // how many days between the last day and now?
1453     $a->dayspending = ceil((stats_get_base_daily() - $latestday)/(60*60*24));
1455     if ($a->dayspending == 0 && $a->daysdone != 0) {
1456         return NULL; // we've only just started...
1457     }
1459     //return error as string
1460     return get_string('statscatchupmode','error',$a);