Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1759 lines (1438 sloc) 64.552 kb
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package core
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/** THESE CONSTANTS ARE USED FOR THE REPORTING PAGE. */
define('STATS_REPORT_LOGINS',1); // double impose logins and unique logins on a line graph. site course only.
define('STATS_REPORT_READS',2); // double impose student reads and teacher reads on a line graph.
define('STATS_REPORT_WRITES',3); // double impose student writes and teacher writes on a line graph.
define('STATS_REPORT_ACTIVITY',4); // 2+3 added up, teacher vs student.
define('STATS_REPORT_ACTIVITYBYROLE',5); // all activity, reads vs writes, selected by role.
// user level stats reports.
define('STATS_REPORT_USER_ACTIVITY',7);
define('STATS_REPORT_USER_ALLACTIVITY',8);
define('STATS_REPORT_USER_LOGINS',9);
define('STATS_REPORT_USER_VIEW',10); // this is the report you see on the user profile.
// admin only ranking stats reports
define('STATS_REPORT_ACTIVE_COURSES',11);
define('STATS_REPORT_ACTIVE_COURSES_WEIGHTED',12);
define('STATS_REPORT_PARTICIPATORY_COURSES',13);
define('STATS_REPORT_PARTICIPATORY_COURSES_RW',14);
// start after 0 = show dailies.
define('STATS_TIME_LASTWEEK',1);
define('STATS_TIME_LAST2WEEKS',2);
define('STATS_TIME_LAST3WEEKS',3);
define('STATS_TIME_LAST4WEEKS',4);
// start after 10 = show weeklies
define('STATS_TIME_LAST2MONTHS',12);
define('STATS_TIME_LAST3MONTHS',13);
define('STATS_TIME_LAST4MONTHS',14);
define('STATS_TIME_LAST5MONTHS',15);
define('STATS_TIME_LAST6MONTHS',16);
// start after 20 = show monthlies
define('STATS_TIME_LAST7MONTHS',27);
define('STATS_TIME_LAST8MONTHS',28);
define('STATS_TIME_LAST9MONTHS',29);
define('STATS_TIME_LAST10MONTHS',30);
define('STATS_TIME_LAST11MONTHS',31);
define('STATS_TIME_LASTYEAR',32);
// different modes for what reports to offer
define('STATS_MODE_GENERAL',1);
define('STATS_MODE_DETAILED',2);
define('STATS_MODE_RANKED',3); // admins only - ranks courses
// Output string when nodebug is on
define('STATS_PLACEHOLDER_OUTPUT', '.');
/**
* Print daily cron progress
* @param string $ident
*/
function stats_progress($ident) {
static $start = 0;
static $init = 0;
if ($ident == 'init') {
$init = $start = microtime(true);
return;
}
$elapsed = round(microtime(true) - $start);
$start = microtime(true);
if (debugging('', DEBUG_ALL)) {
mtrace("$ident:$elapsed ", '');
} else {
mtrace(STATS_PLACEHOLDER_OUTPUT, '');
}
}
/**
* Execute individual daily statistics queries
*
* @param string $sql The query to run
* @return boolean success
*/
function stats_run_query($sql, $parameters = array()) {
global $DB;
try {
$DB->execute($sql, $parameters);
} catch (dml_exception $e) {
if (debugging('', DEBUG_ALL)) {
mtrace($e->getMessage());
}
return false;
}
return true;
}
/**
* Execute daily statistics gathering
*
* @param int $maxdays maximum number of days to be processed
* @return boolean success
*/
function stats_cron_daily($maxdays=1) {
global $CFG, $DB;
$now = time();
$fpcontext = context_course::instance(SITEID, MUST_EXIST);
// read last execution date from db
if (!$timestart = get_config(NULL, 'statslastdaily')) {
$timestart = stats_get_base_daily(stats_get_start_from('daily'));
set_config('statslastdaily', $timestart);
}
// calculate scheduled time
$scheduledtime = stats_get_base_daily() + $CFG->statsruntimestarthour*60*60 + $CFG->statsruntimestartminute*60;
// Note: This will work fine for sites running cron each 4 hours or less (hopefully, 99.99% of sites). MDL-16709
// check to make sure we're due to run, at least 20 hours after last run
if (isset($CFG->statslastexecution) && ((time() - 20*60*60) < $CFG->statslastexecution)) {
mtrace("...preventing stats to run, last execution was less than 20 hours ago.");
return false;
// also check that we are a max of 4 hours after scheduled time, stats won't run after that
} else if (time() > $scheduledtime + 4*60*60) {
mtrace("...preventing stats to run, more than 4 hours since scheduled time.");
return false;
} else {
set_config('statslastexecution', time()); /// Grab this execution as last one
}
$nextmidnight = stats_get_next_day_start($timestart);
// are there any days that need to be processed?
if ($now < $nextmidnight) {
return true; // everything ok and up-to-date
}
$timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
if (!set_cron_lock('statsrunning', $now + $timeout)) {
return false;
}
// first delete entries that should not be there yet
$DB->delete_records_select('stats_daily', "timeend > $timestart");
$DB->delete_records_select('stats_user_daily', "timeend > $timestart");
// Read in a few things we'll use later
$viewactions = stats_get_action_names('view');
$postactions = stats_get_action_names('post');
$guest = (int)$CFG->siteguest;
$guestrole = (int)$CFG->guestroleid;
$defaultfproleid = (int)$CFG->defaultfrontpageroleid;
mtrace("Running daily statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
$days = 0;
$total = 0;
$failed = false; // failed stats flag
$timeout = false;
if (!stats_temp_table_create()) {
$days = 1;
$failed = true;
}
mtrace('Temporary tables created');
if(!stats_temp_table_setup()) {
$days = 1;
$failed = true;
}
mtrace('Enrolments calculated');
$totalactiveusers = $DB->count_records('user', array('deleted' => '0'));
while (!$failed && ($now > $nextmidnight)) {
if ($days >= $maxdays) {
$timeout = true;
break;
}
$days++;
@set_time_limit($timeout - 200);
if ($days > 1) {
// move the lock
set_cron_lock('statsrunning', time() + $timeout, true);
}
$daystart = time();
stats_progress('init');
if (!stats_temp_table_fill($timestart, $nextmidnight)) {
$failed = true;
break;
}
// Find out if any logs available for this day
$sql = "SELECT 'x' FROM {temp_log1} l";
$logspresent = $DB->get_records_sql($sql, null, 0, 1);
if ($logspresent) {
// Insert blank record to force Query 10 to generate additional row when no logs for
// the site with userid 0 exist. Added for backwards compatibility.
$DB->insert_record('temp_log1', array('userid' => 0, 'course' => SITEID, 'action' => ''));
}
// Calculate the number of active users today
$sql = 'SELECT COUNT(DISTINCT u.id)
FROM {user} u
JOIN {temp_log1} l ON l.userid = u.id
WHERE u.deleted = 0';
$dailyactiveusers = $DB->count_records_sql($sql);
stats_progress('0');
// Process login info first
// Note: PostgreSQL doesn't like aliases in HAVING clauses
$sql = "INSERT INTO {temp_stats_user_daily}
(stattype, timeend, courseid, userid, statsreads)
SELECT 'logins', $nextmidnight AS timeend, ".SITEID." AS courseid,
userid, COUNT(id) AS statsreads
FROM {temp_log1} l
WHERE action = 'login'
GROUP BY userid
HAVING COUNT(id) > 0";
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('1');
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID." AS courseid, 0,
COALESCE(SUM(statsreads), 0) as stat1, COUNT('x') as stat2
FROM {temp_stats_user_daily}
WHERE stattype = 'logins' AND timeend = $nextmidnight";
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('2');
// Enrolments and active enrolled users
//
// Unfortunately, we do not know how many users were registered
// at given times in history :-(
// - stat1: enrolled users
// - stat2: enrolled users active in this period
// - SITEID is special case here, because it's all about default enrolment
// in that case, we'll count non-deleted users.
//
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments' as stattype, $nextmidnight as timeend, courseid, roleid,
COUNT(DISTINCT userid) as stat1, 0 as stat2
FROM {temp_enroled}
GROUP BY courseid, roleid";
if (!stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('3');
// Set stat2 to the number distinct users with role assignments in the course that were active
// using table alias in UPDATE does not work in pg < 8.2
$sql = "UPDATE {temp_stats_daily}
SET stat2 = (
SELECT COUNT(DISTINCT userid)
FROM {temp_enroled} te
WHERE roleid = {temp_stats_daily}.roleid
AND courseid = {temp_stats_daily}.courseid
AND EXISTS (
SELECT 'x'
FROM {temp_log1} l
WHERE l.course = {temp_stats_daily}.courseid
AND l.userid = te.userid
)
)
WHERE {temp_stats_daily}.stattype = 'enrolments'
AND {temp_stats_daily}.timeend = $nextmidnight
AND {temp_stats_daily}.courseid IN (
SELECT DISTINCT course FROM {temp_log2})";
if ($logspresent && !stats_run_query($sql, array('courselevel'=>CONTEXT_COURSE))) {
$failed = true;
break;
}
stats_progress('4');
// Now get course total enrolments (roleid==0) - except frontpage
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments', $nextmidnight AS timeend, te.courseid AS courseid, 0 AS roleid,
COUNT(DISTINCT userid) AS stat1, 0 AS stat2
FROM {temp_enroled} te
GROUP BY courseid
HAVING COUNT(DISTINCT userid) > 0";
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('5');
// Set stat 2 to the number of enrolled users who were active in the course
$sql = "UPDATE {temp_stats_daily}
SET stat2 = (
SELECT COUNT(DISTINCT te.userid)
FROM {temp_enroled} te
WHERE te.courseid = {temp_stats_daily}.courseid
AND EXISTS (
SELECT 'x'
FROM {temp_log1} l
WHERE l.course = {temp_stats_daily}.courseid
AND l.userid = te.userid
)
)
WHERE {temp_stats_daily}.stattype = 'enrolments'
AND {temp_stats_daily}.timeend = $nextmidnight
AND {temp_stats_daily}.roleid = 0
AND {temp_stats_daily}.courseid IN (
SELECT l.course
FROM {temp_log2} l
WHERE l.course <> ".SITEID.")";
if ($logspresent && !stats_run_query($sql, array())) {
$failed = true;
break;
}
stats_progress('6');
// Frontpage(==site) enrolments total
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments', $nextmidnight, ".SITEID.", 0, $totalactiveusers AS stat1,
$dailyactiveusers AS stat2" .
$DB->sql_null_from_clause();
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('7');
// Default frontpage role enrolments are all site users (not deleted)
if ($defaultfproleid) {
// first remove default frontpage role counts if created by previous query
$sql = "DELETE
FROM {temp_stats_daily}
WHERE stattype = 'enrolments'
AND courseid = ".SITEID."
AND roleid = $defaultfproleid
AND timeend = $nextmidnight";
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('8');
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments', $nextmidnight, ".SITEID.", $defaultfproleid,
$totalactiveusers AS stat1, $dailyactiveusers AS stat2" .
$DB->sql_null_from_clause();
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('9');
} else {
stats_progress('x');
stats_progress('x');
}
/// individual user stats (including not-logged-in) in each course, this is slow - reuse this data if possible
list($viewactionssql, $params1) = $DB->get_in_or_equal($viewactions, SQL_PARAMS_NAMED, 'view');
list($postactionssql, $params2) = $DB->get_in_or_equal($postactions, SQL_PARAMS_NAMED, 'post');
$sql = "INSERT INTO {temp_stats_user_daily} (stattype, timeend, courseid, userid, statsreads, statswrites)
SELECT 'activity' AS stattype, $nextmidnight AS timeend, course AS courseid, userid,
SUM(CASE WHEN action $viewactionssql THEN 1 ELSE 0 END) AS statsreads,
SUM(CASE WHEN action $postactionssql THEN 1 ELSE 0 END) AS statswrites
FROM {temp_log1} l
GROUP BY userid, course";
if ($logspresent && !stats_run_query($sql, array_merge($params1, $params2))) {
$failed = true;
break;
}
stats_progress('10');
/// How many view/post actions in each course total
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity' AS stattype, $nextmidnight AS timeend, c.id AS courseid, 0,
SUM(CASE WHEN l.action $viewactionssql THEN 1 ELSE 0 END) AS stat1,
SUM(CASE WHEN l.action $postactionssql THEN 1 ELSE 0 END) AS stat2
FROM {course} c, {temp_log1} l
WHERE l.course = c.id
GROUP BY c.id";
if ($logspresent && !stats_run_query($sql, array_merge($params1, $params2))) {
$failed = true;
break;
}
stats_progress('11');
/// how many view actions for each course+role - excluding guests and frontpage
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', $nextmidnight AS timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
FROM (
SELECT pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
FROM {temp_stats_user_daily} sud, (
SELECT DISTINCT te.userid, te.roleid, te.courseid
FROM {temp_enroled} te
WHERE te.roleid <> $guestrole
AND te.userid <> $guest
) pl
WHERE sud.userid = pl.userid
AND sud.courseid = pl.courseid
AND sud.timeend = $nextmidnight
AND sud.stattype='activity'
) inline_view
GROUP BY courseid, roleid
HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
if ($logspresent && !stats_run_query($sql, array('courselevel'=>CONTEXT_COURSE))) {
$failed = true;
break;
}
stats_progress('12');
/// how many view actions from guests only in each course - excluding frontpage
/// normal users may enter course with temporary guest access too
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', $nextmidnight AS timeend, courseid, $guestrole AS roleid,
SUM(statsreads), SUM(statswrites)
FROM (
SELECT sud.courseid, sud.statsreads, sud.statswrites
FROM {temp_stats_user_daily} sud
WHERE sud.timeend = $nextmidnight
AND sud.courseid <> ".SITEID."
AND sud.stattype='activity'
AND (sud.userid = $guest OR sud.userid NOT IN (
SELECT userid
FROM {temp_enroled} te
WHERE te.courseid = sud.courseid
))
) inline_view
GROUP BY courseid
HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
if ($logspresent && !stats_run_query($sql, array())) {
$failed = true;
break;
}
stats_progress('13');
/// How many view actions for each role on frontpage - excluding guests, not-logged-in and default frontpage role
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', $nextmidnight AS timeend, courseid, roleid,
SUM(statsreads), SUM(statswrites)
FROM (
SELECT pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
FROM {temp_stats_user_daily} sud, (
SELECT DISTINCT ra.userid, ra.roleid, c.instanceid AS courseid
FROM {role_assignments} ra
JOIN {context} c ON c.id = ra.contextid
WHERE ra.contextid = :fpcontext
AND ra.roleid <> $defaultfproleid
AND ra.roleid <> $guestrole
AND ra.userid <> $guest
) pl
WHERE sud.userid = pl.userid
AND sud.courseid = pl.courseid
AND sud.timeend = $nextmidnight
AND sud.stattype='activity'
) inline_view
GROUP BY courseid, roleid
HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
if ($logspresent && !stats_run_query($sql, array('fpcontext'=>$fpcontext->id))) {
$failed = true;
break;
}
stats_progress('14');
// How many view actions for default frontpage role on frontpage only
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', timeend, courseid, $defaultfproleid AS roleid,
SUM(statsreads), SUM(statswrites)
FROM (
SELECT sud.timeend AS timeend, sud.courseid, sud.statsreads, sud.statswrites
FROM {temp_stats_user_daily} sud
WHERE sud.timeend = :nextm
AND sud.courseid = :siteid
AND sud.stattype='activity'
AND sud.userid <> $guest
AND sud.userid <> 0
AND sud.userid NOT IN (
SELECT ra.userid
FROM {role_assignments} ra
WHERE ra.roleid <> $guestrole
AND ra.roleid <> $defaultfproleid
AND ra.contextid = :fpcontext)
) inline_view
GROUP BY timeend, courseid
HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
if ($logspresent && !stats_run_query($sql, array('fpcontext'=>$fpcontext->id, 'siteid'=>SITEID, 'nextm'=>$nextmidnight))) {
$failed = true;
break;
}
stats_progress('15');
// How many view actions for guests or not-logged-in on frontpage
$sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT stattype, timeend, courseid, $guestrole AS roleid,
SUM(statsreads) AS stat1, SUM(statswrites) AS stat2
FROM (
SELECT sud.stattype, sud.timeend, sud.courseid,
sud.statsreads, sud.statswrites
FROM {temp_stats_user_daily} sud
WHERE (sud.userid = $guest OR sud.userid = 0)
AND sud.timeend = $nextmidnight
AND sud.courseid = ".SITEID."
AND sud.stattype='activity'
) inline_view
GROUP BY stattype, timeend, courseid
HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
if ($logspresent && !stats_run_query($sql)) {
$failed = true;
break;
}
stats_progress('16');
stats_temp_table_clean();
stats_progress('out');
// remember processed days
set_config('statslastdaily', $nextmidnight);
$elapsed = time()-$daystart;
mtrace(" finished until $nextmidnight: ".userdate($nextmidnight)." (in $elapsed s)");
$total += $elapsed;
$timestart = $nextmidnight;
$nextmidnight = stats_get_next_day_start($nextmidnight);
}
stats_temp_table_drop();
set_cron_lock('statsrunning', null);
if ($failed) {
$days--;
mtrace("...error occurred, completed $days days of statistics in {$total} s.");
return false;
} else if ($timeout) {
mtrace("...stopping early, reached maximum number of $maxdays days ({$total} s) - will continue next time.");
return false;
} else {
mtrace("...completed $days days of statistics in {$total} s.");
return true;
}
}
/**
* Execute weekly statistics gathering
* @return boolean success
*/
function stats_cron_weekly() {
global $CFG, $DB;
$now = time();
// read last execution date from db
if (!$timestart = get_config(NULL, 'statslastweekly')) {
$timestart = stats_get_base_daily(stats_get_start_from('weekly'));
set_config('statslastweekly', $timestart);
}
$nextstartweek = stats_get_next_week_start($timestart);
// are there any weeks that need to be processed?
if ($now < $nextstartweek) {
return true; // everything ok and up-to-date
}
$timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
if (!set_cron_lock('statsrunning', $now + $timeout)) {
return false;
}
// fisrt delete entries that should not be there yet
$DB->delete_records_select('stats_weekly', "timeend > $timestart");
$DB->delete_records_select('stats_user_weekly', "timeend > $timestart");
mtrace("Running weekly statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
$weeks = 0;
while ($now > $nextstartweek) {
@set_time_limit($timeout - 200);
$weeks++;
if ($weeks > 1) {
// move the lock
set_cron_lock('statsrunning', time() + $timeout, true);
}
$logtimesql = "l.time >= $timestart AND l.time < $nextstartweek";
$stattimesql = "timeend > $timestart AND timeend <= $nextstartweek";
$weekstart = time();
stats_progress('init');
/// process login info first
$sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads)
SELECT 'logins', timeend, courseid, userid, COUNT(statsreads)
FROM (
SELECT $nextstartweek AS timeend, ".SITEID." as courseid, l.userid, l.id AS statsreads
FROM {log} l
WHERE action = 'login' AND $logtimesql
) inline_view
GROUP BY timeend, courseid, userid
HAVING COUNT(statsreads) > 0";
$DB->execute($sql);
stats_progress('1');
$sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'logins' AS stattype, $nextstartweek AS timeend, ".SITEID." as courseid, 0,
COALESCE((SELECT SUM(statsreads)
FROM {stats_user_weekly} s1
WHERE s1.stattype = 'logins' AND timeend = $nextstartweek), 0) AS nstat1,
(SELECT COUNT('x')
FROM {stats_user_weekly} s2
WHERE s2.stattype = 'logins' AND timeend = $nextstartweek) AS nstat2" .
$DB->sql_null_from_clause();
$DB->execute($sql);
stats_progress('2');
/// now enrolments averages
$sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments', ntimeend, courseid, roleid, " . $DB->sql_ceil('AVG(stat1)') . ", " . $DB->sql_ceil('AVG(stat2)') . "
FROM (
SELECT $nextstartweek AS ntimeend, courseid, roleid, stat1, stat2
FROM {stats_daily} sd
WHERE stattype = 'enrolments' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, roleid";
$DB->execute($sql);
stats_progress('3');
/// activity read/write averages
$sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', ntimeend, courseid, roleid, SUM(stat1), SUM(stat2)
FROM (
SELECT $nextstartweek AS ntimeend, courseid, roleid, stat1, stat2
FROM {stats_daily}
WHERE stattype = 'activity' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, roleid";
$DB->execute($sql);
stats_progress('4');
/// user read/write averages
$sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads, statswrites)
SELECT 'activity', ntimeend, courseid, userid, SUM(statsreads), SUM(statswrites)
FROM (
SELECT $nextstartweek AS ntimeend, courseid, userid, statsreads, statswrites
FROM {stats_user_daily}
WHERE stattype = 'activity' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, userid";
$DB->execute($sql);
stats_progress('5');
set_config('statslastweekly', $nextstartweek);
$elapsed = time()-$weekstart;
mtrace(" finished until $nextstartweek: ".userdate($nextstartweek) ." (in $elapsed s)");
$timestart = $nextstartweek;
$nextstartweek = stats_get_next_week_start($nextstartweek);
}
set_cron_lock('statsrunning', null);
mtrace("...completed $weeks weeks of statistics.");
return true;
}
/**
* Execute monthly statistics gathering
* @return boolean success
*/
function stats_cron_monthly() {
global $CFG, $DB;
$now = time();
// read last execution date from db
if (!$timestart = get_config(NULL, 'statslastmonthly')) {
$timestart = stats_get_base_monthly(stats_get_start_from('monthly'));
set_config('statslastmonthly', $timestart);
}
$nextstartmonth = stats_get_next_month_start($timestart);
// are there any months that need to be processed?
if ($now < $nextstartmonth) {
return true; // everything ok and up-to-date
}
$timeout = empty($CFG->statsmaxruntime) ? 60*60*24 : $CFG->statsmaxruntime;
if (!set_cron_lock('statsrunning', $now + $timeout)) {
return false;
}
// fisr delete entries that should not be there yet
$DB->delete_records_select('stats_monthly', "timeend > $timestart");
$DB->delete_records_select('stats_user_monthly', "timeend > $timestart");
$startmonth = stats_get_base_monthly($now);
mtrace("Running monthly statistics gathering, starting at $timestart:");
cron_trace_time_and_memory();
$months = 0;
while ($now > $nextstartmonth) {
@set_time_limit($timeout - 200);
$months++;
if ($months > 1) {
// move the lock
set_cron_lock('statsrunning', time() + $timeout, true);
}
$logtimesql = "l.time >= $timestart AND l.time < $nextstartmonth";
$stattimesql = "timeend > $timestart AND timeend <= $nextstartmonth";
$monthstart = time();
stats_progress('init');
/// process login info first
$sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads)
SELECT 'logins', timeend, courseid, userid, COUNT(statsreads)
FROM (
SELECT $nextstartmonth AS timeend, ".SITEID." as courseid, l.userid, l.id AS statsreads
FROM {log} l
WHERE action = 'login' AND $logtimesql
) inline_view
GROUP BY timeend, courseid, userid";
$DB->execute($sql);
stats_progress('1');
$sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'logins' AS stattype, $nextstartmonth AS timeend, ".SITEID." as courseid, 0,
COALESCE((SELECT SUM(statsreads)
FROM {stats_user_monthly} s1
WHERE s1.stattype = 'logins' AND timeend = $nextstartmonth), 0) AS nstat1,
(SELECT COUNT('x')
FROM {stats_user_monthly} s2
WHERE s2.stattype = 'logins' AND timeend = $nextstartmonth) AS nstat2" .
$DB->sql_null_from_clause();
$DB->execute($sql);
stats_progress('2');
/// now enrolments averages
$sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'enrolments', ntimeend, courseid, roleid, " . $DB->sql_ceil('AVG(stat1)') . ", " . $DB->sql_ceil('AVG(stat2)') . "
FROM (
SELECT $nextstartmonth AS ntimeend, courseid, roleid, stat1, stat2
FROM {stats_daily} sd
WHERE stattype = 'enrolments' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, roleid";
$DB->execute($sql);
stats_progress('3');
/// activity read/write averages
$sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
SELECT 'activity', ntimeend, courseid, roleid, SUM(stat1), SUM(stat2)
FROM (
SELECT $nextstartmonth AS ntimeend, courseid, roleid, stat1, stat2
FROM {stats_daily}
WHERE stattype = 'activity' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, roleid";
$DB->execute($sql);
stats_progress('4');
/// user read/write averages
$sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads, statswrites)
SELECT 'activity', ntimeend, courseid, userid, SUM(statsreads), SUM(statswrites)
FROM (
SELECT $nextstartmonth AS ntimeend, courseid, userid, statsreads, statswrites
FROM {stats_user_daily}
WHERE stattype = 'activity' AND $stattimesql
) inline_view
GROUP BY ntimeend, courseid, userid";
$DB->execute($sql);
stats_progress('5');
set_config('statslastmonthly', $nextstartmonth);
$elapsed = time() - $monthstart;
mtrace(" finished until $nextstartmonth: ".userdate($nextstartmonth) ." (in $elapsed s)");
$timestart = $nextstartmonth;
$nextstartmonth = stats_get_next_month_start($nextstartmonth);
}
set_cron_lock('statsrunning', null);
mtrace("...completed $months months of statistics.");
return true;
}
/**
* Return starting date of stats processing
* @param string $str name of table - daily, weekly or monthly
* @return int timestamp
*/
function stats_get_start_from($str) {
global $CFG, $DB;
// are there any data in stats table? Should not be...
if ($timeend = $DB->get_field_sql('SELECT MAX(timeend) FROM {stats_'.$str.'}')) {
return $timeend;
}
// decide what to do based on our config setting (either all or none or a timestamp)
switch ($CFG->statsfirstrun) {
case 'all':
if ($firstlog = $DB->get_field_sql('SELECT MIN(time) FROM {log}')) {
return $firstlog;
}
default:
if (is_numeric($CFG->statsfirstrun)) {
return time() - $CFG->statsfirstrun;
}
// not a number? use next instead
case 'none':
return strtotime('-3 day', time());
}
}
/**
* Start of day
* @param int $time timestamp
* @return start of day
*/
function stats_get_base_daily($time=0) {
global $CFG;
if (empty($time)) {
$time = time();
}
if ($CFG->timezone == 99) {
$time = strtotime(date('d-M-Y', $time));
return $time;
} else {
$offset = get_timezone_offset($CFG->timezone);
$gtime = $time + $offset;
$gtime = intval($gtime / (60*60*24)) * 60*60*24;
return $gtime - $offset;
}
}
/**
* Start of week
* @param int $time timestamp
* @return start of week
*/
function stats_get_base_weekly($time=0) {
global $CFG;
$time = stats_get_base_daily($time);
$startday = $CFG->calendar_startwday;
if ($CFG->timezone == 99) {
$thisday = date('w', $time);
} else {
$offset = get_timezone_offset($CFG->timezone);
$gtime = $time + $offset;
$thisday = gmdate('w', $gtime);
}
if ($thisday > $startday) {
$time = $time - (($thisday - $startday) * 60*60*24);
} else if ($thisday < $startday) {
$time = $time - ((7 + $thisday - $startday) * 60*60*24);
}
return $time;
}
/**
* Start of month
* @param int $time timestamp
* @return start of month
*/
function stats_get_base_monthly($time=0) {
global $CFG;
if (empty($time)) {
$time = time();
}
if ($CFG->timezone == 99) {
return strtotime(date('1-M-Y', $time));
} else {
$time = stats_get_base_daily($time);
$offset = get_timezone_offset($CFG->timezone);
$gtime = $time + $offset;
$day = gmdate('d', $gtime);
if ($day == 1) {
return $time;
}
return $gtime - (($day-1) * 60*60*24);
}
}
/**
* Start of next day
* @param int $time timestamp
* @return start of next day
*/
function stats_get_next_day_start($time) {
$next = stats_get_base_daily($time);
$next = $next + 60*60*26;
$next = stats_get_base_daily($next);
if ($next <= $time) {
//DST trouble - prevent infinite loops
$next = $next + 60*60*24;
}
return $next;
}
/**
* Start of next week
* @param int $time timestamp
* @return start of next week
*/
function stats_get_next_week_start($time) {
$next = stats_get_base_weekly($time);
$next = $next + 60*60*24*9;
$next = stats_get_base_weekly($next);
if ($next <= $time) {
//DST trouble - prevent infinite loops
$next = $next + 60*60*24*7;
}
return $next;
}
/**
* Start of next month
* @param int $time timestamp
* @return start of next month
*/
function stats_get_next_month_start($time) {
$next = stats_get_base_monthly($time);
$next = $next + 60*60*24*33;
$next = stats_get_base_monthly($next);
if ($next <= $time) {
//DST trouble - prevent infinite loops
$next = $next + 60*60*24*31;
}
return $next;
}
/**
* Remove old stats data
*/
function stats_clean_old() {
global $DB;
mtrace("Running stats cleanup tasks...");
cron_trace_time_and_memory();
$deletebefore = stats_get_base_monthly();
// delete dailies older than 3 months (to be safe)
$deletebefore = strtotime('-3 months', $deletebefore);
$DB->delete_records_select('stats_daily', "timeend < $deletebefore");
$DB->delete_records_select('stats_user_daily', "timeend < $deletebefore");
// delete weeklies older than 9 months (to be safe)
$deletebefore = strtotime('-6 months', $deletebefore);
$DB->delete_records_select('stats_weekly', "timeend < $deletebefore");
$DB->delete_records_select('stats_user_weekly', "timeend < $deletebefore");
// don't delete monthlies
mtrace("...stats cleanup finished");
}
function stats_get_parameters($time,$report,$courseid,$mode,$roleid=0) {
global $CFG, $DB;
$param = new stdClass();
$param->params = array();
if ($time < 10) { // dailies
// number of days to go back = 7* time
$param->table = 'daily';
$param->timeafter = strtotime("-".($time*7)." days",stats_get_base_daily());
} elseif ($time < 20) { // weeklies
// number of weeks to go back = time - 10 * 4 (weeks) + base week
$param->table = 'weekly';
$param->timeafter = strtotime("-".(($time - 10)*4)." weeks",stats_get_base_weekly());
} else { // monthlies.
// number of months to go back = time - 20 * months + base month
$param->table = 'monthly';
$param->timeafter = strtotime("-".($time - 20)." months",stats_get_base_monthly());
}
$param->extras = '';
switch ($report) {
// ******************** STATS_MODE_GENERAL ******************** //
case STATS_REPORT_LOGINS:
$param->fields = 'timeend,sum(stat1) as line1,sum(stat2) as line2';
$param->fieldscomplete = true;
$param->stattype = 'logins';
$param->line1 = get_string('statslogins');
$param->line2 = get_string('statsuniquelogins');
if ($courseid == SITEID) {
$param->extras = 'GROUP BY timeend';
}
break;
case STATS_REPORT_READS:
$param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat1 as line1';
$param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
$param->aggregategroupby = 'roleid';
$param->stattype = 'activity';
$param->crosstab = true;
$param->extras = 'GROUP BY timeend,roleid,stat1';
if ($courseid == SITEID) {
$param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1) as line1';
$param->extras = 'GROUP BY timeend,roleid';
}
break;
case STATS_REPORT_WRITES:
$param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, stat2 as line1';
$param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
$param->aggregategroupby = 'roleid';
$param->stattype = 'activity';
$param->crosstab = true;
$param->extras = 'GROUP BY timeend,roleid,stat2';
if ($courseid == SITEID) {
$param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat2) as line1';
$param->extras = 'GROUP BY timeend,roleid';
}
break;
case STATS_REPORT_ACTIVITY:
$param->fields = $DB->sql_concat('timeend','roleid').' AS uniqueid, timeend, roleid, sum(stat1+stat2) as line1';
$param->fieldscomplete = true; // set this to true to avoid anything adding stuff to the list and breaking complex queries.
$param->aggregategroupby = 'roleid';
$param->stattype = 'activity';
$param->crosstab = true;
$param->extras = 'GROUP BY timeend,roleid';
if ($courseid == SITEID) {
$param->extras = 'GROUP BY timeend,roleid';
}
break;
case STATS_REPORT_ACTIVITYBYROLE;
$param->fields = 'stat1 AS line1, stat2 AS line2';
$param->stattype = 'activity';
$rolename = $DB->get_field('role','name', array('id'=>$roleid));
$param->line1 = $rolename . get_string('statsreads');
$param->line2 = $rolename . get_string('statswrites');
if ($courseid == SITEID) {
$param->extras = 'GROUP BY timeend';
}
break;
// ******************** STATS_MODE_DETAILED ******************** //
case STATS_REPORT_USER_ACTIVITY:
$param->fields = 'statsreads as line1, statswrites as line2';
$param->line1 = get_string('statsuserreads');
$param->line2 = get_string('statsuserwrites');
$param->stattype = 'activity';
break;
case STATS_REPORT_USER_ALLACTIVITY:
$param->fields = 'statsreads+statswrites as line1';
$param->line1 = get_string('statsuseractivity');
$param->stattype = 'activity';
break;
case STATS_REPORT_USER_LOGINS:
$param->fields = 'statsreads as line1';
$param->line1 = get_string('statsuserlogins');
$param->stattype = 'logins';
break;
case STATS_REPORT_USER_VIEW:
$param->fields = 'statsreads as line1, statswrites as line2, statsreads+statswrites as line3';
$param->line1 = get_string('statsuserreads');
$param->line2 = get_string('statsuserwrites');
$param->line3 = get_string('statsuseractivity');
$param->stattype = 'activity';
break;
// ******************** STATS_MODE_RANKED ******************** //
case STATS_REPORT_ACTIVE_COURSES:
$param->fields = 'sum(stat1+stat2) AS line1';
$param->stattype = 'activity';
$param->orderby = 'line1 DESC';
$param->line1 = get_string('activity');
$param->graphline = 'line1';
break;
case STATS_REPORT_ACTIVE_COURSES_WEIGHTED:
$threshold = 0;
if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
$threshold = $CFG->statsuserthreshold;
}
$param->fields = '';
$param->sql = 'SELECT activity.courseid, activity.all_activity AS line1, enrolments.highest_enrolments AS line2,
activity.all_activity / enrolments.highest_enrolments as line3
FROM (
SELECT courseid, sum(stat1+stat2) AS all_activity
FROM {stats_'.$param->table.'}
WHERE stattype=\'activity\' AND timeend >= '.(int)$param->timeafter.' AND roleid = 0 GROUP BY courseid
) activity
INNER JOIN
(
SELECT courseid, max(stat1) AS highest_enrolments
FROM {stats_'.$param->table.'}
WHERE stattype=\'enrolments\' AND timeend >= '.(int)$param->timeafter.' AND stat1 > '.(int)$threshold.'
GROUP BY courseid
) enrolments
ON (activity.courseid = enrolments.courseid)
ORDER BY line3 DESC';
$param->line1 = get_string('activity');
$param->line2 = get_string('users');
$param->line3 = get_string('activityweighted');
$param->graphline = 'line3';
break;
case STATS_REPORT_PARTICIPATORY_COURSES:
$threshold = 0;
if (!empty($CFG->statsuserthreshold) && is_numeric($CFG->statsuserthreshold)) {
$threshold = $CFG->statsuserthreshold;
}
$param->fields = '';
$param->sql = 'SELECT courseid, ' . $DB->sql_ceil('avg(all_enrolments)') . ' as line1, ' .
$DB->sql_ceil('avg(active_enrolments)') . ' as line2, avg(proportion_active) AS line3
FROM (
SELECT courseid, timeend, stat2 as active_enrolments,
stat1 as all_enrolments, '.$DB->sql_cast_char2real('stat2').'/'.$DB->sql_cast_char2real('stat1').' AS proportion_active
FROM {stats_'.$param->table.'}
WHERE stattype=\'enrolments\' AND roleid = 0 AND stat1 > '.(int)$threshold.'
) aq
WHERE timeend >= '.(int)$param->timeafter.'
GROUP BY courseid
ORDER BY line3 DESC';
$param->line1 = get_string('users');
$param->line2 = get_string('activeusers');
$param->line3 = get_string('participationratio');
$param->graphline = 'line3';
break;
case STATS_REPORT_PARTICIPATORY_COURSES_RW:
$param->fields = '';
$param->sql = 'SELECT courseid, sum(views) AS line1, sum(posts) AS line2,
avg(proportion_active) AS line3
FROM (
SELECT courseid, timeend, stat1 as views, stat2 AS posts,
'.$DB->sql_cast_char2real('stat2').'/'.$DB->sql_cast_char2real('stat1').' as proportion_active
FROM {stats_'.$param->table.'}
WHERE stattype=\'activity\' AND roleid = 0 AND stat1 > 0
) aq
WHERE timeend >= '.(int)$param->timeafter.'
GROUP BY courseid
ORDER BY line3 DESC';
$param->line1 = get_string('views');
$param->line2 = get_string('posts');
$param->line3 = get_string('participationratio');
$param->graphline = 'line3';
break;
}
/*
if ($courseid == SITEID && $mode != STATS_MODE_RANKED) { // just aggregate all courses.
$param->fields = preg_replace('/(?:sum)([a-zA-Z0-9+_]*)\W+as\W+([a-zA-Z0-9_]*)/i','sum($1) as $2',$param->fields);
$param->extras = ' GROUP BY timeend'.((!empty($param->aggregategroupby)) ? ','.$param->aggregategroupby : '');
}
*/
//TODO must add the SITEID reports to the rest of the reports.
return $param;
}
function stats_get_view_actions() {
return array('view','view all','history');
}
function stats_get_post_actions() {
return array('add','delete','edit','add mod','delete mod','edit section'.'enrol','loginas','new','unenrol','update','update mod');
}
function stats_get_action_names($str) {
global $CFG, $DB;
$mods = $DB->get_records('modules');
$function = 'stats_get_'.$str.'_actions';
$actions = $function();
foreach ($mods as $mod) {
$file = $CFG->dirroot.'/mod/'.$mod->name.'/lib.php';
if (!is_readable($file)) {
continue;
}
require_once($file);
$function = $mod->name.'_get_'.$str.'_actions';
if (function_exists($function)) {
$mod_actions = $function();
if (is_array($mod_actions)) {
$actions = array_merge($actions, $mod_actions);
}
}
}
// The array_values() forces a stack-like array
// so we can later loop over safely...
$actions = array_values(array_unique($actions));
$c = count($actions);
for ($n=0;$n<$c;$n++) {
$actions[$n] = $actions[$n];
}
return $actions;
}
function stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth) {
$now = stats_get_base_daily(time());
// it's really important that it's TIMEEND in the table. ie, tuesday 00:00:00 is monday night.
// so we need to take a day off here (essentially add a day to $now
$now += 60*60*24;
$timeoptions = array();
if ($now - (60*60*24*7) >= $earliestday) {
$timeoptions[STATS_TIME_LASTWEEK] = get_string('numweeks','moodle',1);
}
if ($now - (60*60*24*14) >= $earliestday) {
$timeoptions[STATS_TIME_LAST2WEEKS] = get_string('numweeks','moodle',2);
}
if ($now - (60*60*24*21) >= $earliestday) {
$timeoptions[STATS_TIME_LAST3WEEKS] = get_string('numweeks','moodle',3);
}
if ($now - (60*60*24*28) >= $earliestday) {
$timeoptions[STATS_TIME_LAST4WEEKS] = get_string('numweeks','moodle',4);// show dailies up to (including) here.
}
if ($lastweekend - (60*60*24*56) >= $earliestweek) {
$timeoptions[STATS_TIME_LAST2MONTHS] = get_string('nummonths','moodle',2);
}
if ($lastweekend - (60*60*24*84) >= $earliestweek) {
$timeoptions[STATS_TIME_LAST3MONTHS] = get_string('nummonths','moodle',3);
}
if ($lastweekend - (60*60*24*112) >= $earliestweek) {
$timeoptions[STATS_TIME_LAST4MONTHS] = get_string('nummonths','moodle',4);
}
if ($lastweekend - (60*60*24*140) >= $earliestweek) {
$timeoptions[STATS_TIME_LAST5MONTHS] = get_string('nummonths','moodle',5);
}
if ($lastweekend - (60*60*24*168) >= $earliestweek) {
$timeoptions[STATS_TIME_LAST6MONTHS] = get_string('nummonths','moodle',6); // show weeklies up to (including) here
}
if (strtotime('-7 months',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LAST7MONTHS] = get_string('nummonths','moodle',7);
}
if (strtotime('-8 months',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LAST8MONTHS] = get_string('nummonths','moodle',8);
}
if (strtotime('-9 months',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LAST9MONTHS] = get_string('nummonths','moodle',9);
}
if (strtotime('-10 months',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LAST10MONTHS] = get_string('nummonths','moodle',10);
}
if (strtotime('-11 months',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LAST11MONTHS] = get_string('nummonths','moodle',11);
}
if (strtotime('-1 year',$lastmonthend) >= $earliestmonth) {
$timeoptions[STATS_TIME_LASTYEAR] = get_string('lastyear');
}
$years = (int)date('y', $now) - (int)date('y', $earliestmonth);
if ($years > 1) {
for($i = 2; $i <= $years; $i++) {
$timeoptions[$i*12+20] = get_string('numyears', 'moodle', $i);
}
}
return $timeoptions;
}
function stats_get_report_options($courseid,$mode) {
global $CFG, $DB;
$reportoptions = array();
switch ($mode) {
case STATS_MODE_GENERAL:
$reportoptions[STATS_REPORT_ACTIVITY] = get_string('statsreport'.STATS_REPORT_ACTIVITY);
if ($courseid != SITEID && $context = context_course::instance($courseid)) {
$sql = 'SELECT r.id, r.name FROM {role} r JOIN {stats_daily} s ON s.roleid = r.id WHERE s.courseid = :courseid GROUP BY r.id, r.name';
if ($roles = $DB->get_records_sql($sql, array('courseid' => $courseid))) {
foreach ($roles as $role) {
$reportoptions[STATS_REPORT_ACTIVITYBYROLE.$role->id] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE). ' '.$role->name;
}
}
}
$reportoptions[STATS_REPORT_READS] = get_string('statsreport'.STATS_REPORT_READS);
$reportoptions[STATS_REPORT_WRITES] = get_string('statsreport'.STATS_REPORT_WRITES);
if ($courseid == SITEID) {
$reportoptions[STATS_REPORT_LOGINS] = get_string('statsreport'.STATS_REPORT_LOGINS);
}
break;
case STATS_MODE_DETAILED:
$reportoptions[STATS_REPORT_USER_ACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ACTIVITY);
$reportoptions[STATS_REPORT_USER_ALLACTIVITY] = get_string('statsreport'.STATS_REPORT_USER_ALLACTIVITY);
if (has_capability('report/stats:view', context_system::instance())) {
$site = get_site();
$reportoptions[STATS_REPORT_USER_LOGINS] = get_string('statsreport'.STATS_REPORT_USER_LOGINS);
}
break;
case STATS_MODE_RANKED:
if (has_capability('report/stats:view', context_system::instance())) {
$reportoptions[STATS_REPORT_ACTIVE_COURSES] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES);
$reportoptions[STATS_REPORT_ACTIVE_COURSES_WEIGHTED] = get_string('statsreport'.STATS_REPORT_ACTIVE_COURSES_WEIGHTED);
$reportoptions[STATS_REPORT_PARTICIPATORY_COURSES] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES);
$reportoptions[STATS_REPORT_PARTICIPATORY_COURSES_RW] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES_RW);
}
break;
}
return $reportoptions;
}
/**
* Fix missing entries in the statistics.
*
* This creates a dummy stat when nothing happened during a day/week/month.
*
* @param array $stats array of statistics.
* @param int $timeafter unused.
* @param string $timestr type of statistics to generate (dayly, weekly, monthly).
* @param boolean $line2
* @param boolean $line3
* @return array of fixed statistics.
*/
function stats_fix_zeros($stats,$timeafter,$timestr,$line2=true,$line3=false) {
if (empty($stats)) {
return;
}
$timestr = str_replace('user_','',$timestr); // just in case.
// Gets the current user base time.
$fun = 'stats_get_base_'.$timestr;
$now = $fun();
// Extract the ending time of the statistics.
$actualtimes = array();
$actualtimeshour = null;
foreach ($stats as $statid => $s) {
// Normalise the month date to the 1st if for any reason it's set to later. But we ignore
// anything above or equal to 29 because sometimes we get the end of the month. Also, we will
// set the hours of the result to all of them, that way we prevent DST differences.
if ($timestr == 'monthly') {
$day = date('d', $s->timeend);
if (date('d', $s->timeend) > 1 && date('d', $s->timeend) < 29) {
$day = 1;
}
if (is_null($actualtimeshour)) {
$actualtimeshour = date('H', $s->timeend);
}
$s->timeend = mktime($actualtimeshour, 0, 0, date('m', $s->timeend), $day, date('Y', $s->timeend));
}
$stats[$statid] = $s;
$actualtimes[] = $s->timeend;
}
$actualtimesvalues = array_values($actualtimes);
$timeafter = array_pop($actualtimesvalues);
// Generate a base timestamp for each possible month/week/day.
$times = array();
while ($timeafter < $now) {
$times[] = $timeafter;
if ($timestr == 'daily') {
$timeafter = stats_get_next_day_start($timeafter);
} else if ($timestr == 'weekly') {
$timeafter = stats_get_next_week_start($timeafter);
} else if ($timestr == 'monthly') {
// We can't just simply +1 month because the 31st Jan + 1 month = 2nd of March.
$year = date('Y', $timeafter);
$month = date('m', $timeafter);
$day = date('d', $timeafter);
$dayofnextmonth = $day;
if ($day >= 29) {
$daysinmonth = date('n', mktime(0, 0, 0, $month+1, 1, $year));
if ($day > $daysinmonth) {
$dayofnextmonth = $daysinmonth;
}
}
$timeafter = mktime($actualtimeshour, 0, 0, $month+1, $dayofnextmonth, $year);
} else {
// This will put us in a never ending loop.
return $stats;
}
}
// Add the base timestamp to the statistics if not present.
foreach ($times as $count => $time) {
if (!in_array($time,$actualtimes) && $count != count($times) -1) {
$newobj = new StdClass;
$newobj->timeend = $time;
$newobj->id = 0;
$newobj->roleid = 0;
$newobj->line1 = 0;
if (!empty($line2)) {
$newobj->line2 = 0;
}
if (!empty($line3)) {
$newobj->line3 = 0;
}
$newobj->zerofixed = true;
$stats[] = $newobj;
}
}
usort($stats,"stats_compare_times");
return $stats;
}
// helper function to sort arrays by $obj->timeend
function stats_compare_times($a,$b) {
if ($a->timeend == $b->timeend) {
return 0;
}
return ($a->timeend > $b->timeend) ? -1 : 1;
}
function stats_check_uptodate($courseid=0) {
global $CFG, $DB;
if (empty($courseid)) {
$courseid = SITEID;
}
$latestday = stats_get_start_from('daily');
if ((time() - 60*60*24*2) < $latestday) { // we're ok
return NULL;
}
$a = new stdClass();
$a->daysdone = $DB->get_field_sql("SELECT COUNT(DISTINCT(timeend)) FROM {stats_daily}");
// how many days between the last day and now?
$a->dayspending = ceil((stats_get_base_daily() - $latestday)/(60*60*24));
if ($a->dayspending == 0 && $a->daysdone != 0) {
return NULL; // we've only just started...
}
//return error as string
return get_string('statscatchupmode','error',$a);
}
/**
* Create temporary tables to speed up log generation
*/
function stats_temp_table_create() {
global $CFG, $DB;
$dbman = $DB->get_manager(); // We are going to use database_manager services
stats_temp_table_drop();
$xmlfile = $CFG->dirroot . '/lib/db/install.xml';
$tables = array();
// Allows for the additional xml files to be used (if necessary)
$files = array(
$xmlfile => array(
'stats_daily' => array('temp_stats_daily'),
'stats_user_daily' => array('temp_stats_user_daily'),
'temp_enroled_template' => array('temp_enroled'),
'temp_log_template' => array('temp_log1', 'temp_log2'),
),
);
foreach ($files as $file => $contents) {
$xmldb_file = new xmldb_file($file);
if (!$xmldb_file->fileExists()) {
throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
}
$loaded = $xmldb_file->loadXMLStructure();
if (!$loaded || !$xmldb_file->isLoaded()) {
throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
}
$xmldb_structure = $xmldb_file->getStructure();
foreach ($contents as $template => $names) {
$table = $xmldb_structure->getTable($template);
if (is_null($table)) {
throw new ddl_exception('ddlunknowntable', null, 'The table '. $name .' is not defined in the file '. $xmlfile);
}
$table->setNext(null);
$table->setPrevious(null);
foreach ($names as $name) {
$named = clone $table;
$named->setName($name);
$tables[$name] = $named;
}
}
}
try {
foreach ($tables as $table) {
$dbman->create_temp_table($table);
}
} catch (Exception $e) {
mtrace('Temporary table creation failed: '. $e->getMessage());
return false;
}
return true;
}
/**
* Deletes summary logs table for stats calculation
*/
function stats_temp_table_drop() {
global $DB;
$dbman = $DB->get_manager();
$tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily', 'temp_enroled');
foreach ($tables as $name) {
if ($dbman->table_exists($name)) {
$table = new xmldb_table($name);
try {
$dbman->drop_table($table);
} catch (Exception $e) {
mtrace("Error occured while dropping temporary tables!");
}
}
}
}
/**
* Fills the temporary stats tables with new data
*
* This function is meant to be called once at the start of stats generation
*
* @param timestart timestamp of the start time of logs view
* @param timeend timestamp of the end time of logs view
* @returns boolen success (true) or failure(false)
*/
function stats_temp_table_setup() {
global $DB;
$sql = "INSERT INTO {temp_enroled} (userid, courseid, roleid)
SELECT ue.userid, e.courseid, ra.roleid
FROM {role_assignments} ra
JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
JOIN {enrol} e ON e.courseid = c.instanceid
JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)";
return stats_run_query($sql, array('courselevel' => CONTEXT_COURSE));
}
/**
* Fills the temporary stats tables with new data
*
* This function is meant to be called to get a new day of data
*
* @param timestart timestamp of the start time of logs view
* @param timeend timestamp of the end time of logs view
* @returns boolen success (true) or failure(false)
*/
function stats_temp_table_fill($timestart, $timeend) {
global $DB;
$sql = 'INSERT INTO {temp_log1} (userid, course, action)
SELECT userid, course, action FROM {log}
WHERE time >= ? AND time < ?';
$DB->execute($sql, array($timestart, $timeend));
$sql = 'INSERT INTO {temp_log2} (userid, course, action)
SELECT userid, course, action FROM {temp_log1}';
$DB->execute($sql);
return true;
}
/**
* Deletes summary logs table for stats calculation
*
* @returns boolen success (true) or failure(false)
*/
function stats_temp_table_clean() {
global $DB;
$sql = array();
$sql['up1'] = 'INSERT INTO {stats_daily} (courseid, roleid, stattype, timeend, stat1, stat2)
SELECT courseid, roleid, stattype, timeend, stat1, stat2 FROM {temp_stats_daily}';
$sql['up2'] = 'INSERT INTO {stats_user_daily}
(courseid, userid, roleid, timeend, statsreads, statswrites, stattype)
SELECT courseid, userid, roleid, timeend, statsreads, statswrites, stattype
FROM {temp_stats_user_daily}';
foreach ($sql as $id => $query) {
if (! stats_run_query($query)) {
mtrace("Error during table cleanup!");
return false;
}
}
$tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily');
foreach ($tables as $name) {
$DB->delete_records($name);
}
return true;
}
Jump to Line
Something went wrong with that request. Please try again.