var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
var actual = semver.valid(process.version);
if (!semver.satisfies(actual, expected)) {
- grunt.fail.fatal('Node version too old. Require ' + expected + ', version installed: ' + actual);
+ grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
}
// Windows users can't run grunt in a subdirectory, so allow them to set
$table->set_attribute('class', 'admintable generaltable');
$table->setup();
+ $pluginmanager = core_plugin_manager::instance();
+
foreach ($modules as $module) {
+ $plugininfo = $pluginmanager->get_plugin_info('mod_'.$module->name);
+ $status = $plugininfo->get_status();
- if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
+ if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
$strmodulename = '<span class="notifyproblem">'.$module->name.' ('.get_string('missingfromdisk').')</span>';
$missing = true;
} else {
if ($siteunregistrationform->is_cancelled()) {
redirect(new moodle_url('/admin/registration/index.php'));
} else if ($data = $siteunregistrationform->get_data()) {
- if (\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
- $data->unpublishalluploadedcourses)) {
+ \core\hub\registration::unregister($data->unpublishalladvertisedcourses,
+ $data->unpublishalluploadedcourses);
+ if (!\core\hub\registration::is_registered()) {
redirect(new moodle_url('/admin/registration/index.php'));
}
}
+++ /dev/null
-<?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/>.
-
-/**
- * Script to show all the assignments that have not been upgraded after the main upgrade.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-define('NO_OUTPUT_BUFFERING', true);
-
-require_once(__DIR__ . '/../../../config.php');
-require_once($CFG->libdir . '/adminlib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/locallib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/upgradableassignmentstable.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/upgradableassignmentsbatchform.php');
-
-require_sesskey();
-
-// This calls require_login and checks moodle/site:config.
-admin_externalpage_setup('assignmentupgrade', '', array(), tool_assignmentupgrade_url('batchupgrade'));
-
-$PAGE->set_pagelayout('maintenance');
-$PAGE->navbar->add(get_string('batchupgrade', 'tool_assignmentupgrade'));
-
-$renderer = $PAGE->get_renderer('tool_assignmentupgrade');
-
-$confirm = required_param('confirm', PARAM_BOOL);
-if (!$confirm) {
- print_error('invalidrequest');
- die();
-}
-raise_memory_limit(MEMORY_EXTRA);
-// Release session.
-\core\session\manager::write_close();
-
-echo $renderer->header();
-echo $renderer->heading(get_string('batchupgrade', 'tool_assignmentupgrade'));
-
-$current = 0;
-if (optional_param('upgradeall', false, PARAM_BOOL)) {
- $assignmentids = tool_assignmentupgrade_load_all_upgradable_assignmentids();
-} else {
- $assignmentids = explode(',', optional_param('selected', '', PARAM_TEXT));
-}
-$total = count($assignmentids);
-
-foreach ($assignmentids as $assignmentid) {
- list($summary, $success, $log) = tool_assignmentupgrade_upgrade_assignment($assignmentid);
- $current += 1;
- $params = array('current'=>$current, 'total'=>$total);
- echo $renderer->heading(get_string('upgradeprogress', 'tool_assignmentupgrade', $params), 3);
- echo $renderer->convert_assignment_result($summary, $success, $log);
-}
-
-echo $renderer->continue_button(tool_assignmentupgrade_url('listnotupgraded'));
-echo $renderer->footer();
+++ /dev/null
-<?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 tool_assignmentupgrade.
- *
- * @package tool_assignmentupgrade
- * @copyright 2018 Zig Tan <zig@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace tool_assignmentupgrade\privacy;
-
-use core_privacy\local\metadata\collection;
-use core_privacy\local\request\writer;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Privacy Subsystem for tool_assignmentupgrade implementing metadata, plugin, and user_preference providers.
- *
- * @copyright 2018 Zig Tan <zig@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class provider implements
- \core_privacy\local\metadata\provider,
- \core_privacy\local\request\user_preference_provider {
-
- /**
- * Returns meta data about this system.
- *
- * @param collection $collection The initialised collection to add items to.
- * @return collection A listing of user data stored through this system.
- */
- public static function get_metadata(collection $collection) : collection {
- $collection->add_user_preference(
- 'tool_assignmentupgrade_perpage',
- 'privacy:metadata:preference:perpage'
- );
- return $collection;
- }
-
- /**
- * Export all user preferences for the plugin.
- *
- * @param int $userid The userid of the user whose data is to be exported.
- */
- public static function export_user_preferences(int $userid) {
- $perpage = get_user_preferences('tool_assignmentupgrade_perpage', null, $userid);
- if ($perpage !== null) {
- writer::export_user_preference(
- 'tool_assignmentupgrade',
- 'perpage',
- $perpage,
- get_string('privacy:metadata:preference:perpage', 'tool_assignmentupgrade')
- );
- }
- }
-
-}
+++ /dev/null
-<?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 tool can upgrade old assignment activities to the new assignment activity type
- *
- * The upgrade can be done on any old assignment instance providing it is using one of the core
- * assignment subtypes (online text, single upload, etc).
- * The new assignment module was introduced in Moodle 2.3 and although it completely reproduces
- * the features of the existing assignment type it wasn't designed to replace it entirely as there
- * are many custom assignment types people use and it wouldn't be practical to try to convert them.
- * Instead the existing assignment type will be left in core and people will be encouraged to
- * use the new assignment type.
- *
- * This screen is the main entry-point to the plugin, it gives the admin a list
- * of options available to them.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once(__DIR__ . '/../../../config.php');
-require_once($CFG->libdir . '/adminlib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/locallib.php');
-
-// This calls require_login and checks moodle/site:config.
-admin_externalpage_setup('assignmentupgrade');
-
-$renderer = $PAGE->get_renderer('tool_assignmentupgrade');
-
-$actions = array();
-
-$header = get_string('pluginname', 'tool_assignmentupgrade');
-$actions[] = tool_assignmentupgrade_action::make('listnotupgraded');
-
-echo $renderer->index_page($header, $actions);
+++ /dev/null
-<?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 the assignment upgrade tool
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-$string['areyousure'] = 'Are you sure?';
-$string['areyousuremessage'] = 'Are you sure you want to upgrade the assignment "{$a->name}"?';
-$string['assignmentid'] = 'Assignment ID';
-$string['assignmentnotfound'] = 'Assignment could not be found (id={$a})';
-$string['assignmentsperpage'] = 'Assignments per page';
-$string['assignmenttype'] = 'Assignment type';
-$string['backtoindex'] = 'Back to index';
-$string['batchoperations'] = 'Batch operations';
-$string['batchupgrade'] = 'Upgrade multiple assignments';
-$string['confirmbatchupgrade'] = 'Confirm batch upgrade assignments';
-$string['conversioncomplete'] = 'Assignment converted';
-$string['conversionfailed'] = 'The assignment conversion was not successful. The log from the upgrade was: <br />{$a}';
-$string['listnotupgraded'] = 'List assignments that have not been upgraded';
-$string['listnotupgraded_desc'] = 'You can upgrade individual assignments from here';
-$string['noassignmentsselected'] = 'No assignments selected';
-$string['noassignmentstoupgrade'] = 'There are no assignments that require upgrading';
-$string['notsupported'] = '';
-$string['notupgradedintro'] = 'This page lists the assignments created with an older version of Moodle that have not been upgraded to the new assignment module in Moodle 2.3. Not all assignments can be upgraded - if they were created with a custom assignment subtype, then that subtype will need to be upgraded to the new assignment plugin format in order to complete the upgrade.';
-$string['notupgradedtitle'] = 'Assignments not upgraded';
-$string['pluginname'] = 'Assignment upgrade helper';
-$string['select'] = 'Select';
-$string['submissions'] = 'Submissions';
-$string['supported'] = 'Upgrade';
-$string['updatetable'] = 'Update table';
-$string['unknown'] = 'Unknown';
-$string['upgradeassignmentsummary'] = 'Upgrade assignment: {$a->name} (Course: {$a->shortname})';
-$string['upgradeassignmentsuccess'] = 'Result: Upgrade successful';
-$string['upgradeassignmentfailed'] = 'Result: Upgrade failed. The log from the upgrade was: <br/><div class="tool_assignmentupgrade_upgradelog">{$a->log}</div>';
-$string['upgradable'] = 'Upgradable';
-$string['upgradeselected'] = 'Upgrade selected assignments';
-$string['upgradeselectedcount'] = 'Upgrade {$a} selected assignments?';
-$string['upgradeall'] = 'Upgrade all assignments';
-$string['upgradeallconfirm'] = 'Upgrade all assignments?';
-$string['upgradeprogress'] = 'Upgrade assignment {$a->current} of {$a->total}';
-$string['upgradesingle'] = 'Upgrade single assignment';
-$string['viewcourse'] = 'View the course with the converted assignment';
-$string['privacy:metadata:preference:perpage'] = 'The assignment upgrade records per page preference set for the user.';
+++ /dev/null
-<?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/>.
-
-/**
- * Script to show all the assignments that have not been upgraded after the main upgrade.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once(__DIR__ . '/../../../config.php');
-require_once($CFG->libdir . '/adminlib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/locallib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/upgradableassignmentstable.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/upgradableassignmentsbatchform.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/paginationform.php');
-
-// This calls require_login and checks moodle/site:config.
-admin_externalpage_setup('assignmentupgrade', '', array(), tool_assignmentupgrade_url('listnotupgraded'));
-$PAGE->navbar->add(get_string('listnotupgraded', 'tool_assignmentupgrade'));
-
-$renderer = $PAGE->get_renderer('tool_assignmentupgrade');
-
-$perpage = optional_param('perpage', 0, PARAM_INT);
-if (!$perpage) {
- $perpage = get_user_preferences('tool_assignmentupgrade_perpage', 100);
-} else {
- set_user_preference('tool_assignmentupgrade_perpage', $perpage);
-}
-$assignments = new tool_assignmentupgrade_assignments_table($perpage);
-
-$batchform = new tool_assignmentupgrade_batchoperations_form();
-$data = $batchform->get_data();
-
-if ($data && $data->selectedassignments != '' || $data && isset($data->upgradeall)) {
- require_sesskey();
- echo $renderer->confirm_batch_operation_page($data);
-} else {
- $paginationform = new tool_assignmentupgrade_pagination_form();
- $pagedata = new stdClass();
- $pagedata->perpage = $perpage;
- $paginationform->set_data($pagedata);
- echo $renderer->assignment_list_page($assignments, $batchform, $paginationform);
-}
-
+++ /dev/null
-<?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/>.
-
-/**
- * Assignment upgrade tool library functions
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Get the URL of a script within this plugin.
- * @param string $script the script name, without .php. E.g. 'index'
- * @param array $params URL parameters (optional)
- * @return moodle_url
- */
-function tool_assignmentupgrade_url($script, $params = array()) {
- return new moodle_url('/admin/tool/assignmentupgrade/' . $script . '.php', $params);
-}
-
-/**
- * Class to encapsulate the continue / cancel for batch operations
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_batchoperationconfirm implements renderable {
- /** @var string $continuemessage The message to show above the continue cancel buttons */
- public $continuemessage = '';
- /** @var string $continueurl The url to load if the user clicks continue */
- public $continueurl;
-
- /**
- * Constructor for this class
- * @param stdClass $data - The data from the previous batch form
- */
- public function __construct($data) {
- if (isset($data->upgradeselected)) {
- $this->continuemessage = get_string('upgradeselectedcount',
- 'tool_assignmentupgrade',
- count(explode(',', $data->selectedassignments)));
- $urlparams = array('upgradeselected'=>'1',
- 'confirm'=>'1',
- 'sesskey'=>sesskey(),
- 'selected'=>$data->selectedassignments);
- $this->continueurl = new moodle_url('/admin/tool/assignmentupgrade/batchupgrade.php', $urlparams);
- } else if (isset($data->upgradeall)) {
- if (!tool_assignmentupgrade_any_upgradable_assignments()) {
- $this->continuemessage = get_string('noassignmentstoupgrade', 'tool_assignmentupgrade');
- $this->continueurl = '';
- } else {
- $this->continuemessage = get_string('upgradeallconfirm', 'tool_assignmentupgrade');
- $urlparams = array('upgradeall'=>'1', 'confirm'=>'1', 'sesskey'=>sesskey());
- $this->continueurl = new moodle_url('/admin/tool/assignmentupgrade/batchupgrade.php', $urlparams);
- }
- }
- }
-}
-
-
-/**
- * Class to encapsulate one of the functionalities that this plugin offers.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_action {
- /** @var string the name of this action. */
- public $name;
- /** @var moodle_url the URL to launch this action. */
- public $url;
- /** @var string a description of this aciton. */
- public $description;
-
- /**
- * Constructor to set the fields.
- *
- * In order to create a new tool_assignmentupgrade_action instance you must use
- * the tool_assignmentupgrade_action::make
- * method.
- *
- * @param string $name the name of this action.
- * @param moodle_url $url the URL to launch this action.
- * @param string $description a description of this aciton.
- */
- protected function __construct($name, moodle_url $url, $description) {
- $this->name = $name;
- $this->url = $url;
- $this->description = $description;
- }
-
- /**
- * Make an action with standard values.
- * @param string $shortname internal name of the action. Used to get strings and build a URL.
- * @param array $params any URL params required.
- * @return tool_assignmentupgrade_action
- */
- public static function make($shortname, $params = array()) {
- return new self(
- get_string($shortname, 'tool_assignmentupgrade'),
- tool_assignmentupgrade_url($shortname, $params),
- get_string($shortname . '_desc', 'tool_assignmentupgrade'));
- }
-}
-
-/**
- * Determine if there are any assignments that can be upgraded
- * @return boolean - Are there any assignments that can be upgraded
- */
-function tool_assignmentupgrade_any_upgradable_assignments() {
- global $DB, $CFG;
- require_once($CFG->dirroot . '/mod/assign/locallib.php');
- // First find all the unique assignment types.
- $types = $DB->get_records_sql('SELECT plugin AS assignmenttype,
- value AS version
- FROM {config_plugins}
- WHERE
- name = ? AND
- plugin LIKE ?', array('version', 'assignment_%'));
-
- $upgradabletypes = array();
-
- foreach ($types as $assignment) {
- $shorttype = substr($assignment->assignmenttype, strlen('assignment_'));
- if (assign::can_upgrade_assignment($shorttype, $assignment->version)) {
- $upgradabletypes[] = $shorttype;
- }
- }
- list($sql, $params) = $DB->get_in_or_equal($upgradabletypes);
-
- $count = $DB->count_records_sql('SELECT COUNT(id) FROM {assignment} WHERE assignmenttype ' . $sql, $params);
-
- return $count > 0;
-}
-
-/**
- * Load a list of all the assignmentids that can be upgraded
- * @return array of assignment ids
- */
-function tool_assignmentupgrade_load_all_upgradable_assignmentids() {
- global $DB, $CFG;
- require_once($CFG->dirroot . '/mod/assign/locallib.php');
- // First find all the unique assignment types.
- $types = $DB->get_records_sql('SELECT
- plugin AS assignmenttype,
- value AS version
- FROM {config_plugins}
- WHERE
- name = ? AND
- plugin LIKE ?', array('version', 'assignment_%'));
-
- $upgradabletypes = array();
-
- foreach ($types as $assignment) {
- $shorttype = substr($assignment->assignmenttype, strlen('assignment_'));
- if (assign::can_upgrade_assignment($shorttype, $assignment->version)) {
- $upgradabletypes[] = $shorttype;
- }
- }
-
- list($sql, $params) = $DB->get_in_or_equal($upgradabletypes);
-
- $records = $DB->get_records_sql('SELECT id from {assignment} where assignmenttype ' . $sql, $params);
- $ids = array();
- foreach ($records as $record) {
- $ids[] = $record->id;
- }
-
- return $ids;
-}
-
-
-/**
- * Upgrade a single assignment. This is used by both upgrade single and upgrade batch
- *
- * @param int $assignmentid - The assignment id to upgrade
- * @return array(string, boolean, string) -
- * The array contains
- * - the assignment summary (returned by tool_assignmentupgrade_get_assignment)
- * - success
- * - the upgrade log
- */
-function tool_assignmentupgrade_upgrade_assignment($assignmentid) {
- global $CFG;
- require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
-
- $assignment_upgrader = new assign_upgrade_manager();
- $info = tool_assignmentupgrade_get_assignment($assignmentid);
- if ($info) {
- $log = '';
- $success = $assignment_upgrader->upgrade_assignment($assignmentid, $log);
- } else {
- $success = false;
- $log = get_string('assignmentnotfound', 'tool_assignmentupgrade', $assignmentid);
- $info = new stdClass();
- $info->name = get_string('unknown', 'tool_assignmentupgrade');
- $info->shortname = get_string('unknown', 'tool_assignmentupgrade');
- }
-
- return array($info, $success, $log);
-}
-
-/**
- * Get the information about a assignment to be upgraded.
- * @param int $assignmentid the assignment id.
- * @return stdClass the information about that assignment.
- */
-function tool_assignmentupgrade_get_assignment($assignmentid) {
- global $DB;
- return $DB->get_record_sql("
- SELECT a.id, a.name, c.shortname, c.id AS courseid
- FROM {assignment} a
- JOIN {course} c ON c.id = a.course
- WHERE a.id = ?", array($assignmentid));
-}
-
+++ /dev/null
-M.tool_assignmentupgrade = {
- init_upgrade_table: function(Y) {
-
- Y.use('node', function(Y) {
- checkboxes = Y.all('td.c0 input');
- checkboxes.each(function(node) {
- node.on('change', function(e) {
- rowelement = e.currentTarget.get('parentNode').get('parentNode');
- if (e.currentTarget.get('checked')) {
- rowelement.setAttribute('class', 'selectedrow');
- } else {
- rowelement.setAttribute('class', 'unselectedrow');
- }
- });
-
- rowelement = node.get('parentNode').get('parentNode');
- if (node.get('checked')) {
- rowelement.setAttribute('class', 'selectedrow');
- } else {
- rowelement.setAttribute('class', 'unselectedrow');
- }
- });
- });
-
- var selectall = Y.one('th.c0 input');
- selectall.on('change', function(e) {
- if (e.currentTarget.get('checked')) {
- checkboxes = Y.all('td.c0 input');
- checkboxes.each(function(node) {
- rowelement = node.get('parentNode').get('parentNode');
- node.set('checked', true);
- rowelement.setAttribute('class', 'selectedrow');
- });
- } else {
- checkboxes = Y.all('td.c0 input');
- checkboxes.each(function(node) {
- rowelement = node.get('parentNode').get('parentNode');
- node.set('checked', false);
- rowelement.setAttribute('class', 'unselectedrow');
- });
- }
- });
-
- var upgradeselectedbutton = Y.one('#id_upgradeselected');
- upgradeselectedbutton.on('click', function(e) {
- checkboxes = Y.all('td.c0 input');
- var selectedassignments = [];
- checkboxes.each(function(node) {
- if (node.get('checked')) {
- selectedassignments[selectedassignments.length] = node.get('value');
- }
- });
-
- operation = Y.one('#id_operation');
- assignmentsinput = Y.one('input.selectedassignments');
- assignmentsinput.set('value', selectedassignments.join(','));
- if (selectedassignments.length == 0) {
- alert(M.util.get_string('noassignmentsselected', 'tool_assignmentupgrade'));
- e.preventDefault();
- }
- });
-
- var perpage = Y.one('#id_perpage');
- perpage.on('change', function(e) {
- window.onbeforeunload = null;
- Y.one('.tool_assignmentupgrade_paginationform form').submit();
- });
-
- }
-}
+++ /dev/null
-<?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 the forms to create and edit an instance of this module
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
-
-require_once($CFG->libdir.'/formslib.php');
-
-/**
- * Assignment upgrade table display options
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_pagination_form extends moodleform {
- /**
- * Define this form - called from the parent constructor
- */
- public function definition() {
- $mform = $this->_form;
- $instance = $this->_customdata;
-
- $mform->addElement('header', 'general', get_string('assignmentsperpage', 'tool_assignmentupgrade'));
- // Visible elements.
- $options = array(10=>'10', 20=>'20', 50=>'50', 100=>'100');
- $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options);
-
- // Hidden params.
- $mform->addElement('hidden', 'action', 'saveoptions');
- $mform->setType('action', PARAM_ALPHA);
-
- // Buttons.
- $this->add_action_buttons(false, get_string('updatetable', 'tool_assignmentupgrade'));
- }
-}
-
+++ /dev/null
-<?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/>.
-
-/**
- * Defines the renderer for the assignment upgrade helper plugin.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Renderer for the assignment upgrade helper plugin.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_renderer extends plugin_renderer_base {
-
- /**
- * Render the index page.
- * @param string $detected information about what sort of site was detected.
- * @param array $actions list of actions to show on this page.
- * @return string html to output.
- */
- public function index_page($detected, array $actions) {
- $output = '';
- $output .= $this->header();
- $output .= $this->heading(get_string('pluginname', 'tool_assignmentupgrade'));
- $output .= $this->box($detected);
- $output .= html_writer::start_tag('ul');
- foreach ($actions as $action) {
- $output .= html_writer::tag('li',
- html_writer::link($action->url, $action->name) . ' - ' .
- $action->description);
- }
- $output .= html_writer::end_tag('ul');
- $output .= $this->footer();
- return $output;
- }
-
- /**
- * Render a page that is just a simple message.
- * @param string $message the message to display.
- * @return string html to output.
- */
- public function simple_message_page($message) {
- $output = '';
- $output .= $this->header();
- $output .= $this->heading($message);
- $output .= $this->back_to_index();
- $output .= $this->footer();
- return $output;
- }
-
- /**
- * Render the confirm batch operation page
- * @param stdClass $data Submitted form data with list of assignments to upgrade
- * @return string html to output.
- */
- public function confirm_batch_operation_page(stdClass $data) {
- $output = '';
- $output .= $this->header();
-
- $output .= $this->heading(get_string('confirmbatchupgrade', 'tool_assignmentupgrade'));
- $output .= $this->output->spacer(array(), true);
-
- $output .= $this->container_start('tool_assignmentupgrade_confirmbatch');
-
- $output .= $this->render(new tool_assignmentupgrade_batchoperationconfirm($data));
- $output .= $this->container_end();
-
- $output .= $this->back_to_index();
- $output .= $this->footer();
- return $output;
- }
-
- /**
- * Render the confirm batch continue / cancel links
- * @param tool_assignmentupgrade_batchoperationconfirm $confirm Wrapper class to determine the continue message and url
- * @return string html to output.
- */
- public function render_tool_assignmentupgrade_batchoperationconfirm(tool_assignmentupgrade_batchoperationconfirm $confirm) {
- $output = '';
-
- if ($confirm->continueurl) {
- $output .= $this->output->confirm($confirm->continuemessage,
- $confirm->continueurl,
- tool_assignmentupgrade_url('listnotupgraded'));
- } else {
- $output .= $this->output->box($confirm->continuemessage);
- $output .= $this->output->continue_button(tool_assignmentupgrade_url('listnotupgraded'));
- }
- return $output;
- }
-
- /**
- * Render the list of assignments that still need to be upgraded page.
- * @param tool_assignmentupgrade_assignments_table $assignments of data about assignments.
- * @param tool_assignmentupgrade_batchoperations_form $batchform Submitted form with list of assignments to upgrade
- * @param tool_assignmentupgrade_pagination_form $paginationform Form which contains the preferences for paginating the table
- * @return string html to output.
- */
- public function assignment_list_page(tool_assignmentupgrade_assignments_table $assignments,
- tool_assignmentupgrade_batchoperations_form $batchform,
- tool_assignmentupgrade_pagination_form $paginationform) {
- $output = '';
- $output .= $this->header();
- $this->page->requires->js_init_call('M.tool_assignmentupgrade.init_upgrade_table', array());
- $this->page->requires->string_for_js('noassignmentsselected', 'tool_assignmentupgrade');
-
- $output .= $this->heading(get_string('notupgradedtitle', 'tool_assignmentupgrade'));
- $output .= $this->box(get_string('notupgradedintro', 'tool_assignmentupgrade'));
- $output .= $this->output->spacer(array(), true);
-
- $output .= $this->container_start('tool_assignmentupgrade_upgradetable');
-
- $output .= $this->container_start('tool_assignmentupgrade_paginationform');
- $output .= $this->moodleform($paginationform);
- $output .= $this->container_end();
-
- $output .= $this->flexible_table($assignments, $assignments->get_rows_per_page(), true);
- $output .= $this->container_end();
-
- if ($assignments->anyupgradableassignments) {
- $output .= $this->container_start('tool_assignmentupgrade_batchform');
- $output .= $this->moodleform($batchform);
- $output .= $this->container_end();
- }
-
- $output .= $this->back_to_index();
- $output .= $this->footer();
- return $output;
- }
-
- /**
- * Render the result of an assignment conversion
- * @param stdClass $assignmentsummary data about the assignment to upgrade.
- * @param bool $success Set to true if the outcome of the conversion was a success
- * @param string $log The log from the conversion
- * @return string html to output.
- */
- public function convert_assignment_result($assignmentsummary, $success, $log) {
- $output = '';
-
- $output .= $this->container_start('tool_assignmentupgrade_result');
- $output .= $this->container(get_string('upgradeassignmentsummary', 'tool_assignmentupgrade', $assignmentsummary));
- if (!$success) {
- $output .= $this->container(get_string('conversionfailed', 'tool_assignmentupgrade', $log));
- } else {
- $output .= $this->container(get_string('upgradeassignmentsuccess', 'tool_assignmentupgrade'));
- $url = new moodle_url('/course/view.php', array('id'=>$assignmentsummary->courseid));
- $output .= $this->container(html_writer::link($url, get_string('viewcourse', 'tool_assignmentupgrade')));
- }
- $output .= $this->container_end();
-
- return $output;
- }
-
- /**
- * Render the are-you-sure page to confirm a manual upgrade.
- * @param stdClass $assignmentsummary data about the assignment to upgrade.
- * @return string html to output.
- */
- public function convert_assignment_are_you_sure($assignmentsummary) {
- $output = '';
- $output .= $this->header();
- $output .= $this->heading(get_string('areyousure', 'tool_assignmentupgrade'));
-
- $params = array('id' => $assignmentsummary->id, 'confirmed' => 1, 'sesskey' => sesskey());
- $output .= $this->confirm(get_string('areyousuremessage', 'tool_assignmentupgrade', $assignmentsummary),
- new single_button(tool_assignmentupgrade_url('upgradesingle', $params), get_string('yes')),
- tool_assignmentupgrade_url('listnotupgraded'));
-
- $output .= $this->footer();
- return $output;
- }
-
- /**
- * Helper method dealing with the fact we can not just fetch the output of flexible_table
- *
- * @param flexible_table $table
- * @param int $rowsperpage
- * @param bool $displaylinks Show links in the table
- * @return string HTML
- */
- protected function flexible_table(flexible_table $table, $rowsperpage, $displaylinks) {
-
- $o = '';
- ob_start();
- $table->out($rowsperpage, $displaylinks);
- $o = ob_get_contents();
- ob_end_clean();
-
- return $o;
- }
-
- /**
- * Helper method dealing with the fact we can not just fetch the output of moodleforms
- *
- * @param moodleform $mform
- * @return string HTML
- */
- protected function moodleform(moodleform $mform) {
-
- $o = '';
- ob_start();
- $mform->display();
- $o = ob_get_contents();
- ob_end_clean();
-
- return $o;
- }
-
-
- /**
- * Render a link in a div, such as the 'Back to plugin main page' link.
- * @param string|moodle_url $url the link URL.
- * @param string $text the link text.
- * @return string html to output.
- */
- public function end_of_page_link($url, $text) {
- return html_writer::tag('div', html_writer::link($url, $text), array('class' => 'mdl-align'));
- }
-
- /**
- * Output a link back to the plugin index page.
- * @return string html to output.
- */
- public function back_to_index() {
- return $this->end_of_page_link(tool_assignmentupgrade_url('index'), get_string('backtoindex', 'tool_assignmentupgrade'));
- }
-}
+++ /dev/null
-<?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 this plugin to the admin menu.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die;
-
-if ($hassiteconfig) {
- // Needs this condition or there is error on login page.
- $ADMIN->add('root', new admin_externalpage('assignmentupgrade',
- get_string('pluginname', 'tool_assignmentupgrade'),
- new moodle_url('/admin/tool/assignmentupgrade/index.php')));
-}
+++ /dev/null
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable .c0 {
- display: none;
-}
-
-#page-admin-tool-assignmentupgrade-listnotupgraded.jsenabled .tool_assignmentupgrade_upgradetable .c0 {
- display: table-cell;
-}
-/*
-.gradingbatchoperationsform { display: none; }
-.jsenabled .gradingbatchoperationsform { display: block; }
-*/
-
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.selectedrow td {
- background-color: #fec;
-}
-
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_upgradetable tr.unselectedrow td {
- background-color: white;
-}
-
-#page-admin-tool-assignmentupgrade-listnotupgraded .tool_assignmentupgrade_paginationform .hidden {
- display: none;
-}
+++ /dev/null
-<?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 tests for tool_assignmentupgrade.
- *
- * @package tool_assignmentupgrade
- * @category test
- * @copyright 2018 Zig Tan <zig@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-use \core_privacy\tests\provider_testcase;
-use \core_privacy\local\request\writer;
-use \tool_assignmentupgrade\privacy\provider;
-
-/**
- * Unit tests for tool_assignmentupgrade/classes/privacy/policy
- *
- * @copyright 2018 Zig Tan <zig@moodle.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_privacy_testcase extends provider_testcase {
-
- /**
- * Overriding setUp() function to always reset after tests.
- */
- public function setUp() {
- $this->resetAfterTest(true);
- }
-
- /**
- * Test for provider::test_export_user_preferences().
- */
- public function test_export_user_preferences() {
- // Test setup.
- $user = $this->getDataGenerator()->create_user();
- $this->setUser($user);
-
- // Add a user home page preference for the User.
- set_user_preference('tool_assignmentupgrade_perpage', '100', $user);
-
- // Test the user preference exists.
- $params = [
- 'userid' => $user->id,
- 'name' => 'tool_assignmentupgrade_perpage'
- ];
-
- // Test the user preferences export contains 1 user preference record for the User.
- provider::export_user_preferences($user->id);
- $contextuser = context_user::instance($user->id);
- $writer = writer::with_context($contextuser);
- $this->assertTrue($writer->has_any_data());
-
- $exportedpreferences = $writer->get_user_preferences('tool_assignmentupgrade');
- $this->assertCount(1, (array) $exportedpreferences);
- $this->assertEquals('100', $exportedpreferences->perpage->value);
- }
-
-}
+++ /dev/null
-<?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 the forms to create and edit an instance of this module
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
-
-require_once($CFG->libdir.'/formslib.php');
-
-/**
- * Assignment upgrade batch operations form.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_batchoperations_form extends moodleform {
- /**
- * Define this form - is called from parent constructor.
- */
- public function definition() {
- $mform = $this->_form;
- $instance = $this->_customdata;
-
- $mform->addElement('header', 'general', get_string('batchoperations', 'tool_assignmentupgrade'));
- // Visible elements.
- $mform->addElement('hidden', 'selectedassignments', '', array('class'=>'selectedassignments'));
- $mform->setType('selectedassignments', PARAM_SEQUENCE);
-
- $mform->addElement('submit', 'upgradeselected', get_string('upgradeselected', 'tool_assignmentupgrade'));
- $mform->addElement('submit', 'upgradeall', get_string('upgradeall', 'tool_assignmentupgrade'));
- }
-
-}
-
+++ /dev/null
-<?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 the definition for the grading table which subclassses easy_table
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-require_once($CFG->libdir.'/tablelib.php');
-require_once($CFG->libdir.'/gradelib.php');
-require_once($CFG->dirroot.'/mod/assign/locallib.php');
-
-/**
- * Extends table_sql to provide a table of assignment submissions
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_assignmentupgrade_assignments_table extends table_sql implements renderable {
-
- /** @var int $perpage */
- private $perpage = 10;
- /** @var int $rownum (global index of current row in table) */
- private $rownum = -1;
- /** @var renderer_base for getting output */
- private $output = null;
- /** @var boolean anyupgradableassignments - True if there is one or more assignments that can upgraded */
- public $anyupgradableassignments = false;
-
- /**
- * This table loads a list of the old assignment instances and tests them to see
- * if they can be upgraded
- *
- * @param int $perpage How many per page
- * @param int $rowoffset The starting row for pagination
- */
- public function __construct($perpage, $rowoffset=0) {
- global $PAGE;
- parent::__construct('tool_assignmentupgrade_assignments');
- $this->perpage = $perpage;
- $this->output = $PAGE->get_renderer('tool_assignmentupgrade');
-
- $this->define_baseurl(new moodle_url('/admin/tool/assignmentupgrade/listnotupgraded.php'));
-
- $this->anyupgradableassignments = tool_assignmentupgrade_any_upgradable_assignments();
-
- // Do some business - then set the sql.
- if ($rowoffset) {
- $this->rownum = $rowoffset - 1;
- }
-
- $fields = 'a.id as id,
- a.name as name,
- a.assignmenttype as type,
- c.shortname as courseshortname,
- c.id as courseid,
- COUNT(s.id) as submissioncount';
- $from = '{assignment} a JOIN {course} c ON a.course = c.id ' .
- ' LEFT JOIN {assignment_submissions} s ON a.id = s.assignment';
-
- $where = '1 = 1';
- $where .= ' GROUP BY a.id, a.name, a.assignmenttype, c.shortname, c.id ';
-
- $this->set_sql($fields, $from, $where, array());
- $this->set_count_sql('SELECT COUNT(*) FROM {assignment} a JOIN {course} c ON a.course = c.id', array());
-
- $columns = array();
- $headers = array();
-
- $columns[] = 'select';
- $headers[] = get_string('select', 'tool_assignmentupgrade') .
- '<div class="selectall">' .
- '<input type="checkbox" name="selectall" title="' . get_string('selectall') . '"/>' .
- '</div>';
- $columns[] = 'upgradable';
- $headers[] = get_string('upgradable', 'tool_assignmentupgrade');
- $columns[] = 'id';
- $headers[] = get_string('assignmentid', 'tool_assignmentupgrade');
- $columns[] = 'courseshortname';
- $headers[] = get_string('course');
- $columns[] = 'name';
- $headers[] = get_string('name');
- $columns[] = 'type';
- $headers[] = get_string('assignmenttype', 'tool_assignmentupgrade');
- $columns[] = 'submissioncount';
- $headers[] = get_string('submissions', 'tool_assignmentupgrade');
-
- // Set the columns.
- $this->define_columns($columns);
- $this->define_headers($headers);
- $this->no_sorting('upgradable');
- $this->no_sorting('select');
- }
-
- /**
- * Return the number of rows to display on a single page
- *
- * @return int The number of rows per page
- */
- public function get_rows_per_page() {
- return $this->perpage;
- }
-
- /**
- * Format a link to the assignment instance
- *
- * @param stdClass $row
- * @return string
- */
- public function col_name(stdClass $row) {
- $url = new moodle_url('/mod/assignment/view.php', array('a' => $row->id));
- return html_writer::link($url, $row->name);
- }
-
-
- /**
- * Format a link to the upgrade single tool
- *
- * @param stdClass $row (contains cached result from previous upgradable check)
- * @return string
- */
- public function col_upgradable(stdClass $row) {
- if ($row->upgradable) {
- $urlparams = array('id' => $row->id, 'sesskey' => sesskey());
- $url = new moodle_url('/admin/tool/assignmentupgrade/upgradesingleconfirm.php', $urlparams);
- return html_writer::link($url, get_string('supported', 'tool_assignmentupgrade'));
- } else {
- return get_string('notsupported', 'tool_assignmentupgrade');
- }
- }
-
- /**
- * Insert a checkbox for selecting the current row for batch operations
- *
- * @param stdClass $row
- * @return string
- */
- public function col_select(stdClass $row) {
- global $CFG;
- $version = get_config('assignment_' . $row->type, 'version');
- require_once($CFG->dirroot . '/mod/assign/locallib.php');
- if (assign::can_upgrade_assignment($row->type, $version)) {
- $row->upgradable = true;
- return '<input type="checkbox" name="selectedassignment" value="' . $row->id . '"/>';
- }
- $row->upgradable = false;
- return '';
- }
-
- /**
- * Override the table show_hide_link to not show for select column
- *
- * @param string $column the column name, index into various names.
- * @param int $index numerical index of the column.
- * @return string HTML fragment.
- */
- protected function show_hide_link($column, $index) {
- if ($index > 0) {
- return parent::show_hide_link($column, $index);
- }
- return '';
- }
-}
+++ /dev/null
-<?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/>.
-
-/**
- * Script to show all the assignments that have not been upgraded after the main upgrade.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once(__DIR__ . '/../../../config.php');
-require_once($CFG->libdir . '/adminlib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/locallib.php');
-
-require_sesskey();
-
-$assignmentid = required_param('id', PARAM_INT);
-
-// This calls require_login and checks moodle/site:config.
-admin_externalpage_setup('assignmentupgrade',
- '',
- array(),
- tool_assignmentupgrade_url('upgradesingle', array('id' => $assignmentid)));
-
-$PAGE->navbar->add(get_string('upgradesingle', 'tool_assignmentupgrade'));
-$renderer = $PAGE->get_renderer('tool_assignmentupgrade');
-
-$log = '';
-list($summary, $success, $log) = tool_assignmentupgrade_upgrade_assignment($assignmentid);
-
-echo $renderer->header();
-echo $renderer->heading(get_string('conversioncomplete', 'tool_assignmentupgrade'));
-echo $renderer->convert_assignment_result($summary, $success, $log);
-echo $renderer->continue_button(tool_assignmentupgrade_url('listnotupgraded'));
-echo $renderer->footer();
+++ /dev/null
-<?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/>.
-
-/**
- * Script to show all the assignments that have not been upgraded after the main upgrade.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require_once(__DIR__ . '/../../../config.php');
-require_once($CFG->libdir . '/adminlib.php');
-require_once($CFG->dirroot . '/'.$CFG->admin.'/tool/assignmentupgrade/locallib.php');
-
-require_sesskey();
-
-$assignmentid = required_param('id', PARAM_INT);
-
-// This calls require_login and checks moodle/site:config.
-admin_externalpage_setup('assignmentupgrade',
- '',
- array(),
- tool_assignmentupgrade_url('upgradesingle', array('id' => $assignmentid)));
-
-$PAGE->navbar->add(get_string('upgradesingle', 'tool_assignmentupgrade'));
-$renderer = $PAGE->get_renderer('tool_assignmentupgrade');
-
-$assignmentinfo = tool_assignmentupgrade_get_assignment($assignmentid);
-
-echo $renderer->convert_assignment_are_you_sure($assignmentinfo);
+++ /dev/null
-<?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/>.
-
-/**
- * Unit tests for behat manager.
- *
- * @package tool_behat
- * @copyright 2012 David Monllaó
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/' . $CFG->admin .'/tool/behat/locallib.php');
-require_once($CFG->libdir . '/behat/classes/util.php');
-require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
-
-/**
- * Behat manager tests.
- *
- * @package tool_behat
- * @copyright 2012 David Monllaó
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_behat_manager_testcase extends advanced_testcase {
-
- /**
- * behat_config_manager tests.
- */
- public function test_merge_configs() {
-
- // Simple default config.
- $array1 = array(
- 'the' => 'same',
- 'simple' => 'value',
- 'array' => array(
- 'one' => 'arrayvalue1',
- 'two' => 'arrayvalue2'
- )
- );
-
- // Simple override.
- $array2 = array(
- 'simple' => 'OVERRIDDEN1',
- 'array' => array(
- 'one' => 'OVERRIDDEN2'
- ),
- 'newprofile' => array(
- 'anotherlevel' => array(
- 'andanotherone' => array(
- 'list1',
- 'list2'
- )
- )
- )
- );
-
- $array = testable_behat_config_manager::merge_config($array1, $array2);
- $this->assertDebuggingCalled("Use of merge_config is deprecated, please see behat_config_util");
-
- // Overrides are applied.
- $this->assertEquals('OVERRIDDEN1', $array['simple']);
- $this->assertEquals('OVERRIDDEN2', $array['array']['one']);
-
- // Other values are respected.
- $this->assertNotEmpty($array['array']['two']);
-
- // Completely new nodes are added.
- $this->assertNotEmpty($array['newprofile']);
- $this->assertNotEmpty($array['newprofile']['anotherlevel']['andanotherone']);
- $this->assertEquals('list1', $array['newprofile']['anotherlevel']['andanotherone'][0]);
- $this->assertEquals('list2', $array['newprofile']['anotherlevel']['andanotherone'][1]);
-
- // Complex override changing vectors to scalars and scalars to vectors.
- $array2 = array(
- 'simple' => array(
- 'simple' => 'should',
- 'be' => 'overridden',
- 'by' => 'this-array'
- ),
- 'array' => 'one'
- );
-
- $array = testable_behat_config_manager::merge_config($array1, $array2);
- $this->assertDebuggingCalled("Use of merge_config is deprecated, please see behat_config_util");
-
- // Overrides applied.
- $this->assertNotEmpty($array['simple']);
- $this->assertNotEmpty($array['array']);
- $this->assertTrue(is_array($array['simple']));
- $this->assertFalse(is_array($array['array']));
-
- // Other values are maintained.
- $this->assertEquals('same', $array['the']);
- }
-
- /**
- * behat_config_manager tests.
- */
- public function test_config_file_contents() {
- global $CFG;
-
- $this->resetAfterTest(true);
-
- // Skip tests if behat is not installed.
- $vendorpath = $CFG->dirroot . '/vendor';
- if (!file_exists($vendorpath . '/autoload.php') || !is_dir($vendorpath . '/behat')) {
- $this->markTestSkipped('Behat not installed.');
- }
-
- // Add some fake test url.
- $CFG->behat_wwwroot = 'http://example.com/behat';
-
- // To avoid user value at config.php level.
- unset($CFG->behat_config);
-
- // List.
- $features = array(
- 'feature1',
- 'feature2',
- 'feature3'
- );
-
- // Associative array.
- $stepsdefinitions = array(
- 'micarro' => '/me/lo/robaron',
- 'anoche' => '/cuando/yo/dormia'
- );
-
- $contents = testable_behat_config_manager::get_config_file_contents($features, $stepsdefinitions);
- $this->assertDebuggingCalled("Use of get_config_file_contents is deprecated, please see behat_config_util");
-
- // YAML decides when is is necessary to wrap strings between single quotes, so not controlled
- // values like paths should not be asserted including the key name as they would depend on the
- // directories values.
- $this->assertContains($CFG->dirroot,
- $contents['default']['extensions']['Moodle\BehatExtension']['moodledirroot']);
-
- // Not quoted strings.
- $this->assertEquals('/me/lo/robaron',
- $contents['default']['extensions']['Moodle\BehatExtension']['steps_definitions']['micarro']);
-
- // YAML uses single quotes to wrap URL strings.
- $this->assertEquals($CFG->behat_wwwroot, $contents['default']['extensions']['Behat\MinkExtension']['base_url']);
-
- // Lists.
- $this->assertEquals('feature1', $contents['default']['suites']['default']['paths'][0]);
- $this->assertEquals('feature3', $contents['default']['suites']['default']['paths'][2]);
-
- unset($CFG->behat_wwwroot);
- }
-
-}
-
-/**
- * Allows access to internal methods without exposing them.
- *
- * @package tool_behat
- * @copyright 2012 David Monllaó
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class testable_behat_config_manager extends behat_config_manager {
-
- /**
- * Allow access to protected method
- * @see parent::merge_config()
- * @param mixed $config
- * @param mixed $localconfig
- * @return mixed
- */
- public static function merge_config($config, $localconfig) {
- return parent::merge_config($config, $localconfig);
- }
-
- /**
- * Allow access to protected method
- * @see parent::get_config_file_contents()
- * @param array $features
- * @param array $stepsdefinitions
- * @return string
- */
- public static function get_config_file_contents($features, $stepsdefinitions) {
- return parent::get_config_file_contents($features, $stepsdefinitions);
- }
-}
var SELECTORS = {
APPROVE_BUTTON: '[data-action="approve"]',
DENY_BUTTON: '[data-action="deny"]',
+ COMPLETE_BUTTON: '[data-action="complete"]'
};
/**
*/
var ModalDataRequest = function(root) {
Modal.call(this, root);
-
- if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
- Notification.exception({message: 'No approve button found'});
- }
-
- if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
- Notification.exception({message: 'No deny button found'});
- }
};
ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
data.originalEvent.preventDefault();
}
}.bind(this));
+
+ this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
+ var completeEvent = $.Event(DataPrivacyEvents.complete);
+ this.getRoot().trigger(completeEvent, this);
+
+ if (!completeEvent.isDefaultPrevented()) {
+ this.hide();
+ data.originalEvent.preventDefault();
+ }
+ }.bind(this));
};
// Automatically register with the modal registry the first time this module is imported so that you can create modals
return {
approve: 'tool_dataprivacy-data_request:approve',
deny: 'tool_dataprivacy-data_request:deny',
+ complete: 'tool_dataprivacy-data_request:complete'
};
});
* @type {{APPROVE_REQUEST: string}}
* @type {{DENY_REQUEST: string}}
* @type {{VIEW_REQUEST: string}}
+ * @type {{MARK_COMPLETE: string}}
*/
var ACTIONS = {
APPROVE_REQUEST: '[data-action="approve"]',
DENY_REQUEST: '[data-action="deny"]',
- VIEW_REQUEST: '[data-action="view"]'
+ VIEW_REQUEST: '[data-action="view"]',
+ MARK_COMPLETE: '[data-action="complete"]'
};
/**
};
var promises = Ajax.call([request]);
- var modalTitle = '';
- var modalType = ModalFactory.types.DEFAULT;
$.when(promises[0]).then(function(data) {
if (data.result) {
- // Check if the status is awaiting approval.
- if (data.result.status == 2) {
- modalType = ModalDataRequest.TYPE;
- }
- modalTitle = data.result.typename;
- return Templates.render('tool_dataprivacy/request_details', data.result);
+ return data.result;
}
// Fail.
Notification.addNotification({
});
return false;
- }).then(function(html) {
+ }).then(function(data) {
+ var body = Templates.render('tool_dataprivacy/request_details', data);
+ var templateContext = {
+ approvedeny: data.approvedeny,
+ canmarkcomplete: data.canmarkcomplete
+ };
return ModalFactory.create({
- title: modalTitle,
- body: html,
- type: modalType,
- large: true
- }).then(function(modal) {
- // Handle approve event.
- modal.getRoot().on(DataPrivacyEvents.approve, function() {
- showConfirmation(DataPrivacyEvents.approve, requestId);
- });
-
- // Handle deny event.
- modal.getRoot().on(DataPrivacyEvents.deny, function() {
- showConfirmation(DataPrivacyEvents.deny, requestId);
- });
-
- // Handle hidden event.
- modal.getRoot().on(ModalEvents.hidden, function() {
- // Destroy when hidden.
- modal.destroy();
- });
-
- return modal;
+ title: data.typename,
+ body: body,
+ type: ModalDataRequest.TYPE,
+ large: true,
+ templateContext: templateContext
+ });
+
+ }).then(function(modal) {
+ // Handle approve event.
+ modal.getRoot().on(DataPrivacyEvents.approve, function() {
+ showConfirmation(DataPrivacyEvents.approve, requestId);
+ });
+
+ // Handle deny event.
+ modal.getRoot().on(DataPrivacyEvents.deny, function() {
+ showConfirmation(DataPrivacyEvents.deny, requestId);
});
- }).done(function(modal) {
+
+ // Handle send event.
+ modal.getRoot().on(DataPrivacyEvents.complete, function() {
+ var params = {
+ 'requestid': requestId
+ };
+ handleSave('tool_dataprivacy_mark_complete', params);
+ });
+
+ // Handle hidden event.
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ // Destroy when hidden.
+ modal.destroy();
+ });
+
// Show the modal!
modal.show();
- }).fail(Notification.exception);
+
+ return;
+
+ }).catch(Notification.exception);
});
$(ACTIONS.APPROVE_REQUEST).click(function(e) {
var requestId = $(this).data('requestid');
showConfirmation(DataPrivacyEvents.deny, requestId);
});
+
+ $(ACTIONS.MARK_COMPLETE).click(function(e) {
+ e.preventDefault();
+ showConfirmation(DataPrivacyEvents.complete, $(this).data('requestid'));
+ });
};
/**
function showConfirmation(action, requestId) {
var keys = [];
var wsfunction = '';
+ var params = {
+ 'requestid': requestId
+ };
switch (action) {
case DataPrivacyEvents.approve:
keys = [
];
wsfunction = 'tool_dataprivacy_deny_data_request';
break;
+ case DataPrivacyEvents.complete:
+ keys = [
+ {
+ key: 'markcomplete',
+ component: 'tool_dataprivacy'
+ },
+ {
+ key: 'confirmcompletion',
+ component: 'tool_dataprivacy'
+ }
+ ];
+ wsfunction = 'tool_dataprivacy_mark_complete';
+ break;
}
var modalTitle = '';
// Handle save event.
modal.getRoot().on(ModalEvents.save, function() {
- // Confirm the request.
- var params = {
- 'requestid': requestId
- };
-
- var request = {
- methodname: wsfunction,
- args: params
- };
-
- Ajax.call([request])[0].done(function(data) {
- if (data.result) {
- window.location.reload();
- } else {
- Notification.addNotification({
- message: data.warnings[0].message,
- type: 'error'
- });
- }
- }).fail(Notification.exception);
+ handleSave(wsfunction, params);
});
// Handle hidden event.
modal.destroy();
});
- return modal;
- }).done(function(modal) {
modal.show();
+
+ return;
+
+ }).catch(Notification.exception);
+ }
+
+ /**
+ * Calls a web service function and reloads the page on success and shows a notification.
+ * Displays an error notification, otherwise.
+ *
+ * @param {String} wsfunction The web service function to call.
+ * @param {Object} params The parameters for the web service functoon.
+ */
+ function handleSave(wsfunction, params) {
+ // Confirm the request.
+ var request = {
+ methodname: wsfunction,
+ args: params
+ };
+
+ Ajax.call([request])[0].done(function(data) {
+ if (data.result) {
+ // On success, reload the page so that the data request table will be updated.
+ // TODO: Probably in the future, better to reload the table or the target data request via AJAX.
+ window.location.reload();
+ } else {
+ // Add the notification.
+ Notification.addNotification({
+ message: data.warnings[0].message,
+ type: 'error'
+ });
+ }
}).fail(Notification.exception);
}
namespace tool_dataprivacy;
use coding_exception;
+use context_course;
use context_system;
use core\invalid_persistent_exception;
use core\message\message;
if ($dpoid) {
$datarequest->set('dpo', $dpoid);
}
- $datarequest->set('dpocomment', $comment);
+ // Update the comment if necessary.
+ if (!empty(trim($comment))) {
+ $params = [
+ 'date' => userdate(time()),
+ 'comment' => $comment
+ ];
+ $commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
+ // Check if there's an existing DPO comment.
+ $currentcomment = trim($datarequest->get('dpocomment'));
+ if ($currentcomment) {
+ // Append the new comment to the current comment and give them 1 line space in between.
+ $commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
+ }
+ $datarequest->set('dpocomment', $commenttosave);
+ }
+
return $datarequest->update();
}
* @param data_request $request The data request
* @return int|false
* @throws coding_exception
- * @throws dml_exception
* @throws moodle_exception
*/
public static function notify_dpo($dpo, data_request $request) {
return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
}
+ /**
+ * Checks whether a user can download a data request.
+ *
+ * @param int $userid Target user id (subject of data request)
+ * @param int $requesterid Requester user id (person who requsted it)
+ * @param int|null $downloaderid Person who wants to download user id (default current)
+ * @return bool
+ * @throws coding_exception
+ */
+ public static function can_download_data_request_for_user($userid, $requesterid, $downloaderid = null) {
+ global $USER;
+
+ if (!$downloaderid) {
+ $downloaderid = $USER->id;
+ }
+
+ $usercontext = \context_user::instance($userid);
+ // If it's your own and you have the right capability, you can download it.
+ if ($userid == $downloaderid && has_capability('tool/dataprivacy:downloadownrequest', $usercontext, $downloaderid)) {
+ return true;
+ }
+ // If you can download anyone's in that context, you can download it.
+ if (has_capability('tool/dataprivacy:downloadallrequests', $usercontext, $downloaderid)) {
+ return true;
+ }
+ // If you can have the 'child access' ability to request in that context, and you are the one
+ // who requested it, then you can download it.
+ if ($requesterid == $downloaderid && self::can_create_data_request_for_user($userid, $requesterid)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gets an action menu link to download a data request.
+ *
+ * @param \context_user $usercontext User context (of user who the data is for)
+ * @param int $requestid Request id
+ * @return \action_menu_link_secondary Action menu link
+ * @throws coding_exception
+ */
+ public static function get_download_link(\context_user $usercontext, $requestid) {
+ $downloadurl = moodle_url::make_pluginfile_url($usercontext->id,
+ 'tool_dataprivacy', 'export', $requestid, '/', 'export.zip', true);
+ $downloadtext = get_string('download', 'tool_dataprivacy');
+ return new \action_menu_link_secondary($downloadurl, null, $downloadtext);
+ }
+
/**
* Creates a new data purpose.
*
if ($contextcourse = $context->get_course_context(false)) {
// Below course level we look at module or block level roles + course-assigned roles.
- $courseroles = get_roles_with_assignment_on_context($contextcourse);
- $roles = $courseroles + get_roles_with_assignment_on_context($context);
+ $courseroles = get_roles_used_in_context($contextcourse, false);
+ $roles = $courseroles + get_roles_used_in_context($context, false);
} else {
// We list category + system for others (we don't work with user instances so no need to work about them).
$roles = get_roles_used_in_context($context);
use context_system;
use context_user;
use core\invalid_persistent_exception;
+use core\notification;
use core_user;
use dml_exception;
use external_api;
}
/**
- * Deny a data request.
+ * Make a general enquiry to a DPO.
*
* @since Moodle 3.5
* @param string $message The message to be sent to the DPO.
}
/**
- * Parameter description for deny_data_request().
+ * Parameter description for contact_dpo().
*
* @since Moodle 3.5
* @return external_description
]);
}
+ /**
+ * Parameter description for mark_complete().
+ *
+ * @since Moodle 3.5.2
+ * @return external_function_parameters
+ */
+ public static function mark_complete_parameters() {
+ return new external_function_parameters([
+ 'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
+ ]);
+ }
+
+ /**
+ * Mark a user's general enquiry's status as complete.
+ *
+ * @since Moodle 3.5.2
+ * @param int $requestid The request ID of the general enquiry.
+ * @return array
+ * @throws coding_exception
+ * @throws invalid_parameter_exception
+ * @throws invalid_persistent_exception
+ * @throws restricted_context_exception
+ * @throws dml_exception
+ * @throws moodle_exception
+ */
+ public static function mark_complete($requestid) {
+ global $USER;
+
+ $warnings = [];
+ $params = external_api::validate_parameters(self::mark_complete_parameters(), [
+ 'requestid' => $requestid,
+ ]);
+ $requestid = $params['requestid'];
+
+ // Validate context.
+ $context = context_system::instance();
+ self::validate_context($context);
+
+ $message = get_string('markedcomplete', 'tool_dataprivacy');
+ // Update the data request record.
+ if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
+ // Add notification in the session to be shown when the page is reloaded on the JS side.
+ notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
+ }
+
+ return [
+ 'result' => $result,
+ 'warnings' => $warnings
+ ];
+ }
+
+ /**
+ * Parameter description for mark_complete().
+ *
+ * @since Moodle 3.5.2
+ * @return external_description
+ */
+ public static function mark_complete_returns() {
+ return new external_single_structure([
+ 'result' => new external_value(PARAM_BOOL, 'The processing result'),
+ 'warnings' => new external_warnings()
+ ]);
+ }
+
/**
* Parameter description for get_data_request().
*
// Validate context.
$context = context_system::instance();
self::validate_context($context);
+ $requestpersistent = new data_request($requestid);
require_capability('tool/dataprivacy:managedatarequests', $context);
- $requestpersistent = new data_request($requestid);
$exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
$renderer = $PAGE->get_renderer('tool_dataprivacy');
$result = $exporter->export($renderer);
$result = false;
if ($requestexists) {
$result = api::approve_data_request($requestid);
+
+ // Add notification in the session to be shown when the page is reloaded on the JS side.
+ notification::success(get_string('requestapproved', 'tool_dataprivacy'));
} else {
$warnings[] = [
'item' => $requestid,
$result = false;
if ($requestexists) {
$result = api::deny_data_request($requestid);
+
+ // Add notification in the session to be shown when the page is reloaded on the JS side.
+ notification::success(get_string('requestdenied', 'tool_dataprivacy'));
} else {
$warnings[] = [
'item' => $requestid,
'optional' => true,
'default' => false
],
+ 'approvedeny' => [
+ 'type' => PARAM_BOOL,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'canmarkcomplete' => [
+ 'type' => PARAM_BOOL,
+ 'optional' => true,
+ 'default' => false
+ ],
];
}
$values['messagehtml'] = text_to_html($this->persistent->get('comments'));
- $values['typename'] = helper::get_request_type_string($this->persistent->get('type'));
- $values['typenameshort'] = helper::get_shortened_request_type_string($this->persistent->get('type'));
+ $requesttype = $this->persistent->get('type');
+ $values['typename'] = helper::get_request_type_string($requesttype);
+ $values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
$values['canreview'] = false;
+ $values['approvedeny'] = false;
$values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
+
switch ($this->persistent->get('status')) {
case api::DATAREQUEST_STATUS_PENDING:
$values['statuslabelclass'] = 'label-default';
+ // Request can be manually completed for general enquiry requests.
+ $values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
break;
case api::DATAREQUEST_STATUS_PREPROCESSING:
$values['statuslabelclass'] = 'label-default';
$values['statuslabelclass'] = 'label-info';
// DPO can review the request once it's ready.
$values['canreview'] = true;
+ // Whether the DPO can approve or deny the request.
+ $values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
break;
case api::DATAREQUEST_STATUS_APPROVED:
$values['statuslabelclass'] = 'label-info';
$actiontext = get_string('viewrequest', 'tool_dataprivacy');
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
- if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
- // Approve.
- $actiondata['data-action'] = 'approve';
- $actiontext = get_string('approverequest', 'tool_dataprivacy');
- $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
-
- // Deny.
- $actiondata['data-action'] = 'deny';
- $actiontext = get_string('denyrequest', 'tool_dataprivacy');
- $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ switch ($status) {
+ case api::DATAREQUEST_STATUS_PENDING:
+ // Add action to mark a general enquiry request as complete.
+ if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
+ $actiondata['data-action'] = 'complete';
+ $nameemail = (object)[
+ 'name' => $data->foruser->fullname,
+ 'email' => $data->foruser->email
+ ];
+ $actiondata['data-requestid'] = $data->id;
+ $actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
+ $actiontext = get_string('markcomplete', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ }
+ break;
+ case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
+ // Approve.
+ $actiondata['data-action'] = 'approve';
+ $actiontext = get_string('approverequest', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+
+ // Deny.
+ $actiondata['data-action'] = 'deny';
+ $actiontext = get_string('denyrequest', 'tool_dataprivacy');
+ $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+ break;
+ }
+
+ if ($status == api::DATAREQUEST_STATUS_COMPLETE) {
+ $userid = $data->foruser->id;
+ $usercontext = \context_user::instance($userid, IGNORE_MISSING);
+ if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
+ $actions[] = api::get_download_link($usercontext, $requestid);
+ }
}
$actionsmenu = new action_menu($actions);
$requestid = $request->get('id');
$status = $request->get('status');
$userid = $request->get('userid');
+ $type = $request->get('type');
$usercontext = context_user::instance($userid, IGNORE_MISSING);
if (!$usercontext) {
$requestexporter = new data_request_exporter($request, ['context' => $outputcontext]);
$item = $requestexporter->export($output);
- if ($request->get('userid') != $USER->id) {
+ $self = $request->get('userid') == $USER->id;
+ if (!$self) {
// Append user name if it differs from $USER.
$a = (object)['typename' => $item->typename, 'user' => $item->foruser->fullname];
$item->typename = get_string('requesttypeuser', 'tool_dataprivacy', $a);
$item->statuslabelclass = 'label-success';
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
$cancancel = false;
- $candownload = true;
+ // Show download links only for export-type data requests.
+ $candownload = $type == api::DATAREQUEST_TYPE_EXPORT;
+ if ($usercontext) {
+ $candownload = api::can_download_data_request_for_user(
+ $request->get('userid'), $request->get('requestedby'));
+ }
break;
case api::DATAREQUEST_STATUS_CANCELLED:
case api::DATAREQUEST_STATUS_REJECTED:
$actions[] = new action_menu_link_secondary($cancelurl, null, $canceltext, $canceldata);
}
if ($candownload && $usercontext) {
- $downloadurl = moodle_url::make_pluginfile_url($usercontext->id, 'tool_dataprivacy', 'export', $requestid, '/',
- 'export.zip', true);
- $downloadtext = get_string('download', 'tool_dataprivacy');
- $actions[] = new action_menu_link_secondary($downloadurl, null, $downloadtext);
+ $actions[] = api::get_download_link($usercontext, $requestid);
}
if (!empty($actions)) {
$actionsmenu = new action_menu($actions);
$output = $PAGE->get_renderer('tool_dataprivacy');
$emailonly = false;
+ $notifyuser = true;
switch ($request->type) {
case api::DATAREQUEST_TYPE_EXPORT:
+ // Check if the user is allowed to download their own export. (This is for
+ // institutions which centrally co-ordinate subject access request across many
+ // systems, not just one Moodle instance, so we don't want every instance emailing
+ // the user.)
+ if (!api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->userid)) {
+ $notifyuser = false;
+ }
+
$typetext = get_string('requesttypeexport', 'tool_dataprivacy');
// We want to notify the user in Moodle about the processing results.
$message->notification = 1;
$message->fullmessagehtml = $messagehtml;
// Send message to the user involved.
- if ($emailonly) {
- email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
- } else {
- message_send($message);
+ if ($notifyuser) {
+ if ($emailonly) {
+ email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
+ } else {
+ message_send($message);
+ }
+ mtrace('Message sent to user: ' . $messagetextdata['username']);
}
- mtrace('Message sent to user: ' . $messagetextdata['username']);
- // Send to requester as well if this request was made on behalf of another user who's not a DPO,
- // and has the capability to make data requests for the user (e.g. Parent).
- if (!api::is_site_dpo($request->requestedby) && $foruser->id != $request->requestedby) {
+ // Send to requester as well in some circumstances.
+ if ($foruser->id != $request->requestedby) {
+ $sendtorequester = false;
+ switch ($request->type) {
+ case api::DATAREQUEST_TYPE_EXPORT:
+ // Send to the requester as well if they can download it, unless they are the
+ // DPO. If we didn't notify the user themselves (because they can't download)
+ // then send to requester even if it is the DPO, as in that case the requester
+ // needs to take some action.
+ if (api::can_download_data_request_for_user($request->userid, $request->requestedby, $request->requestedby)) {
+ $sendtorequester = !$notifyuser || !api::is_site_dpo($request->requestedby);
+ }
+ break;
+ case api::DATAREQUEST_TYPE_DELETE:
+ // Send to the requester if they are not the DPO and if they are allowed to
+ // create data requests for the user (e.g. Parent).
+ $sendtorequester = !api::is_site_dpo($request->requestedby) &&
+ api::can_create_data_request_for_user($request->userid, $request->requestedby);
+ break;
+ default:
+ throw new moodle_exception('errorinvalidrequesttype', 'tool_dataprivacy');
+ }
+
// Ensure the requester has the capability to make data requests for this user.
- if (api::can_create_data_request_for_user($request->userid, $request->requestedby)) {
+ if ($sendtorequester) {
$requestedby = core_user::get_user($request->requestedby);
$message->userto = $requestedby;
$messagetextdata['username'] = fullname($requestedby);
'contextlevel' => CONTEXT_USER,
'archetypes' => []
],
+
+ // Capability for users to download the results of their own data request.
+ 'tool/dataprivacy:downloadownrequest' => [
+ 'riskbitmask' => 0,
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_USER,
+ 'archetypes' => [
+ 'user' => CAP_ALLOW
+ ]
+ ],
+
+ // Capability for administrators to download other people's data requests.
+ 'tool/dataprivacy:downloadallrequests' => [
+ 'riskbitmask' => RISK_PERSONAL,
+ 'captype' => 'read',
+ 'contextlevel' => CONTEXT_USER,
+ 'archetypes' => []
+ ],
];
'ajax' => true,
'loginrequired' => true,
],
+ 'tool_dataprivacy_mark_complete' => [
+ 'classname' => 'tool_dataprivacy\external',
+ 'methodname' => 'mark_complete',
+ 'classpath' => '',
+ 'description' => 'Mark a user\'s general enquiry as complete',
+ 'type' => 'write',
+ 'capabilities' => 'tool/dataprivacy:managedatarequests',
+ 'ajax' => true,
+ 'loginrequired' => true,
+ ],
'tool_dataprivacy_get_data_request' => [
'classname' => 'tool_dataprivacy\external',
'methodname' => 'get_data_request',
--- /dev/null
+<?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/>.
+
+/**
+ * tool_dataprivacy plugin upgrade code
+ *
+ * @package tool_dataprivacy
+ * @copyright 2018 Jun Pataleta
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade tool_dataprivacy.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_tool_dataprivacy_upgrade($oldversion) {
+ global $CFG, $DB;
+
+ $dbman = $DB->get_manager();
+
+ if ($oldversion < 2018051405) {
+ // Define table tool_dataprivacy_ctxexpired to be created.
+ $table = new xmldb_table('tool_dataprivacy_ctxexpired');
+
+ // Adding fields to table tool_dataprivacy_ctxexpired.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table tool_dataprivacy_ctxexpired.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
+
+ // Conditionally launch create table for tool_dataprivacy_ctxexpired.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define table tool_dataprivacy_contextlist to be created.
+ $table = new xmldb_table('tool_dataprivacy_contextlist');
+
+ // Adding fields to table tool_dataprivacy_contextlist.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+ // Adding keys to table tool_dataprivacy_contextlist.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ // Conditionally launch create table for tool_dataprivacy_contextlist.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define table tool_dataprivacy_ctxlst_ctx to be created.
+ $table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
+
+ // Adding fields to table tool_dataprivacy_ctxlst_ctx.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+ // Adding keys to table tool_dataprivacy_ctxlst_ctx.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+
+ // Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define table tool_dataprivacy_rqst_ctxlst to be created.
+ $table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
+
+ // Adding fields to table tool_dataprivacy_rqst_ctxlst.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table tool_dataprivacy_rqst_ctxlst.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+ $table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
+ $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+ $table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
+
+ // Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Define field lawfulbases to be added to tool_dataprivacy_purpose.
+ $table = new xmldb_table('tool_dataprivacy_purpose');
+
+ // It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
+ $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
+
+ // Conditionally launch add field lawfulbases.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+
+ // Set a kind-of-random value to lawfulbasis field.
+ $DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
+
+ // We redefine it now as not null.
+ $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
+
+ // Launch change of nullability for field lawfulbases.
+ $dbman->change_field_notnull($table, $field);
+ }
+
+ // Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
+ $table = new xmldb_table('tool_dataprivacy_purpose');
+ $field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
+
+ // Conditionally launch add field sensitivedatareasons.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Dataprivacy savepoint reached.
+ upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
+ }
+
+ return true;
+}
$string['close'] = 'Close';
$string['compliant'] = 'Compliant';
$string['confirmapproval'] = 'Do you really want to approve this data request?';
+$string['confirmcompletion'] = 'Do you really want to mark this user enquiry as complete?';
$string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
$string['confirmdenial'] = 'Do you really want deny this data request?';
$string['contactdataprotectionofficer'] = 'Contact the privacy officer';
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
$string['dataprivacy:managedatarequests'] = 'Manage data requests';
$string['dataprivacy:managedataregistry'] = 'Manage data registry';
+$string['dataprivacy:downloadownrequest'] = 'Download your own exported data';
+$string['dataprivacy:downloadallrequests'] = 'Download exported data for everyone';
$string['dataregistry'] = 'Data registry';
$string['dataregistryinfo'] = 'The data registry enables categories (types of data) and purposes (the reasons for processing data) to be set for all content on the site - from users and courses down to activities and blocks. For each purpose, a retention period may be set. When a retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequests'] = 'Data requests';
+$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
$string['daterequested'] = 'Date requested';
$string['daterequesteddetail'] = 'Date requested:';
$string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
$string['inherit'] = 'Inherit';
$string['lawfulbases'] = 'Lawful bases';
$string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
+$string['markcomplete'] = 'Mark as complete';
+$string['markedcomplete'] = 'Your enquiry has been marked as complete by the privacy officer.';
$string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
$string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
$string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
$string['purposeupdated'] = 'Purpose updated';
$string['replyto'] = 'Reply to';
$string['requestactions'] = 'Actions';
+$string['requestapproved'] = 'The request has been approved';
$string['requestby'] = 'Requested by';
$string['requestbydetail'] = 'Requested by:';
$string['requestcomments'] = 'Comments';
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
+$string['requestdenied'] = 'The request has been denied';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'Requesting for';
+$string['requestmarkedcomplete'] = 'The request has been marked as complete';
$string['requeststatus'] = 'Status';
$string['requestsubmitted'] = 'Your request has been submitted to the privacy officer';
$string['requesttype'] = 'Type';
* @return bool Returns false if we don't find a file.
*/
function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) {
- global $USER;
-
if ($context->contextlevel == CONTEXT_USER) {
// Make sure the user is logged in.
require_login(null, false);
- // Validate the user downloading this archive.
- $usercontext = context_user::instance($USER->id);
- // The user downloading this is not the user the archive has been prepared for. Check if it's the requester (e.g. parent).
- if ($usercontext->instanceid !== $context->instanceid) {
- // Get the data request ID. This should be the first element of the $args array.
- $itemid = $args[0];
- // Fetch the data request object. An invalid ID will throw an exception.
- $datarequest = new \tool_dataprivacy\data_request($itemid);
-
- // Check if the user is the requester and has the capability to make data requests for the target user.
- $candownloadforuser = has_capability('tool/dataprivacy:makedatarequestsforchildren', $context);
- if ($USER->id != $datarequest->get('requestedby') || !$candownloadforuser) {
- return false;
- }
+ // Get the data request ID. This should be the first element of the $args array.
+ $itemid = $args[0];
+ // Fetch the data request object. An invalid ID will throw an exception.
+ $datarequest = new \tool_dataprivacy\data_request($itemid);
+
+ // Check if user is allowed to download it.
+ if (!\tool_dataprivacy\api::can_download_data_request_for_user($context->instanceid, $datarequest->get('requestedby'))) {
+ return false;
}
// All good. Serve the exported data.
}}
{{< core/modal }}
{{$footer}}
- <button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
- <button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
+ {{#approvedeny}}
+ <button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
+ <button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
+ {{/approvedeny}}
+ {{#canmarkcomplete}}
+ <button type="button" class="btn btn-primary" data-action="complete">{{#str}} markcomplete, tool_dataprivacy {{/str}}</button>
+ {{/canmarkcomplete}}
{{/footer}}
{{/ core/modal }}
$this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
}
+ /**
+ * Test for api::can_download_data_request_for_user()
+ */
+ public function test_can_download_data_request_for_user() {
+ $generator = $this->getDataGenerator();
+
+ // Three victims.
+ $victim1 = $generator->create_user();
+ $victim2 = $generator->create_user();
+ $victim3 = $generator->create_user();
+
+ // Assign a user as victim 1's parent.
+ $systemcontext = \context_system::instance();
+ $parentrole = $generator->create_role();
+ assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
+ $parent = $generator->create_user();
+ role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
+
+ // Assign another user as data access wonder woman.
+ $wonderrole = $generator->create_role();
+ assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
+ $staff = $generator->create_user();
+ role_assign($wonderrole, $staff->id, $systemcontext);
+
+ // Finally, victim 3 has been naughty; stop them accessing their own data.
+ $naughtyrole = $generator->create_role();
+ assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
+ role_assign($naughtyrole, $victim3->id, $systemcontext);
+
+ // Victims 1 and 2 can access their own data, regardless of who requested it.
+ $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
+ $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
+
+ // Victim 3 cannot access his own data.
+ $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
+
+ // Victims 1 and 2 cannot access another victim's data.
+ $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
+ $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
+
+ // Staff can access everyone's data.
+ $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
+ $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
+ $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
+
+ // Parent can access victim 1's data only if they requested it.
+ $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
+ $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
+ $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
+ }
+
/**
* Test for api::create_data_request()
*/
* @return array
*/
public function get_data_requests_provider() {
- $generator = new testing_data_generator();
- $user1 = $generator->create_user();
- $user2 = $generator->create_user();
- $user3 = $generator->create_user();
- $user4 = $generator->create_user();
- $user5 = $generator->create_user();
- $users = [$user1, $user2, $user3, $user4, $user5];
$completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
$completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
return [
// Own data requests.
- [$users, $user1, false, $completeonly],
+ ['user', false, $completeonly],
// Non-DPO fetching all requets.
- [$users, $user2, true, $completeonly],
+ ['user', true, $completeonly],
// Admin fetching all completed and cancelled requests.
- [$users, get_admin(), true, $completeandcancelled],
+ ['dpo', true, $completeandcancelled],
// Admin fetching all completed requests.
- [$users, get_admin(), true, $completeonly],
+ ['dpo', true, $completeonly],
// Guest fetching all requests.
- [$users, guest_user(), true, $completeonly],
+ ['guest', true, $completeonly],
];
}
* Test for api::get_data_requests()
*
* @dataProvider get_data_requests_provider
- * @param stdClass[] $users Array of users to create data requests for.
- * @param stdClass $loggeduser The user logging in.
+ * @param string $usertype The type of the user logging in.
* @param boolean $fetchall Whether to fetch all records.
* @param int[] $statuses Status filters.
*/
- public function test_get_data_requests($users, $loggeduser, $fetchall, $statuses) {
+ public function test_get_data_requests($usertype, $fetchall, $statuses) {
+ $generator = new testing_data_generator();
+ $user1 = $generator->create_user();
+ $user2 = $generator->create_user();
+ $user3 = $generator->create_user();
+ $user4 = $generator->create_user();
+ $user5 = $generator->create_user();
+ $users = [$user1, $user2, $user3, $user4, $user5];
+
+ switch ($usertype) {
+ case 'user':
+ $loggeduser = $user1;
+ break;
+ case 'dpo':
+ $loggeduser = get_admin();
+ break;
+ case 'guest':
+ $loggeduser = guest_user();
+ break;
+ }
+
$comment = 'Data %s request comment by user %d';
$exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
$deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
--- /dev/null
+@tool @tool_dataprivacy
+Feature: Contact the privacy officer
+ As a user
+ In order to reach out to the site's privacy officer
+ I need to be able to contact the site's privacy officer in Moodle
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | s1@example.com |
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | contactdataprotectionofficer | 1 |
+ And I log out
+
+ @javascript
+ Scenario: Contacting the privacy officer
+ Given I log in as "student1"
+ And I follow "Profile" in the user menu
+ And I should see "Contact the privacy officer"
+ And I click on "Contact the privacy officer" "link"
+ And I set the field "Message" to "Hello DPO!"
+ And I press "Send"
+ And I should see "Your request has been submitted to the privacy officer"
+ And I click on "Data requests" "link"
+ And I should see "Hello DPO!" in the "General inquiry" "table_row"
--- /dev/null
+@tool @tool_dataprivacy
+Feature: Data export from the privacy API
+ In order to export data for users and meet legal requirements
+ As an admin, user, or parent
+ I need to be able to export data for a user
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname |
+ | victim | Victim User | 1 |
+ | parent | Long-suffering | Parent |
+ And the following "roles" exist:
+ | shortname | name | archetype |
+ | tired | Tired | |
+ And the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | tool/dataprivacy:makedatarequestsforchildren | Allow | tired | System | |
+ And the following "role assigns" exist:
+ | user | role | contextlevel | reference |
+ | parent | tired | User | victim |
+ And the following config values are set as admin:
+ | contactdataprotectionofficer | 1 | tool_dataprivacy |
+
+ @javascript
+ Scenario: As admin, export data for a user and download it
+ Given I log in as "admin"
+ And I navigate to "Users > Privacy and policies > Data requests" in site administration
+ And I follow "New request"
+ And I set the field "Requesting for" to "Victim User 1"
+ And I press "Save changes"
+ Then I should see "Victim User 1"
+ And I should see "Pending" in the "Victim User 1" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Awaiting approval" in the "Victim User 1" "table_row"
+ And I follow "Actions"
+ And I follow "Approve request"
+ And I press "Approve request"
+ And I should see "Approved" in the "Victim User 1" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Complete" in the "Victim User 1" "table_row"
+ And I follow "Actions"
+ And following "Download" should download between "1" and "100000" bytes
+
+ @javascript
+ Scenario: As a student, request data export and then download it when approved
+ Given I log in as "victim"
+ And I follow "Profile" in the user menu
+ And I follow "Data requests"
+ And I follow "New request"
+ And I press "Save changes"
+ Then I should see "Export all of my personal data"
+ And I should see "Pending" in the "Export all of my personal data" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Awaiting approval" in the "Export all of my personal data" "table_row"
+
+ And I log out
+ And I log in as "admin"
+ And I navigate to "Users > Privacy and policies > Data requests" in site administration
+ And I follow "Actions"
+ And I follow "Approve request"
+ And I press "Approve request"
+
+ And I log out
+ And I log in as "victim"
+ And I follow "Profile" in the user menu
+ And I follow "Data requests"
+ And I should see "Approved" in the "Export all of my personal data" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Complete" in the "Export all of my personal data" "table_row"
+ And I follow "Actions"
+ And following "Download" should download between "1" and "100000" bytes
+
+ @javascript
+ Scenario: As a parent, request data export for my child because I don't trust the little blighter
+ Given I log in as "parent"
+ And I follow "Profile" in the user menu
+ And I follow "Data requests"
+ And I follow "New request"
+ And I set the field "Requesting for" to "Victim User 1"
+ And I press "Save changes"
+ Then I should see "Victim User 1"
+ And I should see "Pending" in the "Victim User 1" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Awaiting approval" in the "Victim User 1" "table_row"
+
+ And I log out
+ And I log in as "admin"
+ And I navigate to "Users > Privacy and policies > Data requests" in site administration
+ And I follow "Actions"
+ And I follow "Approve request"
+ And I press "Approve request"
+
+ And I log out
+ And I log in as "parent"
+ And I follow "Profile" in the user menu
+ And I follow "Data requests"
+ And I should see "Approved" in the "Victim User 1" "table_row"
+ And I run all adhoc tasks
+ And I reload the page
+ And I should see "Complete" in the "Victim User 1" "table_row"
+ And I follow "Actions"
+ And following "Download" should download between "1" and "100000" bytes
--- /dev/null
+@tool @tool_dataprivacy
+Feature: Manage data requests
+ As the privacy officer
+ In order to address the privacy-related requests
+ I need to be able to manage the data requests of the site's users
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | John | Doe | s1@example.com |
+ | student2 | Jane | Doe | s2@example.com |
+ And I log in as "admin"
+ And I set the following administration settings values:
+ | contactdataprotectionofficer | 1 |
+ And I log out
+
+ @javascript
+ Scenario: Marking general enquiries as complete
+ Given I log in as "student1"
+ And I follow "Profile" in the user menu
+ And I should see "Contact the privacy officer"
+ And I click on "Contact the privacy officer" "link"
+ And I set the field "Message" to "Hi PO! Can others access my information on your site?"
+ And I press "Send"
+ And I should see "Your request has been submitted to the privacy officer"
+ And I log out
+ And I log in as "student2"
+ And I follow "Profile" in the user menu
+ And I click on "Contact the privacy officer" "link"
+ And I set the field "Message" to "Dear Mr. Privacy Officer, I'd like to know more about GDPR. Thanks!"
+ And I press "Send"
+ And I should see "Your request has been submitted to the privacy officer"
+ And I log out
+ When I log in as "admin"
+ And I navigate to "Users > Privacy and policies > Data requests" in site administration
+ Then I should see "Hi PO!" in the "John Doe" "table_row"
+ And I should see "Dear Mr. Privacy Officer" in the "Jane Doe" "table_row"
+ And I click on "Actions" "link" in the "John Doe" "table_row"
+ And I should see "View the request"
+ And I should see "Mark as complete"
+ And I choose "View the request" in the open action menu
+ And I should see "Hi PO! Can others access my information on your site?"
+ And I press "Mark as complete"
+ And I wait until the page is ready
+ And I should see "Complete" in the "John Doe" "table_row"
+ And I click on "Actions" "link" in the "John Doe" "table_row"
+ And I should see "View the request"
+ But I should not see "Mark as complete"
+ And I press key "27" in ".moodle-actionmenu" "css_element"
+ And I click on "Actions" "link" in the "Jane Doe" "table_row"
+ And I choose "Mark as complete" in the open action menu
+ And I should see "Do you really want to mark this user enquiry as complete?"
+ And I press "Mark as complete"
+ And I wait until the page is ready
+ And I should see "Complete" in the "Jane Doe" "table_row"
+ And I click on "Actions" "link" in the "Jane Doe" "table_row"
+ And I should see "View the request"
+ But I should not see "Mark as complete"
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2018051401;
+$plugin->version = 2018051405;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';
/**
* Legacy log reader.
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This is to be removed in Moodle 4.0
*
* @package logstore_legacy
* @copyright 2013 Petr Skoda {@link http://skodak.org}
use \tool_log\helper\store,
\tool_log\helper\reader;
+ /**
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This is to be removed in Moodle 4.0
+ *
+ * @param \tool_log\log\manager $manager
+ */
public function __construct(\tool_log\log\manager $manager) {
$this->helper_setup($manager);
}
return array($selectwhere, $params, $sort);
}
+ /**
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
+ *
+ * @param string $selectwhere
+ * @param array $params
+ * @param string $sort
+ * @param int $limitfrom
+ * @param int $limitnum
+ * @return array
+ */
public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
global $DB;
/**
* Fetch records using given criteria returning a Traversable object.
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
*
* Note that the traversable object contains a moodle_recordset, so
* remember that is important that you call close() once you finish
/**
* Returns an event from the log data.
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
*
* @param stdClass $data Log data
* @return \core\event\base
return \logstore_legacy\event\legacy_logged::restore_legacy($data);
}
+ /**
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
+ *
+ * @param string $selectwhere
+ * @param array $params
+ * @return int
+ */
public function get_events_select_count($selectwhere, array $params) {
global $DB;
/**
* Are the new events appearing in the reader?
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
*
* @return bool true means new log events are being added, false means no new data will be added
*/
return (bool)$this->get_config('loglegacy', true);
}
+ /**
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo MDL-52805 This will be removed in Moodle 4.0
+ */
public function dispose() {
}
--- /dev/null
+This files describes API changes in /admin/tool/log - plugins,
+information provided here is intended especially for developers.
+
+
+=== 3.6 ===
+
+* The legacy log store is in its first stage of deprecation and is due for removal in Moodle 4.0. Please use one of
+ the other log stores such as "standard" and "database".
\ No newline at end of file
array_walk($data->policies, function($item, $key) {
$item->policytypestr = get_string('policydoctype'.$item->type, 'tool_policy');
+ $item->policyaudiencestr = get_string('policydocaudience'.$item->audience, 'tool_policy');
});
return $data;
$string['inactivatingconfirmyes'] = 'Inactivate';
$string['invalidversionid'] = 'There is no policy with this identifier!';
$string['irevokethepolicy'] = 'Withdraw user consent';
+$string['listactivepolicies'] = 'List of active policies';
$string['minorchange'] = 'Minor change';
$string['minorchangeinfo'] = 'A minor change does not alter the meaning of the policy. Users are not required to agree to the policy again if the edit is marked as a minor change.';
$string['managepolicies'] = 'Manage policies';
"name": "Terms & conditions",
"summary": "Policy <u>summary</u>",
"content": "Policy <em>content</em>",
- "policytypestr": "Site policy"
+ "policytypestr": "Site policy",
+ "policyaudiencestr": "All users"
},
{
"id": "5",
"name": "Privacy",
"summary": "We keep your information private",
"content": "Very private",
- "policytypestr": "Privacy policy"
+ "policytypestr": "Privacy policy",
+ "policyaudiencestr": "Authenticated users"
}
]
}
}}
<a id="top"></a>
-<div id="policies_index" class="m-b-3">
-<ul>
+<div id="policies_index">
+<h1>{{# str }} listactivepolicies, tool_policy {{/ str }}</h1>
+<table class="table">
+ <thead>
+ <tr>
+ <th scope="col">{{# str }}policydocname, tool_policy {{/ str }}</th>
+ <th scope="col">{{# str }}policydoctype, tool_policy {{/ str }}</th>
+ <th scope="col">{{# str }}policydocaudience, tool_policy {{/ str }}</th>
+ </tr>
+ </thead>
+ <tbody>
{{#policies }}
- <li><a href="#policy-{{id}}">{{{name}}} ({{{policytypestr}}})</a></li>
+ <tr>
+ <td><a href="#policy-{{id}}">{{{name}}}</a></td>
+ <td>{{{ policytypestr }}}</td>
+ <td>{{{ policyaudiencestr }}}</td>
+ </tr>
{{/policies }}
-</ul>
+ </tbody>
+</table>
</div>
{{^policies }}
<hr>
<div class="policy_version m-b-3">
<div class="clearfix m-t-2">
- <h1><a id="policy-{{id}}">{{{name}}}</a></h1>
+ <h2><a id="policy-{{id}}">{{{name}}}</a></h2>
</div>
<div class="policy_document_summary clearfix m-b-1">
- <h2>{{# str }} policydocsummary, tool_policy {{/ str }}</h2>
+ <h3>{{# str }} policydocsummary, tool_policy {{/ str }}</h3>
{{{summary}}}
</div>
<div class="policy_document_content m-t-2">
- <h2>{{# str }} policydoccontent, tool_policy {{/ str }}</h2>
+ <h3>{{# str }} policydoccontent, tool_policy {{/ str }}</h3>
{{{content}}}
</div>
<div class="pull-right">
And I log out
# Create new policy document.
And I log in as "admin"
- And I navigate to "Manage policies" node in "Site administration > Privacy and policies"
+ And I navigate to "Manage policies" node in "Site administration > Users > Privacy and policies"
And I should see "Policies and agreements"
And I should see "New policy"
And I follow "New policy"
And I log out
# Create new version of the policy document.
And I log in as "admin"
- And I navigate to "Manage policies" node in "Site administration > Privacy and policies"
+ And I navigate to "Manage policies" node in "Site administration > Users > Privacy and policies"
When I follow "Actions"
Then I should see "View"
And I should see "Edit"
| This privacy policy | 1 | | full text3 | short text3 | active | loggedin |
| This guests policy | 0 | | full text4 | short text4 | active | guest |
And I am on site homepage
+ And I change window size to "large"
And I follow "Log in"
When I press "Log in as a guest"
Then I should see "If you continue browsing this website, you agree to our policies"
This files describes API changes in /admin/tool/* - plugins,
information provided here is intended especially for developers.
+=== 3.6 ===
+
+The assignment upgrade tool has been removed. If you need to upgrade assignments from before Moodle 2.3, you will have to upgrade to any Moodle version from 2.3 to 3.5, upgrade the assignments and then upgrade to a later version.
=== 2.2 ===
set_user_preference('auth_forcepasswordchange', 1, $id);
set_user_preference('create_password', 1, $id);
}
+
+ // Save custom profile fields here.
+ require_once($CFG->dirroot . '/user/profile/lib.php');
+ $user->id = $id;
+ profile_save_data($user);
+
// Make sure user context is present.
context_user::instance($id);
}
return;
}
}
+
+ /**
+ * Return a list of identity providers to display on the login page.
+ *
+ * @param string $wantsurl The requested URL.
+ * @return array List of arrays with keys url, iconurl and name.
+ */
+ public function loginpage_idp_list($wantsurl) {
+ $config = get_config('auth_shibboleth');
+ $result = [];
+
+ // Before displaying the button check that Shibboleth is set-up correctly.
+ if (empty($config->user_attribute)) {
+ return $result;
+ }
+
+ $url = new moodle_url('/auth/shibboleth/index.php');
+ $iconurl = moodle_url::make_pluginfile_url(context_system::instance()->id,
+ 'auth_shibboleth',
+ 'logo',
+ null,
+ '/',
+ $config->auth_logo);
+ $result[] = ['url' => $url, 'iconurl' => $iconurl, 'name' => $config->login_name];
+ return $result;
+ }
}
--- /dev/null
+<?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/>.
+
+/**
+ * Contains a helper class for the Shibboleth authentication plugin.
+ *
+ * @package auth_shibboleth
+ * @copyright 2018 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace auth_shibboleth;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The helper class for the Shibboleth authentication plugin.
+ *
+ * @package auth_shibboleth
+ * @copyright 2018 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+
+ /**
+ * Delete session of user using file sessions.
+ *
+ * @param string $spsessionid SP-provided Shibboleth Session ID
+ * @return \SoapFault or void if everything was fine
+ */
+ public static function logout_file_session($spsessionid) {
+ global $CFG;
+
+ if (!empty($CFG->session_file_save_path)) {
+ $dir = $CFG->session_file_save_path;
+ } else {
+ $dir = $CFG->dataroot . '/sessions';
+ }
+
+ if (is_dir($dir)) {
+ if ($dh = opendir($dir)) {
+ // Read all session files.
+ while (($file = readdir($dh)) !== false) {
+ // Check if it is a file.
+ if (is_file($dir.'/'.$file)) {
+ // Read session file data.
+ $data = file($dir.'/'.$file);
+ if (isset($data[0])) {
+ $usersession = self::unserializesession($data[0]);
+ // Check if we have found session that shall be deleted.
+ if (isset($usersession['SESSION']) && isset($usersession['SESSION']->shibboleth_session_id)) {
+ // If there is a match, delete file.
+ if ($usersession['SESSION']->shibboleth_session_id == $spsessionid) {
+ // Delete session file.
+ if (!unlink($dir.'/'.$file)) {
+ return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
+ }
+ }
+ }
+ }
+ }
+ }
+ closedir($dh);
+ }
+ }
+ }
+
+ /**
+ * Delete session of user using DB sessions.
+ *
+ * @param string $spsessionid SP-provided Shibboleth Session ID
+ */
+ public static function logout_db_session($spsessionid) {
+ global $CFG, $DB;
+
+ $sessions = $DB->get_records_sql(
+ 'SELECT userid, sessdata FROM {sessions} WHERE timemodified > ?',
+ array(time() - $CFG->sessiontimeout)
+ );
+
+ foreach ($sessions as $session) {
+ // Get user session from DB.
+ if (session_decode(base64_decode($session->sessdata))) {
+ if (isset($_SESSION['SESSION']) && isset($_SESSION['SESSION']->shibboleth_session_id)) {
+ // If there is a match, kill the session.
+ if ($_SESSION['SESSION']->shibboleth_session_id == trim($spsessionid)) {
+ // Delete this user's sessions.
+ \core\session\manager::kill_user_sessions($session->userid);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Unserialize a session string.
+ *
+ * @param string $serializedstring
+ * @return array
+ */
+ private static function unserializesession($serializedstring) {
+ $variables = array();
+ $a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+ $counta = count($a);
+ for ($i = 0; $i < $counta; $i = $i + 2) {
+ $variables[$a[$i]] = unserialize($a[$i + 1]);
+ }
+ return $variables;
+ }
+}
<?php if (is_enabled_auth('none')) { // instructions override the rest for security reasons
print_string("loginstepsnone");
} else if ($CFG->registerauth == 'email') {
- if (!empty($CFG->auth_instructions)) {
- echo format_text($CFG->auth_instructions);
+ if (!empty($config->auth_instructions)) {
+ echo format_text($config->auth_instructions);
} else {
print_string("loginsteps", "", "signup.php");
} ?>
</form>
</div>
<?php } else if (!empty($CFG->registerauth)) {
- echo format_text($CFG->auth_instructions); ?>
+ echo format_text($config->auth_instructions); ?>
<div class="signupform">
<form action="../../login/signup.php" method="get" id="signup">
<div><input type="submit" value="<?php print_string("startsignup") ?>" /></div>
</form>
</div>
<?php } else {
- echo format_text($CFG->auth_instructions);
+ echo format_text($config->auth_instructions);
} ?>
</div>
</div>
$string['auth_shib_auth_method'] = 'Authentication method name';
$string['auth_shib_auth_method_description'] = 'Provide a name for the Shibboleth authentication method that is familiar to your users. This could be the name of your Shibboleth federation, e.g. <tt>SWITCHaai Login</tt> or <tt>InCommon Login</tt> or similar.';
+$string['auth_shib_auth_logo'] = 'Authentication method logo';
+$string['auth_shib_auth_logo_description'] = 'Provide a logo for the Shibboleth authentication method that is familiar to your users. This could be the logo of your Shibboleth federation, e.g. <tt>SWITCHaai Login</tt> or <tt>InCommon Login</tt> or similar.';
$string['auth_shib_contact_administrator'] = 'In case you are not associated with the given organizations and you need access to a course on this server, please contact the <a href="mailto:{$a}">Moodle Administrator</a>.';
$string['auth_shibbolethdescription'] = 'Using this method users are created and authenticated using <a href="http://shibboleth.internet2.edu/">Shibboleth</a>.<br />Be sure to read the <a href="../auth/shibboleth/README.txt">README</a> for Shibboleth on how to set up your Moodle with Shibboleth';
$string['auth_shibboleth_errormsg'] = 'Please select the organization you are member of!';
--- /dev/null
+<?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 the hooks for the Shibboleth authentication module.
+ *
+ * @package auth_shibboleth
+ * @copyright 2018 Fabrice Ménard
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Serves the logo file settings.
+ *
+ * @param stdClass $course course object
+ * @param stdClass $cm course module object
+ * @param stdClass $context context object
+ * @param string $filearea file area
+ * @param array $args extra arguments
+ * @param bool $forcedownload whether or not force download
+ * @param array $options additional options affecting the file serving
+ * @return bool false if file not found, does not return if found - justsend the file
+ */
+function auth_shibboleth_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
+ if ($context->contextlevel != CONTEXT_SYSTEM) {
+ return false;
+ }
+
+ if ($filearea !== 'logo' ) {
+ return false;
+ }
+
+ $itemid = 0;
+
+ $filename = array_pop($args);
+ if (!$args) {
+ $filepath = '/';
+ } else {
+ $filepath = '/'.implode('/', $args).'/';
+ }
+
+ $fs = get_file_storage();
+ $file = $fs->get_file($context->id, 'auth_shibboleth', $filearea, $itemid, $filepath, $filename);
+ if (!$file) {
+ return false;
+ }
+
+ send_stored_file($file, null, 0, $forcedownload, $options);
+}
$loginurl = (!empty($CFG->alternateloginurl)) ? $CFG->alternateloginurl : '';
-
- if (!empty($CFG->registerauth) or is_enabled_auth('none') or !empty($CFG->auth_instructions)) {
+ $config = get_config('auth_shibboleth');
+ if (!empty($CFG->registerauth) or is_enabled_auth('none') or !empty($config->auth_instructions)) {
$show_instructions = true;
} else {
$show_instructions = false;
}
- // Set SAML domain cookie
- $config = get_config('auth_shibboleth');
-
-
$IdPs = get_idp_list($config->organization_selection);
if (isset($_POST['idp']) && isset($IdPs[$_POST['idp']])){
$selectedIdP = $_POST['idp'];
}
/******************************************************************************/
-function LogoutNotification($SessionID){
-
- global $CFG, $SESSION, $DB;
-
- // Delete session of user using $SessionID
- if(empty($CFG->dbsessions)) {
-
- // File session
- $dir = $CFG->dataroot .'/sessions';
- if (is_dir($dir)) {
- if ($dh = opendir($dir)) {
- // Read all session files
- while (($file = readdir($dh)) !== false) {
- // Check if it is a file
- if (is_file($dir.'/'.$file)){
- $session_key = preg_replace('/sess_/', '', $file);
-
- // Read session file data
- $data = file($dir.'/'.$file);
- if (isset($data[0])){
- $user_session = unserializesession($data[0]);
-
- // Check if we have found session that shall be deleted
- if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
-
- // If there is a match, delete file
- if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
- // Delete session file
- if (!unlink($dir.'/'.$file)){
- return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
- }
- }
- }
- }
- }
- }
- closedir($dh);
- }
- }
- } else {
- // DB Session
- //TODO: this needs to be rewritten to use new session stuff
- if (!empty($CFG->sessiontimeout)) {
- $ADODB_SESS_LIFE = $CFG->sessiontimeout;
- }
-
- if ($user_session_data = $DB->get_records_sql('SELECT sesskey, sessdata FROM {sessions2} WHERE expiry > NOW()')) {
- foreach ($user_session_data as $session_data) {
-
- // Get user session
- $user_session = adodb_unserialize( urldecode($session_data->sessdata) );
-
- if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
-
- // If there is a match, delete file
- if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
- // Delete this session entry
- if (ADODB_Session::destroy($session_data->sesskey) !== true){
- return new SoapFault('LogoutError', 'Could not delete Moodle session entry in database.');
- }
- }
- }
- }
- }
+/**
+ * Handles SOAP Back-channel logout notification
+ *
+ * @param string $spsessionid SP-provided Shibboleth Session ID
+ * @return SoapFault or void if everything was fine
+ */
+function LogoutNotification($spsessionid) {
+ $sessionclass = \core\session\manager::get_handler_class();
+ switch ($sessionclass) {
+ case '\core\session\file':
+ return \auth_shibboleth\helper::logout_file_session($spsessionid);
+ case '\core\session\database':
+ return \auth_shibboleth\helper::logout_db_session($spsessionid);
+ default:
+ throw new moodle_exception("Shibboleth logout not implemented for '$sessionclass'");
}
-
- // If now SoapFault was thrown the function will return OK as the SP assumes
-
-}
-
-/*****************************************************************************/
-
-// Same function as in adodb, but cannot be used for file session for some reason...
-function unserializesession($serialized_string) {
- $variables = array();
- $a = preg_split("/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
- $counta = count($a);
- for ($i = 0; $i < $counta; $i = $i+2) {
- $variables[$a[$i]] = unserialize($a[$i+1]);
- }
- return $variables;
+ // If no SoapFault was thrown, the function will return OK as the SP assumes.
}
get_string('auth_shib_auth_method', 'auth_shibboleth'),
get_string('auth_shib_auth_method_description', 'auth_shibboleth'), 'Shibboleth Login', PARAM_RAW_TRIMMED));
+ // Authentication method logo.
+ $settings->add(new admin_setting_configstoredfile('auth_shibboleth/auth_logo',
+ get_string('auth_shib_auth_logo', 'auth_shibboleth'),
+ get_string('auth_shib_auth_logo_description', 'auth_shibboleth'), 'logo', 0, ['accepted_types' => ['image']]));
+
// Login directions.
$settings->add(new admin_setting_configtextarea('auth_shibboleth/auth_instructions',
get_string('auth_shib_instructions_key', 'auth_shibboleth'),
This files describes API changes in /auth/shibboleth/*,
information provided here is intended especially for developers.
+=== 3.5.2 ===
+
+* Moved the public function unserializesession in auth/shibboleth/logout.php to auth/shibboleth/classes/helper.php and
+ made it private. This function should not have been used outside of this file.
+
=== 3.3 ===
* The config.html file was migrated to use the admin settings API.
And I set the field "Password" to "teacher1"
And I press "Log in"
# Confirm the notices are displayed.
- Then I should see "1 failed logins since your last login" in the "nav.navbar" "css_element"
+ Then I should see "1 failed logins since your last login" in the ".navbar" "css_element"
And I should see "1 failed logins since your last login" in the "page-footer" "region"
# Confirm the notices disappear when navigating to another page.
And I am on homepage
- And I should not see "1 failed logins since your last login" in the "nav.navbar" "css_element"
+ And I should not see "1 failed logins since your last login" in the ".navbar" "css_element"
And I should not see "1 failed logins since your last login" in the "page-footer" "region"
# Given the user has at least one failed login attempt, when they login, then they should see both header and footer notices.
And I set the field "Password" to "admin"
And I press "Log in"
# Confirm the notices are displayed.
- Then I should see "1 failed logins since your last login" in the "nav.navbar" "css_element"
+ Then I should see "1 failed logins since your last login" in the ".navbar" "css_element"
And I should see "1 failed logins since your last login (Logs)" in the "page-footer" "region"
# Confirm that the link works and that the notices disappear when navigating to another page.
And I click on "Logs" "link" in the "page-footer" "region"
And I should see "User login failed" in the "table.reportlog" "css_element"
- And I should not see "1 failed logins since your last login" in the "nav.navbar" "css_element"
+ And I should not see "1 failed logins since your last login" in the ".navbar" "css_element"
And I should not see "1 failed logins since your last login (Logs)" in the "page-footer" "region"
Background:
Given I log in as "admin"
- And I navigate to "Privacy settings" node in "Site administration > Privacy and policies"
+ And I navigate to "Privacy settings" node in "Site administration > Users > Privacy and policies"
Scenario: Admin provides valid value for 'Age of digital consent'.
Given I set the field "s__agedigitalconsentmap" to multiline:
And I log in as "teacher1"
Scenario: Import course's contents to another course
- Given I am on "Course 2" course homepage with editing mode on
+ Given I am on "Course 2" course homepage
And I should not see "Online users"
And I should not see "Test quiz"
- And I import "Course 1" course into "Course 2" course using this options:
+ And I import "Course 1" course into "Course 2" course using this options:
And I am on "Course 2" course homepage
And I should see "Online users"
And I should see "Test quiz"
// 6b) User cannot, check if we are in some contextlevel with fallback
// 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
// 7b) No fallback, error. End qcat loop
- // 5b) Match, mark q to be mapped
+ // 5b) Random question, must always create new.
+ // 5c) Match, mark q to be mapped
// 8) Check if backup is from Moodle >= 3.5 and error if more than one top-level category in the context.
// Get all the contexts (question banks) in restore for the given contextlevel
break 2; // out from qcat loop (both 7a and 7b), we have decided about ALL categories in context (bank)
}
- // 5b) Match, mark q to be mapped
+ // 5b) Random questions must always be newly created.
+ } else if ($question->qtype == 'random') {
+ // Nothing to mark, newitemid means create
+
+ // 5c) Match, mark q to be mapped.
} else {
self::set_backup_ids_record($restoreid, 'question', $question->id, $matchqid);
}
// 8) Check if backup is made on Moodle >= 3.5 and there are more than one top-level category in the context.
if ($after35 && $topcats > 1) {
- $errors[] = get_string('restoremultipletopcats', 'questions', $contextid);
+ $errors[] = get_string('restoremultipletopcats', 'question', $contextid);
}
}
protected static function is_course_modified($courseid, $since) {
$logmang = get_log_manager();
$readers = $logmang->get_readers('core\log\sql_reader');
- $where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
$params = array('courseid' => $courseid, 'since' => $since);
- foreach ($readers as $reader) {
+
+ foreach ($readers as $readerpluginname => $reader) {
+ $where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
+
+ // Prevent logs of prevous backups causing a false positive.
+ if ($readerpluginname != 'logstore_legacy') {
+ $where .= " and target <> 'course_backup'";
+ }
+
if ($reader->get_events_select_count($where, $params)) {
return true;
}
global $CFG;
require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
+require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
+require_once("$CFG->dirroot/backup/backup.class.php");
/**
* Unit tests for backup cron helper
$this->assertArrayHasKey('1000432000', $backupfiles);
$this->assertEquals('file3.mbz', $backupfiles['1000432000']);
}
+
+ /**
+ * Test {@link backup_cron_automated_helper::is_course_modified}.
+ */
+ public function test_is_course_modified() {
+ $this->resetAfterTest();
+ $this->preventResetByRollback();
+
+ set_config('enabled_stores', 'logstore_standard', 'tool_log');
+ set_config('buffersize', 0, 'logstore_standard');
+ set_config('logguests', 1, 'logstore_standard');
+
+ $course = $this->getDataGenerator()->create_course();
+
+ // New courses should be backed up.
+ $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, 0));
+
+ $timepriortobackup = time();
+ $this->waitForSecond();
+ $otherarray = [
+ 'format' => backup::FORMAT_MOODLE,
+ 'mode' => backup::MODE_GENERAL,
+ 'interactive' => backup::INTERACTIVE_YES,
+ 'type' => backup::TYPE_1COURSE,
+ ];
+ $event = \core\event\course_backup_created::create([
+ 'objectid' => $course->id,
+ 'context' => context_course::instance($course->id),
+ 'other' => $otherarray
+ ]);
+ $event->trigger();
+
+ // If the only action since last backup was a backup then no backup.
+ $this->assertFalse(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
+
+ $course->groupmode = SEPARATEGROUPS;
+ $course->groupmodeforce = true;
+ update_course($course);
+
+ // Updated courses should be backed up.
+ $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
+ }
}
/**
public static function testable_get_backups_to_delete($backupfiles, $now) {
return parent::get_backups_to_delete($backupfiles, $now);
}
+
+ /**
+ * Provides access to protected method get_backups_to_remove.
+ *
+ * @param int $courseid course id to check
+ * @param int $since timestamp, from which to check
+ *
+ * @return bool true if the course was modified, false otherwise. This also returns false if no readers are enabled. This is
+ * intentional, since we cannot reliably determine if any modification was made or not.
+ */
+ public static function testable_is_course_modified($courseid, $since) {
+ return parent::is_course_modified($courseid, $since);
+ }
}
$string['myprofile_settings'] = 'Visible user information';
$string['pluginname'] = 'Logged in user';
$string['privacy:metadata'] = 'The Logged in user block only shows information about the logged in user and does not store data itself.';
-
-// Deprecated since Moodle 3.2.
-$string['display_un'] = 'Display name';
-display_un,block_myprofile
return array('all' => true, 'my' => false, 'tag' => false);
}
- /**
- * Remove old entries from table block_recent_activity
- */
- public function cron() {
- global $DB;
- // Those entries will never be displayed as RECENT anyway.
- $DB->delete_records_select('block_recent_activity', 'timecreated < ?',
- array(time() - COURSE_MAX_RECENT_PERIOD));
- }
-
/**
* Migrates entries from table {log} into {block_recent_activity}
*
--- /dev/null
+<?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/>.
+
+/**
+ * Task for updating RSS feeds for rss client block
+ *
+ * @package block_recent_activity
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_recent_activity\task;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Task for updating RSS feeds for rss client block
+ *
+ * @package block_recent_activity
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cleanup extends \core\task\scheduled_task {
+
+ /**
+ * Name for this task.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('cleanuptask', 'block_recent_activity');
+ }
+
+ /**
+ * Remove old entries from table block_recent_activity
+ */
+ public function execute() {
+ global $DB;
+ // Those entries will never be displayed as RECENT anyway.
+ $DB->delete_records_select('block_recent_activity', 'timecreated < ?',
+ array(time() - COURSE_MAX_RECENT_PERIOD));
+ }
+}
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Lists renamed classes so that the autoloader can make the old names still work.
- *
- * @package mod_quiz
- * @copyright 2014 Tim Hunt
+ * Task definition for block_recent_activity.
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @package block_recent_activity
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
-// Array 'old_class_name' => 'new\class_name'.
-$renamedclasses = array(
-
- // Changed in Moodle 2.8.
- 'quiz_question_bank_view' => 'mod_quiz\question\bank\custom_view',
- 'question_bank_add_to_quiz_action_column' => 'mod_quiz\question\bank\add_action_column',
- 'question_bank_question_name_text_column' => 'mod_quiz\question\bank\question_name_text_column',
+$tasks = array(
+ array(
+ 'classname' => '\block_recent_activity\task\cleanup',
+ 'blocking' => 0,
+ 'minute' => 'R',
+ 'hour' => 'R',
+ 'day' => '*',
+ 'month' => '*',
+ 'dayofweek' => '*',
+ 'disabled' => 0
+ )
);
+
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['cleanuptask'] = 'Cleanup task for recent activity block';
$string['pluginname'] = 'Recent activity';
$string['privacy:metadata'] = 'The recent activity block contains a cache of data stored elsewhere in Moodle.';
$string['privacy:metadata:block_recent_activity'] = 'Temporary log of recent teacher activity. Removed after two days';
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2018052900; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'block_recent_activity'; // Full name of the plugin (used for diagnostics)
-$plugin->cron = 24*3600; // Cron interval 1 day.
\ No newline at end of file
}
}
- /**
- * cron - goes through all the feeds. If the feed has a skipuntil value
- * that is less than the current time cron will attempt to retrieve it
- * with the cache duration set to 0 in order to force the retrieval of
- * the item and refresh the cache.
- *
- * If a feed fails then the skipuntil time of that feed is set to be
- * later than the next expected cron time. The amount of time will
- * increase each time the fetch fails until the maximum is reached.
- *
- * If a feed that has been failing is successfully retrieved it will
- * go back to being handled as though it had never failed.
- *
- * CRON should therefor process requests for permanently broken RSS
- * feeds infrequently, and temporarily unavailable feeds will be tried
- * less often until they become available again.
- *
- * @return boolean Always returns true
- */
- function cron() {
- global $CFG, $DB;
- require_once($CFG->libdir.'/simplepie/moodle_simplepie.php');
-
- // Get the legacy cron time, strangely the cron property of block_base
- // does not seem to get set. This means we must retrive it here.
- $this->cron = $DB->get_field('block', 'cron', array('name' => 'rss_client'));
-
- // We are going to measure execution times
- $starttime = microtime();
- $starttimesec = time();
-
- // Fetch all site feeds.
- $rs = $DB->get_recordset('block_rss_client');
- $counter = 0;
- mtrace('');
- foreach ($rs as $rec) {
- mtrace(' ' . $rec->url . ' ', '');
-
- // Skip feed if it failed recently.
- if ($starttimesec < $rec->skipuntil) {
- mtrace('skipping until ' . userdate($rec->skipuntil));
- continue;
- }
-
- // Fetch the rss feed, using standard simplepie caching
- // so feeds will be renewed only if cache has expired
- core_php_time_limit::raise(60);
-
- $feed = new moodle_simplepie();
- // set timeout for longer than normal to be agressive at
- // fetching feeds if possible..
- $feed->set_timeout(40);
- $feed->set_cache_duration(0);
- $feed->set_feed_url($rec->url);
- $feed->init();
-
- if ($feed->error()) {
- // Skip this feed (for an ever-increasing time if it keeps failing).
- $rec->skiptime = $this->calculate_skiptime($rec->skiptime);
- $rec->skipuntil = time() + $rec->skiptime;
- $DB->update_record('block_rss_client', $rec);
- mtrace("Error: could not load/find the RSS feed - skipping for {$rec->skiptime} seconds.");
- } else {
- mtrace ('ok');
- // It worked this time, so reset the skiptime.
- if ($rec->skiptime > 0) {
- $rec->skiptime = 0;
- $rec->skipuntil = 0;
- $DB->update_record('block_rss_client', $rec);
- }
- // Only increase the counter when a feed is sucesfully refreshed.
- $counter ++;
- }
- }
- $rs->close();
-
- // Show times
- mtrace($counter . ' feeds refreshed (took ' . microtime_diff($starttime, microtime()) . ' seconds)');
-
- return true;
- }
-
/**
* Calculates a new skip time for a record based on the current skip time.
*
* @param int $currentskip The curreent skip time of a record.
* @return int A new skip time that should be set.
*/
- protected function calculate_skiptime($currentskip) {
+ public function calculate_skiptime($currentskip) {
// The default time to skiptime.
$newskiptime = $this->cron * 1.1;
if ($currentskip > 0) {
--- /dev/null
+<?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/>.
+
+/**
+ * Task for updating RSS feeds for rss client block
+ *
+ * @package block_rss_client
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_rss_client\task;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Task for updating RSS feeds for rss client block
+ *
+ * @package block_rss_client
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class refreshfeeds extends \core\task\scheduled_task {
+
+ /**
+ * Name for this task.
+ *
+ * @return string
+ */
+ public function get_name() {
+ return get_string('refreshfeedstask', 'block_rss_client');
+ }
+
+ /**
+ * This task goes through all the feeds. If the feed has a skipuntil value
+ * that is less than the current time cron will attempt to retrieve it
+ * with the cache duration set to 0 in order to force the retrieval of
+ * the item and refresh the cache.
+ *
+ * If a feed fails then the skipuntil time of that feed is set to be
+ * later than the next expected task time. The amount of time will
+ * increase each time the fetch fails until the maximum is reached.
+ *
+ * If a feed that has been failing is successfully retrieved it will
+ * go back to being handled as though it had never failed.
+ *
+ * Task should therefore process requests for permanently broken RSS
+ * feeds infrequently, and temporarily unavailable feeds will be tried
+ * less often until they become available again.
+ */
+ public function execute() {
+ global $CFG, $DB;
+ require_once($CFG->libdir.'/simplepie/moodle_simplepie.php');
+
+ // We are going to measure execution times.
+ $starttime = microtime();
+ $starttimesec = time();
+
+ // Fetch all site feeds.
+ $rs = $DB->get_recordset('block_rss_client');
+ $counter = 0;
+ mtrace('');
+ foreach ($rs as $rec) {
+ mtrace(' ' . $rec->url . ' ', '');
+
+ // Skip feed if it failed recently.
+ if ($starttimesec < $rec->skipuntil) {
+ mtrace('skipping until ' . userdate($rec->skipuntil));
+ continue;
+ }
+
+ // Fetch the rss feed, using standard simplepie caching
+ // so feeds will be renewed only if cache has expired.
+ \core_php_time_limit::raise(60);
+
+ $feed = new \moodle_simplepie();
+ // Set timeout for longer than normal to be agressive at
+ // fetching feeds if possible..
+ $feed->set_timeout(40);
+ $feed->set_cache_duration(0);
+ $feed->set_feed_url($rec->url);
+ $feed->init();
+
+ if ($feed->error()) {
+ // Skip this feed (for an ever-increasing time if it keeps failing).
+ $block = new \block_rss_client();
+ $rec->skiptime = $block->calculate_skiptime($rec->skiptime);
+ $rec->skipuntil = time() + $rec->skiptime;
+ $DB->update_record('block_rss_client', $rec);
+ mtrace("Error: could not load/find the RSS feed - skipping for {$rec->skiptime} seconds.");
+ } else {
+ mtrace ('ok');
+ // It worked this time, so reset the skiptime.
+ if ($rec->skiptime > 0) {
+ $rec->skiptime = 0;
+ $rec->skipuntil = 0;
+ $DB->update_record('block_rss_client', $rec);
+ }
+ // Only increase the counter when a feed is sucesfully refreshed.
+ $counter ++;
+ }
+ }
+ $rs->close();
+
+ // Show times.
+ mtrace($counter . ' feeds refreshed (took ' . microtime_diff($starttime, microtime()) . ' seconds)');
+
+ }
+}
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Version details.
- *
- * @package tool_assignmentupgrade
- * @copyright 2012 NetSpot
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * Task definition for block_rss_client.
+ * @author Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @package block_rss_client
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2018051400;
-$plugin->requires = 2018050800;
-$plugin->component = 'tool_assignmentupgrade';
-$plugin->dependencies = array('mod_assign' => 2018050800);
+$tasks = array(
+ array(
+ 'classname' => '\block_rss_client\task\refreshfeeds',
+ 'blocking' => 0,
+ 'minute' => '*/5',
+ 'hour' => '*',
+ 'day' => '*',
+ 'month' => '*',
+ 'dayofweek' => '*',
+ 'disabled' => 0
+ )
+);
+
$string['privacy:metadata:block_rss_client:url'] = 'The URL of the RSS feed.';
$string['privacy:metadata:block_rss_client:userid'] = 'The ID of the user that added the RSS feed.';
$string['remotenewsfeed'] = 'Remote news feed';
+$string['refreshfeedstask'] = 'Refresh RSS feeds task';
$string['rss_client:addinstance'] = 'Add a new remote RSS feeds block';
$string['rss_client:createprivatefeeds'] = 'Create private RSS feeds';
$string['rss_client:createsharedfeeds'] = 'Create shared RSS feeds';
);
$DB->insert_record('block_rss_client', $record);
- $block = new block_rss_client();
+ $task = new \block_rss_client\task\refreshfeeds();
ob_start();
// Silence SimplePie php notices.
$errorlevel = error_reporting($CFG->debug & ~E_USER_NOTICE);
- $block->cron();
+ $task->execute();
error_reporting($errorlevel);
$cronoutput = ob_get_clean();
}
/**
- * Test that when a feed has an error the skip time is increaed correctly.
+ * Test that when a feed has an error the skip time is increased correctly.
*/
public function test_error() {
global $DB, $CFG;
);
$record3->id = $DB->insert_record('block_rss_client', $record3);
- // Run the cron.
- $block = new block_rss_client();
+ // Run the scheduled task.
+ $task = new \block_rss_client\task\refreshfeeds();
ob_start();
// Silence SimplePie php notices.
$errorlevel = error_reporting($CFG->debug & ~E_USER_NOTICE);
- $block->cron();
+ $task->execute();
error_reporting($errorlevel);
$cronoutput = ob_get_clean();
$skiptime1 = $record->skiptime * 2;
$message1 = 'http://example.com/rss Error: could not load/find the RSS feed - skipping for ' . $skiptime1 . ' seconds.';
$this->assertContains($message1, $cronoutput);
- $skiptime2 = 330; // Assumes that the cron time in the version file is 300.
+ $skiptime2 = 0;
$message2 = 'http://example.com/rss2 Error: could not load/find the RSS feed - skipping for ' . $skiptime2 . ' seconds.';
$this->assertContains($message2, $cronoutput);
$skiptime3 = block_rss_client::CLIENT_MAX_SKIPTIME;
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2018052900; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2018050800; // Requires this Moodle version
$plugin->component = 'block_rss_client'; // Full name of the plugin (used for diagnostics)
-$plugin->cron = 300; // Set min time between cron executions to 300 secs (5 mins)
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery'], function($) {
+define(['jquery', 'core_calendar/repository'], function($, CalendarRepository) {
var SELECTORS = {
EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]',
EVENT_GROUP_ID: '[name="groupid"]',
- SELECT_OPTION: 'option',
- };
-
- /**
- * Parse the group id select element in the event form and pull out
- * the course id from the value to allow us to toggle other select
- * elements based on the course id for the group a user selects.
- *
- * This is a little hacky but I couldn't find a better way to pass
- * the course id for each group id with the limitations of mforms.
- *
- * The group id options are rendered with a value like:
- * "<courseid>-<groupid>"
- * E.g.
- * For a group with id 10 in a course with id 3 the value of the
- * option will be 3-10.
- *
- * @method parseGroupSelect
- * @param {object} formElement The root form element
- */
- var parseGroupSelect = function(formElement) {
- formElement.find(SELECTORS.EVENT_GROUP_ID)
- .find(SELECTORS.SELECT_OPTION)
- .each(function(index, element) {
- element = $(element);
- var value = element.attr('value');
- var splits = value.split('-');
- var courseId = splits[0];
-
- element.attr('data-course-id', courseId);
- });
+ SELECT_OPTION: 'option'
};
/**
*/
var addCourseGroupSelectListeners = function(formElement) {
var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);
- var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID);
- var groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION);
- var filterGroupSelectOptions = function() {
- var selectedCourseId = courseGroupSelect.val();
- var selectedIndex = null;
- var hasGroups = false;
- groupSelectOptions.each(function(index, element) {
- element = $(element);
- if (element.attr('data-course-id') == selectedCourseId) {
- element.removeClass('hidden');
- element.prop('disabled', false);
- hasGroups = true;
- if (selectedIndex === null || element.attr('selected')) {
- selectedIndex = index;
- }
- } else {
- element.addClass('hidden');
- element.prop('disabled', true);
- }
- });
+ var loadGroupSelectOptions = function(groups) {
+ var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),
+ groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),
+ courseGroups = $(groups);
- if (hasGroups) {
- groupSelect.prop('disabled', false);
- } else {
- groupSelect.prop('disabled', true);
- }
-
- groupSelect.prop('selectedIndex', selectedIndex);
+ // Let's clear all options first.
+ groupSelectOptions.remove();
+ groupSelect.prop("disabled", false);
+ courseGroups.each(function(id, group) {
+ $(groupSelect).append($("<option></option>").attr("value", group.id).text(group.name));
+ });
};
- courseGroupSelect.on('change', filterGroupSelectOptions);
- filterGroupSelectOptions();
+ // If the user choose a course in the selector do a WS request to get groups.
+ courseGroupSelect.on('change', function() {
+ var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();
+ CalendarRepository.getCourseGroupsData(courseId)
+ .then(function(groups) {
+ return loadGroupSelectOptions(groups);
+ })
+ .catch(Notification.exception);
+ });
};
/**
*/
var init = function(formId) {
var formElement = $('#' + formId);
-
- parseGroupSelect(formElement);
addCourseGroupSelectListeners(formElement);
};
return Ajax.call([request])[0];
};
+ /**
+ * Get the groups by course id.
+ *
+ * @param {Number} courseid The course id to fetch the groups from.
+ * @return {promise} Resolved with the course groups.
+ */
+ var getCourseGroupsData = function(courseid) {
+ var request = {
+ methodname: 'core_group_get_course_groups',
+ args: {
+ courseid: courseid
+ }
+ };
+
+ return Ajax.call([request])[0];
+ };
+
return {
getEventById: getEventById,
deleteEvent: deleteEvent,
submitCreateUpdateForm: submitCreateUpdateForm,
getCalendarMonthData: getCalendarMonthData,
getCalendarDayData: getCalendarDayData,
- getCalendarUpcomingData: getCalendarUpcomingData
+ getCalendarUpcomingData: getCalendarUpcomingData,
+ getCourseGroupsData: getCourseGroupsData
};
});
*/
protected static $modulecache = array();
+ /**
+ * @var int The requesting user. All capability checks are done against this user.
+ */
+ protected static $requestinguserid;
+
/**
* Initialises the dependency graph if it hasn't yet been.
*/
[self::class, 'apply_component_provide_event_action'],
[self::class, 'apply_component_is_event_visible'],
function ($dbrow) {
+ $requestinguserid = self::get_requesting_user();
+
if (!empty($dbrow->categoryid)) {
// This is a category event. Check that the category is visible to this user.
- $category = \coursecat::get($dbrow->categoryid, IGNORE_MISSING, true);
+ $category = \coursecat::get($dbrow->categoryid, IGNORE_MISSING, true, $requestinguserid);
- if (empty($category) || !$category->is_uservisible()) {
+ if (empty($category) || !$category->is_uservisible($requestinguserid)) {
return true;
}
}
return false;
}
- $instances = get_fast_modinfo($dbrow->courseid)->instances;
+ $instances = get_fast_modinfo($dbrow->courseid, $requestinguserid)->instances;
// If modinfo doesn't know about the module, we should ignore it.
if (!isset($instances[$dbrow->modulename]) || !isset($instances[$dbrow->modulename][$dbrow->instance])) {
}
$coursecontext = \context_course::instance($dbrow->courseid);
- if (!$cm->get_course()->visible && !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+ if (!$cm->get_course()->visible &&
+ !has_capability('moodle/course:viewhiddencourses', $coursecontext, $requestinguserid)) {
return true;
}
- if (!has_capability('moodle/course:view', $coursecontext) && !is_enrolled($coursecontext)) {
+ if (!has_capability('moodle/course:view', $coursecontext, $requestinguserid) &&
+ !is_enrolled($coursecontext, $requestinguserid)) {
return true;
}
* Reset all static caches, called between tests.
*/
public static function reset_caches() {
+ self::$requestinguserid = null;
self::$eventfactory = null;
self::$eventmapper = null;
self::$eventvault = null;
return self::$eventvault;
}
+ /**
+ * Sets the requesting user so that all capability checks are done against this user.
+ * Setting the requesting user (hence calling this function) is optional and if you do not so,
+ * $USER will be used as the requesting user. However, if you wish to set the requesting user yourself,
+ * you should call this function before any other function of the container class is called.
+ *
+ * @param int $userid The user id.
+ * @throws \coding_exception
+ */
+ public static function set_requesting_user($userid) {
+ self::$requestinguserid = $userid;
+ }
+
+ /**
+ * Returns the requesting user id.
+ * It usually is the current user unless it has been set explicitly using set_requesting_user.
+ *
+ * @return int
+ */
+ public static function get_requesting_user() {
+ global $USER;
+
+ return empty(self::$requestinguserid) ? $USER->id : self::$requestinguserid;
+ }
+
/**
* Calls callback 'core_calendar_provide_event_action' from the component responsible for the event
*
$mapper = self::$eventmapper;
$action = null;
if ($event->get_course_module()) {
+ $requestinguserid = self::get_requesting_user();
+ $legacyevent = $mapper->from_event_to_legacy_event($event);
+ // We know for a fact that the the requesting user might be different from the logged in user,
+ // but the event mapper is not aware of that.
+ if (empty($event->user) && !empty($legacyevent->userid)) {
+ $legacyevent->userid = $requestinguserid;
+ }
+
// TODO MDL-58866 Only activity modules currently support this callback.
// Any other event will not be displayed on the dashboard.
$action = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_provide_event_action',
[
- $mapper->from_event_to_legacy_event($event),
- self::$actionfactory
+ $legacyevent,
+ self::$actionfactory,
+ $requestinguserid
]
);
}
$mapper = self::$eventmapper;
$eventvisible = null;
if ($event->get_course_module()) {
+ $requestinguserid = self::get_requesting_user();
+ $legacyevent = $mapper->from_event_to_legacy_event($event);
+ // We know for a fact that the the requesting user might be different from the logged in user,
+ // but the event mapper is not aware of that.
+ if (empty($event->user) && !empty($legacyevent->userid)) {
+ $legacyevent->userid = $requestinguserid;
+ }
+
// TODO MDL-58866 Only activity modules currently support this callback.
$eventvisible = component_callback(
'mod_' . $event->get_course_module()->get('modname'),
'core_calendar_is_event_visible',
[
- $mapper->from_event_to_legacy_event($event)
+ $legacyevent,
+ $requestinguserid
]
);
}
$mform = $this->_form;
$starttime = isset($this->_customdata['starttime']) ? $this->_customdata['starttime'] : 0;
$editoroptions = !(empty($this->_customdata['editoroptions'])) ? $this->_customdata['editoroptions'] : null;
- $eventtypes = calendar_get_all_allowed_types();
+ $courseid = !(empty($this->_customdata['courseid'])) ? $this->_customdata['courseid'] : null;
- if (empty($eventtypes)) {
+ $eventtypes = calendar_get_allowed_event_types($courseid);
+
+ if (in_array(true, $eventtypes, true) === false) {
print_error('nopermissiontoupdatecalendar');
}
* @return array
*/
public function validation($data, $files) {
- global $DB, $CFG;
+ global $DB;
$errors = parent::validation($data, $files);
- $eventtypes = calendar_get_all_allowed_types();
$eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
$coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
- if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
+ $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
+ $eventtypes = calendar_get_allowed_event_types($courseid);
+
+ if (empty($eventtype) || !isset($eventtypes[$eventtype]) || $eventtypes[$eventtype] == false) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
}
- if (isset($data[$coursekey]) && $data[$coursekey] > 0) {
- if ($course = $DB->get_record('course', ['id' => $data[$coursekey]])) {
+ if ($courseid && $courseid > 0) {
+ if ($course = $DB->get_record('course', ['id' => $courseid])) {
if ($data['timestart'] < $course->startdate) {
$errors['timestart'] = get_string('errorbeforecoursestart', 'calendar');
}
}
}
- if ($eventtype == 'course' && empty($data['courseid'])) {
+ if ($eventtype == 'course' && empty($courseid)) {
$errors['courseid'] = get_string('selectacourse');
}
- if ($eventtype == 'group' && empty($data['groupcourseid'])) {
+ if ($eventtype == 'group' && (!empty($courseid) && empty($data['groupid']))) {
+ $errors['groupcourseid'] = get_string('nogroups', 'core_group');
+ }
+
+ if ($eventtype == 'group' && empty($courseid)) {
$errors['groupcourseid'] = get_string('selectacourse');
}
* @param array $eventtypes The available event types for the user
*/
protected function add_event_type_elements($mform, $eventtypes) {
+ global $CFG, $DB;
$options = [];
- if (isset($eventtypes['user'])) {
+ if (!empty($eventtypes['user'])) {
$options['user'] = get_string('user');
}
- if (isset($eventtypes['group'])) {
+ if (!empty($eventtypes['group'])) {
$options['group'] = get_string('group');
}
- if (isset($eventtypes['course'])) {
+ if (!empty($eventtypes['course'])) {
$options['course'] = get_string('course');
}
- if (isset($eventtypes['category'])) {
+ if (!empty($eventtypes['category'])) {
$options['category'] = get_string('category');
}
- if (isset($eventtypes['site'])) {
+ if (!empty($eventtypes['site'])) {
$options['site'] = get_string('site');
}
// If we only have one event type and it's 'user' event then don't bother
// rendering the select boxes because there is no choice for the user to
// make.
- if (count(array_keys($eventtypes)) == 1 && isset($eventtypes['user'])) {
+ if (!empty($eventtypes['user']) && count($options) == 1) {
$mform->addElement('hidden', 'eventtype');
$mform->setType('eventtype', PARAM_TEXT);
$mform->setDefault('eventtype', 'user');
$mform->addElement('select', 'eventtype', get_string('eventkind', 'calendar'), $options);
}
- if (isset($eventtypes['category'])) {
+ if (!empty($eventtypes['category'])) {
$categoryoptions = [];
- foreach ($eventtypes['category'] as $id => $category) {
+ foreach (\coursecat::make_categories_list('moodle/category:manage') as $id => $category) {
$categoryoptions[$id] = $category;
}