Merge branch 'MDL-50325_inconsistent_check_missing_module' of http://github.com/enova...
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Aug 2018 22:06:09 +0000 (00:06 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Aug 2018 22:06:09 +0000 (00:06 +0200)
290 files changed:
Gruntfile.js
admin/registration/index.php
admin/tool/assignmentupgrade/batchupgrade.php [deleted file]
admin/tool/assignmentupgrade/classes/privacy/provider.php [deleted file]
admin/tool/assignmentupgrade/index.php [deleted file]
admin/tool/assignmentupgrade/lang/en/tool_assignmentupgrade.php [deleted file]
admin/tool/assignmentupgrade/listnotupgraded.php [deleted file]
admin/tool/assignmentupgrade/locallib.php [deleted file]
admin/tool/assignmentupgrade/module.js [deleted file]
admin/tool/assignmentupgrade/paginationform.php [deleted file]
admin/tool/assignmentupgrade/renderer.php [deleted file]
admin/tool/assignmentupgrade/settings.php [deleted file]
admin/tool/assignmentupgrade/styles.css [deleted file]
admin/tool/assignmentupgrade/tests/privacy_test.php [deleted file]
admin/tool/assignmentupgrade/upgradableassignmentsbatchform.php [deleted file]
admin/tool/assignmentupgrade/upgradableassignmentstable.php [deleted file]
admin/tool/assignmentupgrade/upgradesingle.php [deleted file]
admin/tool/assignmentupgrade/upgradesingleconfirm.php [deleted file]
admin/tool/behat/tests/manager_test.php [deleted file]
admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/data_registry.php
admin/tool/dataprivacy/classes/output/data_requests_table.php
admin/tool/dataprivacy/classes/output/my_data_requests_page.php
admin/tool/dataprivacy/classes/task/process_data_request_task.php
admin/tool/dataprivacy/db/access.php
admin/tool/dataprivacy/db/upgrade.php [new file with mode: 0644]
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/lib.php
admin/tool/dataprivacy/tests/api_test.php
admin/tool/dataprivacy/tests/behat/dataexport.feature [new file with mode: 0644]
admin/tool/dataprivacy/version.php
admin/tool/log/store/legacy/classes/log/store.php
admin/tool/log/upgrade.txt [new file with mode: 0644]
admin/tool/policy/classes/output/page_viewalldoc.php
admin/tool/policy/lang/en/tool_policy.php
admin/tool/policy/templates/page_viewalldoc.mustache
admin/tool/policy/tests/behat/consent.feature
admin/tool/upgrade.txt
auth/shibboleth/auth.php
auth/shibboleth/classes/helper.php [new file with mode: 0644]
auth/shibboleth/index_form.html
auth/shibboleth/lang/en/auth_shibboleth.php
auth/shibboleth/lib.php [new file with mode: 0644]
auth/shibboleth/login.php
auth/shibboleth/logout.php
auth/shibboleth/settings.php
auth/shibboleth/upgrade.txt
auth/tests/behat/displayloginfailures.feature
auth/tests/behat/validateagedigitalconsentmap.feature
backup/moodle2/tests/behat/import_multiple_times.feature
backup/util/dbops/restore_dbops.class.php
blocks/myprofile/lang/en/block_myprofile.php
blocks/myprofile/lang/en/deprecated.txt
blocks/recent_activity/block_recent_activity.php
blocks/recent_activity/classes/task/cleanup.php [new file with mode: 0644]
blocks/recent_activity/db/tasks.php [moved from mod/quiz/db/renamedclasses.php with 60% similarity]
blocks/recent_activity/lang/en/block_recent_activity.php
blocks/recent_activity/version.php
blocks/rss_client/block_rss_client.php
blocks/rss_client/classes/task/refreshfeeds.php [new file with mode: 0644]
blocks/rss_client/db/tasks.php [moved from admin/tool/assignmentupgrade/version.php with 59% similarity]
blocks/rss_client/lang/en/block_rss_client.php
blocks/rss_client/tests/cron_test.php
blocks/rss_client/version.php
calendar/amd/build/event_form.min.js
calendar/amd/build/repository.min.js
calendar/amd/src/event_form.js
calendar/amd/src/repository.js
calendar/classes/local/event/container.php
calendar/classes/local/event/forms/create.php
calendar/classes/local/event/forms/eventtype.php
calendar/classes/local/event/forms/managesubscriptions.php
calendar/classes/local/event/mappers/create_update_form_mapper.php
calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php
calendar/externallib.php
calendar/lib.php
calendar/managesubscriptions.php
calendar/tests/behat/calendar.feature
calendar/tests/externallib_test.php
calendar/tests/lib_test.php
calendar/tests/raw_event_retrieval_strategy_test.php
calendar/upgrade.txt
comment/lib.php
competency/tests/privacy_test.php
course/ajax/management.php
course/classes/management_renderer.php
course/externallib.php
course/format/singleactivity/lib.php
course/format/upgrade.txt
course/lib.php
course/upgrade.txt
enrol/manual/amd/build/form-potential-user-selector.min.js
enrol/manual/amd/src/form-potential-user-selector.js
enrol/manual/tests/behat/quickenrolment.feature [new file with mode: 0644]
enrol/paypal/ipn.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/settings.php
error/index.php
grade/edit/letter/index.php
group/assign.php
install/lang/el/error.php
install/lang/el/install.php
install/lang/hu/install.php
install/lang/ja/install.php
lang/en/admin.php
lang/en/calendar.php
lang/en/competency.php
lang/en/deprecated.txt
lang/en/form.php
lang/en/hub.php
lang/en/media.php
lang/en/message.php
lang/en/moodle.php
lang/en/webservice.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/ajax.min.js
lib/amd/build/form-autocomplete.min.js
lib/amd/src/ajax.js
lib/amd/src/form-autocomplete.js
lib/behat/classes/behat_config_manager.php
lib/classes/event/base.php
lib/classes/event/message_sent.php
lib/classes/hub/api.php
lib/classes/hub/registration.php
lib/classes/hub/site_unregistration_form.php
lib/classes/message/manager.php
lib/classes/plugin_manager.php
lib/classes/session/manager.php
lib/classes/task/events_cron_task.php [deleted file]
lib/coursecatlib.php
lib/db/caches.php
lib/db/renamedclasses.php
lib/db/services.php
lib/db/tasks.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/editor.js
lib/editor/tests/fixtures/disable_control_example.php [new file with mode: 0644]
lib/editor/tests/fixtures/editor_form.php [new file with mode: 0644]
lib/editor/textarea/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/editor/tinymce/module.js
lib/editor/tinymce/tests/behat/disablecontrol.feature [new file with mode: 0644]
lib/eventslib.php [deleted file]
lib/externallib.php
lib/filebrowser/file_info_context_course.php
lib/filelib.php
lib/form/filemanager.php
lib/form/form.js
lib/form/htmleditor.php
lib/form/submitlink.php [deleted file]
lib/formslib.php
lib/gradelib.php
lib/grouplib.php
lib/medialib.php [deleted file]
lib/messagelib.php
lib/moodlelib.php
lib/outputrenderers.php
lib/password_compat/lib/password.php [deleted file]
lib/phpunit/classes/util.php
lib/questionlib.php
lib/setup.php
lib/tests/admintree_test.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_general.php
lib/tests/coursecatlib_test.php
lib/tests/csslib_test.php
lib/tests/event_test.php
lib/tests/eventslib_test.php [deleted file]
lib/tests/filelib_test.php
lib/tests/gradelib_test.php
lib/tests/grouplib_test.php
lib/tests/medialib_test.php
lib/tests/moodlelib_test.php
lib/tests/plugin_manager_test.php
lib/tests/questionlib_test.php
lib/tests/string_manager_standard_test.php
lib/upgrade.txt
lib/upgradelib.php
media/classes/player.php
media/upgrade.txt
message/lib.php
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/src/notification_popover_controller.js
message/output/popup/mark_notification_read.php
message/tests/events_test.php
message/upgrade.txt
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/backup/moodle2/restore_assign_stepslib.php
mod/assign/lang/en/assign.php
mod/assign/lang/en/deprecated.txt
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/file/tests/locallib_test.php
mod/assign/tests/lib_test.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assignment/lang/en/assignment.php
mod/assignment/view.php
mod/data/lang/en/data.php
mod/data/lang/en/deprecated.txt
mod/feedback/classes/responses_table.php
mod/feedback/lang/en/deprecated.txt
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/show_nonrespondents.php
mod/feedback/upgrade.txt
mod/forum/lib.php
mod/forum/post.php
mod/glossary/lib.php
mod/label/lib.php
mod/label/tests/behat/label_idnumber.feature [new file with mode: 0644]
mod/label/version.php
mod/lesson/essay.php
mod/lesson/locallib.php
mod/lesson/renderer.php
mod/lti/lib.php
mod/lti/service/memberships/classes/local/resources/linkmemberships.php
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/report/statistics/report.php
mod/quiz/upgrade.txt
mod/scorm/datamodels/scorm_13.js
mod/scorm/db/renamedclasses.php [deleted file]
mod/scorm/report/basic/db/renamedclasses.php [deleted file]
mod/scorm/report/basic/upgrade.txt [new file with mode: 0644]
mod/scorm/upgrade.txt
mod/scorm/view.php
mod/upgrade.txt
mod/wiki/lib.php
mod/wiki/locallib.php
mod/wiki/pagelib.php
pix/i/empty.png [moved from pix/i/emtpy.png with 100% similarity]
pix/i/empty.svg [moved from pix/i/emtpy.svg with 100% similarity]
question/classes/bank/action_column_base.php
question/classes/bank/view.php
question/format/gift/format.php
question/format/gift/tests/giftformat_test.php
question/templates/tag_condition.mustache
question/tests/bank_view_test.php [new file with mode: 0644]
question/tests/privacy_provider_test.php
question/type/ddwtos/tests/edit_form_test.php [new file with mode: 0644]
question/type/gapselect/edit_form_base.php
question/type/gapselect/edit_gapselect_form.php
question/type/gapselect/tests/edit_form_test.php
report/upgrade.txt
repository/coursefiles/pix/icon.svg [new file with mode: 0644]
repository/dropbox/classes/dropbox.php
repository/dropbox/tests/api_test.php
repository/local/pix/icon.svg [new file with mode: 0644]
repository/recent/pix/icon.svg [new file with mode: 0644]
repository/user/pix/icon.svg [new file with mode: 0644]
search/classes/base.php
search/classes/document.php
tag/tests/events_test.php
tag/tests/taglib_test.php
tag/upgrade.txt
theme/boost/classes/output/core_course/management/renderer.php
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/undo.scss
theme/boost/style/moodle.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
user/amd/build/name_page_filter.min.js [deleted file]
user/amd/src/name_page_filter.js [deleted file]
user/classes/search/user.php
user/editlib.php
user/index.php
user/lib.php
user/profile/lib.php
user/selector/lib.php
user/tests/behat/addnewuser.feature [new file with mode: 0644]
user/tests/behat/custom_profile_fields.feature [new file with mode: 0644]
user/tests/behat/filter_participants.feature
user/tests/behat/filter_participants_showall.feature [new file with mode: 0644]
user/tests/behat/view_participants.feature
user/tests/fixtures/testable_user_selector.php [new file with mode: 0644]
user/tests/search_test.php
user/tests/userselector_test.php [new file with mode: 0644]
user/upgrade.txt [new file with mode: 0644]
version.php

index 4501fc6..78583d0 100644 (file)
@@ -37,7 +37,7 @@ module.exports = function(grunt) {
     var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
     var actual = semver.valid(process.version);
     if (!semver.satisfies(actual, expected)) {
-        grunt.fail.fatal('Node version too old. Require ' + expected + ', version installed: ' + actual);
+        grunt.fail.fatal('Node version not satisfied. Require ' + expected + ', version installed: ' + actual);
     }
 
     // Windows users can't run grunt in a subdirectory, so allow them to set
index 57d65a3..62f251a 100644 (file)
@@ -40,8 +40,9 @@ if ($unregistration && \core\hub\registration::is_registered()) {
     if ($siteunregistrationform->is_cancelled()) {
         redirect(new moodle_url('/admin/registration/index.php'));
     } else if ($data = $siteunregistrationform->get_data()) {
-        if (\core\hub\registration::unregister($data->unpublishalladvertisedcourses,
-            $data->unpublishalluploadedcourses)) {
+        \core\hub\registration::unregister($data->unpublishalladvertisedcourses,
+            $data->unpublishalluploadedcourses);
+        if (!\core\hub\registration::is_registered()) {
             redirect(new moodle_url('/admin/registration/index.php'));
         }
     }
diff --git a/admin/tool/assignmentupgrade/batchupgrade.php b/admin/tool/assignmentupgrade/batchupgrade.php
deleted file mode 100644 (file)
index 7744063..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?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();
diff --git a/admin/tool/assignmentupgrade/classes/privacy/provider.php b/admin/tool/assignmentupgrade/classes/privacy/provider.php
deleted file mode 100644 (file)
index 452cc15..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?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')
-            );
-        }
-    }
-
-}
diff --git a/admin/tool/assignmentupgrade/index.php b/admin/tool/assignmentupgrade/index.php
deleted file mode 100644 (file)
index 7720ac7..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<?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);
diff --git a/admin/tool/assignmentupgrade/lang/en/tool_assignmentupgrade.php b/admin/tool/assignmentupgrade/lang/en/tool_assignmentupgrade.php
deleted file mode 100644 (file)
index c7e7d26..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?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.';
diff --git a/admin/tool/assignmentupgrade/listnotupgraded.php b/admin/tool/assignmentupgrade/listnotupgraded.php
deleted file mode 100644 (file)
index 0836076..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?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);
-}
-
diff --git a/admin/tool/assignmentupgrade/locallib.php b/admin/tool/assignmentupgrade/locallib.php
deleted file mode 100644 (file)
index 27db509..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-<?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));
-}
-
diff --git a/admin/tool/assignmentupgrade/module.js b/admin/tool/assignmentupgrade/module.js
deleted file mode 100644 (file)
index ab7f670..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-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();
-        });
-
-    }
-}
diff --git a/admin/tool/assignmentupgrade/paginationform.php b/admin/tool/assignmentupgrade/paginationform.php
deleted file mode 100644 (file)
index e4c9028..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?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'));
-    }
-}
-
diff --git a/admin/tool/assignmentupgrade/renderer.php b/admin/tool/assignmentupgrade/renderer.php
deleted file mode 100644 (file)
index 2811232..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-<?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'));
-    }
-}
diff --git a/admin/tool/assignmentupgrade/settings.php b/admin/tool/assignmentupgrade/settings.php
deleted file mode 100644 (file)
index 1a91922..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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')));
-}
diff --git a/admin/tool/assignmentupgrade/styles.css b/admin/tool/assignmentupgrade/styles.css
deleted file mode 100644 (file)
index bdb9fa5..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-#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;
-}
diff --git a/admin/tool/assignmentupgrade/tests/privacy_test.php b/admin/tool/assignmentupgrade/tests/privacy_test.php
deleted file mode 100644 (file)
index d22770c..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<?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);
-    }
-
-}
diff --git a/admin/tool/assignmentupgrade/upgradableassignmentsbatchform.php b/admin/tool/assignmentupgrade/upgradableassignmentsbatchform.php
deleted file mode 100644 (file)
index 291329d..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?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'));
-    }
-
-}
-
diff --git a/admin/tool/assignmentupgrade/upgradableassignmentstable.php b/admin/tool/assignmentupgrade/upgradableassignmentstable.php
deleted file mode 100644 (file)
index a7851b9..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-<?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 '';
-    }
-}
diff --git a/admin/tool/assignmentupgrade/upgradesingle.php b/admin/tool/assignmentupgrade/upgradesingle.php
deleted file mode 100644 (file)
index 8a1f78d..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?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();
diff --git a/admin/tool/assignmentupgrade/upgradesingleconfirm.php b/admin/tool/assignmentupgrade/upgradesingleconfirm.php
deleted file mode 100644 (file)
index 3c41fd9..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?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);
diff --git a/admin/tool/behat/tests/manager_test.php b/admin/tool/behat/tests/manager_test.php
deleted file mode 100644 (file)
index 7b88c80..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-<?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);
-    }
-}
index 9aaea28..8ec0bb5 100644 (file)
@@ -608,6 +608,54 @@ class api {
         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.
      *
index 1435fcd..dbc8f32 100644 (file)
@@ -147,8 +147,8 @@ class data_registry {
 
         if ($contextcourse = $context->get_course_context(false)) {
             // Below course level we look at module or block level roles + course-assigned roles.
-            $courseroles = get_roles_with_assignment_on_context($contextcourse);
-            $roles = $courseroles + get_roles_with_assignment_on_context($context);
+            $courseroles = get_roles_used_in_context($contextcourse, false);
+            $roles = $courseroles + get_roles_used_in_context($context, false);
         } else {
             // We list category + system for others (we don't work with user instances so no need to work about them).
             $roles = get_roles_used_in_context($context);
index d8b0644..51bb133 100644 (file)
@@ -208,6 +208,14 @@ class data_requests_table extends table_sql {
                 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);
index c5e18a1..d82968c 100644 (file)
@@ -95,7 +95,8 @@ class my_data_requests_page implements renderable, templatable {
             $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);
@@ -110,6 +111,10 @@ class my_data_requests_page implements renderable, templatable {
                     $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:
@@ -126,10 +131,7 @@ class my_data_requests_page implements renderable, templatable {
                 $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);
index c58f574..6db9252 100644 (file)
@@ -139,8 +139,17 @@ class process_data_request_task extends adhoc_task {
 
         $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;
@@ -179,18 +188,40 @@ class process_data_request_task extends adhoc_task {
         $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);
index ecc2ec3..ad31867 100644 (file)
@@ -49,4 +49,22 @@ $capabilities = [
         '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' => []
+    ],
 ];
diff --git a/admin/tool/dataprivacy/db/upgrade.php b/admin/tool/dataprivacy/db/upgrade.php
new file mode 100644 (file)
index 0000000..8c0b4fc
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * tool_dataprivacy plugin upgrade code
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade tool_dataprivacy.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_tool_dataprivacy_upgrade($oldversion) {
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager();
+
+    if ($oldversion < 2018051405) {
+        // Define table tool_dataprivacy_ctxexpired to be created.
+        $table = new xmldb_table('tool_dataprivacy_ctxexpired');
+
+        // Adding fields to table tool_dataprivacy_ctxexpired.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table tool_dataprivacy_ctxexpired.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('contextid', XMLDB_KEY_FOREIGN_UNIQUE, array('contextid'), 'context', array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_ctxexpired.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_contextlist to be created.
+        $table = new xmldb_table('tool_dataprivacy_contextlist');
+
+        // Adding fields to table tool_dataprivacy_contextlist.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('component', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+        // Adding keys to table tool_dataprivacy_contextlist.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_contextlist.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_ctxlst_ctx to be created.
+        $table = new xmldb_table('tool_dataprivacy_ctxlst_ctx');
+
+        // Adding fields to table tool_dataprivacy_ctxlst_ctx.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('status', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+
+        // Adding keys to table tool_dataprivacy_ctxlst_ctx.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+
+        // Conditionally launch create table for tool_dataprivacy_ctxlst_ctx.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table tool_dataprivacy_rqst_ctxlst to be created.
+        $table = new xmldb_table('tool_dataprivacy_rqst_ctxlst');
+
+        // Adding fields to table tool_dataprivacy_rqst_ctxlst.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('requestid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('contextlistid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table tool_dataprivacy_rqst_ctxlst.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('requestid', XMLDB_KEY_FOREIGN, array('requestid'), 'tool_dataprivacy_request', array('id'));
+        $table->add_key('contextlistid', XMLDB_KEY_FOREIGN, array('contextlistid'), 'tool_dataprivacy_contextlist', array('id'));
+        $table->add_key('request_contextlist', XMLDB_KEY_UNIQUE, array('requestid', 'contextlistid'));
+
+        // Conditionally launch create table for tool_dataprivacy_rqst_ctxlst.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define field lawfulbases to be added to tool_dataprivacy_purpose.
+        $table = new xmldb_table('tool_dataprivacy_purpose');
+
+        // It is a required field. We initially define and add it as null and later update it to XMLDB_NOTNULL.
+        $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, null, null, null, 'descriptionformat');
+
+        // Conditionally launch add field lawfulbases.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+
+            // Set a kind-of-random value to lawfulbasis field.
+            $DB->set_field('tool_dataprivacy_purpose', 'lawfulbases', 'gdpr_art_6_1_a');
+
+            // We redefine it now as not null.
+            $field = new xmldb_field('lawfulbases', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'descriptionformat');
+
+            // Launch change of nullability for field lawfulbases.
+            $dbman->change_field_notnull($table, $field);
+        }
+
+        // Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
+        $table = new xmldb_table('tool_dataprivacy_purpose');
+        $field = new xmldb_field('sensitivedatareasons', XMLDB_TYPE_TEXT, null, null, null, null, null, 'lawfulbases');
+
+        // Conditionally launch add field sensitivedatareasons.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Dataprivacy savepoint reached.
+        upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
+    }
+
+    return true;
+}
index b01a0b6..4af32a8 100644 (file)
@@ -66,6 +66,8 @@ $string['datadeletionpagehelp'] = 'Data for which the retention period has expir
 $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}';
index 3c66485..73ffc14 100644 (file)
@@ -185,26 +185,18 @@ function tool_dataprivacy_output_fragment_contextlevel_form($args) {
  * @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.
index f4a7a66..c341a63 100644 (file)
@@ -276,6 +276,57 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
         $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()
      */
@@ -417,27 +468,20 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
      * @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],
         ];
     }
 
@@ -445,12 +489,31 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
      * 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);
diff --git a/admin/tool/dataprivacy/tests/behat/dataexport.feature b/admin/tool/dataprivacy/tests/behat/dataexport.feature
new file mode 100644 (file)
index 0000000..3ab0467
--- /dev/null
@@ -0,0 +1,107 @@
+@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
index 01b8b2b..f5c7977 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2018051402;
+$plugin->version   = 2018051405;
 $plugin->requires  = 2018050800;        // Moodle 3.5dev (Build 2018031600) and upwards.
 $plugin->component = 'tool_dataprivacy';
index d40eec0..bcb1d54 100644 (file)
@@ -16,6 +16,8 @@
 
 /**
  * Legacy log reader.
+ * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+ * @todo  MDL-52805 This is to be removed in Moodle 4.0
  *
  * @package    logstore_legacy
  * @copyright  2013 Petr Skoda {@link http://skodak.org}
@@ -30,6 +32,12 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
     use \tool_log\helper\store,
         \tool_log\helper\reader;
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo  MDL-52805 This is to be removed in Moodle 4.0
+     *
+     * @param \tool_log\log\manager $manager
+     */
     public function __construct(\tool_log\log\manager $manager) {
         $this->helper_setup($manager);
     }
@@ -83,6 +91,17 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return array($selectwhere, $params, $sort);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     *
+     * @param  string $selectwhere
+     * @param  array  $params
+     * @param  string $sort
+     * @param  int    $limitfrom
+     * @param  int    $limitnum
+     * @return array
+     */
     public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
         global $DB;
 
@@ -114,6 +133,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Fetch records using given criteria returning a Traversable object.
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * Note that the traversable object contains a moodle_recordset, so
      * remember that is important that you call close() once you finish
@@ -146,6 +167,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Returns an event from the log data.
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * @param stdClass $data Log data
      * @return \core\event\base
@@ -154,6 +177,14 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return \logstore_legacy\event\legacy_logged::restore_legacy($data);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     *
+     * @param  string $selectwhere
+     * @param  array  $params
+     * @return int
+     */
     public function get_events_select_count($selectwhere, array $params) {
         global $DB;
 
@@ -170,6 +201,8 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
 
     /**
      * Are the new events appearing in the reader?
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
      *
      * @return bool true means new log events are being added, false means no new data will be added
      */
@@ -177,6 +210,10 @@ class store implements \tool_log\log\store, \core\log\sql_reader {
         return (bool)$this->get_config('loglegacy', true);
     }
 
+    /**
+     * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
+     * @todo MDL-52805 This will be removed in Moodle 4.0
+     */
     public function dispose() {
     }
 
diff --git a/admin/tool/log/upgrade.txt b/admin/tool/log/upgrade.txt
new file mode 100644 (file)
index 0000000..e1b65e3
--- /dev/null
@@ -0,0 +1,8 @@
+This files describes API changes in /admin/tool/log - plugins,
+information provided here is intended especially for developers.
+
+
+=== 3.6 ===
+
+* The legacy log store is in its first stage of deprecation and is due for removal in Moodle 4.0. Please use one of
+  the other log stores such as "standard" and "database".
\ No newline at end of file
index 5ca5d99..66ea3aa 100644 (file)
@@ -102,6 +102,7 @@ class page_viewalldoc implements renderable, templatable {
 
         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;
index fd29175..3a75285 100644 (file)
@@ -84,6 +84,7 @@ $string['inactivatingconfirm'] = '<p>You are about to inactivate policy <em>\'{$
 $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';
index 89812ff..2f9df36 100644 (file)
                 "name": "Terms &amp; 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">
index 6c504eb..2b9b026 100644 (file)
@@ -364,7 +364,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
     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"
@@ -419,7 +419,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
     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"
@@ -460,6 +460,7 @@ Feature: User must accept policy managed by this plugin when logging in and sign
       | 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"
index 3af7d3a..ec7e17e 100644 (file)
@@ -1,6 +1,9 @@
 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 ===
 
index fe6244c..39ca032 100644 (file)
@@ -277,6 +277,32 @@ class auth_plugin_shibboleth extends auth_plugin_base {
             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;
+    }
 }
 
 
diff --git a/auth/shibboleth/classes/helper.php b/auth/shibboleth/classes/helper.php
new file mode 100644 (file)
index 0000000..c1d5705
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains a helper class for the Shibboleth authentication plugin.
+ *
+ * @package    auth_shibboleth
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace auth_shibboleth;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The helper class for the Shibboleth authentication plugin.
+ *
+ * @package    auth_shibboleth
+ * @copyright  2018 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class helper {
+
+    /**
+     * Delete session of user using file sessions.
+     *
+     * @param string $spsessionid SP-provided Shibboleth Session ID
+     * @return \SoapFault or void if everything was fine
+     */
+    public static function logout_file_session($spsessionid) {
+        global $CFG;
+
+        if (!empty($CFG->session_file_save_path)) {
+            $dir = $CFG->session_file_save_path;
+        } else {
+            $dir = $CFG->dataroot . '/sessions';
+        }
+
+        if (is_dir($dir)) {
+            if ($dh = opendir($dir)) {
+                // Read all session files.
+                while (($file = readdir($dh)) !== false) {
+                    // Check if it is a file.
+                    if (is_file($dir.'/'.$file)) {
+                        // Read session file data.
+                        $data = file($dir.'/'.$file);
+                        if (isset($data[0])) {
+                            $usersession = self::unserializesession($data[0]);
+                            // Check if we have found session that shall be deleted.
+                            if (isset($usersession['SESSION']) && isset($usersession['SESSION']->shibboleth_session_id)) {
+                                // If there is a match, delete file.
+                                if ($usersession['SESSION']->shibboleth_session_id == $spsessionid) {
+                                    // Delete session file.
+                                    if (!unlink($dir.'/'.$file)) {
+                                        return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                closedir($dh);
+            }
+        }
+    }
+
+    /**
+     * Delete session of user using DB sessions.
+     *
+     * @param string $spsessionid SP-provided Shibboleth Session ID
+     */
+    public static function logout_db_session($spsessionid) {
+        global $CFG, $DB;
+
+        $sessions = $DB->get_records_sql(
+            'SELECT userid, sessdata FROM {sessions} WHERE timemodified > ?',
+            array(time() - $CFG->sessiontimeout)
+        );
+
+        foreach ($sessions as $session) {
+            // Get user session from DB.
+            if (session_decode(base64_decode($session->sessdata))) {
+                if (isset($_SESSION['SESSION']) && isset($_SESSION['SESSION']->shibboleth_session_id)) {
+                    // If there is a match, kill the session.
+                    if ($_SESSION['SESSION']->shibboleth_session_id == trim($spsessionid)) {
+                        // Delete this user's sessions.
+                        \core\session\manager::kill_user_sessions($session->userid);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Unserialize a session string.
+     *
+     * @param string $serializedstring
+     * @return array
+     */
+    private static function unserializesession($serializedstring) {
+        $variables = array();
+        $a = preg_split("/(\w+)\|/", $serializedstring, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+        $counta = count($a);
+        for ($i = 0; $i < $counta; $i = $i + 2) {
+            $variables[$a[$i]] = unserialize($a[$i + 1]);
+        }
+        return $variables;
+    }
+}
index 4d7e7df..144c1fd 100644 (file)
@@ -71,8 +71,8 @@ if ($show_instructions) {
 <?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");
               } ?>
@@ -82,14 +82,14 @@ if ($show_instructions) {
                    </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>
index 659e4d1..8ef9ec1 100644 (file)
@@ -25,6 +25,8 @@
 
 $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!';
diff --git a/auth/shibboleth/lib.php b/auth/shibboleth/lib.php
new file mode 100644 (file)
index 0000000..f8ac757
--- /dev/null
@@ -0,0 +1,64 @@
+<?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);
+}
index 721af80..2ed931a 100644 (file)
 
     $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'];
index 83f9234..f514d4e 100644 (file)
@@ -127,75 +127,14 @@ WSDL;
  * @return SoapFault or void if everything was fine
  */
 function LogoutNotification($spsessionid) {
-
-    global $CFG, $SESSION, $DB;
-
-    // Delete session of user using $spsessionid.
-    if(empty($CFG->dbsessions)) {
-
-        // File session
-        $dir = $CFG->dataroot .'/sessions';
-        if (is_dir($dir)) {
-            if ($dh = opendir($dir)) {
-                // Read all session files
-                while (($file = readdir($dh)) !== false) {
-                    // Check if it is a file
-                    if (is_file($dir.'/'.$file)){
-                        $session_key = preg_replace('/sess_/', '', $file);
-
-                        // Read session file data
-                        $data = file($dir.'/'.$file);
-                        if (isset($data[0])){
-                            $usersession = unserializesession($data[0]);
-
-                            // Check if we have found session that shall be deleted
-                            if (isset($usersession['SESSION']) && isset($usersession['SESSION']->shibboleth_session_id)) {
-
-                                // If there is a match, delete file
-                                if ($usersession['SESSION']->shibboleth_session_id == $spsessionid) {
-                                    // Delete session file
-                                    if (!unlink($dir.'/'.$file)){
-                                        return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                closedir($dh);
-            }
-        }
-    } else {
-        // DB Sessions.
-        $sessions = $DB->get_records_sql(
-            'SELECT userid, sessdata FROM {sessions} WHERE timemodified > ?',
-            array(time() - $CFG->sessiontimeout)
-        );
-        foreach ($sessions as $session) {
-            // Get user session from DB.
-            if (session_decode(base64_decode($session->sessdata))) {
-                if (isset($_SESSION['SESSION']) && isset($_SESSION['SESSION']->shibboleth_session_id)) {
-                    // If there is a match, kill the session.
-                    if ($_SESSION['SESSION']->shibboleth_session_id == trim($spsessionid)) {
-                        // Delete this user's sessions.
-                        \core\session\manager::kill_user_sessions($session->userid);
-                    }
-                }
-            }
-        }
+    $sessionclass = \core\session\manager::get_handler_class();
+    switch ($sessionclass) {
+        case '\core\session\file':
+            return \auth_shibboleth\helper::logout_file_session($spsessionid);
+        case '\core\session\database':
+            return \auth_shibboleth\helper::logout_db_session($spsessionid);
+        default:
+            throw new moodle_exception("Shibboleth logout not implemented for '$sessionclass'");
     }
     // If no SoapFault was thrown, the function will return OK as the SP assumes.
 }
-
-/*****************************************************************************/
-
-// Same function as in adodb, but cannot be used for file session for some reason...
-function unserializesession($serialized_string) {
-    $variables = array();
-    $a = preg_split("/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-    $counta = count($a);
-    for ($i = 0; $i < $counta; $i = $i+2) {
-            $variables[$a[$i]] = unserialize($a[$i+1]);
-    }
-    return $variables;
-}
index c901378..e4b4c3a 100644 (file)
@@ -63,6 +63,11 @@ if ($ADMIN->fulltree) {
             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'),
index dbd7b4b..9083025 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /auth/shibboleth/*,
 information provided here is intended especially for developers.
 
+=== 3.5.2 ===
+
+* Moved the public function unserializesession in auth/shibboleth/logout.php to auth/shibboleth/classes/helper.php and
+  made it private. This function should not have been used outside of this file.
+
 === 3.3 ===
 
 * The config.html file was migrated to use the admin settings API.
index 50b9ec6..4b6e2a9 100644 (file)
@@ -25,11 +25,11 @@ Feature: Test the 'showlogfailures' feature works.
     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.
@@ -46,10 +46,10 @@ Feature: Test the 'showlogfailures' feature works.
     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"
index 9fe1753..7381991 100644 (file)
@@ -6,7 +6,7 @@ Feature: Test validation of 'Age of digital consent' setting.
 
   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:
index ccf6dd1..98d495f 100644 (file)
@@ -27,10 +27,10 @@ Feature: Import course's content's twice
     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"
index ec45776..7ef9117 100644 (file)
@@ -604,7 +604,8 @@ abstract class restore_dbops {
         //             6b) User cannot, check if we are in some contextlevel with fallback
         //                 7a) There is fallback, move ALL the qcats to fallback, warn. End qcat loop
         //                 7b) No fallback, error. End qcat loop
-        //         5b) Match, mark q to be mapped
+        //         5b) Random question, must always create new.
+        //         5c) Match, mark q to be mapped
         // 8) Check if backup is from Moodle >= 3.5 and error if more than one top-level category in the context.
 
         // Get all the contexts (question banks) in restore for the given contextlevel
@@ -708,7 +709,11 @@ abstract class restore_dbops {
                                 break 2; // out from qcat loop (both 7a and 7b), we have decided about ALL categories in context (bank)
                             }
 
-                        // 5b) Match, mark q to be mapped
+                        // 5b) Random questions must always be newly created.
+                        } else if ($question->qtype == 'random') {
+                            // Nothing to mark, newitemid means create
+
+                        // 5c) Match, mark q to be mapped.
                         } else {
                             self::set_backup_ids_record($restoreid, 'question', $question->id, $matchqid);
                         }
@@ -718,7 +723,7 @@ abstract class restore_dbops {
 
             // 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);
             }
 
         }
index b7de027..5d886c2 100644 (file)
@@ -46,6 +46,3 @@ $string['myprofile:myaddinstance'] = 'Add a new logged in user block to Dashboar
 $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';
index 6a46c36..0424b73 100644 (file)
@@ -228,16 +228,6 @@ class block_recent_activity extends block_base {
         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}
      *
diff --git a/blocks/recent_activity/classes/task/cleanup.php b/blocks/recent_activity/classes/task/cleanup.php
new file mode 100644 (file)
index 0000000..c3f34c7
--- /dev/null
@@ -0,0 +1,58 @@
+<?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));
+    }
+}
similarity index 60%
rename from mod/quiz/db/renamedclasses.php
rename to blocks/recent_activity/db/tasks.php
index 3cc07a1..0da9677 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Lists renamed classes so that the autoloader can make the old names still work.
- *
- * @package   mod_quiz
- * @copyright 2014 Tim Hunt
+ * Task definition for block_recent_activity.
+ * @author    Farhan Karmali <farhan6318@gmail.com>
+ * @copyright Farhan Karmali 2018
+ * @package   block_recent_activity
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
-// Array 'old_class_name' => 'new\class_name'.
-$renamedclasses = array(
-
-    // Changed in Moodle 2.8.
-    'quiz_question_bank_view'                 => 'mod_quiz\question\bank\custom_view',
-    'question_bank_add_to_quiz_action_column' => 'mod_quiz\question\bank\add_action_column',
-    'question_bank_question_name_text_column' => 'mod_quiz\question\bank\question_name_text_column',
+$tasks = array(
+    array(
+        'classname' => '\block_recent_activity\task\cleanup',
+        'blocking' => 0,
+        'minute' => 'R',
+        'hour' => 'R',
+        'day' => '*',
+        'month' => '*',
+        'dayofweek' => '*',
+        'disabled' => 0
+    )
 );
+
index defbc88..c77dca2 100644 (file)
@@ -23,6 +23,7 @@
  * @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';
index 1943157..48711ce 100644 (file)
@@ -24,7 +24,6 @@
 
 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
index bc6d7c5..2a2fd78 100644 (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) {
diff --git a/blocks/rss_client/classes/task/refreshfeeds.php b/blocks/rss_client/classes/task/refreshfeeds.php
new file mode 100644 (file)
index 0000000..3e34e1b
--- /dev/null
@@ -0,0 +1,124 @@
+<?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)');
+
+    }
+}
similarity index 59%
rename from admin/tool/assignmentupgrade/version.php
rename to blocks/rss_client/db/tasks.php
index 63fd29d..254e8ae 100644 (file)
 // 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
+    )
+);
+
index 3045074..19a9ca5 100644 (file)
@@ -72,6 +72,7 @@ $string['privacy:metadata:block_rss_client:title'] = 'The title of the RSS feed.
 $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';
index 7f99275..bae4cde 100644 (file)
@@ -55,12 +55,12 @@ class block_rss_client_cron_testcase extends advanced_testcase {
         );
         $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();
@@ -69,7 +69,7 @@ class block_rss_client_cron_testcase extends advanced_testcase {
     }
 
     /**
-     * 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;
@@ -114,20 +114,20 @@ class block_rss_client_cron_testcase extends advanced_testcase {
         );
         $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;
index c0e9e59..6f9eac3 100644 (file)
@@ -24,7 +24,6 @@
 
 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)
index 0abfe87..0d3bc5d 100644 (file)
Binary files a/calendar/amd/build/event_form.min.js and b/calendar/amd/build/event_form.min.js differ
index 6b1db42..894dcf9 100644 (file)
Binary files a/calendar/amd/build/repository.min.js and b/calendar/amd/build/repository.min.js differ
index 2b6707d..b64639a 100644 (file)
  * @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'
     };
 
     /**
@@ -69,39 +39,29 @@ define(['jquery'], function($) {
      */
     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);
+        });
     };
 
     /**
@@ -112,8 +72,6 @@ define(['jquery'], function($) {
      */
     var init = function(formId) {
         var formElement = $('#' + formId);
-
-        parseGroupSelect(formElement);
         addCourseGroupSelectListeners(formElement);
     };
 
index 049f819..d8166fa 100644 (file)
@@ -181,6 +181,23 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
         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,
@@ -188,6 +205,7 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
         submitCreateUpdateForm: submitCreateUpdateForm,
         getCalendarMonthData: getCalendarMonthData,
         getCalendarDayData: getCalendarDayData,
-        getCalendarUpcomingData: getCalendarUpcomingData
+        getCalendarUpcomingData: getCalendarUpcomingData,
+        getCourseGroupsData: getCourseGroupsData
     };
 });
index 597eb70..0afc594 100644 (file)
@@ -81,6 +81,11 @@ class container {
      */
     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.
      */
@@ -117,11 +122,13 @@ class container {
                 [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;
                         }
                     }
@@ -131,7 +138,7 @@ class container {
                         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])) {
@@ -156,11 +163,13 @@ class container {
                     }
 
                     $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;
                     }
 
@@ -191,6 +200,7 @@ class container {
      * Reset all static caches, called between tests.
      */
     public static function reset_caches() {
+        self::$requestinguserid = null;
         self::$eventfactory = null;
         self::$eventmapper = null;
         self::$eventvault = null;
@@ -230,6 +240,31 @@ class container {
         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
      *
@@ -245,14 +280,23 @@ class container {
         $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
                 ]
             );
         }
@@ -279,12 +323,21 @@ class container {
         $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
                 ]
             );
         }
index 924a6fa..ff9407b 100644 (file)
@@ -66,9 +66,11 @@ class create extends \moodleform {
         $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');
         }
 
@@ -120,18 +122,20 @@ class create extends \moodleform {
      * @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');
                 }
@@ -140,11 +144,15 @@ class create extends \moodleform {
             }
         }
 
-        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');
         }
 
index 59a9cd7..f848687 100644 (file)
@@ -53,28 +53,29 @@ trait eventtype {
      * @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');
@@ -87,9 +88,9 @@ trait eventtype {
             $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;
             }
 
@@ -97,33 +98,26 @@ trait eventtype {
             $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.
         }
index 0e5a0b6..102761f 100644 (file)
@@ -41,8 +41,8 @@ class managesubscriptions extends \moodleform {
      */
     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');
         }
 
@@ -100,9 +100,10 @@ class managesubscriptions extends \moodleform {
 
         $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');
index 91c1435..032d839 100644 (file)
@@ -58,7 +58,7 @@ class create_update_form_mapper implements create_update_form_mapper_interface {
 
         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') {
@@ -93,12 +93,8 @@ class create_update_form_mapper implements create_update_form_mapper_interface {
                 $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.
index d4775ee..37d1a80 100644 (file)
@@ -96,11 +96,24 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
             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)";
@@ -112,7 +125,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         // 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";
@@ -124,7 +137,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         // 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);
@@ -134,7 +147,7 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         }
 
         // 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);
@@ -168,54 +181,81 @@ class raw_event_retrieval_strategy implements raw_event_retrieval_strategy_inter
         // 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);
         }
index 10fa760..80e60cd 100644 (file)
@@ -865,15 +865,31 @@ class core_calendar_external extends external_api {
         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()) {
index 60d3d0f..c3c7aac 100644 (file)
@@ -1070,7 +1070,7 @@ class calendar_information {
             $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];
@@ -1084,7 +1084,7 @@ class calendar_information {
             $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();
@@ -2039,34 +2039,29 @@ function calendar_events_by_day($events, $month, $year, &$eventsbyday, &$duratio
  *
  * @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);
@@ -2080,11 +2075,11 @@ function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
         $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);
@@ -2096,16 +2091,16 @@ function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
         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']);
                     }
                 }
@@ -2119,7 +2114,7 @@ function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) {
         $courses = false;
     }
 
-    return array($courses, $group, $user);
+    return array($courses, $group, $userid);
 }
 
 /**
@@ -2317,20 +2312,25 @@ function calendar_delete_event_allowed($event) {
  *
  * @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);
@@ -2340,11 +2340,11 @@ function calendar_get_default_courses($courseid = null, $fields = '*', $canmanag
         }, $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);
@@ -2570,76 +2570,6 @@ function calendar_get_allowed_types(&$allowed, $course = null, $groups = null, $
     }
 }
 
-/**
- * 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.
  *
@@ -3328,6 +3258,11 @@ function calendar_get_legacy_events($tstart, $tend, $users, $groups, $courses,
         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,
@@ -3508,18 +3443,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
  */
 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) {
@@ -3534,6 +3469,13 @@ function calendar_output_fragment_event_form($args) {
     }
 
     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,
@@ -3545,16 +3487,19 @@ function calendar_output_fragment_event_form($args) {
         );
 
         // 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 {
@@ -3564,6 +3509,15 @@ function calendar_output_fragment_event_form($args) {
         $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,
@@ -3692,3 +3646,175 @@ function calendar_is_valid_eventtype($type) {
     ];
     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;
+}
index 75ddc09..87289a6 100644 (file)
@@ -61,7 +61,7 @@ if (!calendar_user_can_add_event($course)) {
     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
 ));
@@ -105,26 +105,26 @@ if (!empty($formdata)) {
     }
 }
 
-$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;
@@ -134,19 +134,27 @@ if ($usedefaultfilters) {
     $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;
     }
index 5603daa..5483edb 100644 (file)
@@ -170,8 +170,8 @@ Feature: Perform basic calendar functionality
     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"
index 28b42d3..ed623d0 100644 (file)
@@ -1989,12 +1989,12 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
         $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']);
     }
 
     /**
@@ -2027,7 +2027,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
                 'minute' => 0,
             ],
             'eventtype' => 'group',
-            'groupid' => "{$course->id}-{$group->id}", // The form format.
+            'groupid' => $group->id,
             'groupcourseid' => $course->id,
             'description' => [
                 'text' => '',
@@ -2100,7 +2100,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
                 'minute' => 0,
             ],
             'eventtype' => 'group',
-            'groupid' => "{$course->id}-{$group->id}", // The form format.
+            'groupid' => $group->id,
             'groupcourseid' => $course->id,
             'description' => [
                 'text' => '',
@@ -2174,7 +2174,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
                 'minute' => 0,
             ],
             'eventtype' => 'group',
-            'groupid' => "{$course->id}-{$group->id}", // The form format.
+            'groupid' => $group->id,
             'groupcourseid' => $course->id,
             'description' => [
                 'text' => '',
@@ -2248,7 +2248,7 @@ class core_calendar_externallib_testcase extends externallib_advanced_testcase {
                 'minute' => 0,
             ],
             'eventtype' => 'group',
-            'groupid' => "{$course->id}-{$group->id}", // The form format.
+            'groupid' => $group->id,
             'groupcourseid' => $course->id,
             'description' => [
                 'text' => '',
index bf8b6ca..35809ec 100644 (file)
@@ -418,65 +418,112 @@ class core_calendar_lib_testcase extends advanced_testcase {
         $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.
@@ -504,35 +551,18 @@ class core_calendar_lib_testcase extends advanced_testcase {
 
         // 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();
@@ -545,224 +575,201 @@ class core_calendar_lib_testcase extends advanced_testcase {
         $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));
    &nb