first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,114 @@
<?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/>.
/**
* The report_stats report viewed event.
*
* @package report_stats
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_stats\event;
defined('MOODLE_INTERNAL') || die();
/**
* The report_stats report viewed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int report: (optional) Report type.
* - int time: (optional) Time from which report is viewed.
* - int mode: (optional) Report mode.
* }
*
* @package report_stats
* @since Moodle 2.7
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventreportviewed', 'report_stats');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the statistics report for the course with id '$this->courseid'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/report/stats/index.php', array('id' => $this->courseid, 'mode' => $this->other['mode'],
'report' => $this->other['report']));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['report'])) {
throw new \coding_exception('The \'report\' value must be set in other.');
}
if (!isset($this->other['time'])) {
throw new \coding_exception('The \'time\' value must be set in other.');
}
if (!isset($this->other['mode'])) {
throw new \coding_exception('The \'mode\' value must be set in other.');
}
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
}
public static function get_other_mapping() {
// Nothing to map.
return array();
}
}
@@ -0,0 +1,88 @@
<?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/>.
/**
* The report_stats user report viewed event.
*
* @package report_stats
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_stats\event;
defined('MOODLE_INTERNAL') || die();
/**
* The report_stats user report viewed event class.
*
* @package report_stats
* @since Moodle 2.7
* @copyright 2013 Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_report_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventuserreportviewed', 'report_stats');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the user statistics report for the user with id '$this->relateduserid'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/report/stats/user.php', array('id' => $this->relateduserid, 'course' => $this->courseid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (empty($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
}
}
+270
View File
@@ -0,0 +1,270 @@
<?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/>.
/**
* Privacy Subsystem implementation for report_stats.
*
* @package report_stats
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_stats\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\contextlist;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\userlist;
use \core_privacy\local\request\approved_userlist;
/**
* Privacy Subsystem for report_stats implementing provider.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\subsystem\provider{
/**
* Returns information about the user data stored in this component.
*
* @param collection $collection A list of information about this component
* @return collection The collection object filled out with information about this component.
*/
public static function get_metadata(collection $collection): collection {
$statsuserdaily = [
'courseid' => 'privacy:metadata:courseid',
'userid' => 'privacy:metadata:userid',
'roleid' => 'privacy:metadata:roleid',
'timeend' => 'privacy:metadata:timeend',
'statsreads' => 'privacy:metadata:statsreads',
'statswrites' => 'privacy:metadata:statswrites',
'stattype' => 'privacy:metadata:stattype'
];
$statsuserweekly = [
'courseid' => 'privacy:metadata:courseid',
'userid' => 'privacy:metadata:userid',
'roleid' => 'privacy:metadata:roleid',
'timeend' => 'privacy:metadata:timeend',
'statsreads' => 'privacy:metadata:statsreads',
'statswrites' => 'privacy:metadata:statswrites',
'stattype' => 'privacy:metadata:stattype'
];
$statsusermonthly = [
'courseid' => 'privacy:metadata:courseid',
'userid' => 'privacy:metadata:userid',
'roleid' => 'privacy:metadata:roleid',
'timeend' => 'privacy:metadata:timeend',
'statsreads' => 'privacy:metadata:statsreads',
'statswrites' => 'privacy:metadata:statswrites',
'stattype' => 'privacy:metadata:stattype'
];
$collection->add_database_table('stats_user_daily', $statsuserdaily, 'privacy:metadata:statssummary');
$collection->add_database_table('stats_user_weekly', $statsuserweekly, 'privacy:metadata:statssummary');
$collection->add_database_table('stats_user_monthly', $statsusermonthly, 'privacy:metadata:statssummary');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$params = ['userid' => $userid, 'contextcourse' => CONTEXT_COURSE];
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {stats_user_daily} sud ON sud.courseid = ctx.instanceid AND sud.userid = :userid
WHERE ctx.contextlevel = :contextcourse";
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {stats_user_weekly} suw ON suw.courseid = ctx.instanceid AND suw.userid = :userid
WHERE ctx.contextlevel = :contextcourse";
$contextlist->add_from_sql($sql, $params);
$sql = "SELECT ctx.id
FROM {context} ctx
JOIN {stats_user_monthly} sum ON sum.courseid = ctx.instanceid AND sum.userid = :userid
WHERE ctx.contextlevel = :contextcourse";
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_course) {
return;
}
$params = ['courseid' => $context->instanceid];
$sql = "SELECT userid FROM {stats_user_daily} WHERE courseid = :courseid";
$userlist->add_from_sql('userid', $sql, $params);
$sql = "SELECT userid FROM {stats_user_weekly} WHERE courseid = :courseid";
$userlist->add_from_sql('userid', $sql, $params);
$sql = "SELECT userid FROM {stats_user_monthly} WHERE courseid = :courseid";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Some sneeky person might have sent us the wrong context list. We should check.
if ($contextlist->get_component() != 'report_stats') {
return;
}
// Got to check that someone hasn't foolishly added a context between creating the context list and then filtering down
// to an approved context.
$contexts = array_filter($contextlist->get_contexts(), function($context) {
if ($context->contextlevel == CONTEXT_COURSE) {
return $context;
}
});
$tables = [
'stats_user_daily' => get_string('privacy:dailypath', 'report_stats'),
'stats_user_weekly' => get_string('privacy:weeklypath', 'report_stats'),
'stats_user_monthly' => get_string('privacy:monthlypath', 'report_stats')
];
$courseids = array_map(function($context) {
return $context->instanceid;
}, $contexts);
foreach ($tables as $table => $path) {
list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
$sql = "SELECT s.id, c.fullname, s.roleid, s.timeend, s.statsreads, s.statswrites, s.stattype, c.id as courseid
FROM {" . $table . "} s
JOIN {course} c ON s.courseid = c.id
WHERE s.userid = :userid AND c.id $insql
ORDER BY c.id ASC";
$params['userid'] = $contextlist->get_user()->id;
$records = $DB->get_records_sql($sql, $params);
$statsrecords = [];
foreach ($records as $record) {
$context = \context_course::instance($record->courseid);
if (!isset($statsrecords[$record->courseid])) {
$statsrecords[$record->courseid] = new \stdClass();
$statsrecords[$record->courseid]->context = $context;
}
$statsrecords[$record->courseid]->entries[] = [
'course' => format_string($record->fullname, true, ['context' => $context]),
'roleid' => $record->roleid,
'timeend' => \core_privacy\local\request\transform::datetime($record->timeend),
'statsreads' => $record->statsreads,
'statswrites' => $record->statswrites,
'stattype' => $record->stattype
];
}
foreach ($statsrecords as $coursestats) {
\core_privacy\local\request\writer::with_context($coursestats->context)->export_data([$path],
(object) $coursestats->entries);
}
}
}
/**
* Delete all data for all users in the specified context.
*
* @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
// Check that this context is a course context.
if ($context->contextlevel == CONTEXT_COURSE) {
static::delete_stats($context->instanceid);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
if ($contextlist->get_component() != 'report_stats') {
return;
}
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel == CONTEXT_COURSE) {
static::delete_stats($context->instanceid, $contextlist->get_user()->id);
}
}
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if ($context instanceof \context_course) {
list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$select = "courseid = :courseid AND userid {$usersql}";
$params = ['courseid' => $context->instanceid] + $userparams;
$DB->delete_records_select('stats_user_daily', $select, $params);
$DB->delete_records_select('stats_user_weekly', $select, $params);
$DB->delete_records_select('stats_user_monthly', $select, $params);
}
}
/**
* Deletes stats for a given course.
*
* @param int $courseid The course ID to delete the stats for.
* @param int $userid Optionally a user id to delete records with.
*/
protected static function delete_stats(int $courseid, int $userid = null) {
global $DB;
$params = (isset($userid)) ? ['courseid' => $courseid, 'userid' => $userid] : ['courseid' => $courseid];
$DB->delete_records('stats_user_daily', $params);
$DB->delete_records('stats_user_weekly', $params);
$DB->delete_records('stats_user_monthly', $params);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?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/>.
/**
* Plugin capabilities
*
* @package report_stats
* @copyright 1999 onwards Martin Dougiamas http://moodle.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'report/stats:view' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'coursereport/stats:view',
)
);
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* Post installation and migration code.
*
* @package report
* @subpackage stats
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
function xmldb_report_stats_install() {
global $DB;
}
+30
View File
@@ -0,0 +1,30 @@
<?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/>.
/**
* This file is part of the User section Moodle
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../../config.php');
require_once($CFG->libdir . '/filelib.php');
debugging('This way of generating the chart is deprecated, refer to report_stats_print_chart().', DEBUG_DEVELOPER);
send_file_not_found();
+112
View File
@@ -0,0 +1,112 @@
<?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/>.
/**
* stats report
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\report_helper;
require_once('../../config.php');
require_once($CFG->dirroot.'/report/stats/locallib.php');
require_once($CFG->libdir.'/adminlib.php');
$courseid = optional_param('course', SITEID, PARAM_INT);
$report = optional_param('report', 0, PARAM_INT);
$time = optional_param('time', 0, PARAM_INT);
$mode = optional_param('mode', STATS_MODE_GENERAL, PARAM_INT);
$userid = optional_param('userid', 0, PARAM_INT);
$roleid = 0;
if ($report > 50) {
$roleid = substr($report,1);
$report = 5;
}
if ($report == STATS_REPORT_USER_LOGINS) {
$courseid = SITEID; //override
}
if ($mode == STATS_MODE_RANKED) {
redirect($CFG->wwwroot.'/report/stats/index.php?time='.$time);
}
if (!$course = $DB->get_record("course", array("id"=>$courseid))) {
throw new \moodle_exception("invalidcourseid");
}
if (!empty($userid)) {
$user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
} else {
$user = null;
}
require_login($course);
$context = context_course::instance($course->id);
require_capability('report/stats:view', $context);
$PAGE->set_url(new moodle_url('/report/stats/index.php', array('course' => $course->id,
'report' => $report,
'time' => $time,
'mode' => $mode,
'userid' => $userid)));
navigation_node::override_active_url(new moodle_url('/report/stats/index.php', array('course' => $course->id)));
// Trigger a content view event.
$event = \report_stats\event\report_viewed::create(array('context' => $context, 'relateduserid' => $userid,
'other' => array('report' => $report, 'time' => $time, 'mode' => $mode)));
$event->trigger();
stats_check_uptodate($course->id);
if ($course->id == SITEID) {
admin_externalpage_setup('reportstats', '', null, '', array('pagelayout'=>'report'));
echo $OUTPUT->header();
} else {
$strreports = get_string("reports");
$strstats = get_string('stats');
$PAGE->set_title("$course->shortname: $strstats");
$PAGE->set_heading($course->fullname);
$PAGE->set_pagelayout('report');
echo $OUTPUT->header();
// Print the selected dropdown.
report_helper::print_report_selector(
get_string('pluginname', 'report_stats'),
report_stats_mode_menu($course, $mode, $time, $PAGE->url->out_omit_querystring())
);
}
report_stats_report($course, $report, $mode, $user, $roleid, $time);
if (empty($CFG->enablestats)) {
if (has_capability('moodle/site:config', context_system::instance())) {
redirect("$CFG->wwwroot/$CFG->admin/settings.php?section=stats", get_string('mustenablestats', 'admin'), 3);
} else {
throw new \moodle_exception('statsdisable');
}
}
echo $OUTPUT->footer();
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Strings
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['eventreportviewed'] = 'Statistics report viewed';
$string['eventuserreportviewed'] = 'User statistics report viewed';
$string['nocapability'] = 'Can not access user statistics report';
$string['pluginname'] = 'Statistics';
$string['page-report-stats-x'] = 'Any statistics report';
$string['page-report-stats-index'] = 'Course statistics report';
$string['page-report-stats-user'] = 'User course statistics report';
$string['stats:view'] = 'View course statistics report';
$string['privacy:metadata'] = 'The Statistics plugin does not store any personal data.';
$string['privacy:metadata:courseid'] = 'An identifier for a course';
$string['privacy:metadata:userid'] = 'The user ID linked to this table.';
$string['privacy:metadata:roleid'] = 'The role ID of the user.';
$string['privacy:metadata:timeend'] = 'End time of logs view';
$string['privacy:metadata:statsreads'] = 'Views of content';
$string['privacy:metadata:statswrites'] = 'Content made in the course.';
$string['privacy:metadata:stattype'] = 'The type of stat being recorded';
$string['privacy:metadata:statssummary'] = 'Records basic statistics about user interaction in courses.';
$string['privacy:weeklypath'] = 'Stats weekly';
$string['privacy:dailypath'] = 'Stats daily';
$string['privacy:monthlypath'] = 'Stats monthly';
+158
View File
@@ -0,0 +1,158 @@
<?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/>.
/**
* This file contains functions used by the log reports
*
* This file is also required by /admin/reports/stats/index.php.
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
/**
* This function extends the navigation with the report items
*
* @param navigation_node $navigation The navigation node to extend
* @param stdClass $course The course to object for the report
* @param stdClass $context The context of the course
*/
function report_stats_extend_navigation_course($navigation, $course, $context) {
global $CFG;
if (empty($CFG->enablestats)) {
return;
}
if (has_capability('report/stats:view', $context)) {
$url = new moodle_url('/report/stats/index.php', array('course'=>$course->id));
$navigation->add(get_string('pluginname', 'report_stats'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
}
}
/**
* This function extends the course navigation with the report items
*
* @param navigation_node $navigation The navigation node to extend
* @param stdClass $user
* @param stdClass $course The course to object for the report
*/
function report_stats_extend_navigation_user($navigation, $user, $course) {
global $CFG;
if (empty($CFG->enablestats)) {
return;
}
if (report_stats_can_access_user_report($user, $course)) {
$url = new moodle_url('/report/stats/user.php', array('id'=>$user->id, 'course'=>$course->id));
$navigation->add(get_string('stats'), $url);
}
}
/**
* Is current user allowed to access this report
*
* @private defined in lib.php for performance reasons
*
* @param stdClass $user
* @param stdClass $course
* @return bool
*/
function report_stats_can_access_user_report($user, $course) {
global $USER;
$coursecontext = context_course::instance($course->id);
$personalcontext = context_user::instance($user->id);
if ($user->id == $USER->id) {
if ($course->showreports and (is_viewing($coursecontext, $USER) or is_enrolled($coursecontext, $USER))) {
return true;
}
} else if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)) {
if ($course->showreports and (is_viewing($coursecontext, $user) or is_enrolled($coursecontext, $user))) {
return true;
}
}
// Check if $USER shares group with $user (in case separated groups are enabled and 'moodle/site:accessallgroups' is disabled).
if (!groups_user_groups_visible($course, $user->id)) {
return false;
}
if (has_capability('report/stats:view', $coursecontext)) {
return true;
}
return false;
}
/**
* Return a list of page types
* @param string $pagetype current page type
* @param stdClass $parentcontext Block's parent context
* @param stdClass $currentcontext Current context of block
* @return array
*/
function report_stats_page_type_list($pagetype, $parentcontext, $currentcontext) {
$array = array(
'*' => get_string('page-x', 'pagetype'),
'report-*' => get_string('page-report-x', 'pagetype'),
'report-stats-*' => get_string('page-report-stats-x', 'report_stats'),
'report-stats-index' => get_string('page-report-stats-index', 'report_stats'),
'report-stats-user' => get_string('page-report-stats-user', 'report_stats')
);
return $array;
}
/**
* Callback to verify if the given instance of store is supported by this report or not.
*
* @param string $instance store instance.
*
* @return bool returns true if the store is supported by the report, false otherwise.
*/
function report_stats_supports_logstore($instance) {
if ($instance instanceof \core\log\sql_internal_table_reader) {
return true;
}
return false;
}
/**
* Add nodes to myprofile page.
*
* @param \core_user\output\myprofile\tree $tree Tree object
* @param stdClass $user user object
* @param bool $iscurrentuser
* @param stdClass $course Course object
* @return bool
*/
function report_stats_myprofile_navigation(core_user\output\myprofile\tree $tree, $user, $iscurrentuser, $course) {
global $CFG;
if (empty($CFG->enablestats)) {
return false;
}
if (empty($course)) {
// We want to display these reports under the site context.
$course = get_fast_modinfo(SITEID)->get_course();
}
if (report_stats_can_access_user_report($user, $course)) {
$url = new moodle_url('/report/stats/user.php', array('id' => $user->id, 'course' => $course->id));
$node = new core_user\output\myprofile\node('reports', 'stats', get_string('stats'), null, $url);
$tree->add_node($node);
}
}
+462
View File
@@ -0,0 +1,462 @@
<?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/>.
/**
* Reports implementation
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once(__DIR__.'/lib.php');
require_once($CFG->dirroot.'/lib/statslib.php');
function report_stats_mode_menu($course, $mode, $time, $url) {
global $CFG, $OUTPUT;
/*
$reportoptions = stats_get_report_options($course->id, $mode);
$timeoptions = report_stats_timeoptions($mode);
if (empty($timeoptions)) {
throw new \moodle_exception('nostatstodisplay', '', $CFG->wwwroot.'/course/view.php?id='.$course->id);
}
*/
$options = array();
$options[STATS_MODE_GENERAL] = get_string('statsmodegeneral');
$options[STATS_MODE_DETAILED] = get_string('statsmodedetailed');
if (has_capability('report/stats:view', context_system::instance())) {
$options[STATS_MODE_RANKED] = get_string('reports');
}
$popupurl = $url."?course=$course->id&time=$time";
$select = new single_select(new moodle_url($popupurl), 'mode', $options, $mode, null);
$select->set_label(get_string('reports'), array('class' => 'accesshide'));
$select->formid = 'switchmode';
return $OUTPUT->render($select);
}
function report_stats_timeoptions($mode) {
global $CFG, $DB;
if ($mode == STATS_MODE_DETAILED) {
$earliestday = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_daily}');
$earliestweek = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_weekly}');
$earliestmonth = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_monthly}');
} else {
$earliestday = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_daily}');
$earliestweek = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_weekly}');
$earliestmonth = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_monthly}');
}
if (empty($earliestday)) $earliestday = time();
if (empty($earliestweek)) $earliestweek = time();
if (empty($earliestmonth)) $earliestmonth = time();
$now = stats_get_base_daily();
$lastweekend = stats_get_base_weekly();
$lastmonthend = stats_get_base_monthly();
return stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth);
}
function report_stats_report($course, $report, $mode, $user, $roleid, $time) {
global $CFG, $DB, $OUTPUT;
if ($user) {
$userid = $user->id;
} else {
$userid = 0;
}
$fields = 'c.id,c.shortname,c.visible';
$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$sortstatement = 'ORDER BY c.shortname';
$sql = "SELECT $fields $ccselect FROM {course} c $ccjoin $sortstatement";
$params = array();
$params['contextlevel'] = CONTEXT_COURSE;
$courses = $DB->get_recordset_sql($sql, $params);
$courseoptions = array();
foreach ($courses as $c) {
context_helper::preload_from_record($c);
$context = context_course::instance($c->id);
if (has_capability('report/stats:view', $context)) {
if (isset($c->visible) && $c->visible <= 0) {
// For hidden courses, require visibility check.
if (!has_capability('moodle/course:viewhiddencourses', $context)) {
continue;
}
}
$courseoptions[$c->id] = format_string($c->shortname, true, array('context' => $context));
}
}
$courses->close();
$reportoptions = stats_get_report_options($course->id, $mode);
$timeoptions = report_stats_timeoptions($mode);
if (empty($timeoptions)) {
throw new \moodle_exception('nostatstodisplay', '', $CFG->wwwroot.'/course/view.php?id='.$course->id);
}
$users = array();
$table = new html_table();
$table->width = 'auto';
if ($mode == STATS_MODE_DETAILED) {
$param = stats_get_parameters($time, null, $course->id, $mode, $roleid); // We only care about the table and the time string (if we have time).
list($sort, $moreparams) = users_order_by_sql('u');
$moreparams['courseid'] = $course->id;
$userfieldsapi = \core_user\fields::for_userpic()->including('idnumber');
$fields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
$sql = "SELECT DISTINCT $fields
FROM {stats_user_{$param->table}} s
JOIN {user} u ON u.id = s.userid
WHERE courseid = :courseid";
if (!empty($param->stattype)) {
$sql .= " AND stattype = :stattype";
$moreparams['stattype'] = $param->stattype;
}
if (!empty($time)) {
$sql .= " AND timeend >= :timeafter";
$moreparams['timeafter'] = $param->timeafter;
}
$sql .= " ORDER BY {$sort}";
if (!$us = $DB->get_records_sql($sql, array_merge($param->params, $moreparams))) {
throw new \moodle_exception('nousers');
}
foreach ($us as $u) {
$users[$u->id] = fullname($u, true);
}
$table->align = array('left','left','left','left','left','left','left','left');
$table->data[] = array(html_writer::label(get_string('course'), 'menucourse'), html_writer::select($courseoptions, 'course', $course->id, false),
html_writer::label(get_string('users'), 'menuuserid'), html_writer::select($users, 'userid', $userid, false),
html_writer::label(get_string('statsreporttype'), 'menureport'), html_writer::select($reportoptions, 'report', ($report == 5) ? $report.$roleid : $report, false),
html_writer::label(get_string('statstimeperiod'), 'menutime'), html_writer::select($timeoptions, 'time', $time, false),
'<input type="submit" class="btn btn-secondary" value="'.get_string('view').'" />');
} else if ($mode == STATS_MODE_RANKED) {
$table->align = array('left','left','left','left','left','left');
$table->data[] = array(html_writer::label(get_string('statsreporttype'), 'menureport'), html_writer::select($reportoptions, 'report', ($report == 5) ? $report.$roleid : $report, false),
html_writer::label(get_string('statstimeperiod'), 'menutime'), html_writer::select($timeoptions, 'time', $time, false),
'<input type="submit" class="btn btn-secondary" value="'.get_string('view').'" />');
} else if ($mode == STATS_MODE_GENERAL) {
$table->align = array('left','left','left','left','left','left','left');
$table->data[] = array(html_writer::label(get_string('course'), 'menucourse'), html_writer::select($courseoptions, 'course', $course->id, false),
html_writer::label(get_string('statsreporttype'), 'menureport'), html_writer::select($reportoptions, 'report', ($report == 5) ? $report.$roleid : $report, false),
html_writer::label(get_string('statstimeperiod'), 'menutime'), html_writer::select($timeoptions, 'time', $time, false),
'<input type="submit" class="btn btn-secondary" value="'.get_string('view').'" />');
}
echo '<form action="index.php" method="post">'."\n"
.'<div>'."\n"
.'<input type="hidden" name="mode" value="'.$mode.'" />'."\n";
echo html_writer::table($table);
echo '</div>';
echo '</form>';
// Display the report if:
// - A report has been selected.
// - A time frame has been provided
// - If the mode is not detailed OR a valid user has been selected.
if (!empty($report) && !empty($time) && ($mode !== STATS_MODE_DETAILED || !empty($userid))) {
if ($report == STATS_REPORT_LOGINS && $course->id != SITEID) {
throw new \moodle_exception('reportnotavailable');
}
$param = stats_get_parameters($time, $report, $course->id, $mode, $roleid);
if ($mode == STATS_MODE_DETAILED) {
$param->table = 'user_'.$param->table;
}
if (!empty($param->sql)) {
$sql = $param->sql;
} else {
//TODO: lceanup this ugly mess
$sql = 'SELECT '.((empty($param->fieldscomplete)) ? 'id,roleid,timeend,' : '').$param->fields
.' FROM {stats_'.$param->table.'} WHERE '
.(($course->id == SITEID) ? '' : ' courseid = '.$course->id.' AND ')
.((!empty($userid)) ? ' userid = '.$userid.' AND ' : '')
.((!empty($roleid)) ? ' roleid = '.$roleid.' AND ' : '')
. ((!empty($param->stattype)) ? ' stattype = \''.$param->stattype.'\' AND ' : '')
.' timeend >= '.$param->timeafter
.' '.$param->extras
.' ORDER BY timeend DESC';
}
$stats = $DB->get_records_sql($sql);
if (empty($stats)) {
echo $OUTPUT->notification(get_string('statsnodata'));
} else {
$stats = stats_fix_zeros($stats,$param->timeafter,$param->table,(!empty($param->line2)));
$rolename = '';
$userdisplayname = '';
$coursecontext = context_course::instance($course->id);
if (!empty($roleid) && $role = $DB->get_record('role', ['id' => $roleid])) {
$rolename = ' ' . role_get_name($role, $coursecontext);
}
if (!empty($user)) {
$userdisplayname = ' ' . fullname($user, true);
}
echo $OUTPUT->heading(
format_string($course->shortname) .
' - ' .
get_string('statsreport' . $report) .
$rolename .
$userdisplayname, 3
);
if ($mode == STATS_MODE_DETAILED) {
report_stats_print_chart($course->id, $report, $time, $mode, $userid);
} else {
report_stats_print_chart($course->id, $report, $time, $mode, null, $roleid);
}
$table = new html_table();
$table->align = array('left','center','center','center');
$param->table = str_replace('user_','',$param->table);
switch ($param->table) {
case 'daily' : $period = get_string('day'); break;
case 'weekly' : $period = get_string('week'); break;
case 'monthly': $period = get_string('month', 'form'); break;
default : $period = '';
}
$table->head = array(get_string('periodending','moodle',$period));
if (empty($param->crosstab)) {
$table->head[] = $param->line1;
if (!empty($param->line2)) {
$table->head[] = $param->line2;
}
}
if (!file_exists($CFG->dirroot.'/report/log/index.php')) {
// bad luck, we can not link other report
} else if (empty($param->crosstab)) {
foreach ($stats as $stat) {
$a = array(userdate($stat->timeend - DAYSECS, get_string('strftimedate'), $CFG->timezone),
$stat->line1);
if (isset($stat->line2)) {
$a[] = $stat->line2;
}
if (empty($CFG->loglifetime) || ($stat->timeend-(60*60*24)) >= (time()-60*60*24*$CFG->loglifetime)) {
if (has_capability('report/log:view', context_course::instance($course->id))) {
$a[] = '<a href="'.$CFG->wwwroot.'/report/log/index.php?id='.
$course->id.'&amp;chooselog=1&amp;showusers=1&amp;showcourses=1&amp;user='
.$userid.'&amp;date='.usergetmidnight($stat->timeend-(60*60*24)).'">'
.get_string('course').' ' .get_string('logs').'</a>&nbsp;';
} else {
$a[] = '';
}
}
$table->data[] = $a;
}
} else {
$data = array();
$roles = array();
$times = array();
$missedlines = array();
$coursecontext = context_course::instance($course->id);
$rolenames = get_viewable_roles($coursecontext);
foreach ($stats as $stat) {
if (!empty($stat->zerofixed)) {
$missedlines[] = $stat->timeend;
}
$data[$stat->timeend][$stat->roleid] = $stat->line1;
if ($stat->roleid != 0) {
if (!array_key_exists($stat->roleid, $roles) && array_key_exists($stat->roleid, $rolenames)) {
$roles[$stat->roleid] = $rolenames[$stat->roleid];
}
} else {
if (!array_key_exists($stat->roleid,$roles)) {
$roles[$stat->roleid] = get_string('all');
}
}
if (!array_key_exists($stat->timeend,$times)) {
$times[$stat->timeend] = userdate($stat->timeend - DAYSECS, get_string('strftimedate'),
$CFG->timezone);
}
}
foreach ($data as $time => $rolesdata) {
if (in_array($time,$missedlines)) {
$rolesdata = array();
foreach ($roles as $roleid => $guff) {
$rolesdata[$roleid] = 0;
}
}
else {
foreach (array_keys($roles) as $r) {
if (!array_key_exists($r, $rolesdata)) {
$rolesdata[$r] = 0;
}
}
}
krsort($rolesdata);
$row = array_merge(array($times[$time]),$rolesdata);
if (empty($CFG->loglifetime) || ($stat->timeend-(60*60*24)) >= (time()-60*60*24*$CFG->loglifetime)) {
if (has_capability('report/log:view', context_course::instance($course->id))) {
$row[] = '<a href="'.$CFG->wwwroot.'/report/log/index.php?id='
.$course->id.'&amp;chooselog=1&amp;showusers=1&amp;showcourses=1&amp;user='.$userid
.'&amp;date='.usergetmidnight($time-(60*60*24)).'">'
.get_string('course').' ' .get_string('logs').'</a>&nbsp;';
} else {
$row[] = '';
}
}
$table->data[] = $row;
}
krsort($roles);
$table->head = array_merge($table->head,$roles);
}
$table->head[] = get_string('logs');
//if (!empty($lastrecord)) {
//$lastrecord[] = $lastlink;
//$table->data[] = $lastrecord;
//}
echo html_writer::table($table);
}
}
}
/**
* Fetch statistics data and generate a line chart.
*
* The statistic chart can be view, posts separated by roles and dates.
*
* @param int $courseid course id.
* @param int $report the report type constant eg. STATS_REPORT_LOGINS as defined on statslib.
* @param int $time timestamp of the selected time period.
* @param int $mode the report mode, eg. STATS_MODE_DETAILED as defined on statslib.
* @param int $userid selected user id.
* @param int $roleid selected role id.
*/
function report_stats_print_chart($courseid, $report, $time, $mode, $userid = 0, $roleid = 0) {
global $DB, $CFG, $OUTPUT;
$course = $DB->get_record("course", array("id" => $courseid), '*', MUST_EXIST);
$coursecontext = context_course::instance($course->id);
stats_check_uptodate($course->id);
$param = stats_get_parameters($time, $report, $course->id, $mode, $roleid);
if (!empty($userid)) {
$param->table = 'user_' . $param->table;
}
// TODO: cleanup this ugly mess.
$sql = 'SELECT '.((empty($param->fieldscomplete)) ? 'id,roleid,timeend,' : '').$param->fields
.' FROM {stats_'.$param->table.'} WHERE '
.(($course->id == SITEID) ? '' : ' courseid = '.$course->id.' AND ')
.((!empty($userid)) ? ' userid = '.$userid.' AND ' : '')
.((!empty($roleid)) ? ' roleid = '.$roleid.' AND ' : '')
. ((!empty($param->stattype)) ? ' stattype = \''.$param->stattype.'\' AND ' : '')
.' timeend >= '.$param->timeafter
.' '.$param->extras
.' ORDER BY timeend DESC';
$stats = $DB->get_records_sql($sql, $param->params);
$stats = stats_fix_zeros($stats, $param->timeafter, $param->table, (!empty($param->line2)),
(!empty($param->line3)));
$stats = array_reverse($stats);
$chart = new \core\chart_line();
if (empty($param->crosstab)) {
$data = [];
$times = [];
foreach ($stats as $stat) {
// Build the array of formatted times indexed by timestamp used as labels.
if (!array_key_exists($stat->timeend, $times)) {
$times[$stat->timeend] = userdate($stat->timeend - DAYSECS, get_string('strftimedate'), $CFG->timezone);
// Just add the data if the time hasn't been added yet.
// The number of lines of data must match the number of labels.
$data['line1'][] = $stat->line1;
if (isset($stat->line2)) {
$data['line2'][] = $stat->line2;
}
if (isset($stat->line3)) {
$data['line3'][] = $stat->line3;
}
}
}
foreach ($data as $line => $serie) {
$series = new \core\chart_series($param->{$line}, array_values($serie));
$chart->add_series($series);
}
} else {
$data = array();
$times = array();
$roles = array();
$missedlines = array();
$rolenames = get_viewable_roles($coursecontext);
foreach ($stats as $stat) {
$data[$stat->roleid][$stat->timeend] = $stat->line1;
if (!empty($stat->zerofixed)) {
$missedlines[] = $stat->timeend;
}
if ($stat->roleid != 0) {
if (!array_key_exists($stat->roleid, $roles) && array_key_exists($stat->roleid, $rolenames)) {
$roles[$stat->roleid] = $rolenames[$stat->roleid];
}
} else {
if (!array_key_exists($stat->roleid, $roles)) {
$roles[$stat->roleid] = get_string('all');
}
}
// Build the array of formatted times indexed by timestamp used as labels.
if (!array_key_exists($stat->timeend, $times)) {
$times[$stat->timeend] = userdate($stat->timeend - DAYSECS, get_string('strftimedate'), $CFG->timezone);
}
}
// Fill empty days with zero to avoid chart errors.
foreach (array_keys($times) as $t) {
foreach ($data as $roleid => $stuff) {
if (!array_key_exists($t, $stuff)) {
$data[$roleid][$t] = 0;
}
}
}
krsort($roles);
foreach ($roles as $roleid => $rolename) {
ksort($data[$roleid]);
$series = new \core\chart_series($rolename, array_values($data[$roleid]));
$chart->add_series($series);
}
}
$chart->set_labels(array_values($times));
echo $OUTPUT->render_chart($chart, false);
}
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* Version info
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
// just a link to course report
$ADMIN->add('reports', new admin_externalpage('reportstats', get_string('pluginname', 'report_stats'), "$CFG->wwwroot/report/stats/index.php", 'report/stats:view', empty($CFG->enablestats)));
// no report settings
$settings = null;
+3
View File
@@ -0,0 +1,3 @@
#page-report-stats-index .graph {
margin-bottom: 1em;
}
+96
View File
@@ -0,0 +1,96 @@
<?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/>.
/**
* Tests for stats report events.
*
* @package report_stats
* @copyright 2014 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace report_stats\event;
/**
* Class report_stats_events_testcase
*
* Class for tests related to stats report events.
*
* @package report_stats
* @copyright 2014 Rajesh Taneja <rajesh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class events_test extends \advanced_testcase {
/**
* Setup testcase.
*/
public function setUp(): void {
$this->setAdminUser();
$this->resetAfterTest();
}
/**
* Test the stats report viewed event.
*
* It's not possible to use the moodle API to simulate the viewing of stats report, so here we
* simply create the event and trigger it.
*/
public function test_report_viewed(): void {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
// Trigger event for stats report viewed.
$event = \report_stats\event\report_viewed::create(array('context' => $context, 'relateduserid' => $user->id,
'other' => array('report' => 0, 'time' => 0, 'mode' => 1)));
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\report_stats\event\report_viewed', $event);
$this->assertEquals($context, $event->get_context());
$this->assertEventContextNotUsed($event);
}
/**
* Test the user stats report viewed event.
*
* It's not possible to use the moodle API to simulate the viewing of user stats report, so here we
* simply create the event and trigger it.
*/
public function test_user_report_viewed(): void {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
// Trigger event for user stats report viewed.
$event = \report_stats\event\user_report_viewed::create(array('context' => $context, 'relateduserid' => $user->id));
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\report_stats\event\user_report_viewed', $event);
$this->assertEquals($context, $event->get_context());
$this->assertEventContextNotUsed($event);
}
}
+127
View File
@@ -0,0 +1,127 @@
<?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/>.
/**
* Tests for report library functions.
*
* @package report_stats
* @copyright 2014 onwards Ankit agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace report_stats;
defined('MOODLE_INTERNAL') || die();
/**
* Class report_stats_lib_testcase
*
* @package report_stats
* @copyright 2014 onwards Ankit agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class lib_test extends \advanced_testcase {
/**
* @var stdClass The user.
*/
private $user;
/**
* @var stdClass The course.
*/
private $course;
/**
* @var \core_user\output\myprofile\tree The navigation tree.
*/
private $tree;
public function setUp(): void {
$this->user = $this->getDataGenerator()->create_user();
$this->course = $this->getDataGenerator()->create_course();
$this->tree = new \core_user\output\myprofile\tree();
$this->resetAfterTest();
}
/**
* Test report_log_supports_logstore.
*/
public function test_report_participation_supports_logstore(): void {
$logmanager = get_log_manager();
$allstores = \core_component::get_plugin_list_with_class('logstore', 'log\store');
$supportedstores = array(
'logstore_standard' => '\logstore_standard\log\store'
);
// Make sure all supported stores are installed.
$expectedstores = array_keys(array_intersect($allstores, $supportedstores));
$stores = $logmanager->get_supported_logstores('report_stats');
$stores = array_keys($stores);
foreach ($expectedstores as $expectedstore) {
$this->assertContains($expectedstore, $stores);
}
}
/**
* Tests the report_stats_myprofile_navigation() function.
*/
public function test_report_stats_myprofile_navigation(): void {
$this->setAdminUser();
$iscurrentuser = false;
// Enable stats.
set_config('enablestats', true);
report_stats_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course);
$reflector = new \ReflectionObject($this->tree);
$nodes = $reflector->getProperty('nodes');
$this->assertArrayHasKey('stats', $nodes->getValue($this->tree));
}
/**
* Tests the report_stats_myprofile_navigation() function when stats are disabled.
*/
public function test_report_stats_myprofile_navigation_stats_disabled(): void {
$this->setAdminUser();
$iscurrentuser = false;
// Disable stats.
set_config('enablestats', false);
report_stats_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course);
$reflector = new \ReflectionObject($this->tree);
$nodes = $reflector->getProperty('nodes');
$this->assertArrayNotHasKey('stats', $nodes->getValue($this->tree));
}
/**
* Tests the report_stats_myprofile_navigation() function without permission.
*/
public function test_report_stats_myprofile_navigation_without_permission(): void {
// Try to see as a user without permission.
$this->setUser($this->user);
$iscurrentuser = true;
// Enable stats.
set_config('enablestats', true);
report_stats_myprofile_navigation($this->tree, $this->user, $iscurrentuser, $this->course);
$reflector = new \ReflectionObject($this->tree);
$nodes = $reflector->getProperty('nodes');
$this->assertArrayNotHasKey('stats', $nodes->getValue($this->tree));
}
}
@@ -0,0 +1,320 @@
<?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/>.
/**
* Tests for privacy functions.
*
* @package report_stats
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace report_stats\privacy;
use report_stats\privacy\provider;
use core_privacy\local\request\approved_userlist;
use core_privacy\tests\provider_testcase;
/**
* Privacy provider test for report_stats.
*
* @package report_stats
* @copyright 2018 Adrian Greeve <adriangreeve.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class provider_test extends provider_testcase {
/**
* Convenience function to create stats.
*
* @param int $courseid Course ID for this record.
* @param int $userid User ID for this record.
* @param string $table Stat table to insert into.
*/
protected function create_stats($courseid, $userid, $table) {
global $DB;
$data = (object) [
'courseid' => $courseid,
'userid' => $userid,
'roleid' => 0,
'timeend' => time(),
'statsreads' => rand(1, 50),
'statswrites' => rand(1, 50),
'stattype' => 'activity'
];
$DB->insert_record($table, $data);
}
/**
* Get all of the contexts related to a user and stat tables.
*/
public function test_get_contexts_for_userid(): void {
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$context1 = \context_course::instance($course1->id);
$context2 = \context_course::instance($course2->id);
$context3 = \context_course::instance($course3->id);
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course2->id, $user1->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user2->id, 'stats_user_weekly');
$contextlist = provider::get_contexts_for_userid($user1->id);
$this->assertCount(2, $contextlist->get_contextids());
foreach ($contextlist->get_contexts() as $context) {
$this->assertEquals(CONTEXT_COURSE, $context->contextlevel);
$this->assertNotEquals($context3, $context);
}
$contextlist = provider::get_contexts_for_userid($user2->id);
$this->assertCount(1, $contextlist->get_contextids());
$this->assertEquals($context1, $contextlist->current());
}
/**
* Test that stat data is exported as required.
*/
public function test_export_user_data(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$context1 = \context_course::instance($course1->id);
$context2 = \context_course::instance($course2->id);
$this->create_stats($course1->id, $user->id, 'stats_user_daily');
$this->create_stats($course1->id, $user->id, 'stats_user_daily');
$this->create_stats($course2->id, $user->id, 'stats_user_weekly');
$this->create_stats($course2->id, $user->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user->id, 'stats_user_monthly');
$approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'report_stats', [$context1->id, $context2->id]);
provider::export_user_data($approvedlist);
$writer = \core_privacy\local\request\writer::with_context($context1);
$dailystats = (array) $writer->get_data([get_string('privacy:dailypath', 'report_stats')]);
$this->assertCount(2, $dailystats);
$monthlystats = (array) $writer->get_data([get_string('privacy:monthlypath', 'report_stats')]);
$this->assertCount(1, $monthlystats);
$data = array_shift($monthlystats);
$this->assertEquals($course1->fullname, $data['course']);
$writer = \core_privacy\local\request\writer::with_context($context2);
$monthlystats = (array) $writer->get_data([get_string('privacy:monthlypath', 'report_stats')]);
$this->assertCount(1, $monthlystats);
$data = array_shift($monthlystats);
$this->assertEquals($course2->fullname, $data['course']);
$weeklystats = (array) $writer->get_data([get_string('privacy:weeklypath', 'report_stats')]);
$this->assertCount(1, $weeklystats);
$data = array_shift($weeklystats);
$this->assertEquals($course2->fullname, $data['course']);
}
/**
* Test that stat data is deleted for a whole context.
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$context1 = \context_course::instance($course1->id);
$context2 = \context_course::instance($course2->id);
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course1->id, $user1->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user2->id, 'stats_user_weekly');
$this->create_stats($course2->id, $user2->id, 'stats_user_daily');
$this->create_stats($course2->id, $user2->id, 'stats_user_weekly');
$this->create_stats($course2->id, $user2->id, 'stats_user_monthly');
$dailyrecords = $DB->get_records('stats_user_daily');
$this->assertCount(3, $dailyrecords);
$weeklyrecords = $DB->get_records('stats_user_weekly');
$this->assertCount(2, $weeklyrecords);
$monthlyrecords = $DB->get_records('stats_user_monthly');
$this->assertCount(2, $monthlyrecords);
// Delete all user data for course 1.
provider::delete_data_for_all_users_in_context($context1);
$dailyrecords = $DB->get_records('stats_user_daily');
$this->assertCount(1, $dailyrecords);
$weeklyrecords = $DB->get_records('stats_user_weekly');
$this->assertCount(1, $weeklyrecords);
$monthlyrecords = $DB->get_records('stats_user_monthly');
$this->assertCount(1, $monthlyrecords);
}
/**
* Test that stats are deleted for one user.
*/
public function test_delete_data_for_user(): void {
global $DB;
$this->resetAfterTest();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$context1 = \context_course::instance($course1->id);
$context2 = \context_course::instance($course2->id);
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course1->id, $user1->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user2->id, 'stats_user_weekly');
$this->create_stats($course2->id, $user2->id, 'stats_user_daily');
$this->create_stats($course2->id, $user2->id, 'stats_user_weekly');
$this->create_stats($course2->id, $user2->id, 'stats_user_monthly');
$dailyrecords = $DB->get_records('stats_user_daily');
$this->assertCount(3, $dailyrecords);
$weeklyrecords = $DB->get_records('stats_user_weekly');
$this->assertCount(2, $weeklyrecords);
$monthlyrecords = $DB->get_records('stats_user_monthly');
$this->assertCount(2, $monthlyrecords);
// Delete all user data for course 1.
$approvedlist = new \core_privacy\local\request\approved_contextlist($user1, 'report_stats', [$context1->id]);
provider::delete_data_for_user($approvedlist);
$dailyrecords = $DB->get_records('stats_user_daily');
$this->assertCount(1, $dailyrecords);
$weeklyrecords = $DB->get_records('stats_user_weekly');
$this->assertCount(2, $weeklyrecords);
$monthlyrecords = $DB->get_records('stats_user_monthly');
$this->assertCount(1, $monthlyrecords);
}
/**
* Test that only users within a course context are fetched.
*/
public function test_get_users_in_context(): void {
$this->resetAfterTest();
$component = 'report_stats';
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
// Create course1.
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create course2.
$course2 = $this->getDataGenerator()->create_course();
$coursecontext2 = \context_course::instance($course2->id);
$userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course2->id, $user1->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user2->id, 'stats_user_weekly');
// The list of users within the course context should contain users.
provider::get_users_in_context($userlist1);
$this->assertCount(2, $userlist1);
$this->assertTrue(in_array($user1->id, $userlist1->get_userids()));
$this->assertTrue(in_array($user2->id, $userlist1->get_userids()));
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$this->assertTrue(in_array($user1->id, $userlist2->get_userids()));
// The list of users within other contexts than course should be empty.
$systemcontext = \context_system::instance();
$userlist3 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
$this->resetAfterTest();
$component = 'report_stats';
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
// Create user3.
$user3 = $this->getDataGenerator()->create_user();
// Create course1.
$course1 = $this->getDataGenerator()->create_course();
$coursecontext1 = \context_course::instance($course1->id);
// Create course2.
$course2 = $this->getDataGenerator()->create_course();
$coursecontext2 = \context_course::instance($course2->id);
$this->create_stats($course1->id, $user1->id, 'stats_user_daily');
$this->create_stats($course2->id, $user1->id, 'stats_user_monthly');
$this->create_stats($course1->id, $user2->id, 'stats_user_weekly');
$this->create_stats($course1->id, $user3->id, 'stats_user_weekly');
$userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(3, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// Convert $userlist1 into an approved_contextlist.
$approvedlist1 = new approved_userlist($coursecontext1, $component, [$user1->id, $user2->id]);
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist1);
// Re-fetch users in coursecontext1.
$userlist1 = new \core_privacy\local\request\userlist($coursecontext1, $component);
provider::get_users_in_context($userlist1);
// The approved user data in coursecontext1 should be deleted.
// The user list should still return user3.
$this->assertCount(1, $userlist1);
$this->assertTrue(in_array($user3->id, $userlist1->get_userids()));
// Re-fetch users in coursecontext2.
$userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
provider::get_users_in_context($userlist2);
// The user data in coursecontext2 should be still present.
$this->assertCount(1, $userlist2);
// Convert $userlist2 into an approved_contextlist in the system context.
$systemcontext = \context_system::instance();
$approvedlist2 = new approved_userlist($systemcontext, $component, $userlist2->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist2);
// Re-fetch users in coursecontext2.
$userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
provider::get_users_in_context($userlist2);
// The user data in systemcontext should not be deleted.
$this->assertCount(1, $userlist2);
}
}
+194
View File
@@ -0,0 +1,194 @@
<?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/>.
/**
* Display user activity reports for a course (totals)
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../../config.php');
require_once($CFG->dirroot.'/report/stats/locallib.php');
$userid = required_param('id', PARAM_INT);
$courseid = required_param('course', PARAM_INT);
$user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
$coursecontext = context_course::instance($course->id);
$personalcontext = context_user::instance($user->id);
$pageheading = $course->fullname;
$userfullname = fullname($user);
if ($courseid == SITEID) {
$PAGE->set_context($personalcontext);
$pageheading = $userfullname;
}
if ($USER->id != $user->id and has_capability('moodle/user:viewuseractivitiesreport', $personalcontext)
and !is_enrolled($coursecontext, $USER) and is_enrolled($coursecontext, $user)) {
//TODO: do not require parents to be enrolled in courses - this is a hack!
require_login();
$PAGE->set_course($course);
} else {
require_login($course);
}
if (!report_stats_can_access_user_report($user, $course)) {
// this should never happen
throw new \moodle_exception('nocapability', 'report_stats');
}
$stractivityreport = get_string('activityreport');
$PAGE->set_pagelayout('report');
$PAGE->set_url('/report/stats/user.php', array('id'=>$user->id, 'course'=>$course->id));
$PAGE->navigation->extend_for_user($user);
$PAGE->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed.
// Breadcrumb stuff.
$navigationnode = array(
'name' => get_string('stats'),
'url' => new moodle_url('/report/stats/user.php', array('id' => $user->id, 'course' => $course->id))
);
$PAGE->add_report_nodes($user->id, $navigationnode);
$PAGE->set_title("$course->shortname: $stractivityreport");
$PAGE->set_heading($pageheading);
echo $OUTPUT->header();
if ($courseid != SITEID) {
$backurl = new moodle_url('/user/view.php', ['id' => $userid, 'course' => $courseid]);
echo $OUTPUT->single_button($backurl, get_string('back'), 'get', ['class' => 'mb-3']);
echo $OUTPUT->context_header(
array(
'heading' => $userfullname,
'user' => $user,
'usercontext' => $personalcontext
), 2);
echo $OUTPUT->heading(get_string('statistics', 'moodle'), 2, 'main mt-4 mb-4');
}
// Trigger a user report viewed event.
$event = \report_stats\event\user_report_viewed::create(array('context' => $coursecontext, 'relateduserid' => $user->id));
$event->trigger();
if (empty($CFG->enablestats)) {
throw new \moodle_exception('statsdisable', 'error');
}
$statsstatus = stats_check_uptodate($course->id);
if ($statsstatus !== NULL) {
echo $OUTPUT->notification($statsstatus);
}
$earliestday = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_daily}');
$earliestweek = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_weekly}');
$earliestmonth = $DB->get_field_sql('SELECT MIN(timeend) FROM {stats_user_monthly}');
if (empty($earliestday)) {
$earliestday = time();
}
if (empty($earliestweek)) {
$earliestweek = time();
}
if (empty($earliestmonth)) {
$earliestmonth = time();
}
$now = stats_get_base_daily();
$lastweekend = stats_get_base_weekly();
$lastmonthend = stats_get_base_monthly();
$timeoptions = stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth);
if (empty($timeoptions)) {
throw new \moodle_exception('nostatstodisplay', '',
$CFG->wwwroot.'/course/user.php?id='.$course->id.'&user='.$user->id.'&mode=outline');
}
// use the earliest.
$timekeys = array_keys($timeoptions);
$time = array_pop($timekeys);
$param = stats_get_parameters($time,STATS_REPORT_USER_VIEW,$course->id,STATS_MODE_DETAILED);
$params = $param->params;
$param->table = 'user_'.$param->table;
// Build the conditions and parameters.
$wheres = [
"userid = :userid",
"timeend >= :timeend",
"stattype = :stattype",
];
$params['userid'] = $user->id;
$params['timeend'] = $param->timeafter;
$params['stattype'] = $param->stattype;
// Add condition for course ID when specified.
if ($course->id != SITEID) {
$wheres[] = "courseid = :courseid";
$params['courseid'] = $course->id;
}
// Combine the conditions.
$wheresql = implode(" AND ", $wheres);
// Build the query.
$sql = "
SELECT {$param->fields}
FROM {stats_{$param->table}}
WHERE {$wheresql}
{$param->extras}
ORDER BY timeend DESC";
// Fetch the stats data.
$stats = $DB->get_records_sql($sql, $params);
if (empty($stats)) {
throw new \moodle_exception('nostatstodisplay', '',
$CFG->wwwroot.'/course/user.php?id='.$course->id.'&user='.$user->id.'&mode=outline');
}
report_stats_print_chart($course->id, STATS_REPORT_USER_VIEW, $time, STATS_MODE_DETAILED, $user->id);
// What the heck is this about? -- MD
$stats = stats_fix_zeros($stats,$param->timeafter,$param->table,(!empty($param->line2)),(!empty($param->line3)));
$table = new html_table();
$table->align = array('left','center','center','center');
$param->table = str_replace('user_','',$param->table);
switch ($param->table) {
case 'daily' : $period = get_string('day'); break;
case 'weekly' : $period = get_string('week'); break;
case 'monthly': $period = get_string('month', 'form'); break;
default : $period = '';
}
$table->head = array(get_string('periodending','moodle',$period),$param->line1,$param->line2,$param->line3);
foreach ($stats as $stat) {
if (!empty($stat->zerofixed)) { // Don't know why this is necessary, see stats_fix_zeros above - MD
continue;
}
$a = array(userdate($stat->timeend - DAYSECS, get_string('strftimedate'), $CFG->timezone), $stat->line1);
$a[] = $stat->line2;
$a[] = $stat->line3;
$table->data[] = $a;
}
echo html_writer::table($table);
echo $OUTPUT->footer();
+30
View File
@@ -0,0 +1,30 @@
<?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/>.
/**
* Version info
*
* @package report
* @subpackage stats
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'report_stats'; // Full name of the plugin (used for diagnostics)