+++ /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);
- }
-}
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.
*
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);
$actionsmenu->set_menu_trigger(get_string('actions'));
$actionsmenu->set_owner_selector('request-actions-' . $requestid);
$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);
$cancancel = false;
// 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' => []
+ ],
];
$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}';
* @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.
$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: 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
defined('MOODLE_INTERNAL') || die;
-$plugin->version = 2018051402;
+$plugin->version = 2018051403;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';
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 ===
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;
+ }
}
<?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'];
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'),
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"
// 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);
}
}
$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/>.
/**
- * Deprecation notice for password_compat.
- *
- * @package core
- * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * 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();
-debugging('password_compat is now standard in all versions of PHP that Moodle supports. '
- . 'You no longer need to include the lib/password_compat/lib/password.php',
- DEBUG_DEVELOPER);
+$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;
}
$mform->hideIf('categoryid', 'eventtype', 'noteq', 'category');
}
- if (isset($eventtypes['course'])) {
- $limit = !has_capability('moodle/calendar:manageentries', \context_system::instance());
- $mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => $limit]);
+ $showall = $CFG->calendar_adminseesall && !has_capability('moodle/calendar:manageentries', \context_system::instance());
+ if (!empty($eventtypes['course'])) {
+ $mform->addElement('course', 'courseid', get_string('course'), ['limittoenrolled' => !$showall]);
$mform->hideIf('courseid', 'eventtype', 'noteq', 'course');
}
- if (isset($eventtypes['group'])) {
- $options = ['limittoenrolled' => true];
- // Exclude courses without group.
- if (isset($eventtypes['course']) && isset($eventtypes['groupcourses'])) {
- $options['exclude'] = array_diff(array_keys($eventtypes['course']),
- array_keys($eventtypes['groupcourses']));
- }
-
- $mform->addElement('course', 'groupcourseid', get_string('course'), $options);
+ if (!empty($eventtypes['group'])) {
+ $groups = !(empty($this->_customdata['groups'])) ? $this->_customdata['groups'] : null;
+ // Get the list of courses without groups to filter on the course selector.
+ $sql = "SELECT c.id
+ FROM {course} c
+ WHERE c.id NOT IN (
+ SELECT DISTINCT courseid FROM {groups}
+ )";
+ $coursesnogroup = $DB->get_records_sql($sql);
+ $mform->addElement('course', 'groupcourseid', get_string('course'), ['limittoenrolled' => !$showall,
+ 'exclude' => array_keys($coursesnogroup)]);
$mform->hideIf('groupcourseid', 'eventtype', 'noteq', 'group');
- $groupoptions = [];
- foreach ($eventtypes['group'] as $group) {
- // We are formatting it this way in order to provide the javascript both
- // the course and group ids so that it can enhance the form for the user.
- $index = "{$group->courseid}-{$group->id}";
- $groupoptions[$index] = format_string($group->name, true,
- ['context' => \context_course::instance($group->courseid)]);
- }
-
- $mform->addElement('select', 'groupid', get_string('group'), $groupoptions);
+ $mform->addElement('select', 'groupid', get_string('group'), $groups);
$mform->hideIf('groupid', 'eventtype', 'noteq', 'group');
// We handle the group select hide/show actions on the event_form module.
}
*/
public function definition() {
$mform = $this->_form;
- $eventtypes = calendar_get_all_allowed_types();
- if (empty($eventtypes)) {
+ $eventtypes = calendar_get_allowed_event_types();
+ if (in_array(true, $eventtypes, true) === false) {
print_error('nopermissiontoupdatecalendar');
}
$errors = parent::validation($data, $files);
- $coursekey = isset($data['groupcourseid']) ? 'groupcourseid' : 'courseid';
- $eventtypes = calendar_get_all_allowed_types();
$eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
+ $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
+ $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
+ $eventtypes = calendar_get_allowed_event_types($courseid);
if (empty($eventtype) || !isset($eventtypes[$eventtype])) {
$errors['eventtype'] = get_string('invalideventtype', 'calendar');
if ($legacyevent->eventtype == 'group') {
// Set up the correct value for the to display on the form.
- $data->groupid = "{$legacyevent->courseid}-{$legacyevent->groupid}";
+ $data->groupid = $legacyevent->groupid;
$data->groupcourseid = $legacyevent->courseid;
}
if ($legacyevent->eventtype == 'course') {
$properties->courseid = $data->groupcourseid;
unset($properties->groupcourseid);
}
-
- // Pull the group id back out of the value. The form saves the value
- // as "<courseid>-<groupid>" to allow the javascript to work correctly.
if (isset($data->groupid)) {
- list($courseid, $groupid) = explode('-', $data->groupid);
- $properties->groupid = $groupid;
+ $properties->groupid = $data->groupid;
}
} else {
// Default course id if none is set.
return array();
}
+ if (is_numeric($users)) {
+ $users = array($users);
+ }
+ if (is_numeric($groups)) {
+ $groups = array($groups);
+ }
+ if (is_numeric($courses)) {
+ $courses = array($courses);
+ }
+ if (is_numeric($categories)) {
+ $categories = array($categories);
+ }
+
// Array of filter conditions. To be concatenated by the OR operator.
$filters = [];
// User filter.
- if ((is_array($users) && !empty($users)) or is_numeric($users)) {
+ if (is_array($users) && !empty($users)) {
// Events from a number of users.
list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED);
$filters[] = "(e.userid $insqlusers AND e.courseid = 0 AND e.groupid = 0 AND e.categoryid = 0)";
// Boolean false (no users at all): We don't need to do anything.
// Group filter.
- if ((is_array($groups) && !empty($groups)) or is_numeric($groups)) {
+ if (is_array($groups) && !empty($groups)) {
// Events from a number of groups.
list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
$filters[] = "e.groupid $insqlgroups";
// Boolean false (no groups at all): We don't need to do anything.
// Course filter.
- if ((is_array($courses) && !empty($courses)) or is_numeric($courses)) {
+ if (is_array($courses) && !empty($courses)) {
list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
$filters[] = "(e.groupid = 0 AND e.courseid $insqlcourses)";
$params = array_merge($params, $inparamscourses);
}
// Category filter.
- if ((is_array($categories) && !empty($categories)) or is_numeric($categories)) {
+ if (is_array($categories) && !empty($categories)) {
list($insqlcategories, $inparamscategories) = $DB->get_in_or_equal($categories, SQL_PARAMS_NAMED);
$filters[] = "(e.groupid = 0 AND e.courseid = 0 AND e.categoryid $insqlcategories)";
$params = array_merge($params, $inparamscategories);
// Build SQL subquery and conditions for filtered events based on priorities.
$subquerywhere = '';
$subqueryconditions = [];
-
- // Get the user's courses. Otherwise, get the default courses being shown by the calendar.
- $usercourses = calendar_get_default_courses();
-
- // Set calendar filters.
- list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true);
$subqueryparams = [];
-
- // Flag to indicate whether the query needs to exclude group overrides.
- $viewgroupsonly = false;
-
- if ($user) {
- // Set filter condition for the user's events.
- $subqueryconditions[] = "(ev.userid = :user AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
- $subqueryparams['user'] = $user;
-
- foreach ($usercourses as $courseid) {
- if (has_capability('moodle/site:accessallgroups', \context_course::instance($courseid))) {
- $usergroupmembership = groups_get_all_groups($courseid, $user, 0, 'g.id');
- if (count($usergroupmembership) == 0) {
- $viewgroupsonly = true;
- break;
+ $allusercourses = [];
+
+ if (is_array($users) && !empty($users)) {
+ $userrecords = $DB->get_records_sql("SELECT * FROM {user} WHERE id $insqlusers", $inparamsusers);
+ foreach ($userrecords as $userrecord) {
+ // Get the user's courses. Otherwise, get the default courses being shown by the calendar.
+ $usercourses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce',
+ false, $userrecord->id);
+
+ // Set calendar filters.
+ list($usercourses, $usergroups, $user) = calendar_set_filters($usercourses, true, $userrecord);
+
+ $allusercourses = array_merge($allusercourses, $usercourses);
+
+ // Flag to indicate whether the query needs to exclude group overrides.
+ $viewgroupsonly = false;
+
+ if ($user) {
+ // Set filter condition for the user's events.
+ // Even though $user is a single scalar, we still use get_in_or_equal() because we are inside a loop.
+ list($inusers, $inuserparams) = $DB->get_in_or_equal($user, SQL_PARAMS_NAMED);
+ $subqueryconditions[] = "(ev.userid $inusers AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
+ $subqueryparams = array_merge($subqueryparams, $inuserparams);
+
+ foreach ($usercourses as $courseid) {
+ if (has_capability('moodle/site:accessallgroups', \context_course::instance($courseid), $userrecord)) {
+ $usergroupmembership = groups_get_all_groups($courseid, $user, 0, 'g.id');
+ if (count($usergroupmembership) == 0) {
+ $viewgroupsonly = true;
+ break;
+ }
+ }
}
}
+
+ // Set filter condition for the user's group events.
+ if ($usergroups === true || $viewgroupsonly) {
+ // Fetch group events, but not group overrides.
+ $subqueryconditions[] = "(ev.groupid != 0 AND ev.eventtype = 'group')";
+ } else if (!empty($usergroups)) {
+ // Fetch group events and group overrides.
+ list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
+ $subqueryconditions[] = "(ev.groupid $inusergroups)";
+ $subqueryparams = array_merge($subqueryparams, $inusergroupparams);
+ }
+ }
+ } else if ($users === true) {
+ // Events from ALL users.
+ $subqueryconditions[] = "(ev.userid != 0 AND ev.courseid = 0 AND ev.groupid = 0 AND ev.categoryid = 0)";
+
+ if (is_array($groups)) {
+ // Events from a number of groups.
+ list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED);
+ $subqueryconditions[] = "ev.groupid $insqlgroups";
+ $subqueryparams = array_merge($subqueryparams, $inparamsgroups);
+ } else if ($groups === true) {
+ // Events from ALL groups.
+ $subqueryconditions[] = "ev.groupid != 0";
}
- }
- // Set filter condition for the user's group events.
- if ($usergroups === true || $viewgroupsonly) {
- // Fetch group events, but not group overrides.
- $subqueryconditions[] = "(ev.groupid != 0 AND ev.eventtype = 'group')";
- } else if (!empty($usergroups)) {
- // Fetch group events and group overrides.
- list($inusergroups, $inusergroupparams) = $DB->get_in_or_equal($usergroups, SQL_PARAMS_NAMED);
- $subqueryconditions[] = "(ev.groupid $inusergroups)";
- $subqueryparams = array_merge($subqueryparams, $inusergroupparams);
+ if ($courses === true) {
+ // ALL course events. It's not needed to worry about users' access as $users = true.
+ $subqueryconditions[] = "(ev.groupid = 0 AND ev.courseid != 0 AND ev.categoryid = 0)";
+ }
}
// Get courses to be used for the subquery.
$subquerycourses = [];
if (is_array($courses)) {
$subquerycourses = $courses;
- } else if (is_numeric($courses)) {
- $subquerycourses[] = $courses;
}
// Merge with user courses, if necessary.
- if (!empty($usercourses)) {
- $subquerycourses = array_merge($subquerycourses, $usercourses);
+ if (!empty($allusercourses)) {
+ $subquerycourses = array_merge($subquerycourses, $allusercourses);
// Make sure we remove duplicate values.
$subquerycourses = array_unique($subquerycourses);
}
self::validate_context($context);
parse_str($params['formdata'], $data);
+ $eventtype = isset($data['eventtype']) ? $data['eventtype'] : null;
+ $coursekey = ($eventtype == 'group') ? 'groupcourseid' : 'courseid';
+ $courseid = (!empty($data[$coursekey])) ? $data[$coursekey] : null;
+ $editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
+ $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
+ if ($courseid) {
+ require_once($CFG->libdir . '/grouplib.php');
+ $groupcoursedata = groups_get_course_data($courseid);
+ if (!empty($groupcoursedata->groups)) {
+ $formoptions['groups'] = [];
+ foreach ($groupcoursedata->groups as $groupid => $groupdata) {
+ $formoptions['groups'][$groupid] = $groupdata->name;
+ }
+ }
+ }
+
if (!empty($data['id'])) {
$eventid = clean_param($data['id'], PARAM_INT);
$legacyevent = calendar_event::load($eventid);
$legacyevent->count_repeats();
- $formoptions = ['event' => $legacyevent];
+ $formoptions['event'] = $legacyevent;
$mform = new update_event_form(null, $formoptions, 'post', '', null, true, $data);
} else {
$legacyevent = null;
- $mform = new create_event_form(null, null, 'post', '', null, true, $data);
+ $mform = new create_event_form(null, $formoptions, 'post', '', null, true, $data);
}
if ($validateddata = $mform->get_data()) {
$category = (\coursecat::get($course->category, MUST_EXIST, true))->get_db_record();
} else if (!empty($categoryid)) {
$course = get_site();
- $courses = calendar_get_default_courses();
+ $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce');
// Filter available courses to those within this category or it's children.
$ids = [$categoryid];
$calendar->context = context_coursecat::instance($categoryid);
} else {
$course = get_site();
- $courses = calendar_get_default_courses();
+ $courses = calendar_get_default_courses(null, 'id, category, groupmode, groupmodeforce');
$category = null;
$calendar->context = context_system::instance();
*
* @param array $courseeventsfrom An array of courses to load calendar events for
* @param bool $ignorefilters specify the use of filters, false is set as default
+ * @param stdClass $user The user object. This defaults to the global $USER object.
* @return array An array of courses, groups, and user to load calendar events for based upon filters
*/
-function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
- global $USER, $CFG;
+function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false, stdClass $user = null) {
+ global $CFG, $USER;
- // For backwards compatability we have to check whether the courses array contains
- // just id's in which case we need to load course objects.
- $coursestoload = array();
- foreach ($courseeventsfrom as $id => $something) {
- if (!is_object($something)) {
- $coursestoload[] = $id;
- unset($courseeventsfrom[$id]);
- }
+ if (is_null($user)) {
+ $user = $USER;
}
$courses = array();
- $user = false;
+ $userid = false;
$group = false;
// Get the capabilities that allow seeing group events from all groups.
$allgroupscaps = array('moodle/site:accessallgroups', 'moodle/calendar:manageentries');
- $isloggedin = isloggedin();
+ $isvaliduser = !empty($user->id);
- if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE)) {
+ if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE, $user)) {
$courses = array_keys($courseeventsfrom);
}
- if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) {
+ if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_GLOBAL, $user)) {
$courses[] = SITEID;
}
$courses = array_unique($courses);
$courses[] = SITEID;
}
- if ($ignorefilters || ($isloggedin && calendar_show_event_type(CALENDAR_EVENT_USER))) {
- $user = $USER->id;
+ if ($ignorefilters || ($isvaliduser && calendar_show_event_type(CALENDAR_EVENT_USER, $user))) {
+ $userid = $user->id;
}
- if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP) || $ignorefilters)) {
+ if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP, $user) || $ignorefilters)) {
if (count($courseeventsfrom) == 1) {
$course = reset($courseeventsfrom);
if ($group === false) {
if (!empty($CFG->calendar_adminseesall) && has_any_capability($allgroupscaps, \context_system::instance())) {
$group = true;
- } else if ($isloggedin) {
+ } else if ($isvaliduser) {
$groupids = array();
foreach ($courseeventsfrom as $courseid => $course) {
// If the user is an editing teacher in there.
- if (!empty($USER->groupmember[$course->id])) {
+ if (!empty($user->groupmember[$course->id])) {
// We've already cached the users groups for this course so we can just use that.
- $groupids = array_merge($groupids, $USER->groupmember[$course->id]);
+ $groupids = array_merge($groupids, $user->groupmember[$course->id]);
} else if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) {
// If this course has groups, show events from all of those related to the current user.
- $coursegroups = groups_get_user_groups($course->id, $USER->id);
+ $coursegroups = groups_get_user_groups($course->id, $user->id);
$groupids = array_merge($groupids, $coursegroups['0']);
}
}
$courses = false;
}
- return array($courses, $group, $user);
+ return array($courses, $group, $userid);
}
/**
*
* @param int $courseid (optional) If passed, an additional course can be returned for admins (the current course).
* @param string $fields Comma separated list of course fields to return.
- * @param bool $canmanage If true, this will return the list of courses the current user can create events in, rather
+ * @param bool $canmanage If true, this will return the list of courses the user can create events in, rather
* than the list of courses they see events from (an admin can always add events in a course
* calendar, even if they are not enrolled in the course).
+ * @param int $userid (optional) The user which this function returns the default courses for.
+ * By default the current user.
* @return array $courses Array of courses to display
*/
-function calendar_get_default_courses($courseid = null, $fields = '*', $canmanage=false) {
- global $CFG, $DB;
+function calendar_get_default_courses($courseid = null, $fields = '*', $canmanage = false, int $userid = null) {
+ global $CFG, $USER;
- if (!isloggedin()) {
- return array();
+ if (!$userid) {
+ if (!isloggedin()) {
+ return array();
+ }
+ $userid = $USER->id;
}
- if (has_capability('moodle/calendar:manageentries', context_system::instance()) &&
- (!empty($CFG->calendar_adminseesall) || $canmanage)) {
+ if ((!empty($CFG->calendar_adminseesall) || $canmanage) &&
+ has_capability('moodle/calendar:manageentries', context_system::instance(), $userid)) {
// Add a c. prefix to every field as expected by get_courses function.
$fieldlist = explode(',', $fields);
}, $fieldlist);
$courses = get_courses('all', 'c.shortname', implode(',', $prefixedfields));
} else {
- $courses = enrol_get_my_courses($fields);
+ $courses = enrol_get_users_courses($userid, true, $fields);
}
if ($courseid && $courseid != SITEID) {
- if (empty($courses[$courseid]) && has_capability('moodle/calendar:manageentries', context_system::instance())) {
+ if (empty($courses[$courseid]) && has_capability('moodle/calendar:manageentries', context_system::instance(), $userid)) {
// Allow a site admin to see calendars from courses he is not enrolled in.
// This will come from $COURSE.
$courses[$courseid] = get_course($courseid);
}
}
-/**
- * Get all of the allowed types for all of the courses and groups
- * the logged in user belongs to.
- *
- * The returned array will optionally have 5 keys:
- * 'user' : true if the logged in user can create user events
- * 'site' : true if the logged in user can create site events
- * 'category' : array of course categories that the user can create events for
- * 'course' : array of courses that the user can create events for
- * 'group': array of groups that the user can create events for
- * 'groupcourses' : array of courses that the groups belong to (can
- * be different from the list in 'course'.
- *
- * @return array The array of allowed types.
- */
-function calendar_get_all_allowed_types() {
- global $CFG, $USER, $DB;
-
- require_once($CFG->libdir . '/enrollib.php');
-
- $types = [];
-
- $allowed = new stdClass();
-
- calendar_get_allowed_types($allowed);
-
- if ($allowed->user) {
- $types['user'] = true;
- }
-
- if ($allowed->site) {
- $types['site'] = true;
- }
-
- if (coursecat::has_manage_capability_on_any()) {
- $types['category'] = coursecat::make_categories_list('moodle/category:manage');
- }
-
- // This function warms the context cache for the course so the calls
- // to load the course context in calendar_get_allowed_types don't result
- // in additional DB queries.
- $courses = calendar_get_default_courses(null, 'id, groupmode, groupmodeforce', true);
-
- // We want to pre-fetch all of the groups for each course in a single
- // query to avoid calendar_get_allowed_types from hitting the DB for
- // each separate course.
- $groups = groups_get_all_groups_for_courses($courses);
-
- foreach ($courses as $course) {
- $coursegroups = isset($groups[$course->id]) ? $groups[$course->id] : null;
- calendar_get_allowed_types($allowed, $course, $coursegroups);
-
- if (!empty($allowed->courses)) {
- $types['course'][$course->id] = $course;
- }
-
- if (!empty($allowed->groups)) {
- $types['groupcourses'][$course->id] = $course;
-
- if (!isset($types['group'])) {
- $types['group'] = array_values($allowed->groups);
- } else {
- $types['group'] = array_merge($types['group'], array_values($allowed->groups));
- }
- }
- }
-
- return $types;
-}
-
/**
* See if user can add calendar entries at all used to print the "New Event" button.
*
return $param;
}, [$users, $groups, $courses, $categories]);
+ // If a single user is provided, we can use that for capability checks.
+ // Otherwise current logged in user is used - See MDL-58768.
+ if (is_array($userparam) && count($userparam) == 1) {
+ \core_calendar\local\event\container::set_requesting_user($userparam[0]);
+ }
$mapper = \core_calendar\local\event\container::get_event_mapper();
$events = \core_calendar\local\api::get_events(
$tstart,
*/
function calendar_output_fragment_event_form($args) {
global $CFG, $OUTPUT, $USER;
-
+ require_once($CFG->libdir . '/grouplib.php');
$html = '';
$data = [];
$eventid = isset($args['eventid']) ? clean_param($args['eventid'], PARAM_INT) : null;
$starttime = isset($args['starttime']) ? clean_param($args['starttime'], PARAM_INT) : null;
- $courseid = isset($args['courseid']) ? clean_param($args['courseid'], PARAM_INT) : null;
+ $courseid = (isset($args['courseid']) && $args['courseid'] != SITEID) ? clean_param($args['courseid'], PARAM_INT) : null;
$categoryid = isset($args['categoryid']) ? clean_param($args['categoryid'], PARAM_INT) : null;
$event = null;
$hasformdata = isset($args['formdata']) && !empty($args['formdata']);
$context = \context_user::instance($USER->id);
$editoroptions = \core_calendar\local\event\forms\create::build_editor_options($context);
- $formoptions = ['editoroptions' => $editoroptions];
+ $formoptions = ['editoroptions' => $editoroptions, 'courseid' => $courseid];
$draftitemid = 0;
if ($hasformdata) {
}
if (is_null($eventid)) {
+ if (!empty($courseid)) {
+ $groupcoursedata = groups_get_course_data($courseid);
+ $formoptions['groups'] = [];
+ foreach ($groupcoursedata->groups as $groupid => $groupdata) {
+ $formoptions['groups'][$groupid] = $groupdata->name;
+ }
+ }
$mform = new \core_calendar\local\event\forms\create(
null,
$formoptions,
);
// Let's check first which event types user can add.
- calendar_get_allowed_types($allowed, $courseid);
+ $eventtypes = calendar_get_allowed_event_types($courseid);
// If the user is on course context and is allowed to add course events set the event type default to course.
- if ($courseid != SITEID && !empty($allowed->courses)) {
+ if ($courseid != SITEID && !empty($eventtypes['course'])) {
$data['eventtype'] = 'course';
$data['courseid'] = $courseid;
$data['groupcourseid'] = $courseid;
- } else if (!empty($categoryid) && !empty($allowed->category)) {
+ } else if (!empty($categoryid) && !empty($eventtypes['category'])) {
$data['eventtype'] = 'category';
$data['categoryid'] = $categoryid;
+ } else if (!empty($groupcoursedata) && !empty($eventtypes['group'])) {
+ $data['groupcourseid'] = $courseid;
+ $data['groups'] = $groupcoursedata->groups;
}
$mform->set_data($data);
} else {
$data = array_merge((array) $eventdata, $data);
$event->count_repeats();
$formoptions['event'] = $event;
+
+ if (!empty($event->courseid)) {
+ $groupcoursedata = groups_get_course_data($event->courseid);
+ $formoptions['groups'] = [];
+ foreach ($groupcoursedata->groups as $groupid => $groupdata) {
+ $formoptions['groups'][$groupid] = $groupdata->name;
+ }
+ }
+
$data['description']['text'] = file_prepare_draft_area(
$draftitemid,
$event->context->id,
];
return in_array($type, $validtypes);
}
+
+/**
+ * Get event types the user can create event based on categories, courses and groups
+ * the logged in user belongs to.
+ *
+ * @param int|null $courseid The course id.
+ * @return array The array of allowed types.
+ */
+function calendar_get_allowed_event_types(int $courseid = null) {
+ global $DB, $CFG, $USER;
+
+ $types = [
+ 'user' => false,
+ 'site' => false,
+ 'course' => false,
+ 'group' => false,
+ 'category' => false
+ ];
+
+ if (!empty($courseid) && $courseid != SITEID) {
+ $context = \context_course::instance($courseid);
+ $groups = groups_get_all_groups($courseid);
+
+ $types['user'] = has_capability('moodle/calendar:manageownentries', $context);
+
+ if (has_capability('moodle/calendar:manageentries', $context) || !empty($CFG->calendar_adminseesall)) {
+ $types['course'] = true;
+
+ $types['group'] = (!empty($groups) && has_capability('moodle/site:accessallgroups', $context))
+ || array_filter($groups, function($group) use ($USER) {
+ return groups_is_member($group->id);
+ });
+ } else if (has_capability('moodle/calendar:managegroupentries', $context)) {
+ $types['group'] = (!empty($groups) && has_capability('moodle/site:accessallgroups', $context))
+ || array_filter($groups, function($group) use ($USER) {
+ return groups_is_member($group->id);
+ });
+ }
+ }
+
+ if (has_capability('moodle/calendar:manageentries', \context_course::instance(SITEID))) {
+ $types['site'] = true;
+ }
+
+ if (has_capability('moodle/calendar:manageownentries', \context_system::instance())) {
+ $types['user'] = true;
+ }
+ if (coursecat::has_manage_capability_on_any()) {
+ $types['category'] = true;
+ }
+
+ // We still don't know if the user can create group and course events, so iterate over the courses to find out
+ // if the user has capabilities in one of the courses.
+ if ($types['course'] == false || $types['group'] == false) {
+ if ($CFG->calendar_adminseesall && has_capability('moodle/calendar:manageentries', context_system::instance())) {
+ $sql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . "
+ FROM {course} c
+ JOIN {context} ctx ON ctx.contextlevel = ? AND ctx.instanceid = c.id
+ WHERE c.id IN (
+ SELECT DISTINCT courseid FROM {groups}
+ )";
+ $courseswithgroups = $DB->get_recordset_sql($sql, [CONTEXT_COURSE]);
+ foreach ($courseswithgroups as $course) {
+ context_helper::preload_from_record($course);
+ $context = context_course::instance($course->id);
+
+ if (has_capability('moodle/calendar:manageentries', $context)) {
+ if (has_any_capability(['moodle/site:accessallgroups', 'moodle/calendar:managegroupentries'], $context)) {
+ // The user can manage group entries or access any group.
+ $types['group'] = true;
+ $types['course'] = true;
+ break;
+ }
+ }
+ }
+ $courseswithgroups->close();
+
+ if (false === $types['course']) {
+ // Course is still not confirmed. There may have been no courses with a group in them.
+ $ctxfields = context_helper::get_preload_record_columns_sql('ctx');
+ $sql = "SELECT
+ c.id, c.visible, {$ctxfields}
+ FROM {course}
+ JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
+ $params = [
+ 'contextlevel' => CONTEXT_COURSE,
+ ];
+ $courses = $DB->get_recordset_sql($sql, $params);
+ foreach ($courses as $course) {
+ context_helper::preload_from_record($course);
+ $context = context_course::instance($course->id);
+ if (has_capability('moodle/calendar:manageentries', $context)) {
+ $types['course'] = true;
+ break;
+ }
+ }
+ $courses->close();
+ }
+
+ } else {
+ $courses = calendar_get_default_courses(null, 'id');
+ if (empty($courses)) {
+ return $types;
+ }
+
+ $courseids = array_map(function($c) {
+ return $c->id;
+ }, $courses);
+
+ // Check whether the user has access to create events within courses which have groups.
+ list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+ $sql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . "
+ FROM {course} c
+ JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = c.id
+ WHERE c.id $insql
+ AND c.id IN (SELECT DISTINCT courseid FROM {groups})";
+ $params['contextlevel'] = CONTEXT_COURSE;
+ $courseswithgroups = $DB->get_recordset_sql($sql, $params);
+ foreach ($courseswithgroups as $coursewithgroup) {
+ context_helper::preload_from_record($coursewithgroup);
+ $context = context_course::instance($coursewithgroup->id);
+
+ if (has_capability('moodle/calendar:manageentries', $context)) {
+ // The user has access to manage calendar entries for the whole course.
+ // This includes groups if they have the accessallgroups capability.
+ $types['course'] = true;
+ if (has_capability('moodle/site:accessallgroups', $context)) {
+ // The user also has access to all groups so they can add calendar entries to any group.
+ // The manageentries capability overrides the managegroupentries capability.
+ $types['group'] = true;
+ break;
+ }
+
+ if (empty($types['group']) && has_capability('moodle/calendar:managegroupentries', $context)) {
+ // The user has the managegroupentries capability.
+ // If they have access to _any_ group, then they can create calendar entries within that group.
+ $types['group'] = !empty(groups_get_all_groups($coursewithgroup->id, $USER->id));
+ }
+ }
+
+ // Okay, course and group event types are allowed, no need to keep the loop iteration.
+ if ($types['course'] == true && $types['group'] == true) {
+ break;
+ }
+ }
+ $courseswithgroups->close();
+
+ if (false === $types['course']) {
+ list($insql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
+ $contextsql = "SELECT c.id, " . context_helper::get_preload_record_columns_sql('ctx') . "
+ FROM {course} c
+ JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = c.id
+ WHERE c.id $insql";
+ $params['contextlevel'] = CONTEXT_COURSE;
+ $contextrecords = $DB->get_recordset_sql($contextsql, $params);
+ foreach ($contextrecords as $course) {
+ context_helper::preload_from_record($course);
+ $coursecontext = context_course::instance($course->id);
+ if (has_capability('moodle/calendar:manageentries', $coursecontext)
+ && ($courseid == $course->id || empty($courseid))) {
+ $types['course'] = true;
+ break;
+ }
+ }
+ $contextrecords->close();
+ }
+
+ }
+ }
+
+ return $types;
+}
print_error('errorcannotimport', 'calendar');
}
-$form = new \core_calendar\local\event\forms\managesubscriptions();
+$form = new \core_calendar\local\event\forms\managesubscriptions(null, ['courseid' => $course->id]);
$form->set_data(array(
'course' => $course->id
));
}
}
-$types = calendar_get_all_allowed_types();
+$types = calendar_get_allowed_event_types($courseid);
$searches = [];
$params = [];
$usedefaultfilters = true;
-if (!empty($courseid) && $courseid == SITEID && isset($types['site'])) {
+if (!empty($courseid) && $courseid == SITEID && !empty($types['site'])) {
$searches[] = "(eventtype = 'site')";
$searches[] = "(eventtype = 'user' AND userid = :userid)";
$params['userid'] = $USER->id;
$usedefaultfilters = false;
}
-if (!empty($courseid) && isset($types['course']) && array_key_exists($courseid, $types['course'])) {
+if (!empty($courseid) && !empty($types['course'])) {
$searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid = :courseid)";
$params += ['courseid' => $courseid];
$usedefaultfilters = false;
}
-if (!empty($categoryid) && isset($types['category']) && array_key_exists($categoryid, $types['category'])) {
+if (!empty($categoryid) && !empty($types['category'])) {
$searches[] = "(eventtype = 'category' AND categoryid = :categoryid)";
$params += ['categoryid' => $categoryid];
$usedefaultfilters = false;
$searches[] = "(eventtype = 'user' AND userid = :userid)";
$params['userid'] = $USER->id;
- if (isset($types['site'])) {
+ if (!empty($types['site'])) {
$searches[] = "(eventtype = 'site' AND courseid = :siteid)";
$params += ['siteid' => SITEID];
}
- if (isset($types['course'])) {
- list($courseinsql, $courseparams) = $DB->get_in_or_equal(array_keys($types['course']), SQL_PARAMS_NAMED, 'course');
- $searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid {$courseinsql})";
- $params += $courseparams;
+ if (!empty($types['course'])) {
+ $courses = calendar_get_default_courses(null, 'id', true);
+ if (!empty($courses)) {
+ $courseids = array_map(function ($c) {
+ return $c->id;
+ }, $courses);
+
+ list($courseinsql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'course');
+ $searches[] = "((eventtype = 'course' OR eventtype = 'group') AND courseid {$courseinsql})";
+ $params += $courseparams;
+ }
}
- if (isset($types['category'])) {
- list($categoryinsql, $categoryparams) = $DB->get_in_or_equal(array_keys($types['category']), SQL_PARAMS_NAMED, 'category');
+ if (!empty($types['category'])) {
+ list($categoryinsql, $categoryparams) = $DB->get_in_or_equal(
+ array_keys(\coursecat::make_categories_list('moodle/category:manage')), SQL_PARAMS_NAMED, 'category');
$searches[] = "(eventtype = 'category' AND categoryid {$categoryinsql})";
$params += $categoryparams;
}
And I click on "New event" "button"
When I click on "Save" "button"
Then I should see "Required"
- And I am on site homepage
- And I follow "Calendar"
+ And I am on homepage
+ And I follow "This month"
And I click on "New event" "button"
And I set the field "Type of event" to "Course"
When I click on "Save" "button"
$this->resetAfterTest(true);
$this->setUser($user);
- $this->expectException('moodle_exception');
-
- external_api::clean_returnvalue(
+ $result = external_api::clean_returnvalue(
core_calendar_external::submit_create_update_form_returns(),
core_calendar_external::submit_create_update_form($querystring)
);
+
+ $this->assertTrue($result['validationerror']);
}
/**
'minute' => 0,
],
'eventtype' => 'group',
- 'groupid' => "{$course->id}-{$group->id}", // The form format.
+ 'groupid' => $group->id,
'groupcourseid' => $course->id,
'description' => [
'text' => '',
'minute' => 0,
],
'eventtype' => 'group',
- 'groupid' => "{$course->id}-{$group->id}", // The form format.
+ 'groupid' => $group->id,
'groupcourseid' => $course->id,
'description' => [
'text' => '',
'minute' => 0,
],
'eventtype' => 'group',
- 'groupid' => "{$course->id}-{$group->id}", // The form format.
+ 'groupid' => $group->id,
'groupcourseid' => $course->id,
'description' => [
'text' => '',
'minute' => 0,
],
'eventtype' => 'group',
- 'groupid' => "{$course->id}-{$group->id}", // The form format.
+ 'groupid' => $group->id,
'groupcourseid' => $course->id,
'description' => [
'text' => '',
$this->assertCount(3, $events);
}
- public function test_calendar_get_all_allowed_types_no_types() {
+ public function test_calendar_get_default_courses() {
+ global $USER, $CFG;
+
+ $this->resetAfterTest(true);
+
$generator = $this->getDataGenerator();
$user = $generator->create_user();
- $systemcontext = context_system::instance();
- $sitecontext = context_course::instance(SITEID);
- $roleid = $generator->create_role();
+ $course1 = $generator->create_course();
+ $course2 = $generator->create_course();
+ $course3 = $generator->create_course();
+ $context = context_course::instance($course1->id);
- $generator->role_assign($roleid, $user->id, $systemcontext->id);
- $generator->role_assign($roleid, $user->id, $sitecontext->id);
- $this->setUser($user);
+ $this->setAdminUser();
+ $admin = clone $USER;
- assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $sitecontext, true);
- assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $systemcontext, true);
+ $teacher = $generator->create_user();
+ $generator->enrol_user($teacher->id, $course1->id, 'teacher');
+ $generator->enrol_user($admin->id, $course1->id, 'teacher');
- $types = calendar_get_all_allowed_types();
- $this->assertEmpty($types);
- }
+ $CFG->calendar_adminseesall = false;
- public function test_calendar_get_all_allowed_types_user() {
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $context = context_system::instance();
- $roleid = $generator->create_role();
+ $courses = calendar_get_default_courses();
+ // Only enrolled in one course.
+ $this->assertCount(1, $courses);
+ $courses = calendar_get_default_courses($course2->id);
+ // Enrolled course + current course.
+ $this->assertCount(2, $courses);
+ $CFG->calendar_adminseesall = true;
+ $courses = calendar_get_default_courses();
+ // All courses + SITE.
+ $this->assertCount(4, $courses);
+ $courses = calendar_get_default_courses($course2->id);
+ // All courses + SITE.
+ $this->assertCount(4, $courses);
- $generator->role_assign($roleid, $user->id, $context->id);
- $this->setUser($user);
+ $this->setUser($teacher);
+
+ $CFG->calendar_adminseesall = false;
+
+ $courses = calendar_get_default_courses();
+ // Only enrolled in one course.
+ $this->assertCount(1, $courses);
+ $courses = calendar_get_default_courses($course2->id);
+ // Enrolled course only (ignore current).
+ $this->assertCount(1, $courses);
+ // This setting should not affect teachers.
+ $CFG->calendar_adminseesall = true;
+ $courses = calendar_get_default_courses();
+ // Only enrolled in one course.
+ $this->assertCount(1, $courses);
+ $courses = calendar_get_default_courses($course2->id);
+ // Enrolled course only (ignore current).
+ $this->assertCount(1, $courses);
- assign_capability('moodle/calendar:manageownentries', CAP_ALLOW, $roleid, $context, true);
+ // Now, log out and test again.
+ $this->setUser();
- $types = calendar_get_all_allowed_types();
- $this->assertTrue($types['user']);
+ $CFG->calendar_adminseesall = false;
- assign_capability('moodle/calendar:manageownentries', CAP_PROHIBIT, $roleid, $context, true);
+ $courses = calendar_get_default_courses(null, '*', false, $teacher->id);
+ // Only enrolled in one course.
+ $this->assertCount(1, $courses);
+ $courses = calendar_get_default_courses($course2->id, '*', false, $teacher->id);
+ // Enrolled course only (ignore current).
+ $this->assertCount(1, $courses);
+ // This setting should not affect teachers.
+ $CFG->calendar_adminseesall = true;
+ $courses = calendar_get_default_courses(null, '*', false, $teacher->id);
+ // Only enrolled in one course.
+ $this->assertCount(1, $courses);
+ $courses = calendar_get_default_courses($course2->id, '*', false, $teacher->id);
+ // Enrolled course only (ignore current).
+ $this->assertCount(1, $courses);
- $types = calendar_get_all_allowed_types();
- $this->assertArrayNotHasKey('user', $types);
}
- public function test_calendar_get_all_allowed_types_site() {
+ /**
+ * Confirm that the skip events flag causes the calendar_get_view function
+ * to avoid querying for the calendar events.
+ */
+ public function test_calendar_get_view_skip_events() {
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+
$generator = $this->getDataGenerator();
$user = $generator->create_user();
- $context = context_course::instance(SITEID);
- $roleid = $generator->create_role();
+ $skipnavigation = true;
+ $skipevents = true;
+ $event = create_event([
+ 'eventtype' => 'user',
+ 'userid' => $user->id
+ ]);
- $generator->role_assign($roleid, $user->id, $context->id);
$this->setUser($user);
+ $calendar = \calendar_information::create(time() - 10, SITEID, null);
- assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
-
- $types = calendar_get_all_allowed_types();
- $this->assertTrue($types['site']);
+ list($data, $template) = calendar_get_view($calendar, 'day', $skipnavigation, $skipevents);
+ $this->assertEmpty($data->events);
- assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+ $skipevents = false;
+ list($data, $template) = calendar_get_view($calendar, 'day', $skipnavigation, $skipevents);
- $types = calendar_get_all_allowed_types();
- $this->assertArrayNotHasKey('site', $types);
+ $this->assertEquals($event->id, $data->events[0]->id);
}
- public function test_calendar_get_all_allowed_types_course() {
+ public function test_calendar_get_allowed_event_types_course() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$course1 = $generator->create_course(); // Has capability.
// The user only has the correct capability in course 1 so that is the only
// one that should be in the results.
- $types = calendar_get_all_allowed_types();
- $typecourses = $types['course'];
- $this->assertCount(1, $typecourses);
- $this->assertEquals($course1->id, $typecourses[$course1->id]->id);
+ $types = calendar_get_allowed_event_types($course1->id);
+ $this->assertTrue($types['course']);
- assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context2, true);
+ assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context1, true);
// The user only now has the correct capability in both course 1 and 2 so we
// expect both to be in the results.
- $types = calendar_get_all_allowed_types();
- $typecourses = $types['course'];
- // Sort the results by id ascending to ensure the test is consistent
- // and repeatable.
- usort($typecourses, function($a, $b) {
- $aid = $a->id;
- $bid = $b->id;
-
- if ($aid == $bid) {
- return 0;
- }
- return ($aid < $bid) ? -1 : 1;
- });
-
- $this->assertCount(2, $typecourses);
- $this->assertEquals($course1->id, $typecourses[0]->id);
- $this->assertEquals($course2->id, $typecourses[1]->id);
+ $types = calendar_get_allowed_event_types($course3->id);
+ $this->assertFalse($types['course']);
}
- public function test_calendar_get_all_allowed_types_group_no_groups() {
+ public function test_calendar_get_allowed_event_types_group_no_acces_to_diff_groups() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$course = $generator->create_course();
$this->setUser($user);
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
+ assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
- // The user has the correct capability in the course but there are
- // no groups so we shouldn't see a group type.
- $types = calendar_get_all_allowed_types();
- $typecourses = $types['course'];
- $this->assertCount(1, $typecourses);
- $this->assertEquals($course->id, $typecourses[$course->id]->id);
- $this->assertArrayNotHasKey('group', $types);
- $this->assertArrayNotHasKey('groupcourses', $types);
+ // The user has the correct capability in the course but they aren't a member
+ // of any of the groups and don't have the accessallgroups capability.
+ $types = calendar_get_allowed_event_types($course->id);
+ $this->assertTrue($types['course']);
+ $this->assertFalse($types['group']);
}
- public function test_calendar_get_all_allowed_types_group_no_acces_to_diff_groups() {
+ public function test_calendar_get_allowed_event_types_group_no_groups() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$course = $generator->create_course();
$context = context_course::instance($course->id);
- $group1 = $generator->create_group(array('courseid' => $course->id));
- $group2 = $generator->create_group(array('courseid' => $course->id));
$roleid = $generator->create_role();
-
$generator->enrol_user($user->id, $course->id, 'student');
$generator->role_assign($roleid, $user->id, $context->id);
-
$this->setUser($user);
-
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
- assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
-
- // The user has the correct capability in the course but they aren't a member
- // of any of the groups and don't have the accessallgroups capability.
- $types = calendar_get_all_allowed_types();
- $typecourses = $types['course'];
- $this->assertCount(1, $typecourses);
- $this->assertEquals($course->id, $typecourses[$course->id]->id);
- $this->assertArrayNotHasKey('group', $types);
- $this->assertArrayNotHasKey('groupcourses', $types);
+ // The user has the correct capability in the course but there are
+ // no groups so we shouldn't see a group type.
+ $types = calendar_get_allowed_event_types($course->id);
+ $this->assertTrue($types['course']);
}
- public function test_calendar_get_all_allowed_types_group_access_all_groups() {
+ public function test_calendar_get_allowed_event_types_group_access_all_groups() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$course1 = $generator->create_course();
$course2 = $generator->create_course();
+ $generator->create_group(array('courseid' => $course1->id));
+ $generator->create_group(array('courseid' => $course2->id));
$context1 = context_course::instance($course1->id);
$context2 = context_course::instance($course2->id);
- $group1 = $generator->create_group(array('courseid' => $course1->id));
- $group2 = $generator->create_group(array('courseid' => $course1->id));
$roleid = $generator->create_role();
-
$generator->enrol_user($user->id, $course1->id, 'student');
$generator->enrol_user($user->id, $course2->id, 'student');
$generator->role_assign($roleid, $user->id, $context1->id);
$generator->role_assign($roleid, $user->id, $context2->id);
-
$this->setUser($user);
-
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context1, true);
assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context2, true);
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context1, true);
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context2, true);
-
// The user has the correct capability in the course and has
// the accessallgroups capability.
- $types = calendar_get_all_allowed_types();
- $typecourses = $types['course'];
- $typegroups = $types['group'];
- $typegroupcourses = $types['groupcourses'];
- $idascfunc = function($a, $b) {
- $aid = $a->id;
- $bid = $b->id;
-
- if ($aid == $bid) {
- return 0;
- }
- return ($aid < $bid) ? -1 : 1;
- };
- // Sort the results by id ascending to ensure the test is consistent
- // and repeatable.
- usort($typecourses, $idascfunc);
- usort($typegroups, $idascfunc);
-
- $this->assertCount(2, $typecourses);
- $this->assertEquals($course1->id, $typecourses[0]->id);
- $this->assertEquals($course2->id, $typecourses[1]->id);
- $this->assertCount(1, $typegroupcourses);
- $this->assertEquals($course1->id, $typegroupcourses[$course1->id]->id);
- $this->assertCount(2, $typegroups);
- $this->assertEquals($group1->id, $typegroups[0]->id);
- $this->assertEquals($group2->id, $typegroups[1]->id);
+ $types = calendar_get_allowed_event_types($course1->id);
+ $this->assertTrue($types['group']);
}
-
- public function test_calendar_get_all_allowed_types_group_no_access_all_groups() {
+ public function test_calendar_get_allowed_event_types_group_no_access_all_groups() {
$generator = $this->getDataGenerator();
$user = $generator->create_user();
$course = $generator->create_course();
$context = context_course::instance($course->id);
$group1 = $generator->create_group(array('courseid' => $course->id));
$group2 = $generator->create_group(array('courseid' => $course->id));
- $group3 = $generator->create_group(array('courseid' => $course->id));
$roleid = $generator->create_role();
-
$generator->enrol_user($user->id, $course->id, 'student');
$generator->role_assign($roleid, $user->id, $context->id);
$generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user->id));
$generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user->id));
-
$this->setUser($user);
-
- assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $roleid, $context, true);
-
// The user has the correct capability in the course but can't access
// groups that they are not a member of.
- $types = calendar_get_all_allowed_types();
- $typegroups = $types['group'];
- $typegroupcourses = $types['groupcourses'];
- $idascfunc = function($a, $b) {
- $aid = $a->id;
- $bid = $b->id;
-
- if ($aid == $bid) {
- return 0;
- }
- return ($aid < $bid) ? -1 : 1;
- };
- // Sort the results by id ascending to ensure the test is consistent
- // and repeatable.
- usort($typegroups, $idascfunc);
-
- $this->assertCount(1, $typegroupcourses);
- $this->assertEquals($course->id, $typegroupcourses[$course->id]->id);
- $this->assertCount(2, $typegroups);
- $this->assertEquals($group1->id, $typegroups[0]->id);
- $this->assertEquals($group2->id, $typegroups[1]->id);
+ $types = calendar_get_allowed_event_types($course->id);
+ $this->assertFalse($types['group']);
+ assign_capability('moodle/calendar:manageentries', CAP_ALLOW, $roleid, $context, true);
+ assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $context, true);
+ $types = calendar_get_allowed_event_types($course->id);
+ $this->assertTrue($types['group']);
}
- public function test_calendar_get_default_courses() {
- global $USER, $CFG;
+ /**
+ * This is a setup helper function that create some users, courses, groups and group memberships.
+ * This is useful to prepare the environment for testing the calendar_set_filters function.
+ *
+ * @return array An array of ($users, $courses, $coursegroups)
+ */
+ protected function setup_test_calendar_set_filters() {
+ $generator = $this->getDataGenerator();
- $this->resetAfterTest(true);
+ // Create some users.
+ $users = [];
+ $users[] = $generator->create_user();
+ $users[] = $generator->create_user();
+ $users[] = $generator->create_user();
+
+ // Create some courses.
+ $courses = [];
+ $courses[] = $generator->create_course();
+ $courses[] = $generator->create_course();
+ $courses[] = $generator->create_course();
+ $courses[] = $generator->create_course();
+
+ // Create some groups.
+ $coursegroups = [];
+ $coursegroups[$courses[0]->id] = [];
+ $coursegroups[$courses[0]->id][] = $generator->create_group(['courseid' => $courses[0]->id]);
+ $coursegroups[$courses[0]->id][] = $generator->create_group(['courseid' => $courses[0]->id]);
+ $coursegroups[$courses[2]->id] = [];
+ $coursegroups[$courses[2]->id][] = $generator->create_group(['courseid' => $courses[2]->id]);
+ $coursegroups[$courses[2]->id][] = $generator->create_group(['courseid' => $courses[2]->id]);
+ $coursegroups[$courses[3]->id] = [];
+ $coursegroups[$courses[3]->id][] = $generator->create_group(['courseid' => $courses[3]->id]);
+ $coursegroups[$courses[3]->id][] = $generator->create_group(['courseid' => $courses[3]->id]);
+
+ // Create some enrolments and group memberships.
+ $generator->enrol_user($users[0]->id, $courses[0]->id, 'student');
+ $generator->create_group_member(['groupid' => $coursegroups[$courses[0]->id][0]->id, 'userid' => $users[0]->id]);
+ $generator->enrol_user($users[1]->id, $courses[0]->id, 'student');
+ $generator->create_group_member(['groupid' => $coursegroups[$courses[0]->id][1]->id, 'userid' => $users[1]->id]);
+ $generator->enrol_user($users[0]->id, $courses[1]->id, 'student');
+ $generator->enrol_user($users[0]->id, $courses[2]->id, 'student');
+
+ return array($users, $courses, $coursegroups);
+ }
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $course1 = $generator->create_course();
- $course2 = $generator->create_course();
- $course3 = $generator->create_course();
- $context = context_course::instance($course1->id);
+ /**
+ * This function tests calendar_set_filters for the case when user is not logged in.
+ */
+ public function test_calendar_set_filters_not_logged_in() {
+ $this->resetAfterTest();
- $this->setAdminUser();
- $admin = clone $USER;
+ list($users, $courses, $coursegroups) = $this->setup_test_calendar_set_filters();
- $teacher = $generator->create_user();
- $generator->enrol_user($teacher->id, $course1->id, 'teacher');
- $generator->enrol_user($admin->id, $course1->id, 'teacher');
+ $defaultcourses = calendar_get_default_courses(null, '*', false, $users[0]->id);
+ list($courseids, $groupids, $userid) = calendar_set_filters($defaultcourses);
- $CFG->calendar_adminseesall = false;
+ $this->assertEquals(
+ [$courses[0]->id, $courses[1]->id, $courses[2]->id, SITEID],
+ array_values($courseids),
+ '', 0.0, 10, true);
+ $this->assertFalse($groupids);
+ $this->assertFalse($userid);
+ }
- $courses = calendar_get_default_courses();
- // Only enrolled in one course.
- $this->assertCount(1, $courses);
- $courses = calendar_get_default_courses($course2->id);
- // Enrolled course + current course.
- $this->assertCount(2, $courses);
- $CFG->calendar_adminseesall = true;
- $courses = calendar_get_default_courses();
- // All courses + SITE.
- $this->assertCount(4, $courses);
- $courses = calendar_get_default_courses($course2->id);
- // All courses + SITE.
- $this->assertCount(4, $courses);
+ /**
+ * This function tests calendar_set_filters for the case when no one is logged in, but a user id is provided.
+ */
+ public function test_calendar_set_filters_not_logged_in_with_user() {
+ $this->resetAfterTest();
- $this->setUser($teacher);
+ list($users, $courses, $coursegroups) = $this->setup_test_calendar_set_filters();
- $CFG->calendar_adminseesall = false;
+ $defaultcourses = calendar_get_default_courses(null, '*', false, $users[1]->id);
+ list($courseids, $groupids, $userid) = calendar_set_filters($defaultcourses, false, $users[1]);
- $courses = calendar_get_default_courses();
- // Only enrolled in one course.
- $this->assertCount(1, $courses);
- $courses = calendar_get_default_courses($course2->id);
- // Enrolled course only (ignore current).
- $this->assertCount(1, $courses);
- // This setting should not affect teachers.
- $CFG->calendar_adminseesall = true;
- $courses = calendar_get_default_courses();
- // Only enrolled in one course.
- $this->assertCount(1, $courses);
- $courses = calendar_get_default_courses($course2->id);
- // Enrolled course only (ignore current).
- $this->assertCount(1, $courses);
+ $this->assertEquals(array($courses[0]->id, SITEID), array_values($courseids));
+ $this->assertEquals(array($coursegroups[$courses[0]->id][1]->id), $groupids);
+ $this->assertEquals($users[1]->id, $userid);
+
+ $defaultcourses = calendar_get_default_courses(null, '*', false, $users[0]->id);
+ list($courseids, $groupids, $userid) = calendar_set_filters($defaultcourses, false, $users[0]);
+
+ $this->assertEquals(
+ [$courses[0]->id, $courses[1]->id, $courses[2]->id, SITEID],
+ array_values($courseids),
+ '', 0.0, 10, true);
+ $this->assertEquals(array($coursegroups[$courses[0]->id][0]->id), $groupids);
+ $this->assertEquals($users[0]->id, $userid);
}
/**
- * Confirm that the skip events flag causes the calendar_get_view function
- * to avoid querying for the calendar events.
+ * This function tests calendar_set_filters for the case when user is logged in, but no user id is provided.
*/
- public function test_calendar_get_view_skip_events() {
- $this->resetAfterTest(true);
- $this->setAdminUser();
+ public function test_calendar_set_filters_logged_in_no_user() {
+ $this->resetAfterTest();
- $generator = $this->getDataGenerator();
- $user = $generator->create_user();
- $skipnavigation = true;
- $skipevents = true;
- $event = create_event([
- 'eventtype' => 'user',
- 'userid' => $user->id
- ]);
+ list($users, $courses, $coursegroups) = $this->setup_test_calendar_set_filters();
- $this->setUser($user);
- $calendar = \calendar_information::create(time() - 10, SITEID, null);
+ $this->setUser($users[0]);
+ $defaultcourses = calendar_get_default_courses(null, '*', false, $users[0]->id);
+ list($courseids, $groupids, $userid) = calendar_set_filters($defaultcourses, false);
+ $this->assertEquals([$courses[0]->id, $courses[1]->id, $courses[2]->id, SITEID], array_values($courseids), '', 0.0, 10,
+ true);
+ $this->assertEquals(array($coursegroups[$courses[0]->id][0]->id), $groupids);
+ $this->assertEquals($users[0]->id, $userid);
+ }
- list($data, $template) = calendar_get_view($calendar, 'day', $skipnavigation, $skipevents);
- $this->assertEmpty($data->events);
+ /**
+ * This function tests calendar_set_filters for the case when a user is logged in, but another user id is provided.
+ */
+ public function test_calendar_set_filters_logged_in_another_user() {
+ $this->resetAfterTest();
- $skipevents = false;
- list($data, $template) = calendar_get_view($calendar, 'day', $skipnavigation, $skipevents);
+ list($users, $courses, $coursegroups) = $this->setup_test_calendar_set_filters();
- $this->assertEquals($event->id, $data->events[0]->id);
+ $this->setUser($users[0]);
+ $defaultcourses = calendar_get_default_courses(null, '*', false, $users[1]->id);
+ list($courseids, $groupids, $userid) = calendar_set_filters($defaultcourses, false, $users[1]);
+
+ $this->assertEquals(array($courses[0]->id, SITEID), array_values($courseids));
+ $this->assertEquals(array($coursegroups[$courses[0]->id][1]->id), $groupids);
+ $this->assertEquals($users[1]->id, $userid);
}
}
$this->assertCount(2, $events);
// Disable the lesson module.
- $modulerecord = $DB->get_record('modules', ['name' => 'lesson']);
- $modulerecord->visible = 0;
- $DB->update_record('modules', $modulerecord);
+ $DB->set_field('modules', 'visible', 0, ['name' => 'lesson']);
// Check that we only return the assign event.
$events = $retrievalstrategy->get_raw_events(null, [0], null);
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals('assign', $event->modulename);
+
+ // Now, log out and repeat the above test in the reverse order.
+ $this->setUser();
+
+ // Check that we only return the assign event (given that the lesson module is still disabled).
+ $events = $retrievalstrategy->get_raw_events([$student->id], [0], null);
+ $this->assertCount(1, $events);
+ $event = reset($events);
+ $this->assertEquals('assign', $event->modulename);
+
+ // Enable the lesson module.
+ $DB->set_field('modules', 'visible', 1, ['name' => 'lesson']);
+
+ // Get all events.
+ $events = $retrievalstrategy->get_raw_events(null, [0], null);
+ $this->assertCount(2, $events);
}
/**
calendar_event::create($event, false);
}
- $timestart = $now - 100;
- $timeend = $now + (3 * 86400);
$groups = [$group1->id, $group2->id];
- // Get user override events.
- $this->setUser($useroverridestudent);
- $events = $retrievalstrategy->get_raw_events([$useroverridestudent->id], $groups, [$course->id]);
- $this->assertCount(1, $events);
- $event = reset($events);
- $this->assertEquals('Assignment 1 due date - User override', $event->name);
-
- // Get events for user that does not belong to any group and has no user override events.
- $this->setUser($nogroupstudent);
- $events = $retrievalstrategy->get_raw_events([$nogroupstudent->id], $groups, [$course->id]);
- $this->assertCount(1, $events);
- $event = reset($events);
- $this->assertEquals('Assignment 1 due date', $event->name);
-
- // Get events for user that belongs to groups A and B and has no user override events.
- $this->setUser($group12student);
- $events = $retrievalstrategy->get_raw_events([$group12student->id], $groups, [$course->id]);
- $this->assertCount(1, $events);
- $event = reset($events);
- $this->assertEquals('Assignment 1 due date - Group A override', $event->name);
-
- // Get events for user that belongs to group A and has no user override events.
- $this->setUser($group1student);
- $events = $retrievalstrategy->get_raw_events([$group1student->id], $groups, [$course->id]);
- $this->assertCount(1, $events);
- $event = reset($events);
- $this->assertEquals('Assignment 1 due date - Group A override', $event->name);
+ // Do the following tests multiple times when logged in with different users. Also run the whole set when logged out.
+ // In any cases, the tests should not depend on the logged-in user.
+ foreach ([$useroverridestudent, $nogroupstudent, $group12student, $group1student, null] as $login) {
+ $this->setUser($login);
+
+ // Get user override events.
+ $events = $retrievalstrategy->get_raw_events([$useroverridestudent->id], $groups, [$course->id]);
+ $this->assertCount(1, $events);
+ $event = reset($events);
+ $this->assertEquals('Assignment 1 due date - User override', $event->name);
+
+ // Get events for user that does not belong to any group and has no user override events.
+ $events = $retrievalstrategy->get_raw_events([$nogroupstudent->id], $groups, [$course->id]);
+ $this->assertCount(1, $events);
+ $event = reset($events);
+ $this->assertEquals('Assignment 1 due date', $event->name);
+
+ // Get events for user that belongs to groups A and B and has no user override events.
+ $events = $retrievalstrategy->get_raw_events([$group12student->id], $groups, [$course->id]);
+ $this->assertCount(1, $events);
+ $event = reset($events);
+ $this->assertEquals('Assignment 1 due date - Group A override', $event->name);
+
+ // Get events for user that belongs to group A and has no user override events.
+ $events = $retrievalstrategy->get_raw_events([$group1student->id], $groups, [$course->id]);
+ $this->assertCount(1, $events);
+ $event = reset($events);
+ $this->assertEquals('Assignment 1 due date - Group A override', $event->name);
+ }
// Add repeating events.
$repeatingevents = [
* Test retrieval strategy with category specifications.
*/
public function test_get_raw_events_category() {
- global $DB;
-
$this->resetAfterTest();
$retrievalstrategy = new raw_event_retrieval_strategy();
$generator = $this->getDataGenerator();
$events = $retrievalstrategy->get_raw_events(null, null, null, [$category1->id, $category2->id]);
$this->assertCount(2, $events);
}
+
+ public function test_get_raw_events_for_multiple_users() {
+ $this->resetAfterTest();
+
+ $generator = $this->getDataGenerator();
+
+ // Create users.
+ $user1 = $generator->create_user();
+ $user2 = $generator->create_user();
+ $user3 = $generator->create_user();
+
+ // Create user events.
+ $events = [
+ [
+ 'name' => 'User1 Event',
+ 'eventtype' => 'user',
+ 'userid' => $user1->id,
+ 'timestart' => time(),
+ ], [
+ 'name' => 'User2 Event',
+ 'eventtype' => 'user',
+ 'userid' => $user2->id,
+ 'timestart' => time(),
+ ], [
+ 'name' => 'User3 Event',
+ 'eventtype' => 'user',
+ 'userid' => $user3->id,
+ 'timestart' => time(),
+ ]
+ ];
+ foreach ($events as $event) {
+ calendar_event::create($event, false);
+ }
+
+ $retrievalstrategy = new raw_event_retrieval_strategy();
+
+ // Get all events.
+ $events = $retrievalstrategy->get_raw_events([$user1->id, $user2->id]);
+ $this->assertCount(2, $events);
+ $this->assertEquals(
+ ['User1 Event', 'User2 Event'],
+ array_column($events, 'name'),
+ '', 0.0, 10, true);
+ }
+
+ public function test_get_raw_events_for_groups_with_no_members() {
+ $this->resetAfterTest();
+
+ $generator = $this->getDataGenerator();
+
+ $course = $generator->create_course();
+
+ // Create groups.
+ $group1 = $generator->create_group(['courseid' => $course->id, 'name' => 'Group 1']);
+ $group2 = $generator->create_group(['courseid' => $course->id, 'name' => 'Group 2']);
+
+ // Create group events.
+ $events = [
+ [
+ 'name' => 'Group 1 Event',
+ 'eventtype' => 'group',
+ 'groupid' => $group1->id,
+ 'timestart' => time(),
+ ], [
+ 'name' => 'Group 2 Event',
+ 'eventtype' => 'group',
+ 'groupid' => $group2->id,
+ 'timestart' => time(),
+ ]
+ ];
+ foreach ($events as $event) {
+ calendar_event::create($event, false);
+ }
+
+ $retrievalstrategy = new raw_event_retrieval_strategy;
+
+ // Get group eventsl.
+ $events = $retrievalstrategy->get_raw_events(null, [$group1->id, $group2->id]);
+ $this->assertCount(2, $events);
+ $this->assertEquals(
+ ['Group 1 Event', 'Group 2 Event'],
+ array_column($events, 'name'),
+ '', 0.0, 10, true);
+ }
}
This files describes API changes in /calendar/* ,
information provided here is intended especially for developers.
+=== 3.6 ===
+* calendar_get_default_courses() function now has optional $userid parameter.
+* calendar_set_filters() function now has optional $user parameter.
+* The core_calendar\local\event\container class now provides two new helper methods for getting and setting the requesting user:
+ set_requesting_user() and get_requesting_user().
+
=== 3.5 ===
* core_calendar_external::get_calendar_events now returns the categoryid for category events.
$textareaattrs = array(
'name' => 'content',
'rows' => 2,
- 'id' => 'dlg-content-'.$this->cid
+ 'id' => 'dlg-content-'.$this->cid,
+ 'aria-label' => get_string('addcomment')
);
if (!$this->fullwidth) {
$textareaattrs['cols'] = '20';
// Thats all folks.
// Don't ever even consider putting anything after this. It just wouldn't make sense.
// But you already knew that, you smart developer you.
-exit;
\ No newline at end of file
+exit;
'i/empty',
'',
'moodle',
- array('class' => 'tree-icon', 'title' => get_string('showcategory', 'moodle', $text))
- );
+ array('class' => 'tree-icon'));
$icon = html_writer::span($icon, 'float-left');
}
$actions = \core_course\management\helper::get_category_listitem_actions($category);
$html .= html_writer::end_div();
if ($isexpanded) {
$html .= html_writer::start_tag('ul',
- array('class' => 'ml-1', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
+ array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
$catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
$catatlevel[] = array_shift($selectedcategories);
$catatlevel = array_unique($catatlevel);
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
+define(['jquery', 'core/ajax', 'core/templates', 'core/str'], function($, Ajax, Templates, Str) {
+
+ /** @var {Number} Maximum number of users to show. */
+ var MAXUSERS = 100;
return /** @alias module:enrol_manual/form-potential-user-selector */ {
processResults: function(selector, results) {
var users = [];
- $.each(results, function(index, user) {
- users.push({
- value: user.id,
- label: user._label
+ if ($.isArray(results)) {
+ $.each(results, function(index, user) {
+ users.push({
+ value: user.id,
+ label: user._label
+ });
});
- });
- return users;
+ return users;
+
+ } else {
+ return results;
+ }
},
transport: function(selector, query, success, failure) {
search: query,
searchanywhere: true,
page: 0,
- perpage: 30
+ perpage: MAXUSERS + 1
}
}]);
var promises = [],
i = 0;
- // Render the label.
- $.each(results, function(index, user) {
- var ctx = user,
- identity = [];
- $.each(['idnumber', 'email', 'phone1', 'phone2', 'department', 'institution'], function(i, k) {
- if (typeof user[k] !== 'undefined' && user[k] !== '') {
- ctx.hasidentity = true;
- identity.push(user[k]);
- }
+ if (results.length <= MAXUSERS) {
+ // Render the label.
+ $.each(results, function(index, user) {
+ var ctx = user,
+ identity = [];
+ $.each(['idnumber', 'email', 'phone1', 'phone2', 'department', 'institution'], function(i, k) {
+ if (typeof user[k] !== 'undefined' && user[k] !== '') {
+ ctx.hasidentity = true;
+ identity.push(user[k]);
+ }
+ });
+ ctx.identity = identity.join(', ');
+ promises.push(Templates.render('enrol_manual/form-user-selector-suggestion', ctx));
});
- ctx.identity = identity.join(', ');
- promises.push(Templates.render('enrol_manual/form-user-selector-suggestion', ctx));
- });
- // Apply the label to the results.
- return $.when.apply($.when, promises).then(function() {
- var args = arguments;
- $.each(results, function(index, user) {
- user._label = args[i];
- i++;
+ // Apply the label to the results.
+ return $.when.apply($.when, promises).then(function() {
+ var args = arguments;
+ $.each(results, function(index, user) {
+ user._label = args[i];
+ i++;
+ });
+ success(results);
+ return;
});
- success(results);
- return;
- });
+
+ } else {
+ return Str.get_string('toomanyuserstoshow', 'core', '>' + MAXUSERS).then(function(toomanyuserstoshow) {
+ success(toomanyuserstoshow);
+ return;
+ });
+ }
}).fail(failure);
}
--- /dev/null
+@enrol @enrol_manual
+Feature: Teacher can search and enrol users one by one into the course
+ In order to quickly enrol particular students into my course
+ As a teacher
+ I can search for the students and enrol them into the course
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher001 | Teacher | 001 | teacher001@example.com |
+ | student001 | Student | 001 | student001@example.com |
+ | student002 | Student | 002 | student002@example.com |
+ | student003 | Student | 003 | student003@example.com |
+ | student004 | Student | 004 | student004@example.com |
+ | student005 | Student | 005 | student005@example.com |
+ | student006 | Student | 006 | student006@example.com |
+ | student007 | Student | 007 | student007@example.com |
+ | student008 | Student | 008 | student008@example.com |
+ | student009 | Student | 009 | student009@example.com |
+ | student010 | Student | 010 | student010@example.com |
+ | student011 | Student | 011 | student011@example.com |
+ | student012 | Student | 012 | student012@example.com |
+ | student013 | Student | 013 | student013@example.com |
+ | student014 | Student | 014 | student014@example.com |
+ | student015 | Student | 015 | student015@example.com |
+ | student016 | Student | 016 | student016@example.com |
+ | student017 | Student | 017 | student017@example.com |
+ | student018 | Student | 018 | student018@example.com |
+ | student019 | Student | 019 | student019@example.com |
+ | student020 | Student | 020 | student020@example.com |
+ | student021 | Student | 021 | student021@example.com |
+ | student022 | Student | 022 | student022@example.com |
+ | student023 | Student | 023 | student023@example.com |
+ | student024 | Student | 024 | student024@example.com |
+ | student025 | Student | 025 | student025@example.com |
+ | student026 | Student | 026 | student026@example.com |
+ | student027 | Student | 027 | student027@example.com |
+ | student028 | Student | 028 | student028@example.com |
+ | student029 | Student | 029 | student029@example.com |
+ | student030 | Student | 030 | student030@example.com |
+ | student031 | Student | 031 | student031@example.com |
+ | student032 | Student | 032 | student032@example.com |
+ | student033 | Student | 033 | student033@example.com |
+ | student034 | Student | 034 | student034@example.com |
+ | student035 | Student | 035 | student035@example.com |
+ | student036 | Student | 036 | student036@example.com |
+ | student037 | Student | 037 | student037@example.com |
+ | student038 | Student | 038 | student038@example.com |
+ | student039 | Student | 039 | student039@example.com |
+ | student040 | Student | 040 | student040@example.com |
+ | student041 | Student | 041 | student041@example.com |
+ | student042 | Student | 042 | student042@example.com |
+ | student043 | Student | 043 | student043@example.com |
+ | student044 | Student | 044 | student044@example.com |
+ | student045 | Student | 045 | student045@example.com |
+ | student046 | Student | 046 | student046@example.com |
+ | student047 | Student | 047 | student047@example.com |
+ | student048 | Student | 048 | student048@example.com |
+ | student049 | Student | 049 | student049@example.com |
+ | student050 | Student | 050 | student050@example.com |
+ | student051 | Student | 051 | student051@example.com |
+ | student052 | Student | 052 | student052@example.com |
+ | student053 | Student | 053 | student053@example.com |
+ | student054 | Student | 054 | student054@example.com |
+ | student055 | Student | 055 | student055@example.com |
+ | student056 | Student | 056 | student056@example.com |
+ | student057 | Student | 057 | student057@example.com |
+ | student058 | Student | 058 | student058@example.com |
+ | student059 | Student | 059 | student059@example.com |
+ | student060 | Student | 060 | student060@example.com |
+ | student061 | Student | 061 | student061@example.com |
+ | student062 | Student | 062 | student062@example.com |
+ | student063 | Student | 063 | student063@example.com |
+ | student064 | Student | 064 | student064@example.com |
+ | student065 | Student | 065 | student065@example.com |
+ | student066 | Student | 066 | student066@example.com |
+ | student067 | Student | 067 | student067@example.com |
+ | student068 | Student | 068 | student068@example.com |
+ | student069 | Student | 069 | student069@example.com |
+ | student070 | Student | 070 | student070@example.com |
+ | student071 | Student | 071 | student071@example.com |
+ | student072 | Student | 072 | student072@example.com |
+ | student073 | Student | 073 | student073@example.com |
+ | student074 | Student | 074 | student074@example.com |
+ | student075 | Student | 075 | student075@example.com |
+ | student076 | Student | 076 | student076@example.com |
+ | student077 | Student | 077 | student077@example.com |
+ | student078 | Student | 078 | student078@example.com |
+ | student079 | Student | 079 | student079@example.com |
+ | student080 | Student | 080 | student080@example.com |
+ | student081 | Student | 081 | student081@example.com |
+ | student082 | Student | 082 | student082@example.com |
+ | student083 | Student | 083 | student083@example.com |
+ | student084 | Student | 084 | student084@example.com |
+ | student085 | Student | 085 | student085@example.com |
+ | student086 | Student | 086 | student086@example.com |
+ | student087 | Student | 087 | student087@example.com |
+ | student088 | Student | 088 | student088@example.com |
+ | student089 | Student | 089 | student089@example.com |
+ | student090 | Student | 090 | student090@example.com |
+ | student091 | Student | 091 | student091@example.com |
+ | student092 | Student | 092 | student092@example.com |
+ | student093 | Student | 093 | student093@example.com |
+ | student094 | Student | 094 | student094@example.com |
+ | student095 | Student | 095 | student095@example.com |
+ | student096 | Student | 096 | student096@example.com |
+ | student097 | Student | 097 | student097@example.com |
+ | student098 | Student | 098 | student098@example.com |
+ | student099 | Student | 099 | student099@example.com |
+ And the following "courses" exist:
+ | fullname | shortname |
+ | Course 001 | C001 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher001 | C001 | editingteacher |
+ And I log in as "teacher001"
+ And I am on "Course 001" course homepage
+
+ @javascript
+ Scenario: Teacher can search and enrol one particular student
+ Given I navigate to course participants
+ And I press "Enrol users"
+ When I set the field "Select users" to "student001"
+ And I should see "Student 001"
+ And I click on "Enrol users" "button" in the "Enrol users" "dialogue"
+ Then I should see "Active" in the "Student 001" "table_row"
+
+ @javascript
+ Scenario: Searching for a non-existing user
+ Given I navigate to course participants
+ And I press "Enrol users"
+ And I set the field "Select users" to "qwertyuiop"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
+ Then I should see "No suggestions"
+
+ @javascript
+ Scenario: If there are less than 100 matching users, all are displayed for selection
+ Given I navigate to course participants
+ And I press "Enrol users"
+ When I set the field "Select users" to "example.com"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
+ And I click on "Student 099" item in the autocomplete list
+ Then I should see "Student 099"
+
+ @javascript
+ Scenario: If there are more than 100 matching users, inform there are too many.
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student100 | Student | 100 | student100@example.com |
+ | student101 | Student | 101 | student101@example.com |
+ And I navigate to course participants
+ And I press "Enrol users"
+ When I set the field "Select users" to "example.com"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
+ Then I should see "Too many users (>100) to show"
// @codingStandardsIgnoreLine This script does not require login.
require("../../config.php");
require_once("lib.php");
-require_once($CFG->libdir.'/eventslib.php');
require_once($CFG->libdir.'/enrollib.php');
require_once($CFG->libdir . '/filelib.php');
This is a notification that your enrolment in the course \'{$a->course}\' is due to expire on {$a->timeend}.
If you need help, please contact {$a->enroller}.';
+$string['expirynotifyall'] = 'Teacher and enrolled user';
+$string['expirynotifyenroller'] = 'Teacher only';
$string['groupkey'] = 'Use group enrolment keys';
$string['groupkey_desc'] = 'Use group enrolment keys by default.';
$string['groupkey_help'] = 'In addition to restricting access to the course to only those who know the key, use of group enrolment keys means users are automatically added to groups when they enrol in the course.
*/
protected function get_expirynotify_options() {
$options = array(0 => get_string('no'),
- 1 => get_string('expirynotifyenroller', 'core_enrol'),
- 2 => get_string('expirynotifyall', 'core_enrol'));
+ 1 => get_string('expirynotifyenroller', 'enrol_self'),
+ 2 => get_string('expirynotifyall', 'enrol_self'));
return $options;
}
$settings->add(new admin_setting_configduration('enrol_self/enrolperiod',
get_string('enrolperiod', 'enrol_self'), get_string('enrolperiod_desc', 'enrol_self'), 0));
- $options = array(0 => get_string('no'), 1 => get_string('expirynotifyenroller', 'core_enrol'), 2 => get_string('expirynotifyall', 'core_enrol'));
+ $options = array(0 => get_string('no'),
+ 1 => get_string('expirynotifyenroller', 'enrol_self'),
+ 2 => get_string('expirynotifyall', 'enrol_self'));
$settings->add(new admin_setting_configselect('enrol_self/expirynotify',
get_string('expirynotify', 'core_enrol'), get_string('expirynotify_help', 'core_enrol'), 0, $options));
<?php
require('../config.php');
- require_once($CFG->libdir.'/eventslib.php');
// Form submitted, do not check referer (original page unknown).
if ($form = data_submitted()) {
if (!$admin and empty($data->override)) {
$records = $DB->get_records('grade_letters', array('contextid' => $context->id));
foreach ($records as $record) {
- $DB->delete_record('grade_letters', array('id' => $record->id));
+ $DB->delete_records('grade_letters', array('id' => $record->id));
// Trigger the letter grade deleted event.
$event = \core\event\grade_letter_deleted::create(array(
'objectid' => $record->id,
$string['cachesessionhelp'] = 'User specific cache that expires when the user\'s session ends. Designed to alleviate session bloat/strain.';
$string['cacheapplication'] = 'Application cache';
$string['cacheapplicationhelp'] = 'Cached items are shared among all users and expire by a determined time to live (ttl).';
-// Deprecated since Moodle 3.2.
-$string['mobile'] = 'Mobile';
+
// Deprecated since Moodle 3.3.
$string['loginpasswordautocomplete'] = 'Prevent password autocompletion on login form';
$string['loginpasswordautocomplete_help'] = 'If enabled, users are not allowed to save their account password in their browser.';
$string['yesterday'] = 'Yesterday';
$string['youcandeleteallrepeats'] = 'This event is part of a repeating event series. You can delete this event only, or all {$a} events in the series at once.';
-// Deprecated since Moodle 3.2.
-$string['for'] = 'for';
-
// Deprecated since Moodle 3.4.
$string['quickdownloadcalendar'] = 'Quick download / subscribe to calendar';
$string['ical'] = 'iCal';
$string['usercompetencystatus_inreview'] = 'In review';
$string['usercompetencystatus_waitingforreview'] = 'Waiting for review';
$string['userplans'] = 'Learning plans';
-
-// Deprecated since Moodle 3.2.
-$string['invalidpersistent'] = 'Invalid persistent';
mypreferences,core_grades
myprofile,core
viewallmyentries,core_blog
-modchooserenable,core
-modchooserdisable,core
-invalidpersistent,core_competency
-revealpassword,core_form
-mediasettings,core_media
-legacyheading,core_media
-legacyheading_desc,core_media
-mobile,core_admin
-for,core_calendar
-context,core_message
-discussion,core_message
-emptysearchstring,core_message
-formorethan,core_message
-keywords,core_message
-messagehistory,core_message
-newsearch,core_message
-nosearchresults,core_message
-onlymycourses,core_message
-pagerefreshes,core_message
-page-message-x,core_message
-recent,core_message
-savemysettings,core_message
-search,core_message
-settingssaved,core_message
-strftimedaydatetime,core_message
-timenosee,core_message
-timesent,core_message
-userssearchresults,core_message
loginpasswordautocomplete,core_admin
loginpasswordautocomplete_help,core_admin
deletecomment,core