Bumped to 1.6.8
[moodle.git] / lib / statslib.php
blob8674c19e846589435bb0d33188decf3707efafb4
1 <?php
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_STUDENTACTIVITY',5); // all student activity, reads vs writes
10 define('STATS_REPORT_TEACHERACTIVITY',6); // all teacher activity, reads vs writes
12 // user level stats reports.
13 define('STATS_REPORT_USER_ACTIVITY',7);
14 define('STATS_REPORT_USER_ALLACTIVITY',8);
15 define('STATS_REPORT_USER_LOGINS',9);
16 define('STATS_REPORT_USER_VIEW',10); // this is the report you see on the user profile.
18 // admin only ranking stats reports
19 define('STATS_REPORT_ACTIVE_COURSES',11);
20 define('STATS_REPORT_ACTIVE_COURSES_WEIGHTED',12);
21 define('STATS_REPORT_PARTICIPATORY_COURSES',13);
22 define('STATS_REPORT_PARTICIPATORY_COURSES_RW',14);
24 // start after 0 = show dailies.
25 define('STATS_TIME_LASTWEEK',1);
26 define('STATS_TIME_LAST2WEEKS',2);
27 define('STATS_TIME_LAST3WEEKS',3);
28 define('STATS_TIME_LAST4WEEKS',4);
30 // start after 10 = show weeklies
31 define('STATS_TIME_LAST2MONTHS',12);
33 define('STATS_TIME_LAST3MONTHS',13);
34 define('STATS_TIME_LAST4MONTHS',14);
35 define('STATS_TIME_LAST5MONTHS',15);
36 define('STATS_TIME_LAST6MONTHS',16);
38 // start after 20 = show monthlies
39 define('STATS_TIME_LAST7MONTHS',27);
40 define('STATS_TIME_LAST8MONTHS',28);
41 define('STATS_TIME_LAST9MONTHS',29);
42 define('STATS_TIME_LAST10MONTHS',30);
43 define('STATS_TIME_LAST11MONTHS',31);
44 define('STATS_TIME_LASTYEAR',32);
46 // different modes for what reports to offer
47 define('STATS_MODE_GENERAL',1);
48 define('STATS_MODE_DETAILED',2);
49 define('STATS_MODE_RANKED',3); // admins only - ranks courses
51 // return codes - whether to rerun
52 define('STATS_RUN_COMPLETE',1);
53 define('STATS_RUN_ABORTED',0);
55 function stats_cron_daily () {
56 global $CFG;
58 if (empty($CFG->enablestats)) {
59 return STATS_RUN_ABORTED;
62 if (!$timestart = stats_get_start_from('daily')) {
63 return STATS_RUN_ABORTED;
67 $midnight = stats_getmidnight(time());
69 // check to make sure we're due to run, at least one day after last run
70 if (isset($CFG->statslastdaily) and ((time() - 24*60*60) < $CFG->statslastdaily)) {
71 return STATS_RUN_ABORTED;
74 mtrace("Running daily statistics gathering...");
75 set_config('statslastdaily',time());
77 $return = STATS_RUN_COMPLETE; // optimistic
79 static $daily_modules;
81 if (empty($daily_modules)) {
82 $daily_modules = array();
83 $mods = get_records("modules");
84 foreach ($mods as $mod) {
85 $file = $CFG->dirroot .'/mod/'.$mod->name.'/lib.php';
86 if (!is_readable($file)) {
87 continue;
89 require_once($file);
90 $fname = $mod->name.'_get_daily_stats';
91 if (function_exists($fname)) {
92 $daily_modules[$mod] = $fname;
97 $nextmidnight = stats_get_next_dayend($timestart);
99 if (!$courses = get_records('course','','','','id,1')) {
100 return STATS_RUN_ABORTED;
103 $days = 0;
104 mtrace("starting at $timestart");
105 while ($midnight > $nextmidnight && $timestart < $nextmidnight) {
107 $timesql = " (l.time > $timestart AND l.time < $nextmidnight) ";
108 begin_sql();
109 foreach ($courses as $course) {
111 $stat->students = count_records('user_students','course',$course->id);
112 $stat->teachers = count_records('user_teachers','course',$course->id);
114 $sql = 'SELECT COUNT(DISTINCT(l.userid)) FROM '.$CFG->prefix.'log l JOIN '.$CFG->prefix
115 .'user_students us ON us.userid = l.userid WHERE l.course = '.$course->id.' AND us.course = '.$course->id.' AND '.$timesql;
116 $stat->activestudents = count_records_sql($sql);
118 $sql = str_replace('students','teachers',$sql);
119 $stat->activeteachers = count_records_sql($sql);
121 $sql = 'SELECT COUNT(l.id) FROM '.$CFG->prefix.'log l JOIN '.$CFG->prefix
122 .'user_students us ON us.userid = l.userid WHERE l.course = '.$course->id.' AND us.course = '.$course->id
123 .' AND '.$timesql .' '.stats_get_action_sql_in('view');
124 $stat->studentreads = count_records_sql($sql);
126 $sql = str_replace('students','teachers',$sql);
127 $stat->teacherreads = count_records_sql($sql);
129 $sql = 'SELECT COUNT(l.id) FROM '.$CFG->prefix.'log l JOIN '.$CFG->prefix
130 .'user_students us ON us.userid = l.userid WHERE l.course = '.$course->id.' AND us.course = '.$course->id
131 .' AND '.$timesql.' '.stats_get_action_sql_in('post');
132 $stat->studentwrites = count_records_sql($sql);
134 $sql = str_replace('students','teachers',$sql);
135 $stat->teacherwrites = count_records_sql($sql);
137 $stat->logins = 0;
138 $stat->uniquelogins = 0;
139 if ($course->id == SITEID) {
140 $sql = 'SELECT count(l.id) FROM '.$CFG->prefix.'log l WHERE l.action = \'login\' AND '.$timesql;
141 $stat->logins = count_records_sql($sql);
142 $sql = 'SELECT COUNT(DISTINCT(l.userid)) FROM '.$CFG->prefix.'log l WHERE l.action = \'login\' AND '.$timesql;
143 $stat->uniquelogins = count_records_sql($sql);
146 $stat->courseid = $course->id;
147 $stat->timeend = $nextmidnight;
148 insert_record('stats_daily',$stat,false); // don't worry about the return id, we don't need it.
150 $students = array();
151 $teachers = array();
153 // now do logins.
154 if ($course->id == SITEID) {
155 $sql = 'SELECT l.userid,count(l.id) as count FROM '.$CFG->prefix.'log l WHERE action = \'login\' AND '.$timesql.' GROUP BY userid';
157 if ($logins = get_records_sql($sql)) {
158 foreach ($logins as $l) {
159 $stat->statsreads = $l->count;
160 $stat->userid = $l->userid;
161 $stat->timeend = $nextmidnight;
162 $stat->courseid = SITEID;
163 $stat->statswrites = 0;
164 $stat->stattype = 'logins';
165 $stat->roleid = 1;
166 insert_record('stats_user_daily',$stat,false);
172 stats_get_course_users($course,$timesql,$students,$teachers);
174 foreach ($students as $user) {
175 stats_do_daily_user_cron($course,$user,1,$timesql,$nextmidnight,$daily_modules);
177 foreach ($teachers as $user) {
178 stats_do_daily_user_cron($course,$user,2,$timesql,$nextmidnight,$daily_modules);
181 commit_sql();
182 $timestart = $nextmidnight;
183 $nextmidnight = stats_get_next_dayend($nextmidnight);
184 $days++;
186 if (!stats_check_runtime()) {
187 mtrace("Stopping early! reached maxruntime");
188 $return = STATS_RUN_ABORTED;
189 break;
192 mtrace("got up to ".$timestart);
193 mtrace("Completed $days days");
194 return $return;
199 function stats_cron_weekly () {
201 global $CFG;
203 if (empty($CFG->enablestats)) {
204 STATS_RUN_ABORTED;
207 if (!$timestart = stats_get_start_from('weekly')) {
208 return STATS_RUN_ABORTED;
211 // check to make sure we're due to run, at least one week after last run
212 $sunday = stats_get_base_weekly();
214 if (isset($CFG->statslastweekly) and ((time() - (7*24*60*60)) <= $CFG->statslastweekly)) {
215 return STATS_RUN_ABORTED;
218 mtrace("Running weekly statistics gathering...");
219 set_config('statslastweekly',time());
221 $return = STATS_RUN_COMPLETE; // optimistic
223 static $weekly_modules;
225 if (empty($weekly_modules)) {
226 $weekly_modules = array();
227 $mods = get_records("modules");
228 foreach ($mods as $mod) {
229 $file = $CFG->dirroot .'/mod/'.$mod->name.'/lib.php';
230 if (!is_readable($file)) {
231 continue;
233 require_once($file);
234 $fname = $mod->name.'_get_weekly_stats';
235 if (function_exists($fname)) {
236 $weekly_modules[$mod] = $fname;
241 $nextsunday = stats_get_next_weekend($timestart);
243 if (!$courses = get_records('course','','','','id,1')) {
244 return STATS_RUN_ABORTED;
247 $weeks = 0;
248 mtrace("starting at $timestart");
249 while ($sunday > $nextsunday && $timestart < $nextsunday) {
251 $timesql = " (timeend > $timestart AND timeend < $nextsunday) ";
252 begin_sql();
253 foreach ($courses as $course) {
255 $sql = 'SELECT ceil(avg(students)) as students, ceil(avg(teachers)) as teachers,
256 ceil(avg(activestudents)) as activestudents,ceil(avg(activeteachers)) as activeteachers,
257 sum(studentreads) as studentreads, sum(studentwrites) as studentwrites,
258 sum(teacherreads) as teacherreads, sum(teacherwrites) as teacherwrites,
259 sum(logins) as logins FROM '.$CFG->prefix.'stats_daily WHERE courseid = '.$course->id.' AND '.$timesql;
261 $stat = get_record_sql($sql);
263 $stat->uniquelogins = 0;
264 if ($course->id == SITEID) {
265 $sql = 'SELECT COUNT(DISTINCT(l.userid)) FROM '.$CFG->prefix.'log l WHERE l.action = \'login\' AND '
266 .str_replace('timeend','time',$timesql);
267 $stat->uniquelogins = count_records_sql($sql);
270 $stat->courseid = $course->id;
271 $stat->timeend = $nextsunday;
273 foreach (array_keys((array)$stat) as $key) {
274 if (!isset($stat->$key)) {
275 $stat->$key = 0; // we don't want nulls , so avoid them.
279 insert_record('stats_weekly',$stat,false); // don't worry about the return id, we don't need it.
281 $students = array();
282 $teachers = array();
284 stats_get_course_users($course,$timesql,$students,$teachers);
286 foreach ($students as $user) {
287 stats_do_aggregate_user_cron($course,$user,1,$timesql,$nextsunday,'weekly',$weekly_modules);
290 foreach ($teachers as $user) {
291 stats_do_aggregate_user_cron($course,$user,2,$timesql,$nextsunday,'weekly',$weekly_modules);
295 stats_do_aggregate_user_login_cron($timesql,$nextsunday,'weekly');
296 commit_sql();
297 $timestart = $nextsunday;
298 $nextsunday = stats_get_next_weekend($nextsunday);
299 $weeks++;
301 if (!stats_check_runtime()) {
302 mtrace("Stopping early! reached maxruntime");
303 $return = STATS_RUN_ABORTED;
304 break;
307 mtrace("got up to ".$timestart);
308 mtrace("Completed $weeks weeks");
309 return $return;
313 function stats_cron_monthly () {
314 global $CFG;
316 if (empty($CFG->enablestats)) {
317 return STATS_RUN_ABORTED;
320 if (!$timestart = stats_get_start_from('monthly')) {
321 return STATS_RUN_ABORTED;
324 // check to make sure we're due to run, at least one month after last run
325 $monthend = stats_get_base_monthly();
327 if (isset($CFG->statslastmonthly) and ((time() - (31*24*60*60)) <= $CFG->statslastmonthly)) {
328 return STATS_RUN_ABORTED;
331 mtrace("Running monthly statistics gathering...");
332 set_config('statslastmonthly',time());
334 $return = STATS_RUN_COMPLETE; // optimistic
336 static $monthly_modules;
338 if (empty($monthly_modules)) {
339 $monthly_modules = array();
340 $mods = get_records("modules");
341 foreach ($mods as $mod) {
342 $file = $CFG->dirroot .'/mod/'.$mod->name.'/lib.php';
343 if (!is_readable($file)) {
344 continue;
346 require_once($file);
347 $fname = $mod->name.'_get_monthly_stats';
348 if (function_exists($fname)) {
349 $monthly_modules[$mod] = $fname;
354 $nextmonthend = stats_get_next_monthend($timestart);
356 if (!$courses = get_records('course','','','','id,1')) {
357 return STATS_RUN_ABORTED;
360 $months = 0;
361 mtrace("starting from $timestart");
362 while ($monthend > $nextmonthend && $timestart < $nextmonthend) {
364 $timesql = " (timeend > $timestart AND timeend < $nextmonthend) ";
365 begin_sql();
366 foreach ($courses as $course) {
368 $sql = 'SELECT ceil(avg(students)) as students, ceil(avg(teachers)) as teachers, ceil(avg(activestudents)) as activestudents,ceil(avg(activeteachers)) as activeteachers,
369 sum(studentreads) as studentreads, sum(studentwrites) as studentwrites, sum(teacherreads) as teacherreads, sum(teacherwrites) as teacherwrites,
370 sum(logins) as logins FROM '.$CFG->prefix.'stats_daily WHERE courseid = '.$course->id.' AND '.$timesql;
372 $stat = get_record_sql($sql);
374 $stat->uniquelogins = 0;
375 if ($course->id == SITEID) {
376 $sql = 'SELECT COUNT(DISTINCT(l.userid)) FROM '.$CFG->prefix.'log l WHERE l.action = \'login\' AND '.str_replace('timeend','time',$timesql);
377 $stat->uniquelogins = count_records_sql($sql);
380 $stat->courseid = $course->id;
381 $stat->timeend = $nextmonthend;
383 foreach (array_keys((array)$stat) as $key) {
384 if (!isset($stat->$key)) {
385 $stat->$key = 0; // we don't want nulls , so avoid them.
389 insert_record('stats_monthly',$stat,false); // don't worry about the return id, we don't need it.
391 $students = array();
392 $teachers = array();
394 stats_get_course_users($course,$timesql,$students,$teachers);
396 foreach ($students as $user) {
397 stats_do_aggregate_user_cron($course,$user,1,$timesql,$nextmonthend,'monthly',$monthly_modules);
400 foreach ($teachers as $user) {
401 stats_do_aggregate_user_cron($course,$user,2,$timesql,$nextmonthend,'monthly',$monthly_modules);
404 stats_do_aggregate_user_login_cron($timesql,$nextmonthend,'monthly');
405 commit_sql();
406 $timestart = $nextmonthend;
407 $nextmonthend = stats_get_next_monthend($timestart);
408 $months++;
409 if (!stats_check_runtime()) {
410 mtrace("Stopping early! reached maxruntime");
411 break;
412 $return = STATS_RUN_ABORTED;
415 mtrace("got up to $timestart");
416 mtrace("Completed $months months");
417 return $return;
420 function stats_get_start_from($str) {
421 global $CFG;
423 // if it's not our first run, just return the most recent.
424 if ($timeend = get_field_sql('SELECT timeend FROM '.$CFG->prefix.'stats_'.$str.' ORDER BY timeend DESC LIMIT 1')) {
425 return $timeend;
428 // decide what to do based on our config setting (either all or none or a timestamp)
429 $function = 'stats_get_base_'.$str;
430 switch ($CFG->statsfirstrun) {
431 case 'all':
432 return $function(get_field_sql('SELECT time FROM '.$CFG->prefix.'log ORDER BY time LIMIT 1'));
433 break;
434 case 'none':
435 return $function(strtotime('-1 day',time()));
436 break;
437 default:
438 if (is_numeric($CFG->statsfirstrun)) {
439 return $function(time() - $CFG->statsfirstrun);
441 return false;
442 break;
446 function stats_get_base_daily($time=0) {
447 if (empty($time)) {
448 $time = time();
450 return stats_getmidnight($time);
453 function stats_get_base_weekly($time=0) {
454 if (empty($time)) {
455 $time = time();
457 // if we're currently a monday, last monday will take us back a week
458 $str = 'last monday';
459 if (date('D',$time) == 'Mon')
460 $str = 'now';
462 return stats_getmidnight(strtotime($str,$time));
465 function stats_get_base_monthly($time=0) {
466 if (empty($time)) {
467 $time = time();
469 return stats_getmidnight(strtotime(date('1-M-Y',$time)));
472 function stats_get_next_monthend($lastmonth) {
473 return stats_getmidnight(strtotime(date('1-M-Y',$lastmonth).' +1 month'));
476 function stats_get_next_weekend($lastweek) {
477 return stats_getmidnight(strtotime('+1 week',$lastweek));
480 function stats_get_next_dayend($lastday) {
481 return stats_getmidnight(strtotime('+1 day',$lastday));
484 function stats_clean_old() {
485 mtrace("Running stats cleanup tasks... ");
486 // delete dailies older than 2 months (to be safe)
487 $deletebefore = stats_get_next_monthend(strtotime('-2 months',time()));
488 delete_records_select('stats_daily',"timeend < $deletebefore");
489 delete_records_select('stats_user_daily',"timeend < $deletebefore");
491 // delete weeklies older than 8 months (to be safe)
492 $deletebefore = stats_get_next_monthend(strtotime('-8 months',time()));
493 delete_records_select('stats_weekly',"timeend < $deletebefore");
494 delete_records_select('stats_user_weekly',"timeend < $deletebefore");
496 // don't delete monthlies
499 function stats_get_parameters($time,$report,$courseid,$mode) {
500 global $CFG;
501 if ($time < 10) { // dailies
502 // number of days to go back = 7* time
503 $param->table = 'daily';
504 $param->timeafter = strtotime("-".($time*7)." days",stats_get_base_daily());
505 } elseif ($time < 20) { // weeklies
506 // number of weeks to go back = time - 10 * 4 (weeks) + base week
507 $param->table = 'weekly';
508 $param->timeafter = strtotime("-".(($time - 10)*4)." weeks",stats_get_base_weekly());
509 } else { // monthlies.
510 // number of months to go back = time - 20 * months + base month
511 $param->table = 'monthly';
512 $param->timeafter = strtotime("-".($time - 20)." months",stats_get_base_monthly());
515 $param->extras = '';
517 // compatibility - if we're in postgres, cast to real for some reports.
518 $real = '';
519 if ($CFG->dbtype == 'postgres7') {
520 $real = '::real';
523 switch ($report) {
524 case STATS_REPORT_LOGINS:
525 $param->fields = 'logins as line1,uniquelogins as line2';
526 $param->line1 = get_string('statslogins');
527 $param->line2 = get_string('statsuniquelogins');
528 break;
529 case STATS_REPORT_READS:
530 $param->fields = 'studentreads as line1,teacherreads as line2';
531 $param->line1 = get_string('statsstudentreads');
532 $param->line2 = get_string('statsteacherreads');
533 break;
534 case STATS_REPORT_WRITES:
535 $param->fields = 'studentwrites as line1,teacherwrites as line2';
536 $param->line1 = get_string('statsstudentwrites');
537 $param->line2 = get_string('statsteacherwrites');
538 break;
539 case STATS_REPORT_ACTIVITY:
540 $param->fields = 'studentreads+studentwrites as line1, teacherreads+teacherwrites as line2';
541 $param->line1 = get_string('statsstudentactivity');
542 $param->line2 = get_string('statsteacheractivity');
543 break;
544 case STATS_REPORT_STUDENTACTIVITY:
545 $param->fields = 'studentreads as line1,studentwrites as line2';
546 $param->line1 = get_string('statsstudentreads');
547 $param->line2 = get_string('statsstudentwrites');
548 break;
549 case STATS_REPORT_TEACHERACTIVITY:
550 $param->fields = 'teacherreads as line1,teacherwrites as line2';
551 $param->line1 = get_string('statsteacherreads');
552 $param->line2 = get_string('statsteacherwrites');
553 break;
554 case STATS_REPORT_USER_ACTIVITY:
555 $param->fields = 'statsreads as line1, statswrites as line2';
556 $param->line1 = get_string('statsuserreads');
557 $param->line2 = get_string('statsuserwrites');
558 $param->stattype = 'activity';
559 break;
560 case STATS_REPORT_USER_ALLACTIVITY:
561 $param->fields = 'statsreads+statswrites as line1';
562 $param->line1 = get_string('statsuseractivity');
563 $param->stattype = 'activity';
564 break;
565 case STATS_REPORT_USER_LOGINS:
566 $param->fields = 'statsreads as line1';
567 $param->line1 = get_string('statsuserlogins');
568 $param->stattype = 'logins';
569 break;
570 case STATS_REPORT_USER_VIEW:
571 $param->fields = 'statsreads as line1, statswrites as line2, statsreads+statswrites as line3';
572 $param->line1 = get_string('statsuserreads');
573 $param->line2 = get_string('statsuserwrites');
574 $param->line3 = get_string('statsuseractivity');
575 $param->stattype = 'activity';
576 break;
577 case STATS_REPORT_ACTIVE_COURSES:
578 $param->fields = 'sum(studentreads+studentwrites+teacherreads+teacherwrites) AS line1';
579 $param->orderby = 'line1 DESC';
580 $param->line1 = get_string('activity');
581 $param->graphline = 'line1';
582 break;
583 case STATS_REPORT_ACTIVE_COURSES_WEIGHTED:
584 $param->fields = 'sum(studentreads+studentwrites+teacherreads+teacherwrites) AS line1,'
585 .'max(students+teachers) AS line2,'
586 .'sum(studentreads+studentwrites+teacherreads+teacherwrites)'.$real.'/max(students+teachers)'.$real.' AS line3';
587 $param->extras = 'HAVING max(students+teachers) != 0';
588 if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
589 $param->extras .= ' AND max(students+teachers) > '.$CFG->statsuserthreshold;
591 $param->orderby = 'line3 DESC';
592 $param->line1 = get_string('activity');
593 $param->line2 = get_string('users');
594 $param->line3 = get_string('activityweighted');
595 $param->graphline = 'line3';
596 break;
597 case STATS_REPORT_PARTICIPATORY_COURSES:
598 $param->fields = 'max(students+teachers) as line1,max(activestudents+activeteachers) AS line2,'
599 .'max(activestudents+activeteachers)'.$real.'/max(students+teachers)'.$real.' AS line3';
600 $param->extras = 'HAVING max(students+teachers) != 0';
601 if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
602 $param->extras .= ' AND max(students+teachers) > '.$CFG->statsuserthreshold;
604 $param->orderby = 'line3 DESC';
605 $param->line1 = get_string('users');
606 $param->line2 = get_string('activeusers');
607 $param->line3 = get_string('participationratio');
608 $param->graphline = 'line3';
609 break;
610 case STATS_REPORT_PARTICIPATORY_COURSES_RW:
611 $param->fields = 'sum(studentreads+teacherreads) as line1,sum(studentwrites+teacherwrites) AS line2,'
612 .'sum(studentwrites+teacherwrites)'.$real.'/sum(studentreads+teacherreads)'.$real.' AS line3';
613 $param->extras = 'HAVING sum(studentreads+teacherreads) != 0';
614 $param->orderby = 'line3 DESC';
615 $param->line1 = get_string('views');
616 $param->line2 = get_string('posts');
617 $param->line3 = get_string('participationratio');
618 $param->graphline = 'line3';
619 break;
622 if ($courseid == SITEID && $mode != STATS_MODE_RANKED) { // just aggregate all courses.
623 $param->fields = preg_replace('/([a-zA-Z0-9+_]*)\W+as\W+([a-zA-Z0-9_]*)/','sum($1) as $2',$param->fields);
624 $param->extras = ' GROUP BY timeend';
627 return $param;
630 function stats_get_view_actions() {
631 return array('view','view all','history');
634 function stats_get_post_actions() {
635 return array('add','delete','edit','add mod','delete mod','edit section'.'enrol','loginas','new','unenrol','update','update mod');
638 function stats_get_action_sql_in($str) {
639 global $CFG;
641 $mods = get_records('modules');
642 $function = 'stats_get_'.$str.'_actions';
643 $actions = $function();
644 foreach ($mods as $mod) {
645 $file = $CFG->dirroot .'/mod/'.$mod->name.'/lib.php';
646 if (!is_readable($file)) {
647 continue;
649 require_once($file);
650 $function = $mod->name.'_get_'.$str.'_actions';
651 if (function_exists($function)) {
652 $actions = array_merge($actions,$function());
655 $actions = array_unique($actions);
656 if (empty($actions)) {
657 return ' ';
658 } else if (count($actions) == 1) {
659 return ' AND l.action = '.array_pop($actions).' ';
660 } else {
661 return ' AND l.action IN (\''.implode('\',\'',$actions).'\') ';
666 function stats_get_course_users($course,$timesql,&$students, &$teachers) {
667 global $CFG;
669 $timesql = str_replace('timeend','l.time',$timesql);
671 $sql = 'SELECT DISTINCT(l.userid) as userid,1 as roleid FROM '.$CFG->prefix.'log l JOIN '.$CFG->prefix
672 .'user_students us ON us.userid = l.userid WHERE l.course = '.$course->id.' AND us.course = '.$course->id.' AND '.$timesql;
673 if (!$students = get_records_sql($sql)) {
674 $students = array(); // avoid warnings;
677 $sql = str_replace('students','teachers',$sql);
678 $sql = str_replace(',1',',2',$sql);
680 if (!$teachers = get_records_sql($sql)) {
681 $teachers = array(); // avoid warnings
686 function stats_do_daily_user_cron($course,$user,$roleid,$timesql,$timeend,$mods) {
688 global $CFG;
690 $stat = new StdClass;
691 $stat->userid = $user->userid;
692 $stat->roleid = $roleid;
693 $stat->courseid = $course->id;
694 $stat->stattype = 'activity';
695 $stat->timeend = $timeend;
697 $sql = 'SELECT COUNT(l.id) FROM '.$CFG->prefix.'log l WHERE l.userid = '.$user->userid
698 .' AND l.course = '.$course->id
699 .' AND '.$timesql .' '.stats_get_action_sql_in('view');
701 $stat->statsreads = count_records_sql($sql);
703 $sql = 'SELECT COUNT(l.id) FROM '.$CFG->prefix.'log l WHERE l.userid = '.$user->userid
704 .' AND l.course = '.$course->id
705 .' AND '.$timesql.' '.stats_get_action_sql_in('post');
707 $stat->statswrites = count_records_sql($sql);
709 insert_record('stats_user_daily',$stat,false);
711 // now ask the modules if they want anything.
712 foreach ($mods as $mod => $fname) {
713 mtrace(' doing daily statistics for '.$mod->name);
714 $fname($course,$user,$timeend,$roleid);
718 function stats_do_aggregate_user_cron($course,$user,$roleid,$timesql,$timeend,$timestr,$mods) {
720 global $CFG;
722 $stat = new StdClass;
723 $stat->userid = $user->userid;
724 $stat->roleid = $roleid;
725 $stat->courseid = $course->id;
726 $stat->stattype = 'activity';
727 $stat->timeend = $timeend;
729 $sql = 'SELECT sum(statsreads) as statsreads, sum(statswrites) as statswrites FROM '.$CFG->prefix.'stats_user_daily WHERE courseid = '.$course->id.' AND '.$timesql
730 ." AND roleid=".$roleid." AND userid = ".$stat->userid." AND stattype='activity'"; // add on roleid in case they have teacher and student records.
732 $r = get_record_sql($sql);
733 $stat->statsreads = (empty($r->statsreads)) ? 0 : $r->statsreads;
734 $stat->statswrites = (empty($r->statswrites)) ? 0 : $r->statswrites;
736 insert_record('stats_user_'.$timestr,$stat,false);
738 // now ask the modules if they want anything.
739 foreach ($mods as $mod => $fname) {
740 mtrace(' doing '.$timestr.' statistics for '.$mod->name);
741 $fname($course,$user,$timeend,$roleid);
745 function stats_do_aggregate_user_login_cron($timesql,$timeend,$timestr) {
746 global $CFG;
748 $sql = 'SELECT userid,roleid,sum(statsreads) as statsreads, sum(statswrites) as writes FROM '.$CFG->prefix.'stats_user_daily WHERE stattype = \'logins\' AND '.$timesql.' GROUP BY userid,roleid';
750 if ($users = get_records_sql($sql)) {
751 foreach ($users as $stat) {
752 $stat->courseid = SITEID;
753 $stat->timeend = $timeend;
754 $stat->stattype = 'logins';
756 insert_record('stats_user_'.$timestr,$stat,false);
762 function stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth) {
764 $now = stats_get_base_daily(time());
765 // it's really important that it's TIMEEND in the table. ie, tuesday 00:00:00 is monday night.
766 // so we need to take a day off here (essentially add a day to $now
767 $now += 60*60*24;
769 $timeoptions = array();
771 if ($now - (60*60*24*7) >= $earliestday) {
772 $timeoptions[STATS_TIME_LASTWEEK] = get_string('numweeks','moodle',1);
774 if ($now - (60*60*24*14) >= $earliestday) {
775 $timeoptions[STATS_TIME_LAST2WEEKS] = get_string('numweeks','moodle',2);
777 if ($now - (60*60*24*21) >= $earliestday) {
778 $timeoptions[STATS_TIME_LAST3WEEKS] = get_string('numweeks','moodle',3);
780 if ($now - (60*60*24*28) >= $earliestday) {
781 $timeoptions[STATS_TIME_LAST4WEEKS] = get_string('numweeks','moodle',4);// show dailies up to (including) here.
783 if ($lastweekend - (60*60*24*56) >= $earliestweek) {
784 $timeoptions[STATS_TIME_LAST2MONTHS] = get_string('nummonths','moodle',2);
786 if ($lastweekend - (60*60*24*84) >= $earliestweek) {
787 $timeoptions[STATS_TIME_LAST3MONTHS] = get_string('nummonths','moodle',3);
789 if ($lastweekend - (60*60*24*112) >= $earliestweek) {
790 $timeoptions[STATS_TIME_LAST4MONTHS] = get_string('nummonths','moodle',4);
792 if ($lastweekend - (60*60*24*140) >= $earliestweek) {
793 $timeoptions[STATS_TIME_LAST5MONTHS] = get_string('nummonths','moodle',5);
795 if ($lastweekend - (60*60*24*168) >= $earliestweek) {
796 $timeoptions[STATS_TIME_LAST6MONTHS] = get_string('nummonths','moodle',6); // show weeklies up to (including) here
798 if (strtotime('-7 months',$lastmonthend) >= $earliestmonth) {
799 $timeoptions[STATS_TIME_LAST7MONTHS] = get_string('nummonths','moodle',7);
801 if (strtotime('-8 months',$lastmonthend) >= $earliestmonth) {
802 $timeoptions[STATS_TIME_LAST8MONTHS] = get_string('nummonths','moodle',8);
804 if (strtotime('-9 months',$lastmonthend) >= $earliestmonth) {
805 $timeoptions[STATS_TIME_LAST9MONTHS] = get_string('nummonths','moodle',9);
807 if (strtotime('-10 months',$lastmonthend) >= $earliestmonth) {
808 $timeoptions[STATS_TIME_LAST10MONTHS] = get_string('nummonths','moodle',10);
810 if (strtotime('-11 months',$lastmonthend) >= $earliestmonth) {
811 $timeoptions[STATS_TIME_LAST11MONTHS] = get_string('nummonths','moodle',11);
813 if (strtotime('-1 year',$lastmonthend) >= $earliestmonth) {
814 $timeoptions[STATS_TIME_LASTYEAR] = get_string('lastyear');
817 return $timeoptions;
820 function stats_get_report_options($courseid,$mode) {
822 $reportoptions = array();
824 switch ($mode) {
825 case STATS_MODE_GENERAL:
826 $reportoptions[STATS_REPORT_ACTIVITY] = get_string('statsreport'.STATS_REPORT_ACTIVITY);
827 $reportoptions[STATS_REPORT_STUDENTACTIVITY] = get_string('statsreport'.STATS_REPORT_STUDENTACTIVITY);
828 $reportoptions[STATS_REPORT_TEACHERACTIVITY] = get_string('statsreport'.STATS_REPORT_TEACHERACTIVITY);
829 $reportoptions[STATS_REPORT_READS] = get_string('statsreport'.STATS_REPORT_READS);
830 $reportoptions[STATS_REPORT_WRITES] = get_string('statsreport'.STATS_REPORT_WRITES);
831 if ($courseid == SITEID) {
832 $reportoptions[STATS_REPORT_LOGINS] = get_string('statsreport'.STATS_REPORT_LOGINS);
835 break;
836 case STATS_MODE_DETAILED:
837 $reportoptions[STATS_REPORT_USER_ACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ACTIVITY);
838 $reportoptions[STATS_REPORT_USER_ALLACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ALLACTIVITY);
839 if (isadmin()) {
840 $site = get_site();
841 $reportoptions[STATS_REPORT_USER_LOGINS] = get_string('statsreport'.STATS_REPORT_USER_LOGINS);
843 break;
844 case STATS_MODE_RANKED:
845 if (isadmin()) {
846 $reportoptions[STATS_REPORT_ACTIVE_COURSES] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES);
847 $reportoptions[STATS_REPORT_ACTIVE_COURSES_WEIGHTED] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES_WEIGHTED);
848 $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES);
849 $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES_RW] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES_RW);
851 break;
854 return $reportoptions;
857 function stats_fix_zeros($stats,$timeafter,$timestr,$line2=true,$line3=false) {
859 if (empty($stats)) {
860 return;
863 $timestr = str_replace('user_','',$timestr); // just in case.
864 $fun = 'stats_get_base_'.$timestr;
866 $now = $fun();
868 $times = array();
869 // add something to timeafter since it is our absolute base
870 $actualtimes = array_keys($stats);
871 $timeafter = array_pop($actualtimes);
873 while ($timeafter < $now) {
874 $times[] = $timeafter;
875 if ($timestr == 'daily') {
876 $timeafter = stats_get_next_dayend($timeafter);
877 } else if ($timestr == 'weekly') {
878 $timeafter = stats_get_next_weekend($timeafter);
879 } else if ($timestr == 'monthly') {
880 $timeafter = stats_get_next_monthend($timeafter);
881 } else {
882 return $stats; // this will put us in a never ending loop.
886 foreach ($times as $time) {
887 if (!array_key_exists($time,$stats)) {
888 $newobj = new StdClass;
889 $newobj->timeend = $time;
890 $newobj->id = 0;
891 $newobj->line1 = 0;
892 if (!empty($line2)) {
893 $newobj->line2 = 0;
895 if (!empty($line3)) {
896 $newobj->line3 = 0;
898 $stats[$time] = $newobj;
902 krsort($stats);
903 return $stats;
907 function stats_check_runtime() {
908 global $CFG;
910 if (empty($CFG->statsmaxruntime)) {
911 return true;
914 if ((time() - $CFG->statsrunning) < $CFG->statsmaxruntime) {
915 return true;
918 return false; // we've gone over!
922 function stats_check_uptodate($courseid=0) {
923 global $CFG;
925 if (empty($courseid)) {
926 $courseid = SITEID;
929 $latestday = stats_get_start_from('daily');
931 if ((time() - 60*60*24*2) < $latestday) { // we're ok
932 return NULL;
935 $a = new object();
936 $a->daysdone = get_field_sql("SELECT count(distinct(timeend)) from {$CFG->prefix}stats_daily");
938 // how many days between the last day and now?
939 $a->dayspending = ceil((stats_get_base_daily() - $latestday)/(60*60*24));
941 if ($a->dayspending == 0 && $a->daysdone != 0) {
942 return NULL; // we've only just started...
945 //return error as string
946 return get_string('statscatchupmode','error',$a);
950 // copied from usergetmidnight, but we ignore dst
951 function stats_getmidnight($date, $timezone=99) {
952 $timezone = get_user_timezone_offset($timezone);
953 $userdate = stats_getdate($date, $timezone);
954 return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone,false ); // ignore dst for this.
957 function stats_getdate($time, $timezone=99) {
959 $timezone = get_user_timezone_offset($timezone);
961 if (abs($timezone) > 13) { // Server time
962 return getdate($time);
965 // There is no gmgetdate so we use gmdate instead
966 $time += intval((float)$timezone * HOURSECS);
967 $datestring = strftime('%S_%M_%H_%d_%m_%Y_%w_%j_%A_%B', $time);
968 list(
969 $getdate['seconds'],
970 $getdate['minutes'],
971 $getdate['hours'],
972 $getdate['mday'],
973 $getdate['mon'],
974 $getdate['year'],
975 $getdate['wday'],
976 $getdate['yday'],
977 $getdate['weekday'],
978 $getdate['month']
979 ) = explode('_', $datestring);
981 return $getdate;