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,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/>.
/**
* Privacy Subsystem implementation for report_backups.
*
* @package report_backups
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_backups\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_backups implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+203
View File
@@ -0,0 +1,203 @@
<?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/>.
/**
* A report to display the outcome of scheduled backups
*
* @package report
* @subpackage backups
* @copyright 2007 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->libdir . '/adminlib.php');
// Required for backup::xxx constants.
require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
require_once($CFG->dirroot . '/backup/backup.class.php');
$courseid = optional_param('courseid', 0, PARAM_INT);
$page = optional_param('page', 0, PARAM_INT); // This represents which backup we are viewing.
// Required for constants in backup_cron_automated_helper
require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php');
admin_externalpage_setup('reportbackups', '', null, '', array('pagelayout'=>'report'));
$strftimedatetime = get_string('strftimerecent');
$strerror = get_string('error');
$strok = get_string('statusok');
$strunfinished = get_string('unfinished');
$strskipped = get_string('skipped');
$strwarning = get_string('warning');
$strnotyetrun = get_string('backupnotyetrun');
$strqueued = get_string('queued');
if ($courseid) {
$course = $DB->get_record('course', array('id' => $courseid), 'id, fullname', MUST_EXIST);
// Get the automated backups that have been performed for this course.
$params = array('operation' => backup::OPERATION_BACKUP,
'type' => backup::TYPE_1COURSE,
'itemid' => $course->id,
'interactive' => backup::INTERACTIVE_NO);
if ($backups = $DB->get_records('backup_controllers', $params, 'timecreated DESC',
'id, backupid, status, timecreated', $page, 1)) {
// Get the backup we want to use.
$backup = reset($backups);
// Get the backup status.
if ($backup->status == backup::STATUS_FINISHED_OK) {
$status = $strok;
$statusclass = 'table-success'; // Green.
} else if ($backup->status == backup::STATUS_AWAITING || $backup->status == backup::STATUS_EXECUTING) {
$status = $strunfinished;
$statusclass = 'table-danger'; // Red.
} else { // Else show error.
$status = $strerror;
$statusclass = 'table-danger'; // Red.
}
$table = new html_table();
$table->head = array('');
$table->data = array();
$statusrow = get_string('status') . ' - ' . html_writer::tag('span', $status, array('class' => $statusclass));
$table->data[] = array($statusrow);
// Get the individual logs for this backup.
if ($logs = $DB->get_records('backup_logs', array('backupid' => $backup->backupid), 'timecreated ASC',
'id, message, timecreated')) {
foreach ($logs as $log) {
$table->data[] = array(userdate($log->timecreated, get_string('strftimetime', 'report_backups')) .
' - ' . $log->message);
}
} else {
$table->data[] = array(get_string('nologsfound', 'report_backups'));
}
}
// Set the course name to display.
$coursename = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('backupofcourselogs', 'report_backups', $coursename));
if (isset($backup)) {
// We put this logic down here as we may be viewing a backup that was performed which there were no logs
// recorded for. We still want to display the pagination so the user can still navigate to other backups,
// and we also display a message so they are aware that the backup happened but there were no logs.
$baseurl = new moodle_url('/report/backups/index.php', array('courseid' => $courseid));
$numberofbackups = $DB->count_records('backup_controllers', $params);
$pagingbar = new paging_bar($numberofbackups, $page, 1, $baseurl);
echo $OUTPUT->render($pagingbar);
echo $OUTPUT->heading(get_string('logsofbackupexecutedon', 'report_backups', userdate($backup->timecreated)), 3);
echo html_writer::table($table);
echo $OUTPUT->render($pagingbar);
} else {
echo $OUTPUT->box(get_string('nobackupsfound', 'report_backups'), 'center');
}
echo $OUTPUT->footer();
exit();
}
$table = new html_table;
$table->head = array(
get_string("course"),
get_string("timetaken", "backup"),
get_string("status"),
get_string("backupnext")
);
$table->headspan = array(1, 3, 1, 1);
$table->attributes = array('class' => 'generaltable backup-report');
$table->data = array();
$select = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$sql = "SELECT bc.*, c.id as courseid, c.fullname $select
FROM {backup_courses} bc
JOIN {course} c ON c.id = bc.courseid
$join";
$rs = $DB->get_recordset_sql($sql, array('contextlevel' => CONTEXT_COURSE));
foreach ($rs as $backuprow) {
// Cache the course context
context_helper::preload_from_record($backuprow);
// Prepare a cell to display the status of the entry.
if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_OK) {
$status = $strok;
$statusclass = 'table-success'; // Green.
} else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED) {
$status = $strunfinished;
$statusclass = 'table-danger'; // Red.
} else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_SKIPPED) {
$status = $strskipped;
$statusclass = 'table-success'; // Green.
} else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_WARNING) {
$status = $strwarning;
$statusclass = 'table-warning'; // Orange.
} else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN) {
$status = $strnotyetrun;
$statusclass = 'table-success';
} else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_QUEUED) {
$status = $strqueued;
$statusclass = 'table-success';
} else {
$status = $strerror;
$statusclass = 'table-danger'; // Red.
}
$status = new html_table_cell($status);
$status->attributes = array('class' => $statusclass);
// Create the row and add it to the table
$backuprowname = format_string($backuprow->fullname, true, array('context' => context_course::instance($backuprow->courseid)));
$backuplogsurl = new moodle_url('/report/backups/index.php', array('courseid' => $backuprow->courseid));
$backuplogsicon = new pix_icon('t/viewdetails', get_string('viewlogs', 'report_backups'));
$cells = array(
$backuprowname . ' ' . $OUTPUT->action_icon($backuplogsurl, $backuplogsicon),
userdate($backuprow->laststarttime, $strftimedatetime),
'-',
userdate($backuprow->lastendtime, $strftimedatetime),
$status,
userdate($backuprow->nextstarttime, $strftimedatetime)
);
$table->data[] = new html_table_row($cells);
}
$rs->close();
// Check if we have any results and if not add a no records notification
if (empty($table->data)) {
$cell = new html_table_cell($OUTPUT->notification(get_string('nologsfound')));
$cell->colspan = 6;
$table->data[] = new html_table_row(array($cell));
}
$automatedbackupsenabled = get_config('backup', 'backup_auto_active');
// Display the backup report
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string("backuploglaststatus"));
echo $OUTPUT->box_start();
if (empty($automatedbackupsenabled)) {
// Automated backups aren't active, display a notification.
// Not we don't stop because of this as perhaps scheduled backups are being run
// automatically, or were enabled in the page.
echo $OUTPUT->notification(get_string('automatedbackupsinactive', 'backup'));
}
echo html_writer::table($table);
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
+33
View File
@@ -0,0 +1,33 @@
<?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 for component 'report_backups'
*
* @package report
* @subpackage backups
* @copyright 2007 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['backupofcourselogs'] = 'Backup logs of {$a}';
$string['logsofbackupexecutedon'] = 'Logs of the backup executed on {$a}';
$string['nobackupsfound'] = 'There were no backups found.';
$string['nologsfound'] = 'There were no logs found for this backup.';
$string['pluginname'] = 'Backups report';
$string['strftimetime'] = '%I:%M:%S %p';
$string['viewlogs'] = 'View logs';
$string['privacy:metadata'] = 'The Backups reports plugin does not store any personal data.';
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* Settings for the backups report
*
* @package report
* @subpackage backups
* @copyright 2007 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$ADMIN->add('reports', new admin_externalpage('reportbackups', get_string('backups', 'admin'), "$CFG->wwwroot/report/backups/index.php",'moodle/backup:backupcourse'));
// no report settings
$settings = null;
+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 details.
*
* @package report
* @subpackage backups
* @copyright 2007 onwards Eloy Lafuente (stronk7) {@link http://stronk7.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_backups'; // Full name of the plugin (used for diagnostics)
+10
View File
@@ -0,0 +1,10 @@
/**
* Module to enable inline editing of a comptency grade.
*
* @module report_competency/grading_popup
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("report_competency/grading_popup",["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],(function($,notification,str,ajax,log,templates,Dialogue){var GradingPopup=function(regionSelector,userCompetencySelector){this._regionSelector=regionSelector,this._userCompetencySelector=userCompetencySelector,$(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return GradingPopup.prototype._handleClick=function(e){var cell=$(e.target).closest(this._userCompetencySelector),competencyId=$(cell).data("competencyid"),courseId=$(cell).data("courseid"),userId=$(cell).data("userid");log.debug("Clicked on cell: competencyId="+competencyId+", courseId="+courseId+", userId="+userId);var requests=ajax.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}}]);$.when(requests[0],requests[1]).then(this._contextLoaded.bind(this)).catch(notification.exception)},GradingPopup.prototype._contextLoaded=function(context){return context.displayuser=!0,M.util.js_pending("report_competency/grading_popup:_contextLoaded"),$.when(str.get_string("usercompetencysummary","report_competency"),templates.render("tool_lp/user_competency_summary_in_course",context)).then(function(title,templateData){return new Dialogue(title,templateData[0],(function(){templates.runTemplateJS(templateData[1]),M.util.js_complete("report_competency/grading_popup:_contextLoaded")}),this._refresh.bind(this),!0)}.bind(this))},GradingPopup.prototype._refresh=function(){var region=$(this._regionSelector),courseId=region.data("courseid"),moduleId=region.data("moduleid"),userId=region.data("userid");return""===moduleId&&(moduleId=0),ajax.call([{methodname:"report_competency_data_for_report",args:{courseid:courseId,userid:userId,moduleid:moduleId},done:this._pageContextLoaded.bind(this),fail:notification.exception}])},GradingPopup.prototype._pageContextLoaded=function(context){templates.render("report_competency/report",context).then(function(html,js){templates.replaceNode(this._regionSelector,html,js)}.bind(this)).catch(notification.exception)},GradingPopup.prototype._regionSelector=null,GradingPopup.prototype._userCompetencySelector=null,GradingPopup}));
//# sourceMappingURL=grading_popup.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
/**
* Module to navigation between users in a course.
*
* @module report_competency/user_course_navigation
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("report_competency/user_course_navigation",["jquery"],(function($){var UserCourseNavigation=function(userSelector,moduleSelector,baseUrl,userId,courseId,moduleId){this._baseUrl=baseUrl,this._userId=userId+"",this._courseId=courseId,this._moduleId=moduleId,$(userSelector).on("change",this._userChanged.bind(this)),$(moduleSelector).on("change",this._moduleChanged.bind(this))};return UserCourseNavigation.prototype._userChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_userChanged");var queryStr="?user="+$(e.target).val()+"&id="+this._courseId+"&mod="+this._moduleId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._moduleChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_moduleChanged");var queryStr="?mod="+$(e.target).val()+"&id="+this._courseId+"&user="+this._userId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._userId=null,UserCourseNavigation.prototype._moduleId=null,UserCourseNavigation.prototype._courseId=null,UserCourseNavigation.prototype._baseUrl=null,UserCourseNavigation}));
//# sourceMappingURL=user_course_navigation.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"user_course_navigation.min.js","sources":["../src/user_course_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Module to navigation between users in a course.\n *\n * @module report_competency/user_course_navigation\n * @copyright 2015 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n /**\n * UserCourseNavigation\n *\n * @class report_competency/user_course_navigation\n * @param {String} userSelector The selector of the user element.\n * @param {String} moduleSelector The selector of the module element.\n * @param {String} baseUrl The base url for the page (no params).\n * @param {Number} userId The course id\n * @param {Number} courseId The user id\n * @param {Number} moduleId The activity module (filter)\n */\n var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) {\n this._baseUrl = baseUrl;\n this._userId = userId + '';\n this._courseId = courseId;\n this._moduleId = moduleId;\n\n $(userSelector).on('change', this._userChanged.bind(this));\n $(moduleSelector).on('change', this._moduleChanged.bind(this));\n };\n\n /**\n * The user was changed in the select list.\n *\n * @method _userChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._userChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_userChanged');\n var newUserId = $(e.target).val();\n var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId;\n document.location = this._baseUrl + queryStr;\n };\n\n /**\n * The module was changed in the select list.\n *\n * @method _moduleChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._moduleChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_moduleChanged');\n var newModuleId = $(e.target).val();\n var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId;\n document.location = this._baseUrl + queryStr;\n };\n\n /** @property {Number} The id of the user. */\n UserCourseNavigation.prototype._userId = null;\n /** @property {Number} The id of the module. */\n UserCourseNavigation.prototype._moduleId = null;\n /** @property {Number} The id of the course. */\n UserCourseNavigation.prototype._courseId = null;\n /** @property {String} Plugin base url. */\n UserCourseNavigation.prototype._baseUrl = null;\n\n return UserCourseNavigation;\n});\n"],"names":["define","$","UserCourseNavigation","userSelector","moduleSelector","baseUrl","userId","courseId","moduleId","_baseUrl","_userId","_courseId","_moduleId","on","this","_userChanged","bind","_moduleChanged","prototype","e","M","util","js_pending","queryStr","target","val","document","location"],"mappings":";;;;;;;AAuBAA,kDAAO,CAAC,WAAW,SAASC,OAapBC,qBAAuB,SAASC,aAAcC,eAAgBC,QAASC,OAAQC,SAAUC,eACpFC,SAAWJ,aACXK,QAAUJ,OAAS,QACnBK,UAAYJ,cACZK,UAAYJ,SAEjBP,EAAEE,cAAcU,GAAG,SAAUC,KAAKC,aAAaC,KAAKF,OACpDb,EAAEG,gBAAgBS,GAAG,SAAUC,KAAKG,eAAeD,KAAKF,eAS5DZ,qBAAqBgB,UAAUH,aAAe,SAASI,GAEnDC,EAAEC,KAAKC,WAAW,6DAEdC,SAAW,SADCtB,EAAEkB,EAAEK,QAAQC,MACU,OAASX,KAAKH,UAAY,QAAUG,KAAKF,UAC/Ec,SAASC,SAAWb,KAAKL,SAAWc,UASxCrB,qBAAqBgB,UAAUD,eAAiB,SAASE,GAErDC,EAAEC,KAAKC,WAAW,+DAEdC,SAAW,QADGtB,EAAEkB,EAAEK,QAAQC,MACS,OAASX,KAAKH,UAAY,SAAWG,KAAKJ,QACjFgB,SAASC,SAAWb,KAAKL,SAAWc,UAIxCrB,qBAAqBgB,UAAUR,QAAU,KAEzCR,qBAAqBgB,UAAUN,UAAY,KAE3CV,qBAAqBgB,UAAUP,UAAY,KAE3CT,qBAAqBgB,UAAUT,SAAW,KAEnCP"}
+146
View File
@@ -0,0 +1,146 @@
// 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/>.
/**
* Module to enable inline editing of a comptency grade.
*
* @module report_competency/grading_popup
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'core/templates', 'tool_lp/dialogue'],
function($, notification, str, ajax, log, templates, Dialogue) {
/**
* GradingPopup
*
* @class report_competency/grading_popup
* @param {String} regionSelector The regionSelector
* @param {String} userCompetencySelector The userCompetencySelector
*/
var GradingPopup = function(regionSelector, userCompetencySelector) {
this._regionSelector = regionSelector;
this._userCompetencySelector = userCompetencySelector;
$(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this));
};
/**
* Get the data from the clicked cell and open the popup.
*
* @method _handleClick
* @param {Event} e The event
*/
GradingPopup.prototype._handleClick = function(e) {
var cell = $(e.target).closest(this._userCompetencySelector);
var competencyId = $(cell).data('competencyid');
var courseId = $(cell).data('courseid');
var userId = $(cell).data('userid');
log.debug('Clicked on cell: competencyId=' + competencyId + ', courseId=' + courseId + ', userId=' + userId);
var requests = ajax.call([{
methodname: 'tool_lp_data_for_user_competency_summary_in_course',
args: {userid: userId, competencyid: competencyId, courseid: courseId},
}, {
methodname: 'core_competency_user_competency_viewed_in_course',
args: {userid: userId, competencyid: competencyId, courseid: courseId},
}]);
$.when(requests[0], requests[1])
.then(this._contextLoaded.bind(this))
.catch(notification.exception);
};
/**
* We loaded the context, now render the template.
*
* @method _contextLoaded
* @param {Object} context
* @returns {Promise}
*/
GradingPopup.prototype._contextLoaded = function(context) {
// We have to display user info in popup.
context.displayuser = true;
M.util.js_pending('report_competency/grading_popup:_contextLoaded');
return $.when(
str.get_string('usercompetencysummary', 'report_competency'),
templates.render('tool_lp/user_competency_summary_in_course', context)
)
.then(function(title, templateData) {
return new Dialogue(
title,
templateData[0],
function() {
templates.runTemplateJS(templateData[1]);
M.util.js_complete('report_competency/grading_popup:_contextLoaded');
},
this._refresh.bind(this),
true
);
}.bind(this));
};
/**
* Refresh the page.
*
* @method _refresh
* @returns {Promise}
*/
GradingPopup.prototype._refresh = function() {
var region = $(this._regionSelector);
var courseId = region.data('courseid');
var moduleId = region.data('moduleid');
var userId = region.data('userid');
// The module id is expected to be an integer, so don't pass empty string.
if (moduleId === '') {
moduleId = 0;
}
return ajax.call([{
methodname: 'report_competency_data_for_report',
args: {courseid: courseId, userid: userId, moduleid: moduleId},
done: this._pageContextLoaded.bind(this),
fail: notification.exception
}]);
};
/**
* We loaded the context, now render the template.
*
* @method _pageContextLoaded
* @param {Object} context
*/
GradingPopup.prototype._pageContextLoaded = function(context) {
templates.render('report_competency/report', context)
.then(function(html, js) {
templates.replaceNode(this._regionSelector, html, js);
return;
}.bind(this))
.catch(notification.exception);
};
/** @property {String} The selector for the region with the user competencies */
GradingPopup.prototype._regionSelector = null;
/** @property {String} The selector for the region with a single user competencies */
GradingPopup.prototype._userCompetencySelector = null;
return GradingPopup;
});
@@ -0,0 +1,85 @@
// 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/>.
/**
* Module to navigation between users in a course.
*
* @module report_competency/user_course_navigation
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* UserCourseNavigation
*
* @class report_competency/user_course_navigation
* @param {String} userSelector The selector of the user element.
* @param {String} moduleSelector The selector of the module element.
* @param {String} baseUrl The base url for the page (no params).
* @param {Number} userId The course id
* @param {Number} courseId The user id
* @param {Number} moduleId The activity module (filter)
*/
var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) {
this._baseUrl = baseUrl;
this._userId = userId + '';
this._courseId = courseId;
this._moduleId = moduleId;
$(userSelector).on('change', this._userChanged.bind(this));
$(moduleSelector).on('change', this._moduleChanged.bind(this));
};
/**
* The user was changed in the select list.
*
* @method _userChanged
* @param {Event} e the event
*/
UserCourseNavigation.prototype._userChanged = function(e) {
// Note: This change causes a page reload and is intentionally not paired with a js_complete call.
M.util.js_pending('report_competency/user_course_navigation:_userChanged');
var newUserId = $(e.target).val();
var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId;
document.location = this._baseUrl + queryStr;
};
/**
* The module was changed in the select list.
*
* @method _moduleChanged
* @param {Event} e the event
*/
UserCourseNavigation.prototype._moduleChanged = function(e) {
// Note: This change causes a page reload and is intentionally not paired with a js_complete call.
M.util.js_pending('report_competency/user_course_navigation:_moduleChanged');
var newModuleId = $(e.target).val();
var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId;
document.location = this._baseUrl + queryStr;
};
/** @property {Number} The id of the user. */
UserCourseNavigation.prototype._userId = null;
/** @property {Number} The id of the module. */
UserCourseNavigation.prototype._moduleId = null;
/** @property {Number} The id of the course. */
UserCourseNavigation.prototype._courseId = null;
/** @property {String} Plugin base url. */
UserCourseNavigation.prototype._baseUrl = null;
return UserCourseNavigation;
});
+121
View File
@@ -0,0 +1,121 @@
<?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/>.
namespace report_competency;
use context_course;
use core_competency\external\user_competency_course_exporter;
use core_course\external\course_summary_exporter;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_user\external\user_summary_exporter;
use tool_lp\external\competency_summary_exporter;
/**
* This is the external API for this report.
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* Returns description of data_for_competency_frameworks_manage_page() parameters.
*
* @return external_function_parameters
*/
public static function data_for_report_parameters() {
$courseid = new external_value(
PARAM_INT,
'The course id',
VALUE_REQUIRED
);
$userid = new external_value(
PARAM_INT,
'The user id',
VALUE_REQUIRED
);
$moduleid = new external_value(
PARAM_INT,
'The module id',
VALUE_REQUIRED
);
$params = array(
'courseid' => $courseid,
'userid' => $userid,
'moduleid' => $moduleid,
);
return new external_function_parameters($params);
}
/**
* Loads the data required to render the report.
*
* @param int $courseid The course id
* @param int $userid The user id
* @param int $moduleid The module id
* @return \stdClass
*/
public static function data_for_report($courseid, $userid, $moduleid) {
global $PAGE;
$params = self::validate_parameters(
self::data_for_report_parameters(),
array(
'courseid' => $courseid,
'userid' => $userid,
'moduleid' => $moduleid
)
);
$context = context_course::instance($params['courseid']);
self::validate_context($context);
if (!is_enrolled($context, $params['userid'], 'moodle/competency:coursecompetencygradable')) {
throw new coding_exception('invaliduser');
}
$renderable = new output\report($params['courseid'], $params['userid'], $params['moduleid']);
$renderer = $PAGE->get_renderer('report_competency');
$data = $renderable->export_for_template($renderer);
return $data;
}
/**
* Returns description of data_for_report() result value.
*
* @return external_description
*/
public static function data_for_report_returns() {
return new external_single_structure(array (
'courseid' => new external_value(PARAM_INT, 'Course id'),
'user' => user_summary_exporter::get_read_structure(),
'course' => course_summary_exporter::get_read_structure(),
'usercompetencies' => new external_multiple_structure(
new external_single_structure(array(
'usercompetencycourse' => user_competency_course_exporter::get_read_structure(),
'competency' => competency_summary_exporter::get_read_structure()
))
),
'pushratingstouserplans' => new external_value(PARAM_BOOL, 'True if rating is push to user plans')
));
}
}
@@ -0,0 +1,98 @@
<?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/>.
/**
* Renderer class for report_competency
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_competency\output;
defined('MOODLE_INTERNAL') || die;
use plugin_renderer_base;
use renderable;
/**
* Renderer class for competency breakdown report
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Defer to template.
*
* @param report $page
* @return string html for the page
*/
public function render_report(report $page) {
$data = $page->export_for_template($this);
return parent::render_from_template('report_competency/report', $data);
}
/**
* Defer to template.
*
* @param user_course_navigation $nav
* @return string
*/
public function render_user_course_navigation(user_course_navigation $nav) {
$data = $nav->export_for_template($this);
return parent::render_from_template('report_competency/user_course_navigation', $data);
}
/**
* Output a nofication.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_message($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
return $this->render($n);
}
/**
* Output an error notification.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_problem($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
return $this->render($n);
}
/**
* Output a success notification.
*
* @param string $message the message to print out
* @return string HTML fragment.
* @see \core\output\notification
*/
public function notify_success($message) {
$n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
return $this->render($n);
}
}
+157
View File
@@ -0,0 +1,157 @@
<?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/>.
/**
* Class containing data for learning plan template competencies page
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_competency\output;
use context_course;
use renderable;
use core_user;
use templatable;
use renderer_base;
use moodle_url;
use stdClass;
use core_competency\api;
use core_competency\external\user_competency_course_exporter;
use core_user\external\user_summary_exporter;
use core_competency\external\performance_helper;
use core_competency\url;
use core_competency\user_competency;
use tool_lp\external\competency_summary_exporter;
use core_course\external\course_summary_exporter;
/**
* Class containing data for learning plan template competencies page
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report implements renderable, templatable {
/** @var context $context */
protected $context;
/** @var int $courseid */
protected $courseid;
/** @var int $moduleid */
protected $moduleid;
/** @var array $competencies */
protected $competencies;
/** @var int The user id */
protected $userid;
/**
* Construct this renderable.
*
* @param int $courseid The course id
* @param int $userid The user id
* @param int $moduleid The module id
*/
public function __construct($courseid, $userid, $moduleid) {
$this->courseid = $courseid;
$this->userid = $userid;
$this->moduleid = $moduleid;
$this->context = context_course::instance($courseid);
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $DB;
$data = new stdClass();
$data->courseid = $this->courseid;
$data->moduleid = $this->moduleid;
if (empty($data->moduleid)) {
$data->moduleid = 0;
}
$course = $DB->get_record('course', array('id' => $this->courseid));
$coursecontext = context_course::instance($course->id);
$exporter = new course_summary_exporter($course, array('context' => $coursecontext));
$coursecompetencysettings = api::read_course_competency_settings($course->id);
$data->pushratingstouserplans = $coursecompetencysettings->get('pushratingstouserplans');
$data->course = $exporter->export($output);
$data->usercompetencies = array();
$user = core_user::get_user($this->userid);
$exporter = new user_summary_exporter($user);
$data->user = $exporter->export($output);
$data->usercompetencies = array();
$coursecompetencies = api::list_course_competencies($this->courseid);
$usercompetencycourses = api::list_user_competencies_in_course($this->courseid, $user->id);
if ($this->moduleid > 0) {
$modulecompetencies = api::list_course_module_competencies_in_course_module($this->moduleid);
foreach ($usercompetencycourses as $ucid => $usercompetency) {
$found = false;
foreach ($modulecompetencies as $mcid => $modulecompetency) {
if ($modulecompetency->get('competencyid') == $usercompetency->get('competencyid')) {
$found = true;
break;
}
}
if (!$found) {
// We need to filter out this competency.
unset($usercompetencycourses[$ucid]);
}
}
}
$helper = new performance_helper();
foreach ($usercompetencycourses as $usercompetencycourse) {
$onerow = new stdClass();
$competency = null;
foreach ($coursecompetencies as $coursecompetency) {
if ($coursecompetency['competency']->get('id') == $usercompetencycourse->get('competencyid')) {
$competency = $coursecompetency['competency'];
break;
}
}
if (!$competency) {
continue;
}
$framework = $helper->get_framework_from_competency($competency);
$scale = $helper->get_scale_from_competency($competency);
$exporter = new user_competency_course_exporter($usercompetencycourse, array('scale' => $scale));
$record = $exporter->export($output);
$onerow->usercompetencycourse = $record;
$exporter = new competency_summary_exporter(null, array(
'competency' => $competency,
'framework' => $framework,
'context' => $framework->get_context(),
'relatedcompetencies' => array(),
'linkedcourses' => array()
));
$onerow->competency = $exporter->export($output);
array_push($data->usercompetencies, $onerow);
}
return $data;
}
}
@@ -0,0 +1,146 @@
<?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/>.
/**
* User navigation class.
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_competency\output;
use renderable;
use renderer_base;
use templatable;
use context_course;
use core_user\external\user_summary_exporter;
use core_course\external\course_module_summary_exporter;
use stdClass;
/**
* User course navigation class.
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_course_navigation implements renderable, templatable {
/** @var userid */
protected $userid;
/** @var courseid */
protected $courseid;
/** @var moduleid */
protected $moduleid;
/** @var baseurl */
protected $baseurl;
/**
* Construct.
*
* @param int $userid
* @param int $courseid
* @param int $moduleid
* @param string $baseurl
*/
public function __construct($userid, $courseid, $baseurl, $moduleid) {
$this->userid = $userid;
$this->courseid = $courseid;
$this->moduleid = $moduleid;
$this->baseurl = $baseurl;
}
/**
* Export the data.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output) {
global $CFG, $DB, $SESSION, $PAGE;
$context = context_course::instance($this->courseid);
$data = new stdClass();
$data->userid = $this->userid;
$data->courseid = $this->courseid;
$data->moduleid = $this->moduleid;
if (empty($data->moduleid)) {
// Moduleid is optional.
$data->moduleid = 0;
}
$data->baseurl = $this->baseurl;
$data->groupselector = '';
if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
$context)) {
$course = $DB->get_record('course', array('id' => $this->courseid));
$currentgroup = groups_get_course_group($course, true);
if ($currentgroup !== false) {
$select = groups_allgroups_course_menu($course, $PAGE->url, true, $currentgroup);
$data->groupselector = $select;
}
// Fetch showactive.
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
$showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context);
// Fetch current active group.
$groupmode = groups_get_course_groupmode($course);
$users = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup,
'u.*', null, 0, 0, $showonlyactiveenrol);
$data->users = array();
foreach ($users as $user) {
$data->users[] = (object)[
'id' => $user->id,
'fullname' => fullname($user, has_capability('moodle/site:viewfullnames', $context)),
'selected' => $user->id == $this->userid
];
}
$data->hasusers = true;
$data->hasmodules = true;
$data->modules = array();
$empty = (object)['id' => 0, 'name' => get_string('nofiltersapplied')];
$data->modules[] = $empty;
$modinfo = get_fast_modinfo($this->courseid);
foreach ($modinfo->get_cms() as $cm) {
if ($cm->uservisible) {
$exporter = new course_module_summary_exporter(null, ['cm' => $cm]);
$module = $exporter->export($output);
if ($module->id == $this->moduleid) {
$module->selected = true;
}
$data->modules[] = $module;
$data->hasmodules = true;
}
}
} else {
$data->users = array();
$data->hasusers = false;
}
return $data;
}
}
@@ -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/>.
/**
* Privacy Subsystem implementation for report_competency.
*
* @package report_competency
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_competency\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_competency implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+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/>.
/**
* Competency report webservice functions
*
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = array(
// Learning plan related functions.
'report_competency_data_for_report' => array(
'classname' => 'report_competency\external',
'methodname' => 'data_for_report',
'classpath' => '',
'description' => 'Load the data for the competency report in a course.',
'type' => 'read',
'capabilities' => 'moodle/competency:coursecompetencyview',
'ajax' => true,
)
);
+107
View File
@@ -0,0 +1,107 @@
<?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 page lets users to manage site wide competencies.
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\report_helper;
require_once(__DIR__ . '/../../config.php');
$id = required_param('id', PARAM_INT);
$params = array('id' => $id);
$course = $DB->get_record('course', $params, '*', MUST_EXIST);
require_login($course);
$context = context_course::instance($course->id);
$currentuser = optional_param('user', null, PARAM_INT);
$currentmodule = optional_param('mod', null, PARAM_INT);
if ($currentmodule > 0) {
$cm = get_coursemodule_from_id('', $currentmodule, 0, false, MUST_EXIST);
$context = context_module::instance($cm->id);
}
// Fetch current active group.
$groupmode = groups_get_course_groupmode($course);
$currentgroup = groups_get_course_group($course, true);
if (empty($currentuser)) {
$gradable = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup, 'u.id', null, 0, 1);
if (empty($gradable)) {
$currentuser = 0;
} else {
$currentuser = array_pop($gradable)->id;
}
} else {
$gradable = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup, 'u.id');
if (count($gradable) == 0) {
$currentuser = 0;
} else if (!in_array($currentuser, array_keys($gradable))) {
$currentuser = array_shift($gradable)->id;
}
}
$urlparams = array('id' => $id);
$navurl = new moodle_url('/report/competency/index.php', $urlparams);
$urlparams['user'] = $currentuser;
$urlparams['mod'] = $currentmodule;
$url = new moodle_url('/report/competency/index.php', $urlparams);
$title = get_string('pluginname', 'report_competency');
$coursename = format_string($course->fullname, true, array('context' => $context));
$PAGE->navigation->override_active_url($navurl);
$PAGE->set_url($url);
$PAGE->set_title($title);
$PAGE->set_heading($coursename);
$PAGE->set_pagelayout('incourse');
$output = $PAGE->get_renderer('report_competency');
echo $output->header();
$pluginname = get_string('pluginname', 'report_competency');
report_helper::print_report_selector($pluginname);
$baseurl = new moodle_url('/report/competency/index.php');
$nav = new \report_competency\output\user_course_navigation($currentuser, $course->id, $baseurl, $currentmodule);
$top = $output->render($nav);
if ($currentuser > 0) {
$user = core_user::get_user($currentuser);
$usercontext = context_user::instance($currentuser);
$userheading = array(
'heading' => fullname($user, has_capability('moodle/site:viewfullnames', $context)),
'user' => $user,
'usercontext' => $usercontext
);
if ($currentmodule > 0) {
$title = get_string('filtermodule', 'report_competency', format_string($cm->name));
}
$top .= $output->context_header($userheading, 3);
}
echo $output->container($top, 'clearfix');
if ($currentuser > 0) {
$page = new \report_competency\output\report($course->id, $currentuser, $currentmodule);
echo $output->render($page);
} else {
echo $output->container('', 'clearfix');
echo $output->notify_problem(get_string('noparticipants', 'tool_lp'));
}
echo $output->footer();
@@ -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/>.
/**
* Strings for component 'report_competency', language 'en'
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['competency'] = 'Competency';
$string['coursecompetencybreakdownsummary'] = 'A report of all the students in the course, and their progress towards the course competencies';
$string['notrated'] = 'Not rated';
$string['pluginname'] = 'Competency breakdown';
$string['rating'] = 'Rating';
$string['filtermodule'] = 'Competencies linked to "{$a}"';
$string['usercompetencysummary'] = 'User competency summary';
$string['privacy:metadata'] = 'The Competency breakdown plugin does not store any personal data.';
+66
View File
@@ -0,0 +1,66 @@
<?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/>.
/**
* Public API of the competency report.
*
* Defines the APIs used by competency reports
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @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_competency_extend_navigation_course($navigation, $course, $context) {
if (!get_config('core_competency', 'enabled')) {
return;
}
if (has_capability('moodle/competency:coursecompetencyview', $context)) {
$url = new moodle_url('/report/competency/index.php', array('id' => $course->id));
$name = get_string('pluginname', 'report_competency');
$navigation->add($name, $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
}
}
/**
* This function extends the navigation with the report items
*
* @param navigation_node $navigation The navigation node to extend
* @param cminfo $cm The course module.
*/
function report_competency_extend_navigation_module($navigation, $cm) {
if (!get_config('core_competency', 'enabled')) {
return;
}
if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
context_course::instance($cm->course))) {
$url = new moodle_url('/report/competency/index.php', array('id' => $cm->course, 'mod' => $cm->id));
$name = get_string('pluginname', 'report_competency');
$navigation->add($name, $url, navigation_node::TYPE_SETTING, null, 'competencybreakdown',
new pix_icon('i/competencies', ''))->set_show_in_secondary_navigation(false);
}
}
@@ -0,0 +1,82 @@
{{!
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/>.
}}
{{!
@template report_competency/report
Moodle template competency breakdown report.
This template includes ajax functionality, so it cannot be shown in the template library.
}}
<div data-region="competency-breakdown-report" data-courseid="{{course.id}}" data-userid="{{user.id}}" data-moduleid="{{moduleid}}">
<div data-region="configurecoursecompetencies">
{{#pushratingstouserplans}}
<p class="alert">
{{#str}}coursecompetencyratingsarepushedtouserplans, tool_lp{{/str}}
</p>
{{/pushratingstouserplans}}
{{^pushratingstouserplans}}
<p class="alert alert-info">
{{#str}}coursecompetencyratingsarenotpushedtouserplans, tool_lp{{/str}}
</p>
{{/pushratingstouserplans}}
</div>
<div class="row">
<span class="col-md-6">
<table class="table table-bordered">
<summary class="accesshide">
<p>{{#str}}coursecompetencybreakdownsummary, report_competency{{/str}}</p>
</summary>
<tr>
<th scope="col">
<span>{{#str}}competency, report_competency{{/str}}</span>
</th>
<th scope="col">
<span>{{#str}}rating, report_competency{{/str}}</span>
</th>
</tr>
{{#usercompetencies}}
<tr>
{{#competency}}
<td>
<a href="#" data-action="competency-dialogue" data-id="{{competency.id}}">
{{{competency.shortname}}} <em data-id="{{competency.id}}">{{competency.idnumber}}</em>
</a>
</td>
{{/competency}}
{{#usercompetencycourse}}
<td class="alert {{#proficiency}}alert-success{{/proficiency}}"
data-user-competency="true"
data-userid="{{user.id}}"
data-competencyid="{{competencyid}}"
data-courseid="{{course.id}}">
{{> report_competency/user_competency_summary}}
</td>
{{/usercompetencycourse}}
</tr>
{{/usercompetencies}}
</table>
</span>
</div>
</div>
{{#js}}
require(['tool_lp/competencydialogue'], function(Compdialogue) {
Compdialogue.init();
});
require(['report_competency/grading_popup'], function(Popup) {
(new Popup('[data-region=competency-breakdown-report]', '[data-user-competency=true]'));
});
{{/js}}
@@ -0,0 +1,4 @@
<a href="#" title="{{#str}}usercompetencysummary, report_competency{{/str}}">
{{#grade}}{{gradename}}{{/grade}}
{{^grade}}{{#str}}notrated, report_competency{{/str}}{{/grade}}
</a>
@@ -0,0 +1,64 @@
{{!
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/>.
}}
{{!
@template report_competency/user_course_navigation
Moodle navigation control allowing to jump to a user or filter to an activity.
Example context (json):
{ "hasusers": false, "hasmodules": false}
}}
<div class="float-right border p-2 mb-2">
<p>{{{groupselector}}}</p>
<form class="user-competency-course-navigation">
<input type="hidden" name="user" value="{{userid}}"/>
<input type="hidden" name="id" value="{{courseid}}"/>
<input type="hidden" name="mod" value="{{moduleid}}"/>
{{#hasusers}}
<span>
<label for="user-nav-{{uniqid}}" class="accesshide">{{#str}}jumptouser, tool_lp{{/str}}</label>
<select id="user-nav-{{uniqid}}">
{{#users}}
<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{fullname}}</option>
{{/users}}
</select>
</span>
{{/hasusers}}
{{#hasmodules}}
<span>
<label for="module-nav-{{uniqid}}" class="accesshide">{{#str}}filterbyactivity, tool_lp{{/str}}</label>
<select id="module-nav-{{uniqid}}">
{{#modules}}
<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{name}}</option>
{{/modules}}
</select>
</span>
{{/hasmodules}}
</form>
</div>
{{#js}}
require(['core/form-autocomplete', 'report_competency/user_course_navigation'], function(autocomplete, nav) {
(new nav('#user-nav-{{uniqid}}', '#module-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{courseid}}, {{moduleid}}));
{{#hasusers}}
autocomplete.enhance('#user-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptouser, tool_lp{{/ str }}{{/ quote }});
{{/hasusers}}
{{#hasmodules}}
autocomplete.enhance('#module-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}filterbyactivity, tool_lp{{/ str }}{{/ quote }});
{{/hasmodules}}
});
{{/js}}
@@ -0,0 +1,79 @@
<?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/>.
/**
* Behat competency report definitions.
*
* @package report_competency
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
/**
* Competency report definitions.
*
* @package report_competency
* @category test
* @copyright 2022 Noel De Martin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_report_competency extends behat_base {
/**
* Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
*
* Recognised page names are:
* | pagetype | name meaning | description |
* | Breakdown | Course name | The course competencies breakdown page |
*
* @param string $page identifies which type of page this is, e.g. 'Breakdown'.
* @param string $identifier identifies the particular page, e.g. 'C1'.
* @return moodle_url the corresponding URL.
* @throws Exception with a meaningful error message if the specified page cannot be found.
*/
protected function resolve_page_instance_url(string $page, string $identifier): moodle_url {
switch (strtolower($page)) {
case 'breakdown':
$courseid = $this->get_course_id($identifier);
return new moodle_url('/report/competency/index.php', [
'id' => $courseid,
]);
default:
throw new Exception("Unrecognised page type '{$page}'");
}
}
/**
* Return a list of the exact named selectors for the component.
*
* @return behat_component_named_selector[]
*/
public static function get_exact_named_selectors(): array {
return [
new behat_component_named_selector('breakdown', [
"//*[@data-region='competency-breakdown-report']//table".
"//tr[contains(., //a[@data-action='competency-dialogue'][contains(., %locator%)])]",
]),
new behat_component_named_selector('breakdown rating', [
"//td[position()=2][contains(., //a[@title='User competency summary'][contains(., %locator%)])]",
]),
];
}
}
@@ -0,0 +1,74 @@
@report @javascript @report_competency
Feature: See the competencies for an activity
As a competency grader
In order to perform mark all competencies for an activity
I need to see the competencies linked to one activity in the breakdown report.
Background:
Given the following lp "frameworks" exist:
| shortname | idnumber |
| Test-Framework | ID-FW1 |
And the following lp "competencies" exist:
| shortname | framework |
| Test-Comp1 | ID-FW1 |
| Test-Comp2 | ID-FW1 |
Given the following "courses" exist:
| shortname | fullname |
| C1 | Course 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic |
| student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham |
| student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| page | PageName1 | PageDesc1 | C1 | PAGE1 |
And the following config values are set as admin:
| fullnamedisplay | firstname |
| alternativefullnameformat | middlename, alternatename, firstname, lastname |
And I log in as "admin"
And I am on site homepage
And I follow "Course 1"
And I navigate to "Competencies" in current page administration
And I press "Add competencies to course"
And "Competency picker" "dialogue" should be visible
And I select "Test-Comp1" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
And I press "Add competencies to course"
And "Competency picker" "dialogue" should be visible
And I select "Test-Comp2" of the competency tree
And I click on "Add" "button" in the "Competency picker" "dialogue"
And I am on the PageName1 "page activity editing" page
And I click on "Expand all" "link" in the "region-main" "region"
And I set the field "Course competencies" to "Test-Comp1"
And I press "Save and return to course"
@javascript
Scenario: Go to the competency breakdown report
When I navigate to "Reports" in current page administration
And I click on "Competency breakdown" "link"
And I set the field "Filter competencies by resource or activity" to "PageName1"
Then I should see "Test-Comp1"
And I should not see "Test-Comp2"
And I should see "Ann, Jill, Grainne, Beauchamp"
And I should see "Ann, Jill, Grainne, Beauchamp" in the ".form-autocomplete-selection" "css_element"
And I open the autocomplete suggestions list
And I should see "Jane, Nina, Niamh, Cholmondely" in the ".form-autocomplete-suggestions" "css_element"
And I click on "Not rated" "link"
And I click on "Rate" "button"
And I set the field "Rating" to "A"
And I click on "Rate" "button" in the ".competency-grader" "css_element"
And I click on "Close" "button" in the "User competency summary" "dialogue"
And I click on "PageName1" "autocomplete_selection"
And I should see "Test-Comp1"
And I should see "Test-Comp2"
@accessibility
Scenario: Evaluate the accessibility of the user competency summary dialogue
Given I navigate to "Reports" in current page administration
When I click on "Competency breakdown" "link"
And I click on "Not rated" "link"
And the page should meet accessibility standards
@@ -0,0 +1,26 @@
@report @report_competency
Feature: In a course administration page, navigate through report page, test for course competency page
In order to navigate through report page
As an admin
Go to course administration -> reports -> competency breackdown
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| admin | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario: Selector should be available in the course competency page
Given I log in as "admin"
And I am on "Course 1" course homepage
When I navigate to "Reports" in current page administration
And I click on "Competency breakdown" "link"
Then "Report" "field" should exist in the "tertiary-navigation" "region"
And I should see "Competency breakdown" in the "tertiary-navigation" "region"
+33
View File
@@ -0,0 +1,33 @@
<?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 version info
*
* @package report_competency
* @copyright 2015 Damyon Wiese
* @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_competency'; // Full name of the plugin (used for diagnostics).
$plugin->dependencies = [
'tool_lp' => ANY_VERSION,
];
@@ -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_completion report viewed event.
*
* @package report_completion
* @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_completion\event;
defined('MOODLE_INTERNAL') || die();
/**
* The report_completion report viewed event class.
*
* @package report_completion
* @since Moodle 2.7
* @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @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_OTHER;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventreportviewed', 'report_completion');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the completion report for the course with id '$this->courseid'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/report/completion/index.php', array('course' => $this->courseid));
}
/**
* custom validations.
*
* @throws \coding_exception when validation fails.
* @return void
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context level must be CONTEXT_COURSE.');
}
}
}
@@ -0,0 +1,92 @@
<?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_completion user report viewed event.
*
* @package report_completion
* @copyright 2014 onwards Ankit Agarwal<ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_completion\event;
defined('MOODLE_INTERNAL') || die();
/**
* The report_completion user report viewed event class.
*
* @package report_completion
* @since Moodle 2.7
* @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @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_OTHER;
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventuserreportviewed', 'report_completion');
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the user completion report for the user with id '$this->relateduserid'.";
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/report/completion/user.php', array('course' => $this->courseid, 'id' => $this->relateduserid));
}
/**
* custom validations.
*
* @throws \coding_exception when validation fails.
* @return void
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context level must be CONTEXT_COURSE.');
}
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
}
}
@@ -0,0 +1,45 @@
<?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 provider implementation for report_completion.
*
* @package report_completion
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_completion\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider implementation for report_completion.
*
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+42
View File
@@ -0,0 +1,42 @@
<?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/>.
/**
* Capabilities
*
* @package report_completion
* @copyright 2009 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'report/completion:view' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'coursereport/completion: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 completion
* @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_completion_install() {
global $DB;
}
+751
View File
@@ -0,0 +1,751 @@
<?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/>.
/**
* Course completion progress report
*
* @package report
* @subpackage completion
* @copyright 2009 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\report_helper;
require_once(__DIR__.'/../../config.php');
require_once("{$CFG->libdir}/completionlib.php");
/**
* Configuration
*/
define('COMPLETION_REPORT_PAGE', 25);
define('COMPLETION_REPORT_COL_TITLES', true);
/*
* Setup page, check permissions
*/
// Get course
$courseid = required_param('course', PARAM_INT);
$format = optional_param('format','',PARAM_ALPHA);
$sort = optional_param('sort','',PARAM_ALPHA);
$edituser = optional_param('edituser', 0, PARAM_INT);
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
$context = context_course::instance($course->id);
$url = new moodle_url('/report/completion/index.php', array('course'=>$course->id));
$PAGE->set_url($url);
$PAGE->set_pagelayout('report');
$firstnamesort = ($sort == 'firstname');
$excel = ($format == 'excelcsv');
$csv = ($format == 'csv' || $excel);
if ($csv) {
$dateformat = "%F %T";
} else {
$dateformat = get_string('strftimedatetimeshort', 'langconfig');
}
// Load CSV library
if ($csv) {
require_once("{$CFG->libdir}/csvlib.class.php");
}
// Paging
$start = optional_param('start', 0, PARAM_INT);
$sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS);
$silast = optional_param('silast', 'all', PARAM_NOTAGS);
// Whether to show extra user identity information.
$extrafields = \core_user\fields::get_identity_fields($context, true);
$leftcols = 1 + count($extrafields);
// Check permissions
require_login($course);
require_capability('report/completion:view', $context);
// Get group mode
$group = groups_get_course_group($course, true); // Supposed to verify group
if ($group === 0 && $course->groupmode == SEPARATEGROUPS) {
require_capability('moodle/site:accessallgroups',$context);
}
/**
* Load data
*/
// Retrieve course_module data for all modules in the course
$modinfo = get_fast_modinfo($course);
// Get criteria for course
$completion = new completion_info($course);
if (!$completion->has_criteria()) {
throw new \moodle_exception('nocriteriaset', 'completion', $CFG->wwwroot.'/course/report.php?id='.$course->id);
}
// Get criteria and put in correct order
$criteria = array();
foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_COURSE) as $criterion) {
$criteria[] = $criterion;
}
foreach ($completion->get_criteria(COMPLETION_CRITERIA_TYPE_ACTIVITY) as $criterion) {
$criteria[] = $criterion;
}
foreach ($completion->get_criteria() as $criterion) {
if (!in_array($criterion->criteriatype, array(
COMPLETION_CRITERIA_TYPE_COURSE, COMPLETION_CRITERIA_TYPE_ACTIVITY))) {
$criteria[] = $criterion;
}
}
// Can logged in user mark users as complete?
// (if the logged in user has a role defined in the role criteria)
$allow_marking = false;
$allow_marking_criteria = null;
if (!$csv) {
// Get role criteria
$rcriteria = $completion->get_criteria(COMPLETION_CRITERIA_TYPE_ROLE);
if (!empty($rcriteria)) {
foreach ($rcriteria as $rcriterion) {
$users = get_role_users($rcriterion->role, $context, true);
// If logged in user has this role, allow marking complete
if ($users && in_array($USER->id, array_keys($users))) {
$allow_marking = true;
$allow_marking_criteria = $rcriterion->id;
break;
}
}
}
}
/*
* Setup page header
*/
if ($csv) {
$shortname = format_string($course->shortname, true, array('context' => $context));
$shortname = preg_replace('/[^a-z0-9-]/', '_',core_text::strtolower(strip_tags($shortname)));
$export = new csv_export_writer('comma', '"', 'application/download', $excel);
$export->set_filename('completion-'.$shortname);
} else {
// Navigation and header
$strcompletion = get_string('coursecompletion');
$PAGE->set_title($strcompletion);
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
// Print the selected dropdown.
$pluginname = get_string('pluginname', 'report_completion');
report_helper::print_report_selector($pluginname);
// Handle groups (if enabled)
groups_print_course_menu($course, $CFG->wwwroot.'/report/completion/index.php?course='.$course->id);
}
if ($sifirst !== 'all') {
set_user_preference('ifirst', $sifirst);
}
if ($silast !== 'all') {
set_user_preference('ilast', $silast);
}
if (!empty($USER->preference['ifirst'])) {
$sifirst = $USER->preference['ifirst'];
} else {
$sifirst = 'all';
}
if (!empty($USER->preference['ilast'])) {
$silast = $USER->preference['ilast'];
} else {
$silast = 'all';
}
// Generate where clause
$where = array();
$where_params = array();
if ($sifirst !== 'all') {
$where[] = $DB->sql_like('u.firstname', ':sifirst', false, false);
$where_params['sifirst'] = $sifirst.'%';
}
if ($silast !== 'all') {
$where[] = $DB->sql_like('u.lastname', ':silast', false, false);
$where_params['silast'] = $silast.'%';
}
// Get user match count
$total = $completion->get_num_tracked_users(implode(' AND ', $where), $where_params, $group);
// Total user count
$grandtotal = $completion->get_num_tracked_users('', array(), $group);
// If no users in this course what-so-ever
if (!$grandtotal) {
echo $OUTPUT->container(get_string('err_nousers', 'completion'), 'errorbox errorboxcontent');
echo $OUTPUT->footer();
exit;
}
// Get user data
$progress = array();
if ($total) {
$progress = $completion->get_progress_all(
implode(' AND ', $where),
$where_params,
$group,
$firstnamesort ? 'u.firstname ASC' : 'u.lastname ASC',
$csv ? 0 : COMPLETION_REPORT_PAGE,
$csv ? 0 : $start,
$context
);
}
// Build link for paging
$link = $CFG->wwwroot.'/report/completion/index.php?course='.$course->id;
if (strlen($sort)) {
$link .= '&amp;sort='.$sort;
}
$link .= '&amp;start=';
$pagingbar = '';
// Initials bar.
$prefixfirst = 'sifirst';
$prefixlast = 'silast';
$pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial', get_string('firstname'), $prefixfirst, $url);
$pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $url);
// Do we need a paging bar?
if ($total > COMPLETION_REPORT_PAGE) {
// Paging bar
$pagingbar .= '<div class="paging">';
$pagingbar .= get_string('page').': ';
$sistrings = array();
if ($sifirst != 'all') {
$sistrings[] = "sifirst={$sifirst}";
}
if ($silast != 'all') {
$sistrings[] = "silast={$silast}";
}
$sistring = !empty($sistrings) ? '&amp;'.implode('&amp;', $sistrings) : '';
// Display previous link
if ($start > 0) {
$pstart = max($start - COMPLETION_REPORT_PAGE, 0);
$pagingbar .= "(<a class=\"previous\" href=\"{$link}{$pstart}{$sistring}\">".get_string('previous').'</a>)&nbsp;';
}
// Create page links
$curstart = 0;
$curpage = 0;
while ($curstart < $total) {
$curpage++;
if ($curstart == $start) {
$pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
}
else {
$pagingbar .= "&nbsp;<a href=\"{$link}{$curstart}{$sistring}\">$curpage</a>&nbsp;";
}
$curstart += COMPLETION_REPORT_PAGE;
}
// Display next link
$nstart = $start + COMPLETION_REPORT_PAGE;
if ($nstart < $total) {
$pagingbar .= "&nbsp;(<a class=\"next\" href=\"{$link}{$nstart}{$sistring}\">".get_string('next').'</a>)';
}
$pagingbar .= '</div>';
}
/*
* Draw table header
*/
// Start of table
if (!$csv) {
print '<br class="clearer"/>'; // ugh
$total_header = ($total == $grandtotal) ? $total : "{$total}/{$grandtotal}";
echo $OUTPUT->heading(get_string('allparticipants').": {$total_header}", 3);
print $pagingbar;
if (!$total) {
echo $OUTPUT->notification(get_string('nothingtodisplay'), 'info', false);
echo $OUTPUT->footer();
exit;
}
print '<table id="completion-progress" class="table table-bordered generaltable flexible boxaligncenter
completionreport" cellpadding="5" border="1">';
// Print criteria group names
print PHP_EOL.'<thead><tr style="vertical-align: top">';
echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
get_string('criteriagroup', 'completion') . '</th>';
$current_group = false;
$col_count = 0;
for ($i = 0; $i <= count($criteria); $i++) {
if (isset($criteria[$i])) {
$criterion = $criteria[$i];
if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
++$col_count;
continue;
}
}
// Print header cell
if ($col_count) {
print '<th scope="col" colspan="'.$col_count.'" class="colheader criteriagroup">'.$current_group->get_type_title().'</th>';
}
if (isset($criteria[$i])) {
// Move to next criteria type
$current_group = $criterion;
$col_count = 1;
}
}
// Overall course completion status
print '<th style="text-align: center;">'.get_string('course').'</th>';
print '</tr>';
// Print aggregation methods
print PHP_EOL.'<tr style="vertical-align: top">';
echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
get_string('aggregationmethod', 'completion').'</th>';
$current_group = false;
$col_count = 0;
for ($i = 0; $i <= count($criteria); $i++) {
if (isset($criteria[$i])) {
$criterion = $criteria[$i];
if ($current_group && $criterion->criteriatype === $current_group->criteriatype) {
++$col_count;
continue;
}
}
// Print header cell
if ($col_count) {
$has_agg = array(
COMPLETION_CRITERIA_TYPE_COURSE,
COMPLETION_CRITERIA_TYPE_ACTIVITY,
COMPLETION_CRITERIA_TYPE_ROLE,
);
if (in_array($current_group->criteriatype, $has_agg)) {
// Try load a aggregation method
$method = $completion->get_aggregation_method($current_group->criteriatype);
$method = $method == 1 ? get_string('all') : get_string('any');
} else {
$method = '-';
}
print '<th scope="col" colspan="'.$col_count.'" class="colheader aggheader">'.$method.'</th>';
}
if (isset($criteria[$i])) {
// Move to next criteria type
$current_group = $criterion;
$col_count = 1;
}
}
// Overall course aggregation method
print '<th scope="col" class="colheader aggheader aggcriteriacourse">';
// Get course aggregation
$method = $completion->get_aggregation_method();
print $method == 1 ? get_string('all') : get_string('any');
print '</th>';
print '</tr>';
// Print criteria titles
if (COMPLETION_REPORT_COL_TITLES) {
print PHP_EOL.'<tr>';
echo '<th scope="row" class="rowheader" colspan="' . $leftcols . '">' .
get_string('criteria', 'completion') . '</th>';
foreach ($criteria as $criterion) {
// Get criteria details
$details = $criterion->get_title_detailed();
print '<th scope="col" class="colheader criterianame">';
print '<div class="rotated-text-container"><span class="rotated-text">'.$details.'</span></div>';
print '</th>';
}
// Overall course completion status
print '<th scope="col" class="colheader criterianame">';
print '<div class="rotated-text-container"><span class="rotated-text">'.get_string('coursecomplete', 'completion').'</span></div>';
print '</th></tr>';
}
// Print user heading and icons
print '<tr>';
// User heading / sort option
print '<th scope="col" class="completion-sortchoice" style="clear: both;">';
$sistring = "&amp;silast={$silast}&amp;sifirst={$sifirst}";
if ($firstnamesort) {
print
get_string('firstname')." / <a href=\"./index.php?course={$course->id}{$sistring}\">".
get_string('lastname').'</a>';
} else {
print "<a href=\"./index.php?course={$course->id}&amp;sort=firstname{$sistring}\">".
get_string('firstname').'</a> / '.
get_string('lastname');
}
print '</th>';
// Print user identity columns
foreach ($extrafields as $field) {
echo '<th scope="col" class="completion-identifyfield">' .
\core_user\fields::get_display_name($field) . '</th>';
}
///
/// Print criteria icons
///
foreach ($criteria as $criterion) {
// Generate icon details
$iconlink = '';
$iconalt = ''; // Required
$iconattributes = array('class' => 'icon');
switch ($criterion->criteriatype) {
case COMPLETION_CRITERIA_TYPE_ACTIVITY:
// Display icon
$iconlink = $CFG->wwwroot.'/mod/'.$criterion->module.'/view.php?id='.$criterion->moduleinstance;
$iconattributes['title'] = $modinfo->cms[$criterion->moduleinstance]->get_formatted_name();
$iconalt = get_string('modulename', $criterion->module);
break;
case COMPLETION_CRITERIA_TYPE_COURSE:
// Load course
$crs = $DB->get_record('course', array('id' => $criterion->courseinstance));
// Display icon
$iconlink = $CFG->wwwroot.'/course/view.php?id='.$criterion->courseinstance;
$iconattributes['title'] = format_string($crs->fullname, true, array('context' => context_course::instance($crs->id, MUST_EXIST)));
$iconalt = format_string($crs->shortname, true, array('context' => context_course::instance($crs->id)));
break;
case COMPLETION_CRITERIA_TYPE_ROLE:
// Load role
$role = $DB->get_record('role', array('id' => $criterion->role));
// Display icon
$iconalt = $role->name;
break;
}
// Create icon alt if not supplied
if (!$iconalt) {
$iconalt = $criterion->get_title();
}
// Print icon and cell
print '<th class="criteriaicon">';
print ($iconlink ? '<a href="'.$iconlink.'" title="'.$iconattributes['title'].'">' : '');
print $OUTPUT->render($criterion->get_icon($iconalt, $iconattributes));
print ($iconlink ? '</a>' : '');
print '</th>';
}
// Overall course completion status
print '<th class="criteriaicon">';
print $OUTPUT->pix_icon('i/course', get_string('coursecomplete', 'completion'));
print '</th>';
print '</tr></thead>';
echo '<tbody>';
} else {
// The CSV headers
$row = array();
$row[] = get_string('id', 'report_completion');
$row[] = get_string('name', 'report_completion');
foreach ($extrafields as $field) {
$row[] = \core_user\fields::get_display_name($field);
}
// Add activity headers
foreach ($criteria as $criterion) {
// Handle activity completion differently
if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// Load activity
$mod = $criterion->get_mod_instance();
$row[] = $formattedname = format_string($mod->name, true,
array('context' => context_module::instance($criterion->moduleinstance)));
$row[] = $formattedname . ' - ' . get_string('completiondate', 'report_completion');
}
else {
// Handle all other criteria
$row[] = strip_tags($criterion->get_title_detailed());
}
}
$row[] = get_string('coursecomplete', 'completion');
$export->add_data($row);
}
///
/// Display a row for each user
///
foreach ($progress as $user) {
// User name
if ($csv) {
$row = array();
$row[] = $user->id;
$row[] = fullname($user, has_capability('moodle/site:viewfullnames', $context));
foreach ($extrafields as $field) {
$row[] = $user->{$field};
}
} else {
print PHP_EOL.'<tr id="user-'.$user->id.'">';
if (completion_can_view_data($user->id, $course)) {
$userurl = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id, 'user' => $user->id));
} else {
$userurl = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $course->id));
}
print '<th scope="row"><a href="' . $userurl->out() . '">' .
fullname($user, has_capability('moodle/site:viewfullnames', $context)) . '</a></th>';
foreach ($extrafields as $field) {
echo '<td>'.s($user->{$field}).'</td>';
}
}
// Progress for each course completion criteria
foreach ($criteria as $criterion) {
$criteria_completion = $completion->get_user_completion($user->id, $criterion);
$is_complete = $criteria_completion->is_complete();
// Handle activity completion differently
if ($criterion->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
// Load activity
$activity = $modinfo->cms[$criterion->moduleinstance];
// Get progress information and state
if (array_key_exists($activity->id, $user->progress)) {
$state = $user->progress[$activity->id]->completionstate;
} else if ($is_complete) {
$state = COMPLETION_COMPLETE;
} else {
$state = COMPLETION_INCOMPLETE;
}
if ($is_complete) {
$date = userdate($criteria_completion->timecompleted, $dateformat);
} else {
$date = '';
}
// Work out how it corresponds to an icon
switch($state) {
case COMPLETION_INCOMPLETE : $completiontype = 'n'; break;
case COMPLETION_COMPLETE : $completiontype = 'y'; break;
case COMPLETION_COMPLETE_PASS : $completiontype = 'pass'; break;
case COMPLETION_COMPLETE_FAIL : $completiontype = 'fail'; break;
}
$auto = $activity->completion == COMPLETION_TRACKING_AUTOMATIC;
$completionicon = 'completion-'.($auto ? 'auto' : 'manual').'-'.$completiontype;
$describe = get_string('completion-'.$completiontype, 'completion');
$a = new StdClass();
$a->state = $describe;
$a->date = $date;
$a->user = fullname($user);
$a->activity = $activity->get_formatted_name();
$fulldescribe = get_string('progress-title', 'completion', $a);
if ($csv) {
$row[] = $describe;
$row[] = $date;
} else {
print '<td class="completion-progresscell">';
print $OUTPUT->pix_icon('i/' . $completionicon, $fulldescribe);
print '</td>';
}
continue;
}
// Handle all other criteria
$completiontype = $is_complete ? 'y' : 'n';
$completionicon = 'completion-auto-'.$completiontype;
$describe = get_string('completion-'.$completiontype, 'completion');
$a = new stdClass();
$a->state = $describe;
if ($is_complete) {
$a->date = userdate($criteria_completion->timecompleted, $dateformat);
} else {
$a->date = '';
}
$a->user = fullname($user);
$a->activity = strip_tags($criterion->get_title());
$fulldescribe = get_string('progress-title', 'completion', $a);
if ($csv) {
$row[] = $a->date;
} else {
print '<td class="completion-progresscell">';
if ($allow_marking_criteria === $criterion->id) {
$describe = get_string('completion-'.$completiontype, 'completion');
$toggleurl = new moodle_url(
'/course/togglecompletion.php',
array(
'user' => $user->id,
'course' => $course->id,
'rolec' => $allow_marking_criteria,
'sesskey' => sesskey()
)
);
print '<a href="'.$toggleurl->out().'" title="'.s(get_string('clicktomarkusercomplete', 'report_completion')).'">' .
$OUTPUT->pix_icon('i/completion-manual-' . ($is_complete ? 'y' : 'n'), $describe) . '</a></td>';
} else {
print $OUTPUT->pix_icon('i/' . $completionicon, $fulldescribe) . '</td>';
}
print '</td>';
}
}
// Handle overall course completion
// Load course completion
$params = array(
'userid' => $user->id,
'course' => $course->id
);
$ccompletion = new completion_completion($params);
$completiontype = $ccompletion->is_complete() ? 'y' : 'n';
$describe = get_string('completion-'.$completiontype, 'completion');
$a = new StdClass;
if ($ccompletion->is_complete()) {
$a->date = userdate($ccompletion->timecompleted, $dateformat);
} else {
$a->date = '';
}
$a->state = $describe;
$a->user = fullname($user);
$a->activity = strip_tags(get_string('coursecomplete', 'completion'));
$fulldescribe = get_string('progress-title', 'completion', $a);
if ($csv) {
$row[] = $a->date;
} else {
print '<td class="completion-progresscell">';
// Display course completion status icon
print $OUTPUT->pix_icon('i/completion-auto-' . $completiontype, $fulldescribe);
print '</td>';
}
if ($csv) {
$export->add_data($row);
} else {
print '</tr>';
}
}
if ($csv) {
$export->download_file();
} else {
echo '</tbody>';
}
print '</table>';
$csvurl = new moodle_url('/report/completion/index.php', array('course' => $course->id, 'format' => 'csv'));
$excelurl = new moodle_url('/report/completion/index.php', array('course' => $course->id, 'format' => 'excelcsv'));
print '<ul class="export-actions">';
print '<li><a href="'.$csvurl->out().'">'.get_string('csvdownload','completion').'</a></li>';
print '<li><a href="'.$excelurl->out().'">'.get_string('excelcsvdownload','completion').'</a></li>';
print '</ul>';
echo $OUTPUT->footer($course);
// Trigger a report viewed event.
$event = \report_completion\event\report_viewed::create(array('context' => $context));
$event->trigger();
@@ -0,0 +1,39 @@
<?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/>.
/**
* Lang strings
*
* @package report
* @subpackage completion
* @copyright 2009 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['clicktomarkusercomplete'] = 'Click to mark user complete';
$string['completion:view'] = 'View course completion report';
$string['completiondate'] = 'Completion date';
$string['id'] = 'ID';
$string['name'] = 'Name';
$string['nocapability'] = 'Can not access user completion report';
$string['page-report-completion-x'] = 'Any completion report';
$string['page-report-completion-index'] = 'Course completion report';
$string['page-report-completion-user'] = 'User course completion report';
$string['pluginname'] = 'Course completion';
$string['privacy:metadata'] = 'The Course completion report only shows data stored in other locations.';
$string['eventreportviewed'] = 'Completion report viewed';
$string['eventuserreportviewed'] = 'Completion user report viewed';
+128
View File
@@ -0,0 +1,128 @@
<?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 details.
*
* @package report
* @subpackage completion
* @copyright 2009 Sam Hemelryk
* @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_completion_extend_navigation_course($navigation, $course, $context) {
global $CFG;
require_once($CFG->libdir.'/completionlib.php');
if (has_capability('report/completion:view', $context)) {
$completion = new completion_info($course);
if ($completion->is_enabled() && $completion->has_criteria()) {
$url = new moodle_url('/report/completion/index.php', array('course'=>$course->id));
$navigation->add(get_string('pluginname','report_completion'), $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_completion_extend_navigation_user($navigation, $user, $course) {
return; //TODO: this plugin was not linked from navigation in 2.0, let's keep it that way for now --skodak
if (report_completion_can_access_user_report($user, $course)) {
$url = new moodle_url('/report/completion/user.php', array('id'=>$user->id, 'course'=>$course->id));
$navigation->add(get_string('coursecompletion'), $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_completion_can_access_user_report($user, $course) {
global $USER, $CFG;
if (empty($CFG->enablecompletion)) {
return false;
}
if ($course->id != SITEID and !$course->enablecompletion) {
return false;
}
$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/completion: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_completion_page_type_list($pagetype, $parentcontext, $currentcontext) {
$array = array(
'*' => get_string('page-x', 'pagetype'),
'report-*' => get_string('page-report-x', 'pagetype'),
'report-completion-*' => get_string('page-report-completion-x', 'report_completion'),
'report-completion-index' => get_string('page-report-completion-index', 'report_completion'),
'report-completion-user' => get_string('page-report-completion-user', 'report_completion')
);
return $array;
}
+38
View File
@@ -0,0 +1,38 @@
#page-report-completion-index table#completion-progress {
margin-top: 20px;
margin-bottom: 30px;
}
#page-report-completion-index .export-actions {
text-align: center;
list-style: none;
}
#page-report-completion-index .criterianame,
#page-report-completion-index .criteriaicon,
#page-report-completion-index .completion-progresscell {
text-align: center;
}
/* Custom CSS for rotated header.. */
#page-report-completion-index .rotated-text-container {
display: inline-block;
width: 16px;
}
/*rtl:begin:ignore*/
#page-report-completion-index .rotated-text {
display: inline-block;
white-space: nowrap;
transform: translate(0, 100%) rotate(-90deg);
transform-origin: 0 0;
vertical-align: middle;
}
#page-report-completion-index .rotated-text:after {
content: "";
float: left;
margin-top: 100%;
}
/*rtl:end:ignore*/
@@ -0,0 +1,58 @@
@report @report_completion
Feature: See the completion for items in a course
In order see completion data
As a teacher
I need to view completion report
Background:
Given the following "custom profile fields" exist:
| datatype | shortname | name |
| text | fruit | Fruit |
And the following "users" exist:
| username | firstname | lastname | email | middlename | alternatename | firstnamephonetic | lastnamephonetic | profile_field_fruit |
| teacher1 | Teacher | 1 | teacher1@example.com | | fred | | | |
| student1 | Grainne | Beauchamp | student1@example.com | Ann | Jill | Gronya | Beecham | Kumquat |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber | completion | completionview |
| page | PageName1 | PageDesc1 | C1 | PAGE1 | 1 | 1 |
@javascript
Scenario: The completion report respects user fullname setting
Given the following config values are set as admin:
| fullnamedisplay | firstname |
| alternativefullnameformat | middlename, alternatename, firstname, lastname |
And I am on the "C1" "Course" page logged in as "teacher1"
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Page - PageName1 | 1 |
And I press "Save changes"
And I am on "Course 1" course homepage
When I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
Then I should see "Ann, Jill, Grainne, Beauchamp"
@javascript
Scenario: The completion report displays custom user profile fields
Given the following config values are set as admin:
| showuseridentity | email,profile_field_fruit |
And I am on the "C1" "Course" page logged in as "teacher1"
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| Page - PageName1 | 1 |
And I press "Save changes"
And I am on "Course 1" course homepage
When I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
# We can't refer to table headings by name because they aren't on the first row.
Then the following should exist in the "completionreport" table:
| -1- | -2- | -3- |
| Grainne Beauchamp | student1@example.com | Kumquat |
@@ -0,0 +1,32 @@
@report @report_completion
Feature: In a course administration page, navigate through report page, test for course completion page
In order to navigate through report page
As an admin
Go to course administration -> reports -> course completion
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode | enablecompletion |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| admin | C1 | editingteacher |
| student1 | C1 | student |
@javascript
Scenario: Selector should be available in the course completion page
Given I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Course completion" in current page administration
And I expand all fieldsets
And I set the following fields to these values:
| id_criteria_self | 1 |
And I press "Save changes"
And I am on "Course 1" course homepage
When I navigate to "Reports" in current page administration
And I click on "Course completion" "link" in the "region-main" "region"
Then "Report" "field" should exist in the "tertiary-navigation" "region"
And I should see "Course completion" in the "tertiary-navigation" "region"
@@ -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 report completion events.
*
* @package report_completion
* @copyright 2014 onwards Ankit Agarwal<ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
namespace report_completion\event;
/**
* Class report_completion_events_testcase
*
* Class for tests related to completion report events.
*
* @package report_completion
* @copyright 2014 onwards Ankit Agarwal<ankit.agrr@gmail.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 report viewed event.
*
* It's not possible to use the moodle API to simulate the viewing of log report, so here we
* simply create the event and trigger it.
*/
public function test_report_viewed(): void {
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
// Trigger event for completion report viewed.
$event = \report_completion\event\report_viewed::create(array('context' => $context));
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\report_completion\event\report_viewed', $event);
$this->assertEquals($context, $event->get_context());
$url = new \moodle_url('/report/completion/index.php', array('course' => $course->id));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
}
/**
* Test the user report viewed event.
*
* It's not possible to use the moodle API to simulate the viewing of log report, so here we
* simply create the event and trigger it.
*/
public function test_user_report_viewed(): void {
$course = $this->getDataGenerator()->create_course();
$context = \context_course::instance($course->id);
// Trigger event for completion report viewed.
$event = \report_completion\event\user_report_viewed::create(array('context' => $context, 'relateduserid' => 3));
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\report_completion\event\user_report_viewed', $event);
$this->assertEquals($context, $event->get_context());
$this->assertEquals(3, $event->relateduserid);
$this->assertEquals(new \moodle_url('/report/completion/user.php', array('id' => 3, 'course' => $course->id)),
$event->get_url());
$this->assertEventContextNotUsed($event);
}
}
+304
View File
@@ -0,0 +1,304 @@
<?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 completion report
*
* @package report
* @subpackage completion
* @copyright 2009 Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../../config.php');
require_once($CFG->dirroot.'/report/completion/lib.php');
require_once($CFG->libdir.'/completionlib.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);
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_completion_can_access_user_report($user, $course)) {
// this should never happen
throw new \moodle_exception('nocapability', 'report_completion');
}
$stractivityreport = get_string('activityreport');
$PAGE->set_pagelayout('admin');
$PAGE->set_url('/report/completion/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.
$PAGE->set_title("$course->shortname: $stractivityreport");
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
// Display course completion user report
// Grab all courses the user is enrolled in and their completion status
$sql = "
SELECT DISTINCT
c.id AS id
FROM
{course} c
INNER JOIN
{context} con
ON con.instanceid = c.id
INNER JOIN
{role_assignments} ra
ON ra.contextid = con.id
INNER JOIN
{enrol} e
ON c.id = e.courseid
INNER JOIN
{user_enrolments} ue
ON e.id = ue.enrolid AND ra.userid = ue.userid
AND ra.userid = {$user->id}
";
// Get roles that are tracked by course completion
if ($roles = $CFG->gradebookroles) {
$sql .= '
AND ra.roleid IN ('.$roles.')
';
}
$sql .= '
WHERE
con.contextlevel = '.CONTEXT_COURSE.'
AND c.enablecompletion = 1
';
// If we are looking at a specific course
if ($course->id != 1) {
$sql .= '
AND c.id = '.(int)$course->id.'
';
}
// Check if result is empty
$rs = $DB->get_recordset_sql($sql);
if (!$rs->valid()) {
if ($course->id != 1) {
$error = get_string('nocompletions', 'report_completion'); // TODO: missing string
} else {
$error = get_string('nocompletioncoursesenroled', 'report_completion'); // TODO: missing string
}
echo $OUTPUT->notification($error);
$rs->close(); // not going to loop (but break), close rs
echo $OUTPUT->footer();
die();
}
// Categorize courses by their status
$courses = array(
'inprogress' => array(),
'complete' => array(),
'unstarted' => array()
);
// Sort courses by the user's status in each
foreach ($rs as $course_completion) {
$c_info = new completion_info((object)$course_completion);
// Is course complete?
$coursecomplete = $c_info->is_course_complete($user->id);
// Has this user completed any criteria?
$criteriacomplete = $c_info->count_course_user_data($user->id);
if ($coursecomplete) {
$courses['complete'][] = $c_info;
} else if ($criteriacomplete) {
$courses['inprogress'][] = $c_info;
} else {
$courses['unstarted'][] = $c_info;
}
}
$rs->close(); // after loop, close rs
// Loop through course status groups
foreach ($courses as $type => $infos) {
// If there are courses with this status
if (!empty($infos)) {
echo '<h1 align="center">'.get_string($type, 'report_completion').'</h1>';
echo '<table class="generaltable boxaligncenter">';
echo '<tr class="ccheader">';
echo '<th class="c0 header" scope="col">'.get_string('course').'</th>';
echo '<th class="c1 header" scope="col">'.get_string('requiredcriteria', 'completion').'</th>';
echo '<th class="c2 header" scope="col">'.get_string('status').'</th>';
echo '<th class="c3 header" scope="col" width="15%">'.get_string('info').'</th>';
if ($type === 'complete') {
echo '<th class="c4 header" scope="col">'.get_string('completiondate', 'report_completion').'</th>';
}
echo '</tr>';
// For each course
foreach ($infos as $c_info) {
// Get course info
$c_course = $DB->get_record('course', array('id' => $c_info->course_id));
$course_context = context_course::instance($c_course->id, MUST_EXIST);
$course_name = format_string($c_course->fullname, true, array('context' => $course_context));
// Get completions
$completions = $c_info->get_completions($user->id);
// Save row data
$rows = array();
// For aggregating activity completion
$activities = array();
$activities_complete = 0;
// For aggregating prerequisites
$prerequisites = array();
$prerequisites_complete = 0;
// Loop through course criteria
foreach ($completions as $completion) {
$criteria = $completion->get_criteria();
$complete = $completion->is_complete();
// Activities are a special case, so cache them and leave them till last
if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
$activities[$criteria->moduleinstance] = $complete;
if ($complete) {
$activities_complete++;
}
continue;
}
// Prerequisites are also a special case, so cache them and leave them till last
if ($criteria->criteriatype == COMPLETION_CRITERIA_TYPE_COURSE) {
$prerequisites[$criteria->courseinstance] = $complete;
if ($complete) {
$prerequisites_complete++;
}
continue;
}
$row = array();
$row['title'] = $criteria->get_title();
$row['status'] = $completion->get_status();
$rows[] = $row;
}
// Aggregate activities
if (!empty($activities)) {
$row = array();
$row['title'] = get_string('activitiescomplete', 'report_completion');
$row['status'] = $activities_complete.' of '.count($activities);
$rows[] = $row;
}
// Aggregate prerequisites
if (!empty($prerequisites)) {
$row = array();
$row['title'] = get_string('prerequisitescompleted', 'completion');
$row['status'] = $prerequisites_complete.' of '.count($prerequisites);
array_splice($rows, 0, 0, array($row));
}
$first_row = true;
// Print table
foreach ($rows as $row) {
// Display course name on first row
if ($first_row) {
echo '<tr><td class="c0"><a href="'.$CFG->wwwroot.'/course/view.php?id='.$c_course->id.'">'.$course_name.'</a></td>';
} else {
echo '<tr><td class="c0"></td>';
}
echo '<td class="c1">';
echo $row['title'];
echo '</td><td class="c2">';
switch ($row['status']) {
case 'Yes':
echo get_string('complete');
break;
case 'No':
echo get_string('incomplete', 'report_completion');
break;
default:
echo $row['status'];
}
// Display link on first row
echo '</td><td class="c3">';
if ($first_row) {
echo '<a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$c_course->id.'&user='.$user->id.'">'.get_string('detailedview', 'report_completion').'</a>';
}
echo '</td>';
// Display completion date for completed courses on first row
if ($type === 'complete' && $first_row) {
$params = array(
'userid' => $user->id,
'course' => $c_course->id
);
$ccompletion = new completion_completion($params);
echo '<td class="c4">'.userdate($ccompletion->timecompleted, '%e %B %G').'</td>';
}
$first_row = false;
echo '</tr>';
}
}
echo '</table>';
}
}
echo $OUTPUT->footer();
// Trigger a user report viewed event.
$event = \report_completion\event\user_report_viewed::create(array('context' => $coursecontext, 'relateduserid' => $userid));
$event->trigger();
+31
View File
@@ -0,0 +1,31 @@
<?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 details
*
* @package report
* @subpackage completion
* @copyright 2009 Catalyst IT Ltd
* @author Aaron Barnes <aaronb@catalyst.net.nz>
* @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_completion'; // Full name of the plugin (used for diagnostics)
@@ -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/>.
/**
* Privacy Subsystem implementation for report_configlog.
*
* @package report_configlog
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_configlog\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_configlog implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,219 @@
<?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/>.
namespace report_configlog\reportbuilder\local\entities;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\text;
/**
* Config change entity class implementation
*
* Defines all the columns and filters that can be added to reports that use this entity.
*
* @package report_configlog
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class config_change extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'config_log',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('entityconfigchange', 'report_configlog');
}
/**
* Initialize the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this->add_filter($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('config_log');
// Time modified column.
$columns[] = (new column(
'timemodified',
new lang_string('timemodified', 'report_configlog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timemodified")
->set_is_sortable(true)
->add_callback([format::class, 'userdate']);
// Plugin column.
$columns[] = (new column(
'plugin',
new lang_string('plugin', 'report_configlog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.plugin")
->set_is_sortable(true)
->add_callback(static function(?string $plugin): string {
return $plugin ?? 'core';
});
// Setting column.
$columns[] = (new column(
'setting',
new lang_string('setting', 'report_configlog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.name")
->set_is_sortable(true);
// New value column.
$columns[] = (new column(
'newvalue',
new lang_string('valuenew', 'report_configlog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.value")
->set_is_sortable(true)
->add_callback(static function(?string $value): string {
return format_text($value, FORMAT_PLAIN);
});
// Old value column.
$columns[] = (new column(
'oldvalue',
new lang_string('valueold', 'report_configlog'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_field("{$tablealias}.oldvalue")
->set_is_sortable(true)
->add_callback(static function(?string $oldvalue): string {
return format_text($oldvalue, FORMAT_PLAIN);
});
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('config_log');
// Time modified filter.
$filters[] = (new filter(
date::class,
'timemodified',
new lang_string('timemodified', 'report_configlog'),
$this->get_entity_name(),
"{$tablealias}.timemodified"
))
->add_joins($this->get_joins())
->set_limited_operators([
date::DATE_ANY,
date::DATE_RANGE,
date::DATE_PREVIOUS,
date::DATE_CURRENT,
]);
// Plugin filter.
$filters[] = (new filter(
text::class,
'plugin',
new lang_string('plugin', 'report_configlog'),
$this->get_entity_name(),
"COALESCE({$tablealias}.plugin, 'core')"
))
->add_joins($this->get_joins());
// Setting filter.
$filters[] = (new filter(
text::class,
'setting',
new lang_string('setting', 'report_configlog'),
$this->get_entity_name(),
"{$tablealias}.name"
))
->add_joins($this->get_joins());
// New value filter.
$filters[] = (new filter(
text::class,
'value',
new lang_string('valuenew', 'report_configlog'),
$this->get_entity_name(),
"{$tablealias}.value"
))
->add_joins($this->get_joins());
// Old value filter.
$filters[] = (new filter(
text::class,
'oldvalue',
new lang_string('valueold', 'report_configlog'),
$this->get_entity_name(),
"{$tablealias}.oldvalue"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,116 @@
<?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/>.
namespace report_configlog\reportbuilder\local\systemreports;
use context_system;
use report_configlog\reportbuilder\local\entities\config_change;
use core_reportbuilder\system_report;
use core_reportbuilder\local\entities\user;
use stdClass;
/**
* Config changes system report class implementation
*
* @package report_configlog
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class config_changes extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
// Our main entity, it contains all of the column definitions that we need.
$entitymain = new config_change();
$entitymainalias = $entitymain->get_table_alias('config_log');
$this->set_main_table('config_log', $entitymainalias);
$this->add_entity($entitymain);
// We can join the "user" entity to our "main" entity using standard SQL JOIN.
$entityuser = new user();
$entityuseralias = $entityuser->get_table_alias('user');
$this->add_entity($entityuser
->add_join("LEFT JOIN {user} {$entityuseralias} ON {$entityuseralias}.id = {$entitymainalias}.userid")
);
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns();
$this->add_filters();
// Set if report can be downloaded.
$this->set_downloadable(true, get_string('pluginname', 'report_configlog'));
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
return has_capability('moodle/site:config', context_system::instance());
}
/**
* Adds the columns we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_columns(): void {
$columns = [
'config_change:timemodified',
'user:fullnamewithlink',
'config_change:plugin',
'config_change:setting',
'config_change:newvalue',
'config_change:oldvalue',
];
$this->add_columns_from_entities($columns);
// Default sorting.
$this->set_initial_sort_column('config_change:timemodified', SORT_DESC);
// Custom callback to show 'CLI or install' in fullname column when there is no user.
if ($column = $this->get_column('user:fullnamewithlink')) {
$column->add_callback(static function(string $fullname, stdClass $row): string {
return $fullname ?: get_string('usernone', 'report_configlog');
});
}
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$filters = [
'config_change:plugin',
'config_change:setting',
'config_change:value',
'config_change:oldvalue',
'user:fullname',
'config_change:timemodified',
];
$this->add_filters_from_entities($filters);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?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 mappings for classes that have been renamed so that they meet the requirements of the autoloader.
*
* @package report_configlog
* @copyright 2022 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$renamedclasses = [
// Since Moodle 4.1.
'report_configlog\\local\\systemreports\\config_changes' =>
'report_configlog\\reportbuilder\\local\\systemreports\\config_changes',
'report_configlog\\local\\entities\\config_change' =>
'report_configlog\\reportbuilder\\local\\entities\\config_change',
];
+52
View File
@@ -0,0 +1,52 @@
<?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/>.
/**
* Config changes report
*
* @package report
* @subpackage configlog
* @copyright 2009 Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_reportbuilder\system_report_factory;
use core_reportbuilder\local\filters\text;
use report_configlog\reportbuilder\local\systemreports\config_changes;
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/adminlib.php');
// Allow searching by setting when providing parameter directly.
$search = optional_param('search', '', PARAM_TEXT);
admin_externalpage_setup('reportconfiglog', '', ['search' => $search], '', ['pagelayout' => 'report']);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('configlog', 'report_configlog'));
// Create out report instance, setting initial filtering if required.
$report = system_report_factory::create(config_changes::class, context_system::instance());
if (!empty($search)) {
$report->set_filter_values([
'config_change:setting_operator' => text::IS_EQUAL_TO,
'config_change:setting_value' => $search,
]);
}
echo $report->output();
echo $OUTPUT->footer();
@@ -0,0 +1,41 @@
<?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 for component 'report_configlog', language 'en', branch 'MOODLE_20_STABLE'
*
* @package report
* @subplugin configlog
* @copyright 2009 petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['configlog'] = 'Config changes';
$string['datefrom'] = 'Date from';
$string['dateto'] = 'Date to';
$string['entityconfigchange'] = 'Config change';
$string['plugin'] = 'Plugin';
$string['pluginname'] = 'Config changes';
$string['setting'] = 'Setting';
$string['timemodified'] = 'Date';
$string['user'] = 'User';
$string['usernone'] = 'CLI or install';
$string['user_help'] = 'Search by user first name or last name';
$string['value'] = 'Value';
$string['value_help'] = 'Search by new or original value of the configuration';
$string['valuenew'] = 'New value';
$string['valueold'] = 'Original value';
$string['privacy:metadata'] = 'The Config changes plugin does not store any personal data.';
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* Report settings
*
* @package report
* @subpackage configlog
* @copyright 2009 Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$ADMIN->add('reports', new admin_externalpage('reportconfiglog', get_string('configlog', 'report_configlog'), "$CFG->wwwroot/report/configlog/index.php"));
// no report settings
$settings = null;
@@ -0,0 +1,44 @@
@report @report_configlog @core_reportbuilder
Feature: In a report, admin can see configuration changes
In order see configuration changes
As an admin
I need to view the configuration changes report and use search to filter the report
# Set some config values so the report contains known data.
Background:
Given I log in as "admin"
And I change the window size to "large"
And I set the following administration settings values:
| Initial number of overall feedback fields | 5 |
| Maximum folder download size | 2048 |
| Default city | Perth |
@javascript
Scenario: Display configuration changes report
When I navigate to "Reports > Config changes" in site administration
Then the following should exist in the "reportbuilder-table" table:
| First name | Plugin | Setting | New value | Original value |
| Admin User | quiz | initialnumfeedbacks | 5 | 2 |
| Admin User | folder | maxsizetodownload | 2048 | 0 |
| Admin User | core | defaultcity | Perth | |
@javascript
Scenario Outline: Search configuration changes report
When I navigate to "Reports > Config changes" in site administration
And I click on "Filters" "button"
And I set the following fields in the "<field>" "core_reportbuilder > Filter" to these values:
| <field> operator | Contains |
| <field> value | <search> |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
And I should see "Filters applied"
Then the following should exist in the "reportbuilder-table" table:
| Plugin | Setting | New value |
| <plugin> | <setting> | <value> |
And I should not see "<excluded>" in the "reportbuilder-table" "table"
Examples:
| field | search | plugin | setting | value | excluded |
| Plugin | folder | folder | maxsizetodownload | 2048 | quiz |
| Setting | initialnumfeedbacks | quiz | initialnumfeedbacks | 5 | maxsizetodownload |
| Setting | maxsizetodownload | folder | maxsizetodownload | 2048 | initialnumfeedbacks |
| New value | Perth | core | defaultcity | Perth | maxsizetodownload |
| Full name | Admin User | core | defaultcity | Perth | zzzzzzzzz |
+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 details.
*
* @package report
* @subpackage configlog
* @copyright 2011 Petr Skoda
* @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_configlog'; // Full name of the plugin (used for diagnostics)
@@ -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/>.
/**
* Privacy Subsystem implementation for report_courseoverview.
*
* @package report_courseoverview
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_courseoverview\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_courseoverview implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+41
View File
@@ -0,0 +1,41 @@
<?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/>.
/**
* Report capabilities
*
* @package report_courseoverview
* @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();
$capabilities = array(
'report/courseoverview:view' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/site:viewreports',
)
);
+153
View File
@@ -0,0 +1,153 @@
<?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/>.
/**
* Course overview report
*
* @package report
* @subpackage courseoverview
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->dirroot.'/lib/statslib.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot.'/report/courseoverview/locallib.php');
$report = optional_param('report', STATS_REPORT_ACTIVE_COURSES, PARAM_INT);
$time = optional_param('time', 0, PARAM_INT);
$numcourses = optional_param('numcourses', 20, PARAM_INT);
$systemcontext = context_system::instance();
if (empty($CFG->enablestats)) {
if (has_capability('moodle/site:config', $systemcontext)) {
redirect("$CFG->wwwroot/$CFG->admin/search.php?query=enablestats", get_string('mustenablestats', 'admin'), 3);
} else {
throw new \moodle_exception('statsdisable');
}
}
admin_externalpage_setup('reportcourseoverview', '', null, '', array('pagelayout'=>'report'));
echo $OUTPUT->header();
$course = get_site();
stats_check_uptodate($course->id);
$strreports = get_string('reports');
$strcourseoverview = get_string('courseoverview');
$reportoptions = stats_get_report_options($course->id,STATS_MODE_RANKED);
$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();
$timeoptions = stats_get_time_options($now,$lastweekend,$lastmonthend,$earliestday,$earliestweek,$earliestmonth);
if (empty($timeoptions)) {
throw new \moodle_exception('nostatstodisplay', 'error', $CFG->wwwroot.'/course/view.php?id='.$course->id);
}
echo html_writer::start_tag('form', ['action' => 'index.php', 'method' => 'post',
'class' => 'd-flex flex-wrap align-items-center']);
echo html_writer::start_tag('div');
$table = new html_table();
$table->width = '*';
$table->align = array('left','left','left','left','left','left');
$reporttypemenu = html_writer::label(get_string('statsreporttype'), 'menureport', false, array('class' => 'accesshide'));
$reporttypemenu .= html_writer::select($reportoptions,'report',$report, false);
$timeoptionsmenu = html_writer::label(get_string('time'), 'menutime', false, array('class' => 'accesshide'));
$timeoptionsmenu .= html_writer::select($timeoptions,'time',$time, false);
$table->data[] = array(get_string('statsreporttype'),$reporttypemenu,
get_string('statstimeperiod'),$timeoptionsmenu,
html_writer::label(get_string('numberofcourses'), 'numcourses', false, array('class' => 'accesshide')) .
html_writer::empty_tag('input', array('type' => 'text', 'class' => 'form-control',
'id' => 'numcourses', 'name' => 'numcourses', 'size' => '3', 'maxlength' => '2',
'value' => $numcourses)),
html_writer::empty_tag('input', array('type' => 'submit', 'class' => 'btn btn-secondary',
'value' => get_string('view'))));
echo html_writer::table($table);
echo html_writer::end_tag('div');
echo html_writer::end_tag('form');
echo $OUTPUT->heading($reportoptions[$report]);
if (!empty($report) && !empty($time)) {
$param = stats_get_parameters($time,$report,SITEID,STATS_MODE_RANKED);
if (!empty($param->sql)) {
$sql = $param->sql;
} else {
$sql = "SELECT courseid,".$param->fields."
FROM {".'stats_'.$param->table."}
WHERE timeend >= $param->timeafter AND stattype = 'activity' AND roleid = 0
GROUP BY courseid
$param->extras
ORDER BY $param->orderby";
}
$courses = $DB->get_records_sql($sql, $param->params, 0, $numcourses);
if (empty($courses)) {
echo $OUTPUT->notification(get_string('statsnodata'));
echo '</td></tr></table>';
} else {
require_capability('report/courseoverview:view', $systemcontext);
echo html_writer::start_div();
report_courseoverview_print_chart($report, $time, $numcourses);
echo html_writer::end_div();
$table = new html_table();
$table->align = array('left','center','center','center');
$table->head = array(get_string('course'),$param->line1);
if (!empty($param->line2)) {
$table->head[] = $param->line2;
}
if (!empty($param->line3)) {
$table->head[] = $param->line3;
}
foreach ($courses as $c) {
$a = array();
$a[] = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$c->courseid.'">'.$DB->get_field('course', 'shortname', array('id'=>$c->courseid)).'</a>';
$a[] = $c->line1;
if (isset($c->line2)) {
$a[] = $c->line2;
}
if (isset($c->line3)) {
$a[] = round($c->line3,2);
}
$table->data[] = $a;
}
echo html_writer::table($table);
}
}
echo $OUTPUT->footer();
@@ -0,0 +1,28 @@
<?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 for component 'report_courseoverview'.
*
* @package report
* @subpackage courseoverview
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['courseoverview:view'] = 'View course overview report';
$string['pluginname'] = 'Course overview';
$string['privacy:metadata'] = 'The Course overview plugin does not store any personal data.';
+81
View File
@@ -0,0 +1,81 @@
<?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 course overview report.
*
* @package report_courseoverview
* @copyright 2016 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
require_once('../../config.php');
require_once($CFG->dirroot . '/lib/statslib.php');
/**
* Gather course overview data and print the chart.
*
* @param int $report represents the report type field on the course overview report filter.
* @param int $time represents the time period field on the course overview report filter.
* @param int $numcourses represents the number of courses field on the course overview report filter.
* @return void
*/
function report_courseoverview_print_chart($report, $time, $numcourses) {
global $DB, $OUTPUT, $PAGE;
$param = stats_get_parameters($time, $report, SITEID, STATS_MODE_RANKED);
if (!empty($param->sql)) {
$sql = $param->sql;
} else {
$sql = "SELECT courseid, $param->fields
FROM {" . 'stats_' . $param->table . "}
WHERE timeend >= $param->timeafter
AND stattype = 'activity'
AND roleid = 0
GROUP BY courseid
$param->extras
ORDER BY $param->orderby";
}
$courses = $DB->get_records_sql($sql, $param->params, 0, $numcourses);
if (empty($courses)) {
$PAGE->set_url('/report/courseoverview/index.php');
throw new \moodle_exception('statsnodata', 'error', $PAGE->url->out());
}
$data = [];
$i = 0;
foreach ($courses as $c) {
$data['labels'][$i] = $DB->get_field('course', 'shortname', array('id' => $c->courseid));
// Line3 represents the third column of the report table except for the most active users report.
// It is a float number and can be participation radio or activity per user.
if (isset($c->line3)) {
$data['series'][$i] = round($c->line3, 2);
} else {
$data['series'][$i] = $c->{$param->graphline};
}
$i++;
}
$chart = new \core\chart_bar();
$series = new \core\chart_series($param->{$param->graphline}, $data['series']);
$chart->add_series($series);
$chart->set_labels($data['labels']);
echo $OUTPUT->render($chart);
}
+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/>.
/**
* Graph
*
* @package report
* @subpackage courseoverview
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../config.php');
require_once($CFG->libdir . '/filelib.php');
debugging('This way of generating the chart is deprecated, refer to report_courseoverview_print_chart().', DEBUG_DEVELOPER);
send_file_not_found();
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* Report settings
*
* @package report
* @subpackage courseoverview
* @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;
$ADMIN->add('reports', new admin_externalpage('reportcourseoverview', get_string('pluginname', 'report_courseoverview'), "$CFG->wwwroot/report/courseoverview/index.php",'report/courseoverview:view', empty($CFG->enablestats)));
// no report settings
$settings = null;
+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 courseoverview
* @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;
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'report_courseoverview'; // Full name of the plugin (used for diagnostics)
+91
View File
@@ -0,0 +1,91 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Event list filter form.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_eventlist_filter_form extends moodleform {
/**
* Form definition method.
*/
public function definition() {
$mform = $this->_form;
$mform->disable_form_change_checker();
$componentarray = $this->_customdata['components'];
$edulevelarray = $this->_customdata['edulevel'];
$crudarray = $this->_customdata['crud'];
$mform->addElement('header', 'displayinfo', get_string('filter', 'report_eventlist'));
$mform->addElement('text', 'eventname', get_string('name', 'report_eventlist'));
$mform->setType('eventname', PARAM_RAW);
$mform->addElement('selectgroups', 'eventcomponent', get_string('component', 'report_eventlist'),
self::group_components_by_type($componentarray));
$mform->addElement('select', 'eventedulevel', get_string('edulevel', 'report_eventlist'), $edulevelarray);
$mform->addElement('select', 'eventcrud', get_string('crud', 'report_eventlist'), $crudarray);
$buttonarray = array();
$buttonarray[] = $mform->createElement('button', 'filterbutton', get_string('filter', 'report_eventlist'));
$buttonarray[] = $mform->createElement('button', 'clearbutton', get_string('clear', 'report_eventlist'));
$mform->addGroup($buttonarray, 'filterbuttons', '', array(' '), false);
}
/**
* Group list of component names by type for use in grouped select element
*
* @param string[] $components
* @return array[] Component type => [...Components]
*/
private static function group_components_by_type(array $components): array {
$pluginmanager = core_plugin_manager::instance();
$result = [];
foreach ($components as $component) {
// Core sub-systems are grouped together and are denoted by a distinct lang string.
if (strpos($component, 'core') === 0) {
$componenttype = get_string('core', 'report_eventlist');
$componentname = get_string('coresubsystem', 'report_eventlist', $component);
} else {
[$type] = core_component::normalize_component($component);
$componenttype = $pluginmanager->plugintype_name_plural($type);
$componentname = $pluginmanager->plugin_name($component);
}
$result[$componenttype][$component] = $componentname;
}
// Sort returned components according to their type, followed by name.
core_collator::ksort($result);
array_walk($result, function(array &$componenttype) {
core_collator::asort($componenttype);
});
// Prepend "All" option.
array_unshift($result, [0 => get_string('all', 'report_eventlist')]);
return $result;
}
}
+334
View File
@@ -0,0 +1,334 @@
<?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/>.
/**
* Event documentation
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for returning system event information.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_eventlist_list_generator {
/**
* Convenience method. Returns all of the core events either with or without details.
*
* @param bool $detail True will return details, but no abstract classes, False will return all events, but no details.
* @return array All events.
*/
public static function get_all_events_list($detail = true) {
global $CFG;
// Disable developer debugging as deprecated events will fire warnings.
// Setup backup variables to restore the following settings back to what they were when we are finished.
$debuglevel = $CFG->debug;
$debugdisplay = $CFG->debugdisplay;
$debugdeveloper = $CFG->debugdeveloper;
$CFG->debug = 0;
$CFG->debugdisplay = false;
$CFG->debugdeveloper = false;
// List of exceptional events that will cause problems if displayed.
$eventsignore = [
\core\event\unknown_logged::class,
];
$eventinformation = [];
$events = core_component::get_component_classes_in_namespace(null, 'event');
foreach (array_keys($events) as $event) {
// We need to filter all classes that extend event base, or the base class itself.
if (is_a($event, \core\event\base::class, true) && !in_array($event, $eventsignore)) {
if ($detail) {
$reflectionclass = new ReflectionClass($event);
if (!$reflectionclass->isAbstract()) {
$eventinformation = self::format_data($eventinformation, "\\{$event}");
}
} else {
$parts = explode('\\', $event);
$eventinformation["\\{$event}"] = array_shift($parts);
}
}
}
// Now enable developer debugging as event information has been retrieved.
$CFG->debug = $debuglevel;
$CFG->debugdisplay = $debugdisplay;
$CFG->debugdeveloper = $debugdeveloper;
return $eventinformation;
}
/**
* Return all of the core event files.
*
* @param bool $detail True will return details, but no abstract classes, False will return all events, but no details.
* @return array Core events.
*
* @deprecated since 4.0 use {@see get_all_events_list} instead
*/
public static function get_core_events_list($detail = true) {
global $CFG;
debugging(__FUNCTION__ . '() is deprecated, please use report_eventlist_list_generator::get_all_events_list() instead',
DEBUG_DEVELOPER);
// Disable developer debugging as deprecated events will fire warnings.
// Setup backup variables to restore the following settings back to what they were when we are finished.
$debuglevel = $CFG->debug;
$debugdisplay = $CFG->debugdisplay;
$debugdeveloper = $CFG->debugdeveloper;
$CFG->debug = 0;
$CFG->debugdisplay = false;
$CFG->debugdeveloper = false;
$eventinformation = array();
$directory = $CFG->libdir . '/classes/event';
$files = self::get_file_list($directory);
// Remove exceptional events that will cause problems being displayed.
if (isset($files['unknown_logged'])) {
unset($files['unknown_logged']);
}
foreach ($files as $file => $location) {
$functionname = '\\core\\event\\' . $file;
// Check to see if this is actually a valid event.
if (method_exists($functionname, 'get_static_info')) {
if ($detail) {
$ref = new \ReflectionClass($functionname);
if (!$ref->isAbstract() && $file != 'manager') {
$eventinformation = self::format_data($eventinformation, $functionname);
}
} else {
$eventinformation[$functionname] = $file;
}
}
}
// Now enable developer debugging as event information has been retrieved.
$CFG->debug = $debuglevel;
$CFG->debugdisplay = $debugdisplay;
$CFG->debugdeveloper = $debugdeveloper;
return $eventinformation;
}
/**
* Returns the appropriate string for the CRUD character.
*
* @param string $crudcharacter The CRUD character.
* @return string get_string for the specific CRUD character.
*/
public static function get_crud_string($crudcharacter) {
switch ($crudcharacter) {
case 'c':
return get_string('create', 'report_eventlist');
break;
case 'u':
return get_string('update', 'report_eventlist');
break;
case 'd':
return get_string('delete', 'report_eventlist');
break;
case 'r':
default:
return get_string('read', 'report_eventlist');
break;
}
}
/**
* Returns the appropriate string for the event education level.
*
* @param int $edulevel Takes either the edulevel constant or string.
* @return string get_string for the specific education level.
*/
public static function get_edulevel_string($edulevel) {
switch ($edulevel) {
case \core\event\base::LEVEL_PARTICIPATING:
return get_string('participating', 'report_eventlist');
break;
case \core\event\base::LEVEL_TEACHING:
return get_string('teaching', 'report_eventlist');
break;
case \core\event\base::LEVEL_OTHER:
default:
return get_string('other', 'report_eventlist');
break;
}
}
/**
* Returns a list of files (events) with a full directory path for events in a specified directory.
*
* @param string $directory location of files.
* @return array full location of files from the specified directory.
*/
private static function get_file_list($directory) {
global $CFG;
$directoryroot = $CFG->dirroot;
$finaleventfiles = array();
if (is_dir($directory)) {
if ($handle = opendir($directory)) {
$eventfiles = scandir($directory);
foreach ($eventfiles as $file) {
if ($file != '.' && $file != '..') {
// Ignore the file if it is external to the system.
if (strrpos($directory, $directoryroot) !== false) {
$location = substr($directory, strlen($directoryroot));
$eventname = substr($file, 0, -4);
$finaleventfiles[$eventname] = $location . '/' . $file;
}
}
}
}
}
return $finaleventfiles;
}
/**
* This function returns an array of all events for the plugins of the system.
*
* @param bool $detail True will return details, but no abstract classes, False will return all events, but no details.
* @return array A list of events from all plug-ins.
*
* @deprecated since 4.0 use {@see get_all_events_list} instead
*/
public static function get_non_core_event_list($detail = true) {
global $CFG;
debugging(__FUNCTION__ . '() is deprecated, please use report_eventlist_list_generator::get_all_events_list() instead',
DEBUG_DEVELOPER);
// Disable developer debugging as deprecated events will fire warnings.
// Setup backup variables to restore the following settings back to what they were when we are finished.
$debuglevel = $CFG->debug;
$debugdisplay = $CFG->debugdisplay;
$debugdeveloper = $CFG->debugdeveloper;
$CFG->debug = 0;
$CFG->debugdisplay = false;
$CFG->debugdeveloper = false;
$noncorepluginlist = array();
$plugintypes = \core_component::get_plugin_types();
foreach ($plugintypes as $plugintype => $notused) {
$pluginlist = \core_component::get_plugin_list($plugintype);
foreach ($pluginlist as $plugin => $directory) {
$plugindirectory = $directory . '/classes/event';
foreach (self::get_file_list($plugindirectory) as $eventname => $notused) {
$plugineventname = '\\' . $plugintype . '_' . $plugin . '\\event\\' . $eventname;
// Check that this is actually an event.
if (method_exists($plugineventname, 'get_static_info')) {
if ($detail) {
$ref = new \ReflectionClass($plugineventname);
if (!$ref->isAbstract()) {
$noncorepluginlist = self::format_data($noncorepluginlist, $plugineventname);
}
} else {
$noncorepluginlist[$plugineventname] = $eventname;
}
}
}
}
}
// Now enable developer debugging as event information has been retrieved.
$CFG->debug = $debuglevel;
$CFG->debugdisplay = $debugdisplay;
$CFG->debugdeveloper = $debugdeveloper;
return $noncorepluginlist;
}
/**
* Get the full list of observers for the system.
*
* @return array An array of observers in the system.
*/
public static function get_observer_list() {
$events = \core\event\manager::get_all_observers();
foreach ($events as $key => $observers) {
foreach ($observers as $observerskey => $observer) {
$events[$key][$observerskey]->parentplugin =
\core_plugin_manager::instance()->get_parent_of_subplugin($observer->plugintype);
}
}
return $events;
}
/**
* Returns the event data list section with url links and other formatting.
*
* @param array $eventdata The event data list section.
* @param string $eventfullpath Full path to the events for this plugin / subplugin.
* @return array The event data list section with additional formatting.
*/
private static function format_data($eventdata, $eventfullpath) {
// Get general event information.
$eventdata[$eventfullpath] = $eventfullpath::get_static_info();
// Create a link for further event detail.
$url = new \moodle_url('eventdetail.php', array('eventname' => $eventfullpath));
$link = \html_writer::link($url, $eventfullpath::get_name_with_info());
$eventdata[$eventfullpath]['fulleventname'] = \html_writer::span($link);
$eventdata[$eventfullpath]['fulleventname'] .= \html_writer::empty_tag('br');
$eventdata[$eventfullpath]['fulleventname'] .= \html_writer::span($eventdata[$eventfullpath]['eventname'],
'report-eventlist-name');
$eventdata[$eventfullpath]['crud'] = self::get_crud_string($eventdata[$eventfullpath]['crud']);
$eventdata[$eventfullpath]['edulevel'] = self::get_edulevel_string($eventdata[$eventfullpath]['edulevel']);
// Mess around getting since information.
$ref = new \ReflectionClass($eventdata[$eventfullpath]['eventname']);
$eventdocbloc = $ref->getDocComment();
$sincepattern = "/since\s*Moodle\s([0-9]+.[0-9]+)/i";
preg_match($sincepattern, $eventdocbloc, $result);
if (isset($result[1])) {
$eventdata[$eventfullpath]['since'] = $result[1];
} else {
$eventdata[$eventfullpath]['since'] = null;
}
// Human readable plugin information to go with the component.
$pluginstring = explode('\\', $eventfullpath);
if ($pluginstring[1] !== 'core') {
$component = $eventdata[$eventfullpath]['component'];
$manager = get_string_manager();
if ($manager->string_exists('pluginname', $pluginstring[1])) {
$eventdata[$eventfullpath]['component'] = \html_writer::span(get_string('pluginname', $pluginstring[1]));
}
}
// Raw event data to be used to sort the "Event name" column.
$eventdata[$eventfullpath]['raweventname'] = $eventfullpath::get_name_with_info() . ' ' . $eventdata[$eventfullpath]['eventname'];
// Unset information that is not currently required.
unset($eventdata[$eventfullpath]['action']);
unset($eventdata[$eventfullpath]['target']);
return $eventdata;
}
}
@@ -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/>.
/**
* Privacy Subsystem implementation for report_eventlist.
*
* @package report_eventlist
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_eventlist\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_eventlist implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+173
View File
@@ -0,0 +1,173 @@
<?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/>.
/**
* Event report renderer.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Renderer for event report.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class report_eventlist_renderer extends plugin_renderer_base {
/**
* Renders the event list page with filter form and datatable.
*
* @param eventfilter_form $form Event filter form.
* @param array $tabledata An array of event data to be used by the datatable.
* @return string HTML to be displayed.
*/
public function render_event_list($form, $tabledata) {
$title = get_string('pluginname', 'report_eventlist');
// Header.
$html = $this->output->header();
$html .= $this->output->heading($title);
// Form.
ob_start();
$form->display();
$html .= ob_get_contents();
ob_end_clean();
$this->page->requires->yui_module('moodle-report_eventlist-eventfilter', 'Y.M.report_eventlist.EventFilter.init',
array(array('tabledata' => $tabledata)));
$this->page->requires->strings_for_js(array(
'eventname',
'component',
'action',
'crud',
'edulevel',
'affectedtable',
'dname',
'legacyevent',
'since'
), 'report_eventlist');
$html .= html_writer::start_div('report-eventlist-data-table', array('id' => 'report-eventlist-table'));
$html .= html_writer::end_div();
$html .= $this->output->footer();
return $html;
}
/**
* Event detail renderer.
*
* @param array $observerlist A list of observers that consume this event.
* @param array $eventinformation A list of information about the event.
* @return string HTML to be displayed.
*/
public function render_event_detail($observerlist, $eventinformation) {
$titlehtml = $this->output->header();
$titlehtml .= $this->output->heading($eventinformation['title']);
$html = html_writer::start_tag('dl', array('class' => 'list'));
$explanation = nl2br($eventinformation['explanation']);
$html .= html_writer::tag('dt', get_string('eventexplanation', 'report_eventlist'));
$html .= html_writer::tag('dd', $explanation);
if (isset($eventinformation['crud'])) {
$html .= html_writer::tag('dt', get_string('crud', 'report_eventlist'));
$html .= html_writer::tag('dd', $eventinformation['crud']);
}
if (isset($eventinformation['edulevel'])) {
$html .= html_writer::tag('dt', get_string('edulevel', 'report_eventlist'));
$html .= html_writer::tag('dd', $eventinformation['edulevel']);
}
if (isset($eventinformation['objecttable'])) {
$html .= html_writer::tag('dt', get_string('affectedtable', 'report_eventlist'));
$html .= html_writer::tag('dd', $eventinformation['objecttable']);
}
if (isset($eventinformation['legacyevent'])) {
$html .= html_writer::tag('dt', get_string('legacyevent', 'report_eventlist'));
$html .= html_writer::tag('dd', $eventinformation['legacyevent']);
}
if (isset($eventinformation['parentclass'])) {
$url = new moodle_url('eventdetail.php', array('eventname' => $eventinformation['parentclass']));
$html .= html_writer::tag('dt', get_string('parentevent', 'report_eventlist'));
$html .= html_writer::tag('dd', html_writer::link($url, $eventinformation['parentclass']));
}
if (isset($eventinformation['abstract'])) {
$html .= html_writer::tag('dt', get_string('abstractclass', 'report_eventlist'));
$html .= html_writer::tag('dd', get_string('yes', 'report_eventlist'));
}
if (isset($eventinformation['typeparameter'])) {
$html .= html_writer::tag('dt', get_string('typedeclaration', 'report_eventlist'));
foreach ($eventinformation['typeparameter'] as $typeparameter) {
$html .= html_writer::tag('dd', $typeparameter);
}
}
if (isset($eventinformation['otherparameter'])) {
$html .= html_writer::tag('dt', get_string('othereventparameters', 'report_eventlist'));
foreach ($eventinformation['otherparameter'] as $otherparameter) {
$html .= html_writer::tag('dd', $otherparameter);
}
}
// List observers consuming this event if there are any.
if (!empty($observerlist)) {
$html .= html_writer::tag('dt', get_string('relatedobservers', 'report_eventlist'));
foreach ($observerlist as $observer) {
if ($observer->plugin == 'core') {
$html .= html_writer::tag('dd', $observer->plugin);
} else {
$manager = get_string_manager();
$pluginstring = $observer->plugintype . '_' . $observer->plugin;
if ($manager->string_exists('pluginname', $pluginstring)) {
if (!empty($observer->parentplugin)) {
$string = get_string('pluginname', $pluginstring) . ' (' . $observer->parentplugin
. ' ' . $pluginstring . ')';
} else {
$string = get_string('pluginname', $pluginstring) . ' (' . $pluginstring . ')';
}
} else {
$string = $observer->plugintype . ' ' . $observer->plugin;
}
$html .= html_writer::tag('dd', $string);
}
}
}
$html .= html_writer::end_div();
$html .= html_writer::end_tag('dl');
$pagecontent = new html_table();
$pagecontent->data = array(array($html));
$pagehtml = $titlehtml . html_writer::table($pagecontent);
$pagehtml .= $this->output->footer();
return $pagehtml;
}
}
+142
View File
@@ -0,0 +1,142 @@
<?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/>.
/**
* Event developer detail.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/adminlib.php');
// Required parameters.
$eventname = required_param('eventname', PARAM_RAW);
admin_externalpage_setup('reporteventlists');
// Retrieve all events in a list.
$completelist = report_eventlist_list_generator::get_all_events_list(false);
// Check that $eventname is a valid event.
if (!array_key_exists($eventname, $completelist)) {
throw new \moodle_exception('errorinvalidevent', 'report_eventlist');
}
// Break up the full event name to usable parts.
$component = explode('\\', $eventname);
$directory = core_component::get_component_directory($component[1]);
// File and directory information.
$directory = $directory . '/classes/event';
// Verify that the directory is valid.
if (!is_dir($directory)) {
throw new \moodle_exception('errorinvaliddirectory', 'report_eventlist');
}
$filename = end($component);
$eventfiles = $directory . '/' . $filename . '.php';
$title = $eventname::get_name_with_info();
// Define event information.
$eventinformation = array('title' => $title);
$eventcontents = file_get_contents($eventfiles);
$eventinformation['filecontents'] = $eventcontents;
$ref = new \ReflectionClass($eventname);
$eventinformation['explanation'] = $eventname::get_explanation($eventname);
// Get event information nicely if we can.
if (!$ref->isAbstract()) {
$eventinformation = array_merge($eventinformation, $eventname::get_static_info());
$eventinformation['crud'] = report_eventlist_list_generator::get_crud_string($eventinformation['crud']);
$eventinformation['edulevel'] = report_eventlist_list_generator::get_edulevel_string($eventinformation['edulevel']);
} else {
$eventinformation['abstract'] = true;
if ($eventname != '\core\event\base') {
// No choice but to get information the hard way.
// Strip out CRUD information.
$crudpattern = "/(\['crud'\]\s=\s')(\w)/";
$result = array();
preg_match($crudpattern, $eventcontents, $result);
if (!empty($result[2])) {
$eventinformation['crud'] = report_eventlist_list_generator::get_crud_string($result[2]);
}
// Strip out edulevel information.
$edulevelpattern = "/(\['edulevel'\]\s=\sself\:\:)(\w*)/";
$result = array();
preg_match($edulevelpattern, $eventcontents, $result);
if (!empty($result[2])) {
$educationlevel = constant('\core\event\base::' . $result[2]);
$eventinformation['edulevel'] = report_eventlist_list_generator::get_edulevel_string($educationlevel);
}
// Retrieve object table information.
$affectedtablepattern = "/(\['objecttable'\]\s=\s')(\w*)/";
$result = array();
preg_match($affectedtablepattern, $eventcontents, $result);
if (!empty($result[2])) {
$eventinformation['objecttable'] = $result[2];
}
}
}
// I can't think of a nice way to get the following information.
// Searching to see if @type has been used for the 'other' field in the event.
$othertypepattern = "/(@type\s([\w|\s|.]*))+/";
$typeparams = array();
preg_match_all($othertypepattern, $eventcontents, $typeparams);
if (!empty($typeparams[2])) {
$eventinformation['typeparameter'] = array();
foreach ($typeparams[2] as $typeparameter) {
$eventinformation['typeparameter'][] = $typeparameter;
}
}
// Retrieving the 'other' event field information.
$otherpattern = "/(\*\s{5,}-([\w|\s]*\:[\w|\s|\(|\)|.]*))/";
$typeparams = array();
preg_match_all($otherpattern, $eventcontents, $typeparams);
if (!empty($typeparams[2])) {
$eventinformation['otherparameter'] = array();
foreach ($typeparams[2] as $typeparameter) {
$eventinformation['otherparameter'][] = $typeparameter;
}
}
// Get parent class information.
if ($parentclass = get_parent_class($eventname)) {
$eventinformation['parentclass'] = '\\' . $parentclass;
}
// Fetch all the observers to be matched with this event.
$allobserverslist = report_eventlist_list_generator::get_observer_list();
$observers = array();
if (isset($allobserverslist['\\core\\event\\base'])) {
$observers = $allobserverslist['\\core\\event\\base'];
}
if (isset($allobserverslist[$eventname])) {
$observers = array_merge($observers, $allobserverslist[$eventname]);
}
$PAGE->set_primary_active_tab('siteadminnode');
$PAGE->set_secondary_active_tab('reports');
// OUTPUT.
$renderer = $PAGE->get_renderer('report_eventlist');
echo $renderer->render_event_detail($observers, $eventinformation);
+53
View File
@@ -0,0 +1,53 @@
<?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/>.
/**
* Event documentation.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('reporteventlists');
// Retrieve all events in a list.
$completelist = report_eventlist_list_generator::get_all_events_list();
$tabledata = array();
$components = array();
$edulevel = array('0' => get_string('all', 'report_eventlist'));
$crud = array('0' => get_string('all', 'report_eventlist'));
foreach ($completelist as $value) {
$components[] = explode('\\', $value['eventname'])[1];
$edulevel[] = $value['edulevel'];
$crud[] = $value['crud'];
$tabledata[] = (object)$value;
}
$components = array_unique($components);
$edulevel = array_unique($edulevel);
$crud = array_unique($crud);
// Create the filter form for the table.
$filtersection = new report_eventlist_filter_form(null, array('components' => $components, 'edulevel' => $edulevel,
'crud' => $crud));
// Output.
$renderer = $PAGE->get_renderer('report_eventlist');
echo $renderer->render_event_list($filtersection, $tabledata);
@@ -0,0 +1,61 @@
<?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 for component 'report_event', language 'en', branch 'MOODLE_27_STABLE'
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['abstractclass'] = 'Abstract class';
$string['action'] = 'Action';
$string['affectedtable'] = 'Affected table';
$string['all'] = 'All';
$string['clear'] = 'Clear';
$string['component'] = 'Component';
$string['core'] = 'Core';
$string['coresubsystem'] = 'Subsystem ({$a})';
$string['create'] = 'create';
$string['crud'] = 'Database query type';
$string['delete'] = 'delete';
$string['details'] = 'Details';
$string['dname'] = 'Name';
$string['edulevel'] = 'Education level';
$string['errorinvalidevent'] = 'The event provided is not a valid event.';
$string['errorinvaliddirectory'] = 'The event directory does not exist or is invalid.';
$string['eventcode'] = 'Event Code';
$string['eventexplanation'] = 'Explanation of the event';
$string['eventname'] = 'Event name';
$string['filter'] = 'Filter';
$string['legacyevent'] = 'Legacy event';
$string['name'] = 'Name';
$string['objecttable'] = 'Object table';
$string['other'] = 'Other';
$string['otherinformation'] = 'Other information:';
$string['othereventparameters'] = 'Other event parameters';
$string['parentevent'] = 'Parent Event';
$string['participating'] = 'Participating';
$string['pluginname'] = 'Events list';
$string['read'] = 'read';
$string['relatedobservers'] = 'Plugins observing this event';
$string['since'] = 'Since';
$string['teaching'] = 'Teaching';
$string['typedeclaration'] = 'Other event parameters';
$string['update'] = 'update';
$string['yes'] = 'yes';
$string['privacy:metadata'] = 'The Events list plugin does not store any personal data.';
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* Adds the event list link to the admin tree
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
if ($hassiteconfig) {
$url = $CFG->wwwroot . '/report/eventlist/index.php';
$ADMIN->add('reports', new admin_externalpage('reporteventlists', get_string('pluginname', 'report_eventlist'), $url));
// No report settings.
$settings = null;
}
+39
View File
@@ -0,0 +1,39 @@
.report-eventlist-name {
color: #888;
font-size: 0.75em;
}
.report-eventlist-datatable-table > div > table {
width: 100%;
}
#page-admin-report-eventlist-index dt {
float: left;
text-align: right;
width: 20em;
}
#page-admin-report-eventlist-index dd {
display: block;
text-align: left;
margin-left: 21em;
}
#page-admin-report-eventlist-index dd + dd {
clear: left;
}
@media (max-width: 767px) {
#page-admin-report-eventlist-index dt {
width: 100%;
text-align: left;
}
#page-admin-report-eventlist-index dd {
margin-left: 0;
}
#page-admin-report-eventlist-index dd + dd {
margin-left: 0;
}
}
@@ -0,0 +1,63 @@
@report @report_eventlist
Feature: Page contains a list of events
In order to find information about events
As a user
I need to check the page contents
@javascript
Scenario: Event list page is viewable and filtering works
Given I log in as "admin"
And I navigate to "Reports > Events list" in site administration
And I should see "Event name"
And I set the field "eventname" to "phase"
And I press "filterbutton"
And I should see "Phase switched"
And I should not see "Comment created"
And I press "clearbutton"
And I set the field "eventcomponent" to "URL"
And I press "filterbutton"
And I should see "Course module instance list viewed"
And I should not see "User added to cohort"
And I press "clearbutton"
And I set the field "eventedulevel" to "Teaching"
And I press "filterbutton"
And I should see "Attempt deleted"
And I should not see "Quiz attempt abandoned"
And I press "clearbutton"
And I set the field "eventcrud" to "delete"
And I press "filterbutton"
And I should see "Cohort deleted"
And I should not see "Cohort updated"
And I press "clearbutton"
And I set the field "eventcomponent" to "Assignment"
And I set the field "eventedulevel" to "Participating"
And I press "filterbutton"
And I should see "A submission has been submitted"
And I should not see "An extension has been granted"
And I press "clearbutton"
And I set the field "eventedulevel" to "Other"
And I set the field "eventcrud" to "read"
And I press "filterbutton"
And I should see "Notes viewed"
And I should not see "Highscores viewed"
And I press "clearbutton"
And I set the field "eventname" to "viewed"
And I set the field "eventcomponent" to "Forum"
And I set the field "eventedulevel" to "Participating"
And I set the field "eventcrud" to "read"
And I press "filterbutton"
Then I should see "User report viewed"
And I should not see "Subscribers viewed"
@javascript
Scenario: Details of an event are viewable
Given I log in as "admin"
And I navigate to "Reports > Events list" in site administration
And I should see "Event name"
And I follow "Blog association created"
And I should see "Blog association created"
And I should see "blog_association"
And I should see "create"
And I should see "Log store manager (tool_log)"
And I follow "\core\event\base"
Then I should see "core: base"
+8
View File
@@ -0,0 +1,8 @@
This file describes API changes in /report/eventlist/*,
information provided here is intended especially for developers.
=== 4.0 ===
* The following methods have been deprecated in favour of a single `get_all_events_list` method:
- report_eventlist_list_generator::get_core_events_list
- report_eventlist_list_generator::get_non_core_event_list
+29
View File
@@ -0,0 +1,29 @@
<?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 details.
*
* @package report_eventlist
* @copyright 2014 Adrian Greeve <adrian@moodle.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_eventlist'; // Full name of the plugin (used for diagnostics).
@@ -0,0 +1,276 @@
YUI.add('moodle-report_eventlist-eventfilter', function (Y, NAME) {
/**
* A tool for displaying and filtering system events.
*
* @module moodle-report_eventlist-eventfilter
*/
/**
* A tool for displaying and filtering system events.
*
* @class M.report_eventlist.EventFilter
* @extends Base
* @constructor
*/
function EventFilter() {
EventFilter.superclass.constructor.apply(this, arguments);
}
var SELECTORS = {
EVENTNAME: '#id_eventname',
EVENTCOMPONENT: '#id_eventcomponent',
EVENTEDULEVEL: '#id_eventedulevel',
EVENTCRUD: '#id_eventcrud',
FILTERBUTTON: '#id_filterbutton',
CLEARBUTTON: '#id_clearbutton'
};
Y.extend(EventFilter, Y.Base, {
/**
* A reference to the datatable.
*
* @property _table
* @type DataTable
* @private
*/
_table: null,
/**
* A reference to the eventname text element.
*
* @property _eventName
* @type node
* @private
*/
_eventName: null,
/**
* A reference to the component select box element.
*
* @property _component
* @type node
* @private
*/
_component: null,
/**
* A reference to the education level select box element.
*
* @property _eduLevel
* @type node
* @private
*/
_eduLevel: null,
/**
* A reference to the CRUD select box element.
*
* @property _crud
* @type node
* @private
*/
_crud: null,
/**
* Initializer.
* Basic setup and delegations.
*
* @method initializer
*/
initializer: function() {
var filterButton = Y.one(SELECTORS.FILTERBUTTON),
clearButton = Y.one(SELECTORS.CLEARBUTTON);
this._createTable(this.get('tabledata'));
this._eventName = Y.one(SELECTORS.EVENTNAME);
this._component = Y.one(SELECTORS.EVENTCOMPONENT);
this._eduLevel = Y.one(SELECTORS.EVENTEDULEVEL);
this._crud = Y.one(SELECTORS.EVENTCRUD);
this._eventName.on('valuechange', this._totalFilter, this);
filterButton.on('click', this._totalFilter, this);
clearButton.on('click', this._clearFilter, this);
},
/**
* Create the table for displaying all of the event information.
*
* @param {array} tableData Event data for populating the table.
* @method _createTable
* @private
* @chainable
*/
_createTable: function(tableData) {
var table = new Y.DataTable({
columns: [
{
key: "fulleventname",
label: M.util.get_string('eventname', 'report_eventlist'),
allowHTML: true,
sortable: true,
/**
* Custom sort of the fulleventname column.
* This will sort via the event name rather than the event path.
*
* @param {object} eventDataListA Event data record module A.
* @param {object} eventDataListB Event data record module B.
* @param {boolean} desc True sorts list in descending order and false sorts in Ascending order.
* @return {number} order for which the column should be sorted.
* @method sortFn
*/
sortFn: function(eventDataListA, eventDataListB, desc) {
var rawEventDataA = eventDataListA.getAttrs().raweventname,
rawEventDataB = eventDataListB.getAttrs().raweventname,
order = (rawEventDataA > rawEventDataB ? 1 : -1);
return desc ? -order : order;
},
title: M.util.get_string('eventname', 'report_eventlist')
}, {
key: "component",
label: M.util.get_string('component', 'report_eventlist'),
allowHTML: true,
sortable: true,
title: M.util.get_string('component', 'report_eventlist')
}, {
key: "edulevel",
label: M.util.get_string('edulevel', 'report_eventlist'),
sortable: true,
title: M.util.get_string('edulevel', 'report_eventlist')
}, {
key: "crud",
label: M.util.get_string('crud', 'report_eventlist'),
sortable: true,
title: M.util.get_string('crud', 'report_eventlist')
}, {
key: "objecttable",
label: M.util.get_string('affectedtable', 'report_eventlist'),
sortable: true,
title: M.util.get_string('affectedtable', 'report_eventlist')
}, {
key: "since",
label: M.util.get_string('since', 'report_eventlist'),
sortable: true,
title: M.util.get_string('since', 'report_eventlist')
}, {
key: "legacyevent",
label: M.util.get_string('legacyevent', 'report_eventlist'),
sortable: true,
title: M.util.get_string('legacyevent', 'report_eventlist')
}
],
data: tableData,
strings: {
sortBy: '{title}',
reverseSortBy: '{title}'
}
});
// Display the table.
table.render("#report-eventlist-table");
table.get('boundingBox').addClass('report-eventlist-datatable-table');
this._table = table;
return this;
},
/**
* Filters the entries being displayed in the table.
*
* @method totalFilter
* @private
*/
_totalFilter: function() {
// Get all of the details of the filter elements
var eventNameFilter = this._eventName.get('value').toLowerCase(),
// Component selected value.
componentValue = this._component.get('value'),
// Education level selected text.
eduLevelFilter = this._eduLevel.get('options').item(this._eduLevel.get('selectedIndex')).get('text').toLowerCase(),
// Education level selected value.
eduLevelValue = this._eduLevel.get('value'),
// CRUD selected text.
crudFilter = this._crud.get('options').item(this._crud.get('selectedIndex')).get('text').toLowerCase(),
// CRUD selected value.
crudValue = this._crud.get('value'),
i,
filtered = [];
// Loop through the rows and put the ones we want into the filter.
for (i = 0; i < this.get('tabledata').length; i++) {
// These variables will either be false or true depending on the statement outcome.
var fullEventText = Y.Node.create(this.get('tabledata')[i].fulleventname).get('text'),
eventNameValue = fullEventText.toLowerCase().indexOf(eventNameFilter) >= 0,
componentFilterValue = this.get('tabledata')[i].eventname.indexOf('\\' + componentValue + '\\event\\') >= 0,
eduLevelFilterValue = this.get('tabledata')[i].edulevel.toLowerCase().indexOf(eduLevelFilter) >= 0,
crudFilterValue = this.get('tabledata')[i].crud.toLowerCase().indexOf(crudFilter) >= 0;
// If the name field is empty then add to the filter.
if (eventNameFilter === '') {
eventNameValue = true;
}
// If the component is set to 'all' then add to the filter.
if (componentValue === '0') {
componentFilterValue = true;
}
// If the education level is set to 'all' then add to the filter.
if (eduLevelValue === '0') {
eduLevelFilterValue = true;
}
// If the CRUD field is set to 'all' then add to the filter.
if (crudValue === '0') {
crudFilterValue = true;
}
// If any of the Values here is false then don't add to the filter (all must be true).
if (eventNameValue && componentFilterValue && eduLevelFilterValue && crudFilterValue) {
filtered.push(this.get('tabledata')[i]);
}
}
// Display the table again with the new data.
this._table.set('data', filtered);
},
/**
* Clears the filtered table data and changes the filter form to default.
*
* @method _clearFilter
* @private
*/
_clearFilter: function() {
// Reset filter form elements
this._eventName.set('value', '');
this._component.set('value', '0');
this._eduLevel.set('value', '0');
this._crud.set('value', '0');
// Reset the table data back to the original.
this._table.set('data', this.get('tabledata'));
}
}, {
NAME: 'eventFilter',
ATTRS: {
/**
* Data for the table.
*
* @attribute tabledata.
* @type Array
* @writeOnce
*/
tabledata: {
value: null
}
}
});
Y.namespace('M.report_eventlist.EventFilter').init = function(config) {
return new EventFilter(config);
};
}, '@VERSION@', {
"requires": [
"base",
"event",
"node",
"node-event-delegate",
"datatable",
"autocomplete",
"autocomplete-filters"
]
});
@@ -0,0 +1 @@
YUI.add("moodle-report_eventlist-eventfilter",function(d,e){function t(){t.superclass.constructor.apply(this,arguments)}var l="#id_eventname",n="#id_eventcomponent",i="#id_eventedulevel",a="#id_eventcrud",r="#id_filterbutton",s="#id_clearbutton";d.extend(t,d.Base,{_table:null,_eventName:null,_component:null,_eduLevel:null,_crud:null,initializer:function(){var e=d.one(r),t=d.one(s);this._createTable(this.get("tabledata")),this._eventName=d.one(l),this._component=d.one(n),this._eduLevel=d.one(i),this._crud=d.one(a),this._eventName.on("valuechange",this._totalFilter,this),e.on("click",this._totalFilter,this),t.on("click",this._clearFilter,this)},_createTable:function(e){e=new d.DataTable({columns:[{key:"fulleventname",label:M.util.get_string("eventname","report_eventlist"),allowHTML:!0,sortable:!0,sortFn:function(e,t,l){e=e.getAttrs().raweventname,t=t.getAttrs().raweventname<e?1:-1;return l?-t:t},title:M.util.get_string("eventname","report_eventlist")},{key:"component",label:M.util.get_string("component","report_eventlist"),allowHTML:!0,sortable:!0,title:M.util.get_string("component","report_eventlist")},{key:"edulevel",label:M.util.get_string("edulevel","report_eventlist"),sortable:!0,title:M.util.get_string("edulevel","report_eventlist")},{key:"crud",label:M.util.get_string("crud","report_eventlist"),sortable:!0,title:M.util.get_string("crud","report_eventlist")},{key:"objecttable",label:M.util.get_string("affectedtable","report_eventlist"),sortable:!0,title:M.util.get_string("affectedtable","report_eventlist")},{key:"since",label:M.util.get_string("since","report_eventlist"),sortable:!0,title:M.util.get_string("since","report_eventlist")},{key:"legacyevent",label:M.util.get_string("legacyevent","report_eventlist"),sortable:!0,title:M.util.get_string("legacyevent","report_eventlist")}],data:e,strings:{sortBy:"{title}",reverseSortBy:"{title}"}});return e.render("#report-eventlist-table"),e.get("boundingBox").addClass("report-eventlist-datatable-table"),this._table=e,this},_totalFilter:function(){for(var e,t,l,n,i=this._eventName.get("value").toLowerCase(),a=this._component.get("value"),r=this._eduLevel.get("options").item(this._eduLevel.get("selectedIndex")).get("text").toLowerCase(),s=this._eduLevel.get("value"),v=this._crud.get("options").item(this._crud.get("selectedIndex")).get("text").toLowerCase(),_=this._crud.get("value"),o=[],u=0;u<this.get("tabledata").length;u++)e=0<=d.Node.create(this.get("tabledata")[u].fulleventname).get("text").toLowerCase().indexOf(i),t=0<=this.get("tabledata")[u].eventname.indexOf("\\"+a+"\\event\\"),l=0<=this.get("tabledata")[u].edulevel.toLowerCase().indexOf(r),n=0<=this.get("tabledata")[u].crud.toLowerCase().indexOf(v),"0"===a&&(t=!0),"0"===s&&(l=!0),"0"===_&&(n=!0),(e=""===i?!0:e)&&t&&l&&n&&o.push(this.get("tabledata")[u]);this._table.set("data",o)},_clearFilter:function(){this._eventName.set("value",""),this._component.set("value","0"),this._eduLevel.set("value","0"),this._crud.set("value","0"),this._table.set("data",this.get("tabledata"))}},{NAME:"eventFilter",ATTRS:{tabledata:{value:null}}}),d.namespace("M.report_eventlist.EventFilter").init=function(e){return new t(e)}},"@VERSION@",{requires:["base","event","node","node-event-delegate","datatable","autocomplete","autocomplete-filters"]});
@@ -0,0 +1,276 @@
YUI.add('moodle-report_eventlist-eventfilter', function (Y, NAME) {
/**
* A tool for displaying and filtering system events.
*
* @module moodle-report_eventlist-eventfilter
*/
/**
* A tool for displaying and filtering system events.
*
* @class M.report_eventlist.EventFilter
* @extends Base
* @constructor
*/
function EventFilter() {
EventFilter.superclass.constructor.apply(this, arguments);
}
var SELECTORS = {
EVENTNAME: '#id_eventname',
EVENTCOMPONENT: '#id_eventcomponent',
EVENTEDULEVEL: '#id_eventedulevel',
EVENTCRUD: '#id_eventcrud',
FILTERBUTTON: '#id_filterbutton',
CLEARBUTTON: '#id_clearbutton'
};
Y.extend(EventFilter, Y.Base, {
/**
* A reference to the datatable.
*
* @property _table
* @type DataTable
* @private
*/
_table: null,
/**
* A reference to the eventname text element.
*
* @property _eventName
* @type node
* @private
*/
_eventName: null,
/**
* A reference to the component select box element.
*
* @property _component
* @type node
* @private
*/
_component: null,
/**
* A reference to the education level select box element.
*
* @property _eduLevel
* @type node
* @private
*/
_eduLevel: null,
/**
* A reference to the CRUD select box element.
*
* @property _crud
* @type node
* @private
*/
_crud: null,
/**
* Initializer.
* Basic setup and delegations.
*
* @method initializer
*/
initializer: function() {
var filterButton = Y.one(SELECTORS.FILTERBUTTON),
clearButton = Y.one(SELECTORS.CLEARBUTTON);
this._createTable(this.get('tabledata'));
this._eventName = Y.one(SELECTORS.EVENTNAME);
this._component = Y.one(SELECTORS.EVENTCOMPONENT);
this._eduLevel = Y.one(SELECTORS.EVENTEDULEVEL);
this._crud = Y.one(SELECTORS.EVENTCRUD);
this._eventName.on('valuechange', this._totalFilter, this);
filterButton.on('click', this._totalFilter, this);
clearButton.on('click', this._clearFilter, this);
},
/**
* Create the table for displaying all of the event information.
*
* @param {array} tableData Event data for populating the table.
* @method _createTable
* @private
* @chainable
*/
_createTable: function(tableData) {
var table = new Y.DataTable({
columns: [
{
key: "fulleventname",
label: M.util.get_string('eventname', 'report_eventlist'),
allowHTML: true,
sortable: true,
/**
* Custom sort of the fulleventname column.
* This will sort via the event name rather than the event path.
*
* @param {object} eventDataListA Event data record module A.
* @param {object} eventDataListB Event data record module B.
* @param {boolean} desc True sorts list in descending order and false sorts in Ascending order.
* @return {number} order for which the column should be sorted.
* @method sortFn
*/
sortFn: function(eventDataListA, eventDataListB, desc) {
var rawEventDataA = eventDataListA.getAttrs().raweventname,
rawEventDataB = eventDataListB.getAttrs().raweventname,
order = (rawEventDataA > rawEventDataB ? 1 : -1);
return desc ? -order : order;
},
title: M.util.get_string('eventname', 'report_eventlist')
}, {
key: "component",
label: M.util.get_string('component', 'report_eventlist'),
allowHTML: true,
sortable: true,
title: M.util.get_string('component', 'report_eventlist')
}, {
key: "edulevel",
label: M.util.get_string('edulevel', 'report_eventlist'),
sortable: true,
title: M.util.get_string('edulevel', 'report_eventlist')
}, {
key: "crud",
label: M.util.get_string('crud', 'report_eventlist'),
sortable: true,
title: M.util.get_string('crud', 'report_eventlist')
}, {
key: "objecttable",
label: M.util.get_string('affectedtable', 'report_eventlist'),
sortable: true,
title: M.util.get_string('affectedtable', 'report_eventlist')
}, {
key: "since",
label: M.util.get_string('since', 'report_eventlist'),
sortable: true,
title: M.util.get_string('since', 'report_eventlist')
}, {
key: "legacyevent",
label: M.util.get_string('legacyevent', 'report_eventlist'),
sortable: true,
title: M.util.get_string('legacyevent', 'report_eventlist')
}
],
data: tableData,
strings: {
sortBy: '{title}',
reverseSortBy: '{title}'
}
});
// Display the table.
table.render("#report-eventlist-table");
table.get('boundingBox').addClass('report-eventlist-datatable-table');
this._table = table;
return this;
},
/**
* Filters the entries being displayed in the table.
*
* @method totalFilter
* @private
*/
_totalFilter: function() {
// Get all of the details of the filter elements
var eventNameFilter = this._eventName.get('value').toLowerCase(),
// Component selected value.
componentValue = this._component.get('value'),
// Education level selected text.
eduLevelFilter = this._eduLevel.get('options').item(this._eduLevel.get('selectedIndex')).get('text').toLowerCase(),
// Education level selected value.
eduLevelValue = this._eduLevel.get('value'),
// CRUD selected text.
crudFilter = this._crud.get('options').item(this._crud.get('selectedIndex')).get('text').toLowerCase(),
// CRUD selected value.
crudValue = this._crud.get('value'),
i,
filtered = [];
// Loop through the rows and put the ones we want into the filter.
for (i = 0; i < this.get('tabledata').length; i++) {
// These variables will either be false or true depending on the statement outcome.
var fullEventText = Y.Node.create(this.get('tabledata')[i].fulleventname).get('text'),
eventNameValue = fullEventText.toLowerCase().indexOf(eventNameFilter) >= 0,
componentFilterValue = this.get('tabledata')[i].eventname.indexOf('\\' + componentValue + '\\event\\') >= 0,
eduLevelFilterValue = this.get('tabledata')[i].edulevel.toLowerCase().indexOf(eduLevelFilter) >= 0,
crudFilterValue = this.get('tabledata')[i].crud.toLowerCase().indexOf(crudFilter) >= 0;
// If the name field is empty then add to the filter.
if (eventNameFilter === '') {
eventNameValue = true;
}
// If the component is set to 'all' then add to the filter.
if (componentValue === '0') {
componentFilterValue = true;
}
// If the education level is set to 'all' then add to the filter.
if (eduLevelValue === '0') {
eduLevelFilterValue = true;
}
// If the CRUD field is set to 'all' then add to the filter.
if (crudValue === '0') {
crudFilterValue = true;
}
// If any of the Values here is false then don't add to the filter (all must be true).
if (eventNameValue && componentFilterValue && eduLevelFilterValue && crudFilterValue) {
filtered.push(this.get('tabledata')[i]);
}
}
// Display the table again with the new data.
this._table.set('data', filtered);
},
/**
* Clears the filtered table data and changes the filter form to default.
*
* @method _clearFilter
* @private
*/
_clearFilter: function() {
// Reset filter form elements
this._eventName.set('value', '');
this._component.set('value', '0');
this._eduLevel.set('value', '0');
this._crud.set('value', '0');
// Reset the table data back to the original.
this._table.set('data', this.get('tabledata'));
}
}, {
NAME: 'eventFilter',
ATTRS: {
/**
* Data for the table.
*
* @attribute tabledata.
* @type Array
* @writeOnce
*/
tabledata: {
value: null
}
}
});
Y.namespace('M.report_eventlist.EventFilter').init = function(config) {
return new EventFilter(config);
};
}, '@VERSION@', {
"requires": [
"base",
"event",
"node",
"node-event-delegate",
"datatable",
"autocomplete",
"autocomplete-filters"
]
});
@@ -0,0 +1,10 @@
{
"name": "moodle-report_eventlist-eventfilter",
"builds": {
"moodle-report_eventlist-eventfilter": {
"jsfiles": [
"eventfilter.js"
]
}
}
}
+261
View File
@@ -0,0 +1,261 @@
/**
* A tool for displaying and filtering system events.
*
* @module moodle-report_eventlist-eventfilter
*/
/**
* A tool for displaying and filtering system events.
*
* @class M.report_eventlist.EventFilter
* @extends Base
* @constructor
*/
function EventFilter() {
EventFilter.superclass.constructor.apply(this, arguments);
}
var SELECTORS = {
EVENTNAME: '#id_eventname',
EVENTCOMPONENT: '#id_eventcomponent',
EVENTEDULEVEL: '#id_eventedulevel',
EVENTCRUD: '#id_eventcrud',
FILTERBUTTON: '#id_filterbutton',
CLEARBUTTON: '#id_clearbutton'
};
Y.extend(EventFilter, Y.Base, {
/**
* A reference to the datatable.
*
* @property _table
* @type DataTable
* @private
*/
_table: null,
/**
* A reference to the eventname text element.
*
* @property _eventName
* @type node
* @private
*/
_eventName: null,
/**
* A reference to the component select box element.
*
* @property _component
* @type node
* @private
*/
_component: null,
/**
* A reference to the education level select box element.
*
* @property _eduLevel
* @type node
* @private
*/
_eduLevel: null,
/**
* A reference to the CRUD select box element.
*
* @property _crud
* @type node
* @private
*/
_crud: null,
/**
* Initializer.
* Basic setup and delegations.
*
* @method initializer
*/
initializer: function() {
var filterButton = Y.one(SELECTORS.FILTERBUTTON),
clearButton = Y.one(SELECTORS.CLEARBUTTON);
this._createTable(this.get('tabledata'));
this._eventName = Y.one(SELECTORS.EVENTNAME);
this._component = Y.one(SELECTORS.EVENTCOMPONENT);
this._eduLevel = Y.one(SELECTORS.EVENTEDULEVEL);
this._crud = Y.one(SELECTORS.EVENTCRUD);
this._eventName.on('valuechange', this._totalFilter, this);
filterButton.on('click', this._totalFilter, this);
clearButton.on('click', this._clearFilter, this);
},
/**
* Create the table for displaying all of the event information.
*
* @param {array} tableData Event data for populating the table.
* @method _createTable
* @private
* @chainable
*/
_createTable: function(tableData) {
var table = new Y.DataTable({
columns: [
{
key: "fulleventname",
label: M.util.get_string('eventname', 'report_eventlist'),
allowHTML: true,
sortable: true,
/**
* Custom sort of the fulleventname column.
* This will sort via the event name rather than the event path.
*
* @param {object} eventDataListA Event data record module A.
* @param {object} eventDataListB Event data record module B.
* @param {boolean} desc True sorts list in descending order and false sorts in Ascending order.
* @return {number} order for which the column should be sorted.
* @method sortFn
*/
sortFn: function(eventDataListA, eventDataListB, desc) {
var rawEventDataA = eventDataListA.getAttrs().raweventname,
rawEventDataB = eventDataListB.getAttrs().raweventname,
order = (rawEventDataA > rawEventDataB ? 1 : -1);
return desc ? -order : order;
},
title: M.util.get_string('eventname', 'report_eventlist')
}, {
key: "component",
label: M.util.get_string('component', 'report_eventlist'),
allowHTML: true,
sortable: true,
title: M.util.get_string('component', 'report_eventlist')
}, {
key: "edulevel",
label: M.util.get_string('edulevel', 'report_eventlist'),
sortable: true,
title: M.util.get_string('edulevel', 'report_eventlist')
}, {
key: "crud",
label: M.util.get_string('crud', 'report_eventlist'),
sortable: true,
title: M.util.get_string('crud', 'report_eventlist')
}, {
key: "objecttable",
label: M.util.get_string('affectedtable', 'report_eventlist'),
sortable: true,
title: M.util.get_string('affectedtable', 'report_eventlist')
}, {
key: "since",
label: M.util.get_string('since', 'report_eventlist'),
sortable: true,
title: M.util.get_string('since', 'report_eventlist')
}, {
key: "legacyevent",
label: M.util.get_string('legacyevent', 'report_eventlist'),
sortable: true,
title: M.util.get_string('legacyevent', 'report_eventlist')
}
],
data: tableData,
strings: {
sortBy: '{title}',
reverseSortBy: '{title}'
}
});
// Display the table.
table.render("#report-eventlist-table");
table.get('boundingBox').addClass('report-eventlist-datatable-table');
this._table = table;
return this;
},
/**
* Filters the entries being displayed in the table.
*
* @method totalFilter
* @private
*/
_totalFilter: function() {
// Get all of the details of the filter elements
var eventNameFilter = this._eventName.get('value').toLowerCase(),
// Component selected value.
componentValue = this._component.get('value'),
// Education level selected text.
eduLevelFilter = this._eduLevel.get('options').item(this._eduLevel.get('selectedIndex')).get('text').toLowerCase(),
// Education level selected value.
eduLevelValue = this._eduLevel.get('value'),
// CRUD selected text.
crudFilter = this._crud.get('options').item(this._crud.get('selectedIndex')).get('text').toLowerCase(),
// CRUD selected value.
crudValue = this._crud.get('value'),
i,
filtered = [];
// Loop through the rows and put the ones we want into the filter.
for (i = 0; i < this.get('tabledata').length; i++) {
// These variables will either be false or true depending on the statement outcome.
var fullEventText = Y.Node.create(this.get('tabledata')[i].fulleventname).get('text'),
eventNameValue = fullEventText.toLowerCase().indexOf(eventNameFilter) >= 0,
componentFilterValue = this.get('tabledata')[i].eventname.indexOf('\\' + componentValue + '\\event\\') >= 0,
eduLevelFilterValue = this.get('tabledata')[i].edulevel.toLowerCase().indexOf(eduLevelFilter) >= 0,
crudFilterValue = this.get('tabledata')[i].crud.toLowerCase().indexOf(crudFilter) >= 0;
// If the name field is empty then add to the filter.
if (eventNameFilter === '') {
eventNameValue = true;
}
// If the component is set to 'all' then add to the filter.
if (componentValue === '0') {
componentFilterValue = true;
}
// If the education level is set to 'all' then add to the filter.
if (eduLevelValue === '0') {
eduLevelFilterValue = true;
}
// If the CRUD field is set to 'all' then add to the filter.
if (crudValue === '0') {
crudFilterValue = true;
}
// If any of the Values here is false then don't add to the filter (all must be true).
if (eventNameValue && componentFilterValue && eduLevelFilterValue && crudFilterValue) {
filtered.push(this.get('tabledata')[i]);
}
}
// Display the table again with the new data.
this._table.set('data', filtered);
},
/**
* Clears the filtered table data and changes the filter form to default.
*
* @method _clearFilter
* @private
*/
_clearFilter: function() {
// Reset filter form elements
this._eventName.set('value', '');
this._component.set('value', '0');
this._eduLevel.set('value', '0');
this._crud.set('value', '0');
// Reset the table data back to the original.
this._table.set('data', this.get('tabledata'));
}
}, {
NAME: 'eventFilter',
ATTRS: {
/**
* Data for the table.
*
* @attribute tabledata.
* @type Array
* @writeOnce
*/
tabledata: {
value: null
}
}
});
Y.namespace('M.report_eventlist.EventFilter').init = function(config) {
return new EventFilter(config);
};
@@ -0,0 +1,13 @@
{
"moodle-report_eventlist-eventfilter": {
"requires": [
"base",
"event",
"node",
"node-event-delegate",
"datatable",
"autocomplete",
"autocomplete-filters"
]
}
}
@@ -0,0 +1,55 @@
<?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/>.
/**
* Infected file report renderer
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_infectedfiles\output;
use report_infectedfiles\table\infectedfiles_table;
defined('MOODLE_INTERNAL') || die();
/**
* Infected file report renderer
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends \plugin_renderer_base {
/**
* Render the table
*
* @param infectedfiles_table $table table of infected files
* @return false|string return html code of the table
* @throws \coding_exception
* @throws \moodle_exception
*/
protected function render_infectedfiles_table(infectedfiles_table $table) {
ob_start();
$table->display($table->pagesize, false);
$o = ob_get_contents();
ob_end_clean();
return $o;
}
}
@@ -0,0 +1,172 @@
<?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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_infectedfiles\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request;
defined('MOODLE_INTERNAL') || die();
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
request\plugin\provider,
request\core_userlist_provider {
/**
* This plugin stores the userid of infected users.
*
* @param collection $collection the collection object to add data to.
* @return collection The populated collection.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table(
'infected_files',
[
'userid' => 'privacy:metadata:infected_files:userid',
'filename' => 'privacy:metadata:infected_files:filename',
'timecreated' => 'privacy:metadata:infected_files:timecreated',
],
'privacy:metadata:infected_files'
);
return $collection;
}
/**
* This function gets the contexts containing data for a userid.
*
* @param int $userid The userid to get contexts for.
* @return request\contextlist the context list for the user.
*/
public static function get_contexts_for_userid(int $userid): request\contextlist {
$contextlist = new request\contextlist();
// The system context is the only context where information is stored.
$contextlist->add_system_context();
return $contextlist;
}
/**
* This function exports user data on infected files from the contextlist provided.
*
* @param request\approved_contextlist $contextlist
* @return void
*/
public static function export_user_data(request\approved_contextlist $contextlist) {
global $DB;
foreach ($contextlist as $context) {
// We only export from system context.
if ($context->contextlevel === CONTEXT_SYSTEM) {
$userid = $contextlist->get_user()->id;
$exportdata = [];
$records = $DB->get_records('infected_files', ['userid' => $userid]);
foreach ($records as $record) {
// Export only the data that does not expose internal information.
$data = [];
$data['userid'] = $record->userid;
$data['timecreated'] = $record->timecreated;
$data['filename'] = $record->filename;
$exportdata[] = $data;
}
// Now export this data in the infected files table as subcontext.
request\writer::with_context($context)->export_data(
[get_string('privacy:metadata:infected_files_subcontext', 'report_infectedfiles')],
(object) $exportdata
);
}
}
}
/**
* As this report tracks potential attempted security violations,
* This data should not be deleted at request. This would allow for an
* avenue for a malicious user to cover their tracks. This function deliberately
* does no deletes.
*
* @param \context $context the context to delete for.
* @return void
*/
public static function delete_data_for_all_users_in_context(\context $context) {
return;
}
/**
* As this report tracks potential attempted security violations,
* This data should not be deleted at request. This would allow for an
* avenue for a malicious user to cover their tracks. This function deliberately
* does no deletes.
*
* @param \core_privacy\local\request\approved_contextlist $contextlist the contextlist to delete for.
* @return void
*/
public static function delete_data_for_user(request\approved_contextlist $contextlist) {
return;
}
/**
* This gets the list of users inside of the provided context. In this case, its only system context
* which contains users.
*
* @param \core_privacy\local\request\userlist $userlist
* @return void
*/
public static function get_users_in_context(request\userlist $userlist) {
$context = $userlist->get_context();
if ($context->contextlevel === CONTEXT_SYSTEM) {
// If we are checking system context, we need to get all distinct userids from the table.
$sql = 'SELECT DISTINCT userid
FROM {infected_files}';
$userlist->add_from_sql('userid', $sql, []);
}
}
/**
* As this report tracks potential attempted security violations,
* This data should not be deleted at request. This would allow for an
* avenue for a malicious user to cover their tracks. This function deliberately
* does no deletes.
*
* @param request\approved_userlist $userlist
* @return void
*/
public static function delete_data_for_users(request\approved_userlist $userlist) {
return;
}
}
@@ -0,0 +1,264 @@
<?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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_infectedfiles\table;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/tablelib.php');
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class infectedfiles_table extends \table_sql implements \renderable {
/** @var int current page. */
protected $page;
/**
* Table constructor
*
* @param int $uniqueid table id
* @param \moodle_url $url page url
* @param int $page current page
* @param int $perpage number or record per page
* @throws \coding_exception
*/
public function __construct($uniqueid, \moodle_url $url, $page = 0, $perpage = 30) {
parent::__construct($uniqueid);
$this->set_attribute('class', 'report_infectedfiles');
// Set protected properties.
$this->pagesize = $perpage;
$this->page = $page;
// Define columns in the table.
$this->define_table_columns();
// Define configs.
$this->define_table_configs($url);
}
/**
* Table columns and corresponding headers
*
* @throws \coding_exception
*/
protected function define_table_columns() {
$cols = array(
'filename' => get_string('filename', 'report_infectedfiles'),
'author' => get_string('author', 'report_infectedfiles'),
'reason' => get_string('reason', 'report_infectedfiles'),
'timecreated' => get_string('timecreated', 'report_infectedfiles'),
'actions' => get_string('actions'),
);
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
}
/**
* Define table configuration
*
* @param \moodle_url $url
*/
protected function define_table_configs(\moodle_url $url) {
// Set table url.
$this->define_baseurl($url);
// Set table configs.
$this->collapsible(false);
$this->sortable(false);
$this->pageable(true);
}
/**
* Builds the SQL query.
*
* @param bool $count When true, return the count SQL.
* @return array containing sql to use and an array of params.
*/
protected function get_sql_and_params($count = false): array {
if ($count) {
$select = "COUNT(1)";
} else {
$select = "*";
}
$sql = "SELECT $select
FROM {infected_files}";
$params = array();
if (!$count) {
$sql .= " ORDER BY timecreated DESC";
}
return array($sql, $params);
}
/**
* Get data.
*
* @param int $pagesize number of records to fetch
* @param bool $useinitialsbar initial bar
* @throws \dml_exception
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
list($countsql, $countparams) = $this->get_sql_and_params(true);
list($sql, $params) = $this->get_sql_and_params();
$total = $DB->count_records_sql($countsql, $countparams);
$this->pagesize($pagesize, $total);
$this->rawdata = $DB->get_records_sql($sql, $params, $this->get_page_start(), $this->get_page_size());
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Column to display the authors fullname from userid.
*
* @param \stdClass $row the row from sql.
* @return string the authors name.
*/
protected function col_author($row): string {
// Get user fullname from ID.
$user = \core_user::get_user($row->userid);
$url = new \moodle_url('/user/profile.php', ['id' => $row->userid]);
return \html_writer::link($url, fullname($user));
}
/**
* Column to display the failure reason.
*
* @param \stdClass $row the row from sql.
* @return string the formatted reason.
*/
protected function col_reason($row) {
return format_text($row->reason);
}
/**
* Custom actions column
*
* @param \stdClass $row an incident record.
* @return string content of action column.
* @throws \coding_exception
* @throws \moodle_exception
*/
protected function col_actions($row): string {
global $OUTPUT;
$filename = $row->quarantinedfile;
$fileid = $row->id;
// If the file isn't found, we can do nothing in this column.
// This shouldn't happen, unless the file is manually deleted from the server externally.
if (!\core\antivirus\quarantine::quarantined_file_exists($filename)) {
return '';
}
$links = '';
$managefilepage = new \moodle_url('/report/infectedfiles/index.php');
// Download.
$downloadparams = ['file' => $fileid, 'action' => 'download', 'sesskey' => sesskey()];
$downloadurl = new \moodle_url($managefilepage, $downloadparams);
$downloadconfirm = new \confirm_action(get_string('confirmdownload', 'report_infectedfiles'));
$links .= $OUTPUT->action_icon(
$downloadurl,
new \pix_icon('t/download', get_string('download')),
$downloadconfirm
);
// Delete.
$deleteparams = ['file' => $fileid, 'action' => 'delete', 'sesskey' => sesskey()];
$deleteurl = new \moodle_url($managefilepage, $deleteparams);
$deleteconfirm = new \confirm_action(get_string('confirmdelete', 'report_infectedfiles'));
$links .= $OUTPUT->action_icon(
$deleteurl,
new \pix_icon('t/delete', get_string('delete')),
$deleteconfirm
);
return $links;
}
/**
* Custom time column.
*
* @param \stdClass $row an incident record.
* @return string time created in user-friendly format.
*/
protected function col_timecreated($row): string {
return userdate($row->timecreated);
}
/**
* Display table with download all and delete all buttons
*
* @param int $pagesize number or records perpage
* @param bool $useinitialsbar use the bar or not
* @param string $downloadhelpbutton help button
* @return void
* @throws \coding_exception
* @throws \moodle_exception
*/
public function display($pagesize, $useinitialsbar, $downloadhelpbutton='') {
global $OUTPUT;
// Output the table, and then display buttons.
$this->out($pagesize, $useinitialsbar, $downloadhelpbutton);
$managefilepage = new \moodle_url('/report/infectedfiles/index.php');
// If there are no rows, dont bother rendering extra buttons.
if (empty($this->rawdata)) {
return;
}
// Delete All.
$deleteallparams = ['action' => 'deleteall', 'sesskey' => sesskey()];
$deleteallurl = new \moodle_url($managefilepage, $deleteallparams);
$deletebutton = new \single_button($deleteallurl, get_string('deleteall'), 'post', \single_button::BUTTON_PRIMARY);
$deletebutton->add_confirm_action(get_string('confirmdeleteall', 'report_infectedfiles'));
echo $OUTPUT->render($deletebutton);
echo "&nbsp";
// Download All.
$downloadallparams = ['action' => 'downloadall', 'sesskey' => sesskey()];
$downloadallurl = new \moodle_url($managefilepage, $downloadallparams);
$downloadbutton = new \single_button($downloadallurl, get_string('downloadall'), 'post', \single_button::BUTTON_PRIMARY);
$downloadbutton->add_confirm_action(get_string('confirmdownloadall', 'report_infectedfiles'));
echo $OUTPUT->render($downloadbutton);
}
}
+74
View File
@@ -0,0 +1,74 @@
<?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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once($CFG->libdir.'/adminlib.php');
use \core\antivirus\quarantine;
admin_externalpage_setup('reportinfectedfiles', '', null, '', array('pagelayout' => 'report'));
$action = optional_param('action', '', PARAM_TEXT);
// If action exists, we need to process the actions safely.
if (!empty($action)) {
// Nothing can be done without a valid sesskey.
require_sesskey();
// For any cancel actions, just reload the page clean.
$cancel = $PAGE->url;
// Decide on page action.
switch ($action) {
case 'download':
$fileid = required_param('file', PARAM_INT);
quarantine::download_quarantined_file($fileid);
break;
case 'downloadall':
quarantine::download_all_quarantined_files();
break;
case 'delete':
$fileid = required_param('file', PARAM_INT);
quarantine::delete_quarantined_file($fileid);
break;
case 'deleteall':
// Remove file until current time.
quarantine::clean_up_quarantine_folder(time());
break;
}
// Reload page cleanly once actions are processed.
redirect($PAGE->url);
}
// Once actions are dealt with, display the page.
$page = optional_param('page', 0, PARAM_INT);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('infectedfiles', 'report_infectedfiles'));
$table = new \report_infectedfiles\table\infectedfiles_table('report-infectedfiles-report-table', $PAGE->url, $page);
$table->define_baseurl($PAGE->url);
echo $PAGE->get_renderer('report_infectedfiles')->render($table);
echo $OUTPUT->footer();
@@ -0,0 +1,40 @@
<?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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['author'] = 'Author';
$string['confirmdelete'] = 'Are you sure you want to delete this file?';
$string['confirmdeleteall'] = 'Are you sure you want to delete all files?';
$string['confirmdownload'] = 'Are you sure you want to download this file?';
$string['confirmdownloadall'] = 'Are you sure you want to download all files?';
$string['filename'] = 'File name';
$string['infectedfiles'] = 'Antivirus failures';
$string['privacy:metadata:infected_files'] = 'This table stores information on antivirus failures detected by the system.';
$string['privacy:metadata:infected_files:filename'] = 'The name of the infected file uploaded by the user.';
$string['privacy:metadata:infected_files:timecreated'] = 'The timestamp of when a user uploaded an infected file.';
$string['privacy:metadata:infected_files:userid'] = 'The user ID of the user who uploaded an infected file.';
$string['privacy:metadata:infected_files_subcontext'] = 'Antivirus failures';
$string['pluginname'] = 'Infected files';
$string['quarantinedfile'] = 'Quarantined file';
$string['reason'] = 'Failure reason';
$string['timecreated'] = 'Time created';
+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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$ADMIN->add('reports', new admin_externalpage('reportinfectedfiles',
get_string('infectedfiles', 'report_infectedfiles'),
"$CFG->wwwroot/report/infectedfiles/index.php"));
$settings = null;
+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/>.
/**
* Infected file report
*
* @package report_infectedfiles
* @author Nathan Nguyen <nathannguyen@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->component = 'report_infectedfiles';
+69
View File
@@ -0,0 +1,69 @@
<?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/>.
/**
* Forwards the user to the action they selected.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
$predictionid = required_param('predictionid', PARAM_INT);
$actionname = required_param('action', PARAM_ALPHANUMEXT);
$forwardurl = required_param('forwardurl', PARAM_LOCALURL);
if (!\core_analytics\manager::is_analytics_enabled()) {
$PAGE->set_context(\context_system::instance());
$renderer = $PAGE->get_renderer('report_insights');
echo $renderer->render_analytics_disabled();
exit(0);
}
list($model, $prediction, $context) = \core_analytics\manager::get_prediction($predictionid, true);
if ($context->contextlevel < CONTEXT_COURSE) {
// Only for higher levels than course.
$PAGE->set_context($context);
}
if (empty($forwardurl)) {
$params = array('modelid' => $model->get_id(), 'contextid' => $context->id);
$forwardurl = new \moodle_url('/report/insights/insights.php', $params);
}
$params = array('predictionid' => $prediction->get_prediction_data()->id, 'action' => $actionname, 'forwardurl' => $forwardurl);
$url = new \moodle_url('/report/insights/action.php', $params);
$PAGE->set_url($url);
$modelready = $model->is_enabled() && $model->is_trained() && $model->predictions_exist($context);
if (!$modelready) {
$PAGE->set_pagelayout('report');
// We don't want to disclose the name of the model if it has not been enabled.
$PAGE->set_title($context->get_context_name());
$PAGE->set_heading($context->get_context_name());
echo $OUTPUT->header();
echo $OUTPUT->notification(get_string('disabledmodel', 'report_insights'), \core\output\notification::NOTIFY_INFO);
echo $OUTPUT->footer();
exit(0);
}
$prediction->action_executed($actionname, $model->get_target());
redirect($forwardurl);
+9
View File
@@ -0,0 +1,9 @@
define("report_insights/actions",["exports","core/str","core/ajax","core/notification","core/url","core/modal_events","core/modal_save_cancel"],(function(_exports,_str,_ajax,_notification,_url,_modal_events,_modal_save_cancel){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Module to manage report insights actions that are executed using AJAX.
*
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initBulk=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification),_url=_interopRequireDefault(_url),_modal_events=_interopRequireDefault(_modal_events),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel);const executeAction=(predictionIds,predictionContainers,actionName)=>{var predictionids,actionname;(predictionids=predictionIds,actionname=actionName,_ajax.default.call([{methodname:"report_insights_action_executed",args:{actionname:actionname,predictionids:predictionids}}])[0]).then((()=>{const tableNode=(predictionContainers=>{for(const el of predictionContainers)if(el.closest("table"))return el.closest("table");return null})(predictionContainers);if(predictionContainers.forEach((el=>el.remove())),!tableNode.querySelector("tbody > tr")){const params={contextid:tableNode.closest("div.insight-container").dataset.contextId,modelid:tableNode.closest("div.insight-container").dataset.modelId};window.location.assign(_url.default.relativeUrl("report/insights/insights.php",params,!1))}})).catch(_notification.default.exception)};_exports.initBulk=rootNode=>{document.addEventListener("click",(e=>{const action=e.target.closest("".concat(rootNode," [data-bulk-actionname]"));if(!action)return;e.preventDefault();const actionName=action.dataset.bulkActionname,actionVisibleName=action.textContent.trim(),predictionContainers=Array.from(document.querySelectorAll('.insights-list input[data-togglegroup^="insight-bulk-action-"][data-toggle="slave"]:checked')).map((checkbox=>checkbox.closest("tr[data-prediction-id]"))),predictionIds=predictionContainers.map((el=>el.dataset.predictionId));if(0===predictionIds.length)return;const stringParams={action:actionVisibleName,nitems:predictionIds.length};_modal_save_cancel.default.create({title:actionVisibleName,body:(0,_str.get_string)("confirmbulkaction","report_insights",stringParams),buttons:{save:(0,_str.get_string)("confirm")},show:!0}).then((modal=>(modal.getRoot().on(_modal_events.default.save,(function(){return executeAction(predictionIds,predictionContainers,actionName)})),modal))).catch(_notification.default.exception)}))}}));
//# sourceMappingURL=actions.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Message users.
*
* @module report_insights/message_users
* @copyright 2019 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("report_insights/message_users",["jquery","core/str","core/log","core/modal_save_cancel","core/modal_events","core/templates","core/notification","core/ajax"],(function($,Str,Log,ModalSaveCancel,ModalEvents,Templates,Notification,Ajax){var SELECTORS_BULKACTIONSELECT="#formactionid",MessageUsers=function(rootNode,actionName){this.actionName=actionName,this.attachEventListeners(rootNode)};return MessageUsers.prototype.actionName=null,MessageUsers.prototype.modal=null,MessageUsers.prototype.attachEventListeners=function(rootNode){$(rootNode+" button[data-bulk-sendmessage]").on("click",function(e){e.preventDefault();var cTarget=$(e.currentTarget),users={},predictionToUserMapping=cTarget.data("prediction-to-user-id");return $('.insights-list input[data-togglegroup^="insight-bulk-action"][data-toggle="slave"]:checked').each((function(index,value){var predictionId=$(value).closest("tr[data-prediction-id]").data("prediction-id");if(void 0!==predictionToUserMapping[predictionId]){var userId=predictionToUserMapping[predictionId];users[predictionId]=userId}else Log.error("Unknown user for prediction "+predictionId)})),0===Object.keys(users).length||this.showSendMessage(users),this}.bind(this))},MessageUsers.prototype.showSendMessage=function(users){var userIds=new Set(Object.values(users));if(0==userIds.length)return $.Deferred().resolve().promise();var titlePromise=null;titlePromise=1==userIds.size?Str.get_string("sendbulkmessagesingle","core_message"):Str.get_string("sendbulkmessage","core_message",userIds.size),ModalSaveCancel.create({body:Templates.render("core_user/send_bulk_message",{}),title:titlePromise,buttons:{save:titlePromise},show:!0}).then(function(modal){return this.modal=modal,this.modal.getRoot().on(ModalEvents.hidden,function(){$(SELECTORS_BULKACTIONSELECT).focus(),this.modal.getRoot().remove()}.bind(this)),this.modal.getRoot().on(ModalEvents.save,this.submitSendMessage.bind(this,users)),this.modal}.bind(this))},MessageUsers.prototype.submitSendMessage=function(users){var messageText=this.modal.getRoot().find("form textarea").val(),messages=[];new Set(Object.values(users)).forEach((function(userId){messages.push({touserid:userId,text:messageText})}));var actionName=this.actionName,message=null;return Ajax.call([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0].then((function(messageIds){return 1==messageIds.length?Str.get_string("sendbulkmessagesentsingle","core_message"):Str.get_string("sendbulkmessagesent","core_message",messageIds.length)})).then((function(msg){return message=msg,Ajax.call([{methodname:"report_insights_action_executed",args:{actionname:actionName,predictionids:Object.keys(users)}}])[0]})).then((function(){return Notification.addNotification({message:message,type:"success"}),!0})).catch(Notification.exception)},{init:function(rootNode,actionName){return new MessageUsers(rootNode,actionName)}}}));
//# sourceMappingURL=message_users.min.js.map
File diff suppressed because one or more lines are too long
+129
View File
@@ -0,0 +1,129 @@
// 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/>.
/**
* Module to manage report insights actions that are executed using AJAX.
*
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* This module manages prediction actions that require AJAX requests.
*
* @module report_insights/actions
*/
import {get_string as getString} from 'core/str';
import Ajax from 'core/ajax';
import Notification from 'core/notification';
import Url from 'core/url';
import ModalEvents from 'core/modal_events';
import ModalSaveCancel from 'core/modal_save_cancel';
/**
* Executes the provided action.
*
* @param {Array} predictionids
* @param {String} actionname
* @return {Promise}
*/
const markActionExecuted = (predictionids, actionname) => Ajax.call([
{
methodname: 'report_insights_action_executed',
args: {
actionname,
predictionids,
},
}
])[0];
const getPredictionTable = (predictionContainers) => {
for (const el of predictionContainers) {
if (el.closest('table')) {
return el.closest('table');
}
}
return null;
};
const executeAction = (predictionIds, predictionContainers, actionName) => {
markActionExecuted(predictionIds, actionName).then(() => {
// Remove the selected elements from the list.
const tableNode = getPredictionTable(predictionContainers);
predictionContainers.forEach((el) => el.remove());
if (!tableNode.querySelector('tbody > tr')) {
const params = {
contextid: tableNode.closest('div.insight-container').dataset.contextId,
modelid: tableNode.closest('div.insight-container').dataset.modelId,
};
window.location.assign(Url.relativeUrl("report/insights/insights.php", params, false));
}
return;
}).catch(Notification.exception);
};
/**
* Attach on click handlers for bulk actions.
*
* @param {String} rootNode
* @access public
*/
export const initBulk = (rootNode) => {
document.addEventListener('click', (e) => {
const action = e.target.closest(`${rootNode} [data-bulk-actionname]`);
if (!action) {
return;
}
e.preventDefault();
const actionName = action.dataset.bulkActionname;
const actionVisibleName = action.textContent.trim();
const predictionContainers = Array.from(document.querySelectorAll(
'.insights-list input[data-togglegroup^="insight-bulk-action-"][data-toggle="slave"]:checked',
)).map((checkbox) => checkbox.closest('tr[data-prediction-id]'));
const predictionIds = predictionContainers.map((el) => el.dataset.predictionId);
if (predictionIds.length === 0) {
// No items selected message.
return;
}
const stringParams = {
action: actionVisibleName,
nitems: predictionIds.length,
};
ModalSaveCancel.create({
title: actionVisibleName,
body: getString('confirmbulkaction', 'report_insights', stringParams),
buttons: {
save: getString('confirm'),
},
show: true,
}).then((modal) => {
modal.getRoot().on(ModalEvents.save, function() {
// The action is now confirmed, sending an action for it.
return executeAction(predictionIds, predictionContainers, actionName);
});
return modal;
}).catch(Notification.exception);
});
};
+204
View File
@@ -0,0 +1,204 @@
// 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/>.
/**
* Message users.
*
* @module report_insights/message_users
* @copyright 2019 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/str', 'core/log', 'core/modal_save_cancel', 'core/modal_events', 'core/templates',
'core/notification', 'core/ajax'],
function($, Str, Log, ModalSaveCancel, ModalEvents, Templates, Notification, Ajax) {
var SELECTORS = {
BULKACTIONSELECT: "#formactionid"
};
/**
* Constructor.
*
* @param {String} rootNode
* @param {String} actionName
*/
var MessageUsers = function(rootNode, actionName) {
this.actionName = actionName;
this.attachEventListeners(rootNode);
};
/**
* @var {String} actionName
* @private
*/
MessageUsers.prototype.actionName = null;
/**
* @var {Modal} modal
* @private
*/
MessageUsers.prototype.modal = null;
/**
* Attach the event listener to the send message bulk action.
* @param {String} rootNode
*/
MessageUsers.prototype.attachEventListeners = function(rootNode) {
$(rootNode + ' button[data-bulk-sendmessage]').on('click', function(e) {
e.preventDefault();
var cTarget = $(e.currentTarget);
// Using an associative array in case there is more than 1 prediction for the same user.
var users = {};
var predictionToUserMapping = cTarget.data('prediction-to-user-id');
var checkedSelector = '.insights-list input[data-togglegroup^="insight-bulk-action"][data-toggle="slave"]:checked';
$(checkedSelector).each(function(index, value) {
var predictionId = $(value).closest('tr[data-prediction-id]').data('prediction-id');
if (typeof predictionToUserMapping[predictionId] === 'undefined') {
Log.error('Unknown user for prediction ' + predictionId);
return;
}
var userId = predictionToUserMapping[predictionId];
users[predictionId] = userId;
});
if (Object.keys(users).length === 0) {
return this;
}
this.showSendMessage(users);
return this;
}.bind(this));
};
/**
* Show the send message popup.
*
* @method showSendMessage
* @private
* @param {Object} users Prediction id to user id mapping.
* @returns {Promise}
*/
MessageUsers.prototype.showSendMessage = function(users) {
var userIds = new Set(Object.values(users));
if (userIds.length == 0) {
// Nothing to do.
return $.Deferred().resolve().promise();
}
var titlePromise = null;
if (userIds.size == 1) {
titlePromise = Str.get_string('sendbulkmessagesingle', 'core_message');
} else {
titlePromise = Str.get_string('sendbulkmessage', 'core_message', userIds.size);
}
// eslint-disable-next-line promise/catch-or-return
ModalSaveCancel.create({
body: Templates.render('core_user/send_bulk_message', {}),
title: titlePromise,
buttons: {
save: titlePromise,
},
show: true,
})
.then(function(modal) {
// Keep a reference to the modal.
this.modal = modal;
// We want to focus on the action select when the dialog is closed.
this.modal.getRoot().on(ModalEvents.hidden, function() {
$(SELECTORS.BULKACTIONSELECT).focus();
this.modal.getRoot().remove();
}.bind(this));
this.modal.getRoot().on(ModalEvents.save, this.submitSendMessage.bind(this, users));
return this.modal;
}.bind(this));
};
/**
* Send a message to these users.
*
* @method submitSendMessage
* @private
* @param {Object} users Prediction id to user id mapping.
* @returns {Promise}
*/
MessageUsers.prototype.submitSendMessage = function(users) {
var messageText = this.modal.getRoot().find('form textarea').val();
var messages = [];
var userIds = new Set(Object.values(users));
userIds.forEach(function(userId) {
messages.push({touserid: userId, text: messageText});
});
var actionName = this.actionName;
var message = null;
return Ajax.call([{
methodname: 'core_message_send_instant_messages',
args: {messages: messages}
}])[0].then(function(messageIds) {
if (messageIds.length == 1) {
return Str.get_string('sendbulkmessagesentsingle', 'core_message');
} else {
return Str.get_string('sendbulkmessagesent', 'core_message', messageIds.length);
}
}).then(function(msg) {
// Save this for the following callback. Now that we got everything
// done we can flag this action as executed.
message = msg;
return Ajax.call([{
methodname: 'report_insights_action_executed',
args: {
actionname: actionName,
predictionids: Object.keys(users)
}
}])[0];
}).then(function() {
Notification.addNotification({
message: message,
type: "success"
});
return true;
}).catch(Notification.exception);
};
return /** @alias module:report_insights/message_users */ {
// Public variables and functions.
/**
* @method init
* @param {String} rootNode
* @param {String} actionName
* @returns {MessageUsers}
*/
'init': function(rootNode, actionName) {
return new MessageUsers(rootNode, actionName);
}
};
});
+214
View File
@@ -0,0 +1,214 @@
<?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/>.
namespace report_insights;
use core_external\external_api;
use core_external\external_value;
use core_external\external_single_structure;
use core_external\external_multiple_structure;
use core_external\external_function_parameters;
use core_external\external_warnings;
/**
* This is the external API for this component.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* set_notuseful_prediction parameters.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function set_notuseful_prediction_parameters() {
return new external_function_parameters(
array(
'predictionid' => new external_value(PARAM_INT, 'The prediction id', VALUE_REQUIRED)
)
);
}
/**
* Flags a prediction as fixed so no need to display it any more.
*
* @param int $predictionid
* @return array an array of warnings and a boolean
* @since Moodle 3.4
*/
public static function set_notuseful_prediction($predictionid) {
$params = self::validate_parameters(self::set_notuseful_prediction_parameters(), array('predictionid' => $predictionid));
list($model, $prediction, $context) = self::validate_prediction($params['predictionid']);
$prediction->action_executed(\core_analytics\prediction::ACTION_NOT_USEFUL, $model->get_target());
$success = true;
return array('success' => $success, 'warnings' => array());
}
/**
* set_notuseful_prediction return
*
* @return \core_external\external_description
* @since Moodle 3.4
*/
public static function set_notuseful_prediction_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the prediction was successfully flagged as not useful.'),
'warnings' => new external_warnings(),
)
);
}
/**
* Deprecated in favour of action_executed.
*/
public static function set_notuseful_prediction_is_deprecated() {
return true;
}
/**
* set_fixed_prediction parameters.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function set_fixed_prediction_parameters() {
return new external_function_parameters(
array(
'predictionid' => new external_value(PARAM_INT, 'The prediction id', VALUE_REQUIRED)
)
);
}
/**
* Flags a prediction as fixed so no need to display it any more.
*
* @param int $predictionid
* @return array an array of warnings and a boolean
* @since Moodle 3.4
*/
public static function set_fixed_prediction($predictionid) {
$params = self::validate_parameters(self::set_fixed_prediction_parameters(), array('predictionid' => $predictionid));
list($model, $prediction, $context) = self::validate_prediction($params['predictionid']);
$prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $model->get_target());
$success = true;
return array('success' => $success, 'warnings' => array());
}
/**
* set_fixed_prediction return
*
* @return \core_external\external_description
* @since Moodle 3.4
*/
public static function set_fixed_prediction_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the prediction was successfully flagged as fixed.'),
'warnings' => new external_warnings(),
)
);
}
/**
* Deprecated in favour of action_executed.
*/
public static function set_fixed_prediction_is_deprecated() {
return true;
}
/**
* action_executed parameters.
*
* @return external_function_parameters
* @since Moodle 3.8
*/
public static function action_executed_parameters() {
return new external_function_parameters (
array(
'actionname' => new external_value(PARAM_ALPHANUMEXT, 'The name of the action', VALUE_REQUIRED),
'predictionids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Prediction id', VALUE_REQUIRED),
'Array of prediction ids'
),
)
);
}
/**
* Stores an action executed over a group of predictions.
*
* @param string $actionname
* @param array $predictionids
* @return array an array of warnings and a boolean
* @since Moodle 3.8
*/
public static function action_executed(string $actionname, array $predictionids) {
$params = self::validate_parameters(self::action_executed_parameters(),
array('actionname' => $actionname, 'predictionids' => $predictionids));
foreach ($params['predictionids'] as $predictionid) {
list($model, $prediction, $context) = self::validate_prediction($predictionid);
// The method action_executed checks that the provided action is valid.
$prediction->action_executed($actionname, $model->get_target());
}
return array('warnings' => array());
}
/**
* action_executed return
*
* @return \core_external\external_description
* @since Moodle 3.8
*/
public static function action_executed_returns() {
return new external_single_structure(
array(
'warnings' => new external_warnings(),
)
);
}
/**
* Validates access to the prediction and returns it.
*
* @param int $predictionid
* @return array array($model, $prediction, $context)
*/
protected static function validate_prediction($predictionid) {
list($model, $prediction, $context) = \core_analytics\manager::get_prediction($predictionid);
self::validate_context($context);
return array($model, $prediction, $context);
}
}
@@ -0,0 +1,120 @@
<?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/>.
/**
* Output helper to export actions for rendering.
*
* @package report_insights
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_insights\output;
defined('MOODLE_INTERNAL') || die();
/**
* Output helper to export actions for rendering.
*
* @package report_insights
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class actions_exporter {
/**
* Add the prediction actions renderable.
*
* @param \core_analytics\local\target\base $target
* @param \renderer_base $output
* @param \core_analytics\prediction $prediction
* @param bool $includedetailsaction
* @return \stdClass|false
*/
public static function add_prediction_actions(\core_analytics\local\target\base $target, \renderer_base $output,
\core_analytics\prediction $prediction, bool $includedetailsaction = false) {
$actions = $target->prediction_actions($prediction, $includedetailsaction);
if ($actions) {
$actionsmenu = new \action_menu();
// Add all actions defined by the target.
foreach ($actions as $action) {
$actionsmenu->add_primary_action($action->get_action_link());
}
return $actionsmenu->export_for_template($output);
}
return false;
}
/**
* Add bulk actions renderables.
*
* Note that if you are planning to render the bulk actions, the provided predictions must share the same predicted value.
*
* @param \core_analytics\local\target\base $target
* @param \renderer_base $output
* @param \core_analytics\prediction[] $predictions Bulk actions for this set of predictions.
* @param \context $context The context of these predictions.
* @return \stdClass[]|false
*/
public static function add_bulk_actions(\core_analytics\local\target\base $target, \renderer_base $output, array $predictions,
\context $context) {
global $USER;
$bulkactions = $target->bulk_actions($predictions);
if ($context->contextlevel === CONTEXT_USER) {
// Remove useful / notuseful if the current user is not part of the users who receive the insight (e.g. a site manager
// who looks at the generated insights for a particular user).
$insightusers = $target->get_insights_users($context);
if (empty($insightusers[$USER->id])) {
foreach ($bulkactions as $key => $action) {
if ($action->get_action_name() === 'useful' || $action->get_action_name() === 'notuseful') {
unset($bulkactions[$key]);
}
}
}
}
if (!$bulkactions) {
return false;
}
$actionsmenu = [];
// All the predictions share a common predicted value.
$predictionvalue = reset($predictions)->get_prediction_data()->prediction;
// Add all actions defined by the target.
foreach ($bulkactions as $action) {
$action->get_action_link()->set_attribute('data-togglegroup', 'insight-bulk-action-' . $predictionvalue);
$actionsmenu[] = $action->get_action_link()->export_for_template($output);
}
if (empty($actionsmenu)) {
return false;
}
return $actionsmenu;
}
}
+265
View File
@@ -0,0 +1,265 @@
<?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/>.
/**
* Single insight view page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_insights\output;
use core_analytics\prediction;
defined('MOODLE_INTERNAL') || die();
/**
* Single insight view page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class insight implements \renderable, \templatable {
/**
* @var \core_analytics\model
*/
protected $model;
/**
* @var \core_analytics\prediction
*/
protected $prediction;
/**
* @var bool
*/
protected $includedetailsaction = false;
/**
* @var \context
*/
protected $context;
/**
* Constructor
*
* @param \core_analytics\prediction $prediction
* @param \core_analytics\model $model
* @param bool $includedetailsaction
* @param \context $context
* @return void
*/
public function __construct(\core_analytics\prediction $prediction, \core_analytics\model $model, $includedetailsaction,
\context $context) {
$this->prediction = $prediction;
$this->model = $model;
$this->includedetailsaction = $includedetailsaction;
$this->context = $context;
}
/**
* Exports the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output) {
// Get the prediction data.
$predictiondata = $this->prediction->get_prediction_data();
$target = $this->model->get_target();
$data = new \stdClass();
$data->modelid = $this->model->get_id();
$data->contextid = $this->context->id;
$data->predictionid = $predictiondata->id;
$targetname = $target->get_name();
$data->insightname = format_string($targetname);
$targetinfostr = $targetname->get_identifier() . 'info';
if (get_string_manager()->string_exists($targetinfostr, $targetname->get_component())) {
$data->insightdescription = get_string($targetinfostr, $targetname->get_component());
}
$data->showpredictionheading = true;
if (!$target->is_linear()) {
$nclasses = count($target::get_classes());
$nignoredclasses = count($target->ignored_predicted_classes());
if ($nclasses - $nignoredclasses <= 1) {
// Hide the prediction heading if there is only 1 class displayed. Otherwise it is redundant with the insight name.
$data->showpredictionheading = false;
}
}
// Get the details.
$data->timecreated = userdate($predictiondata->timecreated);
$data->timerange = '';
if (!empty($predictiondata->timestart) && !empty($predictiondata->timeend)) {
$timerange = new \stdClass();
$timerange->timestart = userdate($predictiondata->timestart);
$timerange->timeend = userdate($predictiondata->timeend);
$data->timerange = get_string('timerangewithdata', 'report_insights', $timerange);
}
// Sample info (determined by the analyser).
list($data->sampledescription, $samplerenderable) = $this->model->prediction_sample_description($this->prediction);
// Sampleimage is a renderable we should pass it to HTML.
if ($samplerenderable) {
$data->sampleimage = $output->render($samplerenderable);
}
// Prediction info.
$predictedvalue = $predictiondata->prediction;
$data->predictiondisplayvalue = $target->get_display_value($predictedvalue);
list($data->style, $data->outcomeicon) = self::get_calculation_display($target,
floatval($predictedvalue), $output);
$data->actions = actions_exporter::add_prediction_actions($target, $output, $this->prediction,
$this->includedetailsaction);
$data->bulkactions = actions_exporter::add_bulk_actions($target, $output, [$this->prediction], $this->context);
// Calculated indicators values.
$data->calculations = array();
$calculations = $this->prediction->get_calculations();
foreach ($calculations as $calculation) {
// Hook for indicators with extra features that should not be displayed (e.g. discrete indicators).
if (!$calculation->indicator->should_be_displayed($calculation->value, $calculation->subtype)) {
continue;
}
if ($calculation->value === null) {
// We don't show values that could not be calculated.
continue;
}
$obj = new \stdClass();
$obj->name = call_user_func(array($calculation->indicator, 'get_name'));
$obj->displayvalue = $calculation->indicator->get_display_value($calculation->value, $calculation->subtype);
list($obj->style, $obj->outcomeicon) = self::get_calculation_display($calculation->indicator,
floatval($calculation->value), $output, $calculation->subtype);
$identifier = $calculation->indicator->get_name()->get_identifier() . 'def';
$component = $calculation->indicator->get_name()->get_component();
if (get_string_manager()->string_exists($identifier, $component)) {
$obj->outcomehelp = (new \help_icon($identifier, $component))->export_for_template($output);
}
$data->calculations[] = $obj;
}
if (empty($data->calculations)) {
$data->nocalculations = (object)array(
'message' => get_string('nodetailsavailable', 'report_insights'),
'closebutton' => false
);
}
// This is only rendered in report_insights/insight_details template for predictions with no action.
// We need it to automatically enable the bulk action buttons in report/insights/prediction.php.
$filtered = [
\core_analytics\prediction::ACTION_FIXED,
\core_analytics\prediction::ACTION_NOT_USEFUL,
\core_analytics\prediction::ACTION_USEFUL,
\core_analytics\prediction::ACTION_NOT_APPLICABLE,
\core_analytics\prediction::ACTION_INCORRECTLY_FLAGGED,
];
if (!$this->prediction->get_executed_actions($filtered)) {
$toggleall = new \core\output\checkbox_toggleall('insight-bulk-action-' . $predictedvalue, true, [
'id' => 'id-toggle-all-' . $predictedvalue,
'name' => 'toggle-all-' . $predictedvalue,
'classes' => 'hidden',
'label' => get_string('selectall'),
'labelclasses' => 'sr-only',
'checked' => false,
]);
$data->hiddencheckboxtoggleall = $output->render($toggleall);
$toggle = new \core\output\checkbox_toggleall('insight-bulk-action-' . $predictedvalue, false, [
'id' => 'id-select-' . $data->predictionid,
'name' => 'select-' . $data->predictionid,
'label' => get_string('selectprediction', 'report_insights', $data->sampledescription),
'labelclasses' => 'accesshide',
]);
$data->toggleslave = $output->render($toggle);
}
return $data;
}
/**
* Returns display info for the calculated value outcome.
*
* @param \core_analytics\calculable $calculable
* @param float $value
* @param \renderer_base $output
* @param string|false $subtype
* @return array The style as 'success', 'info', 'warning' or 'danger' and pix_icon
*/
public static function get_calculation_display(\core_analytics\calculable $calculable, $value, $output, $subtype = false) {
$outcome = $calculable->get_calculation_outcome($value, $subtype);
switch ($outcome) {
case \core_analytics\calculable::OUTCOME_NEUTRAL:
$style = '';
$text = get_string('outcomeneutral', 'report_insights');
$icon = 't/check';
break;
case \core_analytics\calculable::OUTCOME_VERY_POSITIVE:
$style = 'success';
$text = get_string('outcomeverypositive', 'report_insights');
$icon = 't/approve';
break;
case \core_analytics\calculable::OUTCOME_OK:
$style = 'info';
$text = get_string('outcomeok', 'report_insights');
$icon = 't/check';
break;
case \core_analytics\calculable::OUTCOME_NEGATIVE:
$style = 'warning';
$text = get_string('outcomenegative', 'report_insights');
$icon = 'i/warning';
break;
case \core_analytics\calculable::OUTCOME_VERY_NEGATIVE:
$style = 'danger';
$text = get_string('outcomeverynegative', 'report_insights');
$icon = 'i/warning';
break;
default:
throw new \coding_exception('The outcome returned by ' . get_class($calculable) . '::get_calculation_outcome is ' .
'not one of the accepted values. Please use \core_analytics\calculable::OUTCOME_VERY_POSITIVE, ' .
'\core_analytics\calculable::OUTCOME_OK, \core_analytics\calculable::OUTCOME_NEGATIVE, ' .
'\core_analytics\calculable::OUTCOME_VERY_NEGATIVE or \core_analytics\calculable::OUTCOME_NEUTRAL');
}
$icon = new \pix_icon($icon, $text);
return array($style, $icon->export_for_template($output));
}
/**
* Model getter.
*
* @return \core_analytics\model
*/
public function get_model(): \core_analytics\model {
return $this->model;
}
}
@@ -0,0 +1,224 @@
<?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/>.
/**
* Insights list page.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_insights\output;
defined('MOODLE_INTERNAL') || die();
/**
* Shows report_insights insights list.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class insights_list implements \renderable, \templatable {
/**
* @var \core_analytics\model
*/
protected $model;
/**
* @var \context
*/
protected $context;
/**
* @var \core_analytics\model[]
*/
protected $othermodels;
/**
* @var int
*/
protected $page;
/**
* @var int
*/
protected $perpage;
/**
* Constructor
*
* @param \core_analytics\model $model
* @param \context $context
* @param \core_analytics\model[] $othermodels
* @param int $page
* @param int $perpage The max number of results to fetch
* @return void
*/
public function __construct(\core_analytics\model $model, \context $context, $othermodels, $page = 0, $perpage = 100) {
$this->model = $model;
$this->context = $context;
$this->othermodels = $othermodels;
$this->page = $page;
$this->perpage = $perpage;
}
/**
* Exports the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output) {
global $PAGE;
$target = $this->model->get_target();
$data = new \stdClass();
$data->modelid = $this->model->get_id();
$data->contextid = $this->context->id;
$targetname = $target->get_name();
$data->insightname = format_string($targetname);
$targetinfostr = $targetname->get_identifier() . 'info';
if (get_string_manager()->string_exists($targetinfostr, $targetname->get_component())) {
$data->insightdescription = get_string($targetinfostr, $targetname->get_component());
}
$data->showpredictionheading = true;
if (!$target->is_linear()) {
$nclasses = count($target::get_classes());
$nignoredclasses = count($target->ignored_predicted_classes());
if ($nclasses - $nignoredclasses <= 1) {
// Hide the prediction heading if there is only 1 class displayed. Otherwise it is redundant with the insight name.
$data->showpredictionheading = false;
}
}
$total = 0;
if ($this->model->uses_insights()) {
$target->add_bulk_actions_js();
$predictionsdata = $this->model->get_predictions($this->context, true, $this->page, $this->perpage);
if (!$this->model->is_static()) {
$notification = new \core\output\notification(get_string('justpredictions', 'report_insights'));
$data->nostaticmodelnotification = $notification->export_for_template($output);
}
$data->predictions = array();
$predictionvalues = array();
$insights = array();
if ($predictionsdata) {
list($total, $predictions) = $predictionsdata;
if ($predictions) {
// No bulk actions if no predictions.
$data->bulkactions = actions_exporter::add_bulk_actions($target, $output, $predictions, $this->context);
}
$data->multiplepredictions = count($predictions) > 1 ? true : false;
foreach ($predictions as $prediction) {
$predictedvalue = $prediction->get_prediction_data()->prediction;
// Only need to fill this data once.
if (!isset($predictionvalues[$predictedvalue])) {
$preddata = array();
$preddata['predictiondisplayvalue'] = $target->get_display_value($predictedvalue);
list($preddata['style'], $preddata['outcomeicon']) =
insight::get_calculation_display($target, floatval($predictedvalue), $output);
$predictionvalues[$predictedvalue] = $preddata;
}
$insightrenderable = new \report_insights\output\insight($prediction, $this->model, true, $this->context);
$insights[$predictedvalue][] = $insightrenderable->export_for_template($output);
}
// Order predicted values.
if ($target->is_linear()) {
// During regression what we will be interested on most of the time is in low values so let's show them first.
ksort($predictionvalues);
} else {
// During classification targets flag "not that important" samples as 0 so let's show them at the end.
krsort($predictionvalues);
}
// Ok, now we have all the data we want, put it into a format that mustache can handle.
foreach ($predictionvalues as $key => $prediction) {
if (isset($insights[$key])) {
$toggleall = new \core\output\checkbox_toggleall('insight-bulk-action-' . $key, true, [
'id' => 'id-toggle-all-' . $key,
'name' => 'toggle-all-' . $key,
'label' => get_string('selectall'),
'labelclasses' => 'sr-only',
'checked' => false
]);
$prediction['checkboxtoggleall'] = $output->render($toggleall);
$prediction['predictedvalue'] = $key;
$prediction['insights'] = $insights[$key];
}
$data->predictions[] = $prediction;
}
}
if (empty($insights) && $this->page == 0) {
if ($this->model->any_prediction_obtained()) {
$data->noinsights = get_string('noinsights', 'analytics');
} else {
$data->noinsights = get_string('nopredictionsyet', 'analytics');
}
}
} else {
$data->noinsights = get_string('noinsights', 'analytics');
}
if (!empty($data->noinsights)) {
$notification = new \core\output\notification($data->noinsights);
$data->noinsights = $notification->export_for_template($output);
}
$url = $PAGE->url;
if ($this->othermodels) {
$options = array();
foreach ($this->othermodels as $model) {
$options[$model->get_id()] = $model->get_target()->get_name();
}
// New moodle_url instance returned by magic_get_url.
$url->remove_params('modelid');
$modelselector = new \single_select($url, 'modelid', $options, '',
array('' => get_string('selectotherinsights', 'report_insights')));
$data->modelselector = $modelselector->export_for_template($output);
}
// Add the 'perpage' parameter to the url which is later used to generate the pagination links.
$url->param('perpage', $this->perpage);
$data->pagingbar = $output->render(new \paging_bar($total, $this->page, $this->perpage, $url));
return $data;
}
}
+146
View File
@@ -0,0 +1,146 @@
<?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/>.
/**
* Renderer.
*
* @package report_insights
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_insights\output;
defined('MOODLE_INTERNAL') || die();
use plugin_renderer_base;
use templatable;
use renderable;
/**
* Renderer class.
*
* @package report_insights
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Renders the list of insights
*
* @param renderable $renderable
* @return string HTML
*/
protected function render_insights_list(renderable $renderable) {
$data = $renderable->export_for_template($this);
return parent::render_from_template('report_insights/insights_list', $data);
}
/**
* Renders an insight
*
* @param renderable $renderable
* @return string HTML
*/
protected function render_insight(renderable $renderable) {
$data = $renderable->export_for_template($this);
$renderable->get_model()->get_target()->add_bulk_actions_js();
return parent::render_from_template('report_insights/insight_details', $data);
}
/**
* Model disabled info.
*
* @param \stdClass $insightinfo
* @return string HTML
*/
public function render_model_disabled($insightinfo) {
// We don't want to disclose the name of the model if it has not been enabled.
$this->page->set_title($insightinfo->contextname);
$this->page->set_heading($insightinfo->contextname);
$output = $this->output->header();
$output .= $this->output->notification(get_string('disabledmodel', 'report_insights'),
\core\output\notification::NOTIFY_INFO);
$output .= $this->output->footer();
return $output;
}
/**
* Model without insights info.
*
* @param \context $context
* @return string HTML
*/
public function render_no_insights(\context $context) {
// We don't want to disclose the name of the model if it has not been enabled.
$this->page->set_title($context->get_context_name());
$this->page->set_heading($context->get_context_name());
$output = $this->output->header();
$output .= $this->output->notification(get_string('noinsights', 'analytics'),
\core\output\notification::NOTIFY_INFO);
$output .= $this->output->footer();
return $output;
}
/**
* Model which target does not generate insights.
*
* @param \context $context
* @return string HTML
*/
public function render_no_insights_model(\context $context) {
// We don't want to disclose the name of the model if it has not been enabled.
$this->page->set_title($context->get_context_name());
$this->page->set_heading($context->get_context_name());
$output = $this->output->header();
$output .= $this->output->notification(get_string('noinsightsmodel', 'analytics'),
\core\output\notification::NOTIFY_INFO);
$output .= $this->output->footer();
return $output;
}
/**
* Renders an analytics disabled notification.
*
* @return string HTML
*/
public function render_analytics_disabled() {
global $FULLME;
$this->page->set_url($FULLME);
$this->page->set_title(get_string('pluginname', 'report_insights'));
$this->page->set_heading(get_string('pluginname', 'report_insights'));
$output = $this->output->header();
$output .= $this->output->notification(get_string('analyticsdisabled', 'analytics'),
\core\output\notification::NOTIFY_INFO);
$output .= \html_writer::tag('a', get_string('continue'), ['class' => 'btn btn-primary',
'href' => (new \moodle_url('/'))->out()]);
$output .= $this->output->footer();
return $output;
}
}
@@ -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/>.
/**
* Privacy Subsystem implementation for report_insights.
*
* @package report_insights
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace report_insights\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for report_insights implementing null_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\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+57
View File
@@ -0,0 +1,57 @@
<?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/>.
/**
* Report insights webservice definitions.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$functions = array(
'report_insights_set_notuseful_prediction' => array(
'classname' => 'report_insights\external',
'methodname' => 'set_notuseful_prediction',
'description' => 'Flags the prediction as not useful.',
'type' => 'write',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'report_insights_set_fixed_prediction' => array(
'classname' => 'report_insights\external',
'methodname' => 'set_fixed_prediction',
'description' => 'Flags a prediction as fixed.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
),
'report_insights_action_executed' => array(
'classname' => 'report_insights\external',
'methodname' => 'action_executed',
'description' => 'Stores an action executed over a group of predictions.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
)
);
+50
View File
@@ -0,0 +1,50 @@
<?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/>.
/**
* Forwards the user to the action they selected.
*
* @package report_insights
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_login();
$actionvisiblename = required_param('actionvisiblename', PARAM_NOTAGS);
$PAGE->set_pagelayout('popup');
$PAGE->set_context(\context_system::instance());
if (!\core_analytics\manager::is_analytics_enabled()) {
$renderer = $PAGE->get_renderer('report_insights');
echo $renderer->render_analytics_disabled();
exit(0);
}
$PAGE->set_title(get_string('insights', 'report_insights'));
$PAGE->set_url(new \moodle_url('/report/insights/done.php'));
echo $OUTPUT->header();
$notification = new \core\output\notification(get_string('actionsaved', 'report_insights', $actionvisiblename),
\core\output\notification::NOTIFY_SUCCESS);
$notification->set_show_closebutton(false);
echo $OUTPUT->render($notification);
echo $OUTPUT->footer();
+177
View File
@@ -0,0 +1,177 @@
<?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/>.
/**
* View model insights.
*
* @package report_insights
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\report_helper;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/adminlib.php');
$contextid = required_param('contextid', PARAM_INT);
$modelid = optional_param('modelid', false, PARAM_INT);
$page = optional_param('page', 0, PARAM_INT);
$perpage = optional_param('perpage', 100, PARAM_INT);
if ($perpage > 1000) {
$perpage = 1000;
}
list($context, $course, $cm) = get_context_info_array($contextid);
require_login($course, false, $cm);
if ($context->contextlevel < CONTEXT_COURSE) {
// Only for higher levels than course.
$PAGE->set_context($context);
}
if (!\core_analytics\manager::is_analytics_enabled()) {
$renderer = $PAGE->get_renderer('report_insights');
echo $renderer->render_analytics_disabled();
exit(0);
}
\core_analytics\manager::check_can_list_insights($context);
// Get all models that are enabled, trained and have predictions at this context.
$othermodels = \core_analytics\manager::get_all_models(true, true, $context);
array_filter($othermodels, function($model) use ($context) {
// Discard insights that are not linked unless you are a manager.
if (!$model->get_target()->link_insights_report()) {
try {
\core_analytics\manager::check_can_manage_models();
} catch (\required_capability_exception $e) {
return false;
}
}
return true;
});
if (!$modelid && count($othermodels)) {
// Autoselect the only available model.
$model = reset($othermodels);
$modelid = $model->get_id();
}
if ($modelid) {
unset($othermodels[$modelid]);
}
// The URL in navigation only contains the contextid.
$params = array('contextid' => $contextid);
$navurl = new \moodle_url('/report/insights/insights.php', $params);
// This is the real page url, we need it to include the modelid so pagination and
// other stuff works as expected.
$url = clone $navurl;
if ($modelid) {
$url->param('modelid', $modelid);
}
$PAGE->set_url($url);
$PAGE->set_pagelayout('report');
if ($context->contextlevel === CONTEXT_SYSTEM) {
admin_externalpage_setup('reportinsights', '', $url->params(), $url->out(false), array('pagelayout' => 'report'));
} else if ($context->contextlevel === CONTEXT_USER) {
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
$PAGE->navigation->extend_for_user($user);
$PAGE->add_report_nodes($user->id, array(
'name' => get_string('insights', 'report_insights'),
'url' => $url
));
}
$PAGE->navigation->override_active_url($navurl);
$renderer = $PAGE->get_renderer('report_insights');
// No models with insights available at this context level.
if (!$modelid) {
echo $renderer->render_no_insights($context);
exit(0);
}
$model = new \core_analytics\model($modelid);
if (!$model->get_target()->link_insights_report()) {
// Only manager access if this target does not link the insights report.
\core_analytics\manager::check_can_manage_models();
}
$insightinfo = new stdClass();
// Don't show prefix for course-level context.
$withprefix = $context->contextlevel <> CONTEXT_COURSE;
$insightinfo->contextname = $context->get_context_name($withprefix);
$insightinfo->insightname = $model->get_target()->get_name();
if (!$model->is_enabled()) {
echo $renderer->render_model_disabled($insightinfo);
exit(0);
}
if (!$model->uses_insights()) {
echo $renderer->render_no_insights_model($context);
exit(0);
}
if ($context->id == SYSCONTEXTID) {
$PAGE->set_heading(get_site()->shortname);
} else {
$PAGE->set_heading($insightinfo->contextname);
}
$PAGE->set_title($insightinfo->insightname);
// Some models generate one single prediction per context. We can directly show the prediction details in this case.
if ($model->get_analyser()::one_sample_per_analysable()) {
// Param $perpage to 2 so we can detect if this model's analyser is using one_sample_per_analysable incorrectly.
$predictionsdata = $model->get_predictions($context, true, 0, 2);
if ($predictionsdata) {
list($total, $predictions) = $predictionsdata;
if ($total > 1) {
throw new \coding_exception('This model\'s analyser processed more than one sample for a single analysable element.' .
'Therefore, the analyser\'s one_sample_per_analysable() method should return false.');
}
$prediction = reset($predictions);
$redirecturl = new \moodle_url('/report/insights/prediction.php', ['id' => $prediction->get_prediction_data()->id]);
redirect($redirecturl);
}
}
echo $OUTPUT->header();
if ($course) {
// Print selected drop down.
$pluginname = get_string('pluginname', 'report_insights');
report_helper::print_report_selector($pluginname);
}
$renderable = new \report_insights\output\insights_list($model, $context, $othermodels, $page, $perpage);
echo $renderer->render($renderable);
$eventdata = array (
'context' => $context,
'other' => array('modelid' => $model->get_id())
);
\core\event\insights_viewed::create($eventdata)->trigger();
echo $OUTPUT->footer();
@@ -0,0 +1,48 @@
<?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 for report_insights.
*
* @package report_insights
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['actionsaved'] = 'Your feedback of \'{$a}\' has been saved.';
$string['confirmbulkaction'] = 'Are you sure you want to flag the {$a->nitems} selected predictions as {$a->action}?';
$string['disabledmodel'] = 'This model has been disabled by an administrator.';
$string['indicators'] = 'Indicators';
$string['insight'] = 'Insight';
$string['insights'] = 'Insights';
$string['justpredictions'] = 'Please note that the following insights are only predictions. It is not possible to predict the future with any certainty. The insights are provided so that action can be taken as necessary to prevent any negative predictions becoming reality.';
$string['outcome'] = 'Outcome';
$string['outcomenegative'] = 'Negative outcome';
$string['outcomeneutral'] = 'Neutral outcome';
$string['outcomeok'] = 'OK outcome';
$string['outcomepositive'] = 'Positive outcome';
$string['outcomeverypositive'] = 'Very positive outcome';
$string['outcomeverynegative'] = 'Very negative outcome';
$string['pluginname'] = 'Insights';
$string['prediction'] = 'Prediction';
$string['predictiondetails'] = 'Prediction details';
$string['nodetailsavailable'] = 'No prediction details are relevant.';
$string['selectprediction'] = 'Select {$a} for bulk action';
$string['timecreated'] = 'Time predicted';
$string['timerange'] = 'Analysis interval';
$string['timerangewithdata'] = '{$a->timestart} to {$a->timeend}';
$string['selectotherinsights'] = 'Select other insights...';
$string['privacy:metadata'] = 'The Insights plugin does not store any personal data.';

Some files were not shown because too many files have changed in this diff Show More