Merge branch 'MDL-43334-master' of git://github.com/damyon/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Aug 2018 22:11:16 +0000 (00:11 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Aug 2018 22:11:16 +0000 (00:11 +0200)
349 files changed:
Gruntfile.js
admin/modules.php
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/amd/build/data_request_modal.min.js
admin/tool/dataprivacy/amd/build/events.min.js
admin/tool/dataprivacy/amd/build/requestactions.min.js
admin/tool/dataprivacy/amd/src/data_request_modal.js
admin/tool/dataprivacy/amd/src/events.js
admin/tool/dataprivacy/amd/src/requestactions.js
admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/data_registry.php
admin/tool/dataprivacy/classes/external.php
admin/tool/dataprivacy/classes/external/data_request_exporter.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/services.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/templates/data_request_modal.mustache
admin/tool/dataprivacy/tests/api_test.php
admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature [new file with mode: 0644]
admin/tool/dataprivacy/tests/behat/dataexport.feature [new file with mode: 0644]
admin/tool/dataprivacy/tests/behat/manage_data_requests.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/db/auth.php
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
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/tests/cronhelper_test.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/dndupload.js
course/dnduploadlib.php
course/externallib.php
course/format/singleactivity/lib.php
course/format/upgrade.txt
course/lib.php
course/renderer.php
course/templates/activity_navigation.mustache
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
filter/manage.php
grade/edit/letter/index.php
group/assign.php
install/lang/el/error.php
install/lang/el/install.php
install/lang/eu/langconfig.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/build/tag.min.js
lib/amd/src/ajax.js
lib/amd/src/form-autocomplete.js
lib/amd/src/tag.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/dndupload.js
lib/form/filemanager.php
lib/form/form.js
lib/form/htmleditor.php
lib/form/submitlink.php [deleted file]
lib/form/url.js
lib/form/url.php
lib/formslib.php
lib/gradelib.php
lib/grouplib.php
lib/ltiprovider/src/ToolProvider/ToolProvider.php
lib/medialib.php [deleted file]
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/password_compat/lib/password.php [deleted file]
lib/phpunit/classes/util.php
lib/questionlib.php
lib/setup.php
lib/tablelib.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/amd/build/message_area_contacts.min.js
message/amd/src/message_area_contacts.js
message/externallib.php
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/tests/externallib_test.php
message/upgrade.txt
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/backup/moodle2/restore_assign_stepslib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lang/en/deprecated.txt
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/file/locallib.php
mod/assign/submission/file/tests/locallib_test.php
mod/assign/tests/behat/assign_hidden.feature [new file with mode: 0644]
mod/assign/tests/behat/grading_status.feature
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/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/tests/lib_test.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/index.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/report/statistics/report.php
mod/quiz/tests/locallib_test.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/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/fixtures/testable_core_question_column.php [new file with mode: 0644]
question/tests/privacy_provider_test.php
question/tests/question_bank_column_test.php [new file with mode: 0644]
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/filepicker.js
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/manage.php
tag/templates/add_tag_collection.mustache [new file with mode: 0644]
tag/templates/add_tags.mustache [new file with mode: 0644]
tag/templates/combine_tags.mustache [new file with mode: 0644]
tag/tests/behat/edit_tag.feature
tag/tests/events_test.php
tag/tests/taglib_test.php
tag/upgrade.txt
theme/boost/classes/output/core/course_renderer.php
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/boost/templates/core_form/element-url.mustache
theme/boost/templates/course_search_form.mustache
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/participants_table.php
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 fb112e7..01f67a1 100644 (file)
     $table->set_attribute('class', 'admintable generaltable');
     $table->setup();
 
+    $pluginmanager = core_plugin_manager::instance();
+
     foreach ($modules as $module) {
+        $plugininfo = $pluginmanager->get_plugin_info('mod_'.$module->name);
+        $status = $plugininfo->get_status();
 
-        if (!file_exists("$CFG->dirroot/mod/$module->name/lib.php")) {
+        if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
             $strmodulename = '<span class="notifyproblem">'.$module->name.' ('.get_string('missingfromdisk').')</span>';
             $missing = true;
         } else {
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 7e9bda8..e8b22a2 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/data_request_modal.min.js and b/admin/tool/dataprivacy/amd/build/data_request_modal.min.js differ
index 1d4e973..0ecae4c 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/events.min.js and b/admin/tool/dataprivacy/amd/build/events.min.js differ
index 586a2da..c405d17 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/requestactions.min.js and b/admin/tool/dataprivacy/amd/build/requestactions.min.js differ
index abf717b..5f841c5 100644 (file)
@@ -29,6 +29,7 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
         var SELECTORS = {
             APPROVE_BUTTON: '[data-action="approve"]',
             DENY_BUTTON: '[data-action="deny"]',
+            COMPLETE_BUTTON: '[data-action="complete"]'
         };
 
         /**
@@ -38,14 +39,6 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
          */
         var ModalDataRequest = function(root) {
             Modal.call(this, root);
-
-            if (!this.getFooter().find(SELECTORS.APPROVE_BUTTON).length) {
-                Notification.exception({message: 'No approve button found'});
-            }
-
-            if (!this.getFooter().find(SELECTORS.DENY_BUTTON).length) {
-                Notification.exception({message: 'No deny button found'});
-            }
         };
 
         ModalDataRequest.TYPE = 'tool_dataprivacy-data_request';
@@ -80,6 +73,16 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
                     data.originalEvent.preventDefault();
                 }
             }.bind(this));
+
+            this.getModal().on(CustomEvents.events.activate, SELECTORS.COMPLETE_BUTTON, function(e, data) {
+                var completeEvent = $.Event(DataPrivacyEvents.complete);
+                this.getRoot().trigger(completeEvent, this);
+
+                if (!completeEvent.isDefaultPrevented()) {
+                    this.hide();
+                    data.originalEvent.preventDefault();
+                }
+            }.bind(this));
         };
 
         // Automatically register with the modal registry the first time this module is imported so that you can create modals
index 9398dc4..4e7ff77 100644 (file)
@@ -26,5 +26,6 @@ define([], function() {
     return {
         approve: 'tool_dataprivacy-data_request:approve',
         deny: 'tool_dataprivacy-data_request:deny',
+        complete: 'tool_dataprivacy-data_request:complete'
     };
 });
index c0941f8..4f3c406 100644 (file)
@@ -39,11 +39,13 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
      * @type {{APPROVE_REQUEST: string}}
      * @type {{DENY_REQUEST: string}}
      * @type {{VIEW_REQUEST: string}}
+     * @type {{MARK_COMPLETE: string}}
      */
     var ACTIONS = {
         APPROVE_REQUEST: '[data-action="approve"]',
         DENY_REQUEST: '[data-action="deny"]',
-        VIEW_REQUEST: '[data-action="view"]'
+        VIEW_REQUEST: '[data-action="view"]',
+        MARK_COMPLETE: '[data-action="complete"]'
     };
 
     /**
@@ -73,16 +75,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
             };
 
             var promises = Ajax.call([request]);
-            var modalTitle = '';
-            var modalType = ModalFactory.types.DEFAULT;
             $.when(promises[0]).then(function(data) {
                 if (data.result) {
-                    // Check if the status is awaiting approval.
-                    if (data.result.status == 2) {
-                        modalType = ModalDataRequest.TYPE;
-                    }
-                    modalTitle = data.result.typename;
-                    return Templates.render('tool_dataprivacy/request_details', data.result);
+                    return data.result;
                 }
                 // Fail.
                 Notification.addNotification({
@@ -91,35 +86,51 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
                 });
                 return false;
 
-            }).then(function(html) {
+            }).then(function(data) {
+                var body = Templates.render('tool_dataprivacy/request_details', data);
+                var templateContext = {
+                    approvedeny: data.approvedeny,
+                    canmarkcomplete: data.canmarkcomplete
+                };
                 return ModalFactory.create({
-                    title: modalTitle,
-                    body: html,
-                    type: modalType,
-                    large: true
-                }).then(function(modal) {
-                    // Handle approve event.
-                    modal.getRoot().on(DataPrivacyEvents.approve, function() {
-                        showConfirmation(DataPrivacyEvents.approve, requestId);
-                    });
-
-                    // Handle deny event.
-                    modal.getRoot().on(DataPrivacyEvents.deny, function() {
-                        showConfirmation(DataPrivacyEvents.deny, requestId);
-                    });
-
-                    // Handle hidden event.
-                    modal.getRoot().on(ModalEvents.hidden, function() {
-                        // Destroy when hidden.
-                        modal.destroy();
-                    });
-
-                    return modal;
+                    title: data.typename,
+                    body: body,
+                    type: ModalDataRequest.TYPE,
+                    large: true,
+                    templateContext: templateContext
+                });
+
+            }).then(function(modal) {
+                // Handle approve event.
+                modal.getRoot().on(DataPrivacyEvents.approve, function() {
+                    showConfirmation(DataPrivacyEvents.approve, requestId);
+                });
+
+                // Handle deny event.
+                modal.getRoot().on(DataPrivacyEvents.deny, function() {
+                    showConfirmation(DataPrivacyEvents.deny, requestId);
                 });
-            }).done(function(modal) {
+
+                // Handle send event.
+                modal.getRoot().on(DataPrivacyEvents.complete, function() {
+                    var params = {
+                        'requestid': requestId
+                    };
+                    handleSave('tool_dataprivacy_mark_complete', params);
+                });
+
+                // Handle hidden event.
+                modal.getRoot().on(ModalEvents.hidden, function() {
+                    // Destroy when hidden.
+                    modal.destroy();
+                });
+
                 // Show the modal!
                 modal.show();
-            }).fail(Notification.exception);
+
+                return;
+
+            }).catch(Notification.exception);
         });
 
         $(ACTIONS.APPROVE_REQUEST).click(function(e) {
@@ -135,6 +146,11 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
             var requestId = $(this).data('requestid');
             showConfirmation(DataPrivacyEvents.deny, requestId);
         });
+
+        $(ACTIONS.MARK_COMPLETE).click(function(e) {
+            e.preventDefault();
+            showConfirmation(DataPrivacyEvents.complete, $(this).data('requestid'));
+        });
     };
 
     /**
@@ -146,6 +162,9 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
     function showConfirmation(action, requestId) {
         var keys = [];
         var wsfunction = '';
+        var params = {
+            'requestid': requestId
+        };
         switch (action) {
             case DataPrivacyEvents.approve:
                 keys = [
@@ -173,6 +192,19 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
                 ];
                 wsfunction = 'tool_dataprivacy_deny_data_request';
                 break;
+            case DataPrivacyEvents.complete:
+                keys = [
+                    {
+                        key: 'markcomplete',
+                        component: 'tool_dataprivacy'
+                    },
+                    {
+                        key: 'confirmcompletion',
+                        component: 'tool_dataprivacy'
+                    }
+                ];
+                wsfunction = 'tool_dataprivacy_mark_complete';
+                break;
         }
 
         var modalTitle = '';
@@ -189,26 +221,7 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
 
             // Handle save event.
             modal.getRoot().on(ModalEvents.save, function() {
-                // Confirm the request.
-                var params = {
-                    'requestid': requestId
-                };
-
-                var request = {
-                    methodname: wsfunction,
-                    args: params
-                };
-
-                Ajax.call([request])[0].done(function(data) {
-                    if (data.result) {
-                        window.location.reload();
-                    } else {
-                        Notification.addNotification({
-                            message: data.warnings[0].message,
-                            type: 'error'
-                        });
-                    }
-                }).fail(Notification.exception);
+                handleSave(wsfunction, params);
             });
 
             // Handle hidden event.
@@ -217,9 +230,39 @@ function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Modal
                 modal.destroy();
             });
 
-            return modal;
-        }).done(function(modal) {
             modal.show();
+
+            return;
+
+        }).catch(Notification.exception);
+    }
+
+    /**
+     * Calls a web service function and reloads the page on success and shows a notification.
+     * Displays an error notification, otherwise.
+     *
+     * @param {String} wsfunction The web service function to call.
+     * @param {Object} params The parameters for the web service functoon.
+     */
+    function handleSave(wsfunction, params) {
+        // Confirm the request.
+        var request = {
+            methodname: wsfunction,
+            args: params
+        };
+
+        Ajax.call([request])[0].done(function(data) {
+            if (data.result) {
+                // On success, reload the page so that the data request table will be updated.
+                // TODO: Probably in the future, better to reload the table or the target data request via AJAX.
+                window.location.reload();
+            } else {
+                // Add the notification.
+                Notification.addNotification({
+                    message: data.warnings[0].message,
+                    type: 'error'
+                });
+            }
         }).fail(Notification.exception);
     }
 
index 42acb42..8ec0bb5 100644 (file)
@@ -24,6 +24,7 @@
 namespace tool_dataprivacy;
 
 use coding_exception;
+use context_course;
 use context_system;
 use core\invalid_persistent_exception;
 use core\message\message;
@@ -426,7 +427,22 @@ class api {
         if ($dpoid) {
             $datarequest->set('dpo', $dpoid);
         }
-        $datarequest->set('dpocomment', $comment);
+        // Update the comment if necessary.
+        if (!empty(trim($comment))) {
+            $params = [
+                'date' => userdate(time()),
+                'comment' => $comment
+            ];
+            $commenttosave = get_string('datecomment', 'tool_dataprivacy', $params);
+            // Check if there's an existing DPO comment.
+            $currentcomment = trim($datarequest->get('dpocomment'));
+            if ($currentcomment) {
+                // Append the new comment to the current comment and give them 1 line space in between.
+                $commenttosave = $currentcomment . PHP_EOL . PHP_EOL . $commenttosave;
+            }
+            $datarequest->set('dpocomment', $commenttosave);
+        }
+
         return $datarequest->update();
     }
 
@@ -521,7 +537,6 @@ class api {
      * @param data_request $request The data request
      * @return int|false
      * @throws coding_exception
-     * @throws dml_exception
      * @throws moodle_exception
      */
     public static function notify_dpo($dpo, data_request $request) {
@@ -593,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 6587c40..e14e072 100644 (file)
@@ -31,6 +31,7 @@ use context_helper;
 use context_system;
 use context_user;
 use core\invalid_persistent_exception;
+use core\notification;
 use core_user;
 use dml_exception;
 use external_api;
@@ -144,7 +145,7 @@ class external extends external_api {
     }
 
     /**
-     * Deny a data request.
+     * Make a general enquiry to a DPO.
      *
      * @since Moodle 3.5
      * @param string $message The message to be sent to the DPO.
@@ -210,7 +211,7 @@ class external extends external_api {
     }
 
     /**
-     * Parameter description for deny_data_request().
+     * Parameter description for contact_dpo().
      *
      * @since Moodle 3.5
      * @return external_description
@@ -222,6 +223,70 @@ class external extends external_api {
         ]);
     }
 
+    /**
+     * Parameter description for mark_complete().
+     *
+     * @since Moodle 3.5.2
+     * @return external_function_parameters
+     */
+    public static function mark_complete_parameters() {
+        return new external_function_parameters([
+            'requestid' => new external_value(PARAM_INT, 'The request ID', VALUE_REQUIRED)
+        ]);
+    }
+
+    /**
+     * Mark a user's general enquiry's status as complete.
+     *
+     * @since Moodle 3.5.2
+     * @param int $requestid The request ID of the general enquiry.
+     * @return array
+     * @throws coding_exception
+     * @throws invalid_parameter_exception
+     * @throws invalid_persistent_exception
+     * @throws restricted_context_exception
+     * @throws dml_exception
+     * @throws moodle_exception
+     */
+    public static function mark_complete($requestid) {
+        global $USER;
+
+        $warnings = [];
+        $params = external_api::validate_parameters(self::mark_complete_parameters(), [
+            'requestid' => $requestid,
+        ]);
+        $requestid = $params['requestid'];
+
+        // Validate context.
+        $context = context_system::instance();
+        self::validate_context($context);
+
+        $message = get_string('markedcomplete', 'tool_dataprivacy');
+        // Update the data request record.
+        if ($result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE, $USER->id, $message)) {
+            // Add notification in the session to be shown when the page is reloaded on the JS side.
+            notification::success(get_string('requestmarkedcomplete', 'tool_dataprivacy'));
+        }
+
+        return [
+            'result' => $result,
+            'warnings' => $warnings
+        ];
+    }
+
+    /**
+     * Parameter description for mark_complete().
+     *
+     * @since Moodle 3.5.2
+     * @return external_description
+     */
+    public static function mark_complete_returns() {
+        return new external_single_structure([
+            'result' => new external_value(PARAM_BOOL, 'The processing result'),
+            'warnings' => new external_warnings()
+        ]);
+    }
+
     /**
      * Parameter description for get_data_request().
      *
@@ -258,9 +323,9 @@ class external extends external_api {
         // Validate context.
         $context = context_system::instance();
         self::validate_context($context);
+        $requestpersistent = new data_request($requestid);
         require_capability('tool/dataprivacy:managedatarequests', $context);
 
-        $requestpersistent = new data_request($requestid);
         $exporter = new data_request_exporter($requestpersistent, ['context' => $context]);
         $renderer = $PAGE->get_renderer('tool_dataprivacy');
         $result = $exporter->export($renderer);
@@ -326,6 +391,9 @@ class external extends external_api {
         $result = false;
         if ($requestexists) {
             $result = api::approve_data_request($requestid);
+
+            // Add notification in the session to be shown when the page is reloaded on the JS side.
+            notification::success(get_string('requestapproved', 'tool_dataprivacy'));
         } else {
             $warnings[] = [
                 'item' => $requestid,
@@ -395,6 +463,9 @@ class external extends external_api {
         $result = false;
         if ($requestexists) {
             $result = api::deny_data_request($requestid);
+
+            // Add notification in the session to be shown when the page is reloaded on the JS side.
+            notification::success(get_string('requestdenied', 'tool_dataprivacy'));
         } else {
             $warnings[] = [
                 'item' => $requestid,
index 9996349..93b33e3 100644 (file)
@@ -102,6 +102,16 @@ class data_request_exporter extends persistent_exporter {
                 'optional' => true,
                 'default' => false
             ],
+            'approvedeny' => [
+                'type' => PARAM_BOOL,
+                'optional' => true,
+                'default' => false
+            ],
+            'canmarkcomplete' => [
+                'type' => PARAM_BOOL,
+                'optional' => true,
+                'default' => false
+            ],
         ];
     }
 
@@ -140,14 +150,19 @@ class data_request_exporter extends persistent_exporter {
 
         $values['messagehtml'] = text_to_html($this->persistent->get('comments'));
 
-        $values['typename'] = helper::get_request_type_string($this->persistent->get('type'));
-        $values['typenameshort'] = helper::get_shortened_request_type_string($this->persistent->get('type'));
+        $requesttype = $this->persistent->get('type');
+        $values['typename'] = helper::get_request_type_string($requesttype);
+        $values['typenameshort'] = helper::get_shortened_request_type_string($requesttype);
 
         $values['canreview'] = false;
+        $values['approvedeny'] = false;
         $values['statuslabel'] = helper::get_request_status_string($this->persistent->get('status'));
+
         switch ($this->persistent->get('status')) {
             case api::DATAREQUEST_STATUS_PENDING:
                 $values['statuslabelclass'] = 'label-default';
+                // Request can be manually completed for general enquiry requests.
+                $values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
                 break;
             case api::DATAREQUEST_STATUS_PREPROCESSING:
                 $values['statuslabelclass'] = 'label-default';
@@ -156,6 +171,8 @@ class data_request_exporter extends persistent_exporter {
                 $values['statuslabelclass'] = 'label-info';
                 // DPO can review the request once it's ready.
                 $values['canreview'] = true;
+                // Whether the DPO can approve or deny the request.
+                $values['approvedeny'] = in_array($requesttype, [api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_TYPE_DELETE]);
                 break;
             case api::DATAREQUEST_STATUS_APPROVED:
                 $values['statuslabelclass'] = 'label-info';
index 97918d9..51bb133 100644 (file)
@@ -180,16 +180,40 @@ class data_requests_table extends table_sql {
         $actiontext = get_string('viewrequest', 'tool_dataprivacy');
         $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
 
-        if ($status == api::DATAREQUEST_STATUS_AWAITING_APPROVAL) {
-            // Approve.
-            $actiondata['data-action'] = 'approve';
-            $actiontext = get_string('approverequest', 'tool_dataprivacy');
-            $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
-
-            // Deny.
-            $actiondata['data-action'] = 'deny';
-            $actiontext = get_string('denyrequest', 'tool_dataprivacy');
-            $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+        switch ($status) {
+            case api::DATAREQUEST_STATUS_PENDING:
+                // Add action to mark a general enquiry request as complete.
+                if ($data->type == api::DATAREQUEST_TYPE_OTHERS) {
+                    $actiondata['data-action'] = 'complete';
+                    $nameemail = (object)[
+                        'name' => $data->foruser->fullname,
+                        'email' => $data->foruser->email
+                    ];
+                    $actiondata['data-requestid'] = $data->id;
+                    $actiondata['data-replytoemail'] = get_string('nameemail', 'tool_dataprivacy', $nameemail);
+                    $actiontext = get_string('markcomplete', 'tool_dataprivacy');
+                    $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+                }
+                break;
+            case api::DATAREQUEST_STATUS_AWAITING_APPROVAL:
+                // Approve.
+                $actiondata['data-action'] = 'approve';
+                $actiontext = get_string('approverequest', 'tool_dataprivacy');
+                $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+
+                // Deny.
+                $actiondata['data-action'] = 'deny';
+                $actiontext = get_string('denyrequest', 'tool_dataprivacy');
+                $actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
+                break;
+        }
+
+        if ($status == api::DATAREQUEST_STATUS_COMPLETE) {
+            $userid = $data->foruser->id;
+            $usercontext = \context_user::instance($userid, IGNORE_MISSING);
+            if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
+                $actions[] = api::get_download_link($usercontext, $requestid);
+            }
         }
 
         $actionsmenu = new action_menu($actions);
index 25229f0..d82968c 100644 (file)
@@ -82,6 +82,7 @@ class my_data_requests_page implements renderable, templatable {
             $requestid = $request->get('id');
             $status = $request->get('status');
             $userid = $request->get('userid');
+            $type = $request->get('type');
 
             $usercontext = context_user::instance($userid, IGNORE_MISSING);
             if (!$usercontext) {
@@ -94,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);
@@ -107,7 +109,12 @@ class my_data_requests_page implements renderable, templatable {
                     $item->statuslabelclass = 'label-success';
                     $item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
                     $cancancel = false;
-                    $candownload = true;
+                    // Show download links only for export-type data requests.
+                    $candownload = $type == api::DATAREQUEST_TYPE_EXPORT;
+                    if ($usercontext) {
+                        $candownload = api::can_download_data_request_for_user(
+                                $request->get('userid'), $request->get('requestedby'));
+                    }
                     break;
                 case api::DATAREQUEST_STATUS_CANCELLED:
                 case api::DATAREQUEST_STATUS_REJECTED:
@@ -124,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' => []
+    ],
 ];
index b72b201..9c71e8c 100644 (file)
@@ -43,6 +43,16 @@ $functions = [
         'ajax'          => true,
         'loginrequired' => true,
     ],
+    'tool_dataprivacy_mark_complete' => [
+        'classname'     => 'tool_dataprivacy\external',
+        'methodname'    => 'mark_complete',
+        'classpath'     => '',
+        'description'   => 'Mark a user\'s general enquiry as complete',
+        'type'          => 'write',
+        'capabilities'  => 'tool/dataprivacy:managedatarequests',
+        'ajax'          => true,
+        'loginrequired' => true,
+    ],
     'tool_dataprivacy_get_data_request' => [
         'classname'     => 'tool_dataprivacy\external',
         'methodname'    => 'get_data_request',
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 aa64047..4af32a8 100644 (file)
@@ -46,6 +46,7 @@ $string['categoryupdated'] = 'Category updated';
 $string['close'] = 'Close';
 $string['compliant'] = 'Compliant';
 $string['confirmapproval'] = 'Do you really want to approve this data request?';
+$string['confirmcompletion'] = 'Do you really want to mark this user enquiry as complete?';
 $string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
 $string['confirmdenial'] = 'Do you really want deny this data request?';
 $string['contactdataprotectionofficer'] = 'Contact the privacy officer';
@@ -65,11 +66,14 @@ $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}';
 $string['datarequestemailsubject'] = 'Data request: {$a}';
 $string['datarequests'] = 'Data requests';
+$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
 $string['daterequested'] = 'Date requested';
 $string['daterequesteddetail'] = 'Date requested:';
 $string['defaultsinfo'] = 'Default categories and purposes are applied to all newly created instances.';
@@ -151,6 +155,8 @@ $string['httpwarning'] = 'Any data downloaded from this site may not be encrypte
 $string['inherit'] = 'Inherit';
 $string['lawfulbases'] = 'Lawful bases';
 $string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
+$string['markcomplete'] = 'Mark as complete';
+$string['markedcomplete'] = 'Your enquiry has been marked as complete by the privacy officer.';
 $string['messageprovider:contactdataprotectionofficer'] = 'Data requests';
 $string['messageprovider:datarequestprocessingresults'] = 'Data request processing results';
 $string['messageprovider:notifyexceptions'] = 'Data requests exceptions notifications';
@@ -197,12 +203,15 @@ $string['purposeslist'] = 'List of data purposes';
 $string['purposeupdated'] = 'Purpose updated';
 $string['replyto'] = 'Reply to';
 $string['requestactions'] = 'Actions';
+$string['requestapproved'] = 'The request has been approved';
 $string['requestby'] = 'Requested by';
 $string['requestbydetail'] = 'Requested by:';
 $string['requestcomments'] = 'Comments';
 $string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
+$string['requestdenied'] = 'The request has been denied';
 $string['requestemailintro'] = 'You have received a data request:';
 $string['requestfor'] = 'Requesting for';
+$string['requestmarkedcomplete'] = 'The request has been marked as complete';
 $string['requeststatus'] = 'Status';
 $string['requestsubmitted'] = 'Your request has been submitted to the privacy officer';
 $string['requesttype'] = 'Type';
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 cae5022..e5a1ff9 100644 (file)
 }}
 {{< core/modal }}
     {{$footer}}
-        <button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
-        <button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
+        {{#approvedeny}}
+            <button type="button" class="btn btn-primary" data-action="approve">{{#str}} approve, tool_dataprivacy {{/str}}</button>
+            <button type="button" class="btn btn-secondary" data-action="deny">{{#str}} deny, tool_dataprivacy {{/str}}</button>
+        {{/approvedeny}}
+        {{#canmarkcomplete}}
+            <button type="button" class="btn btn-primary" data-action="complete">{{#str}} markcomplete, tool_dataprivacy {{/str}}</button>
+        {{/canmarkcomplete}}
     {{/footer}}
 {{/ core/modal }}
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/contact_privacy_officer.feature b/admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature
new file mode 100644 (file)
index 0000000..e015d19
--- /dev/null
@@ -0,0 +1,26 @@
+@tool @tool_dataprivacy
+Feature: Contact the privacy officer
+  As a user
+  In order to reach out to the site's privacy officer
+  I need to be able to contact the site's privacy officer in Moodle
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email          |
+      | student1 | Student   | 1        | s1@example.com |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | contactdataprotectionofficer | 1 |
+    And I log out
+
+  @javascript
+  Scenario: Contacting the privacy officer
+    Given I log in as "student1"
+    And I follow "Profile" in the user menu
+    And I should see "Contact the privacy officer"
+    And I click on "Contact the privacy officer" "link"
+    And I set the field "Message" to "Hello DPO!"
+    And I press "Send"
+    And I should see "Your request has been submitted to the privacy officer"
+    And I click on "Data requests" "link"
+    And I should see "Hello DPO!" in the "General inquiry" "table_row"
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
diff --git a/admin/tool/dataprivacy/tests/behat/manage_data_requests.feature b/admin/tool/dataprivacy/tests/behat/manage_data_requests.feature
new file mode 100644 (file)
index 0000000..bdc43e7
--- /dev/null
@@ -0,0 +1,58 @@
+@tool @tool_dataprivacy
+Feature: Manage data requests
+  As the privacy officer
+  In order to address the privacy-related requests
+  I need to be able to manage the data requests of the site's users
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email          |
+      | student1 | John      | Doe      | s1@example.com |
+      | student2 | Jane      | Doe      | s2@example.com |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | contactdataprotectionofficer | 1 |
+    And I log out
+
+  @javascript
+  Scenario: Marking general enquiries as complete
+    Given I log in as "student1"
+    And I follow "Profile" in the user menu
+    And I should see "Contact the privacy officer"
+    And I click on "Contact the privacy officer" "link"
+    And I set the field "Message" to "Hi PO! Can others access my information on your site?"
+    And I press "Send"
+    And I should see "Your request has been submitted to the privacy officer"
+    And I log out
+    And I log in as "student2"
+    And I follow "Profile" in the user menu
+    And I click on "Contact the privacy officer" "link"
+    And I set the field "Message" to "Dear Mr. Privacy Officer, I'd like to know more about GDPR. Thanks!"
+    And I press "Send"
+    And I should see "Your request has been submitted to the privacy officer"
+    And I log out
+    When I log in as "admin"
+    And I navigate to "Users > Privacy and policies > Data requests" in site administration
+    Then I should see "Hi PO!" in the "John Doe" "table_row"
+    And I should see "Dear Mr. Privacy Officer" in the "Jane Doe" "table_row"
+    And I click on "Actions" "link" in the "John Doe" "table_row"
+    And I should see "View the request"
+    And I should see "Mark as complete"
+    And I choose "View the request" in the open action menu
+    And I should see "Hi PO! Can others access my information on your site?"
+    And I press "Mark as complete"
+    And I wait until the page is ready
+    And I should see "Complete" in the "John Doe" "table_row"
+    And I click on "Actions" "link" in the "John Doe" "table_row"
+    And I should see "View the request"
+    But I should not see "Mark as complete"
+    And I press key "27" in ".moodle-actionmenu" "css_element"
+    And I click on "Actions" "link" in the "Jane Doe" "table_row"
+    And I choose "Mark as complete" in the open action menu
+    And I should see "Do you really want to mark this user enquiry as complete?"
+    And I press "Mark as complete"
+    And I wait until the page is ready
+    And I should see "Complete" in the "Jane Doe" "table_row"
+    And I click on "Actions" "link" in the "Jane Doe" "table_row"
+    And I should see "View the request"
+    But I should not see "Mark as complete"
index 7b7bde4..f5c7977 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2018051401;
+$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 bcb26df..5c2fe8c 100644 (file)
@@ -473,6 +473,12 @@ class auth_plugin_db extends auth_plugin_base {
                     set_user_preference('auth_forcepasswordchange', 1, $id);
                     set_user_preference('create_password',          1, $id);
                 }
+
+                // Save custom profile fields here.
+                require_once($CFG->dirroot . '/user/profile/lib.php');
+                $user->id = $id;
+                profile_save_data($user);
+
                 // Make sure user context is present.
                 context_user::instance($id);
             }
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 7bb0bec..f514d4e 100644 (file)
@@ -120,85 +120,21 @@ WSDL;
 }
 /******************************************************************************/
 
-function LogoutNotification($SessionID){
-
-    global $CFG, $SESSION, $DB;
-
-    // Delete session of user using $SessionID
-    if(empty($CFG->dbsessions)) {
-
-        // File session
-        $dir = $CFG->dataroot .'/sessions';
-        if (is_dir($dir)) {
-            if ($dh = opendir($dir)) {
-                // Read all session files
-                while (($file = readdir($dh)) !== false) {
-                    // Check if it is a file
-                    if (is_file($dir.'/'.$file)){
-                        $session_key = preg_replace('/sess_/', '', $file);
-
-                        // Read session file data
-                        $data = file($dir.'/'.$file);
-                        if (isset($data[0])){
-                            $user_session = unserializesession($data[0]);
-
-                            // Check if we have found session that shall be deleted
-                            if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
-
-                                // If there is a match, delete file
-                                if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
-                                    // Delete session file
-                                    if (!unlink($dir.'/'.$file)){
-                                        return new SoapFault('LogoutError', 'Could not delete Moodle session file.');
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                closedir($dh);
-            }
-        }
-    } else {
-        // DB Session
-        //TODO: this needs to be rewritten to use new session stuff
-        if (!empty($CFG->sessiontimeout)) {
-            $ADODB_SESS_LIFE   = $CFG->sessiontimeout;
-        }
-
-            if ($user_session_data = $DB->get_records_sql('SELECT sesskey, sessdata FROM {sessions2} WHERE expiry > NOW()')) {
-            foreach ($user_session_data as $session_data) {
-
-                // Get user session
-                $user_session = adodb_unserialize( urldecode($session_data->sessdata) );
-
-                if (isset($user_session['SESSION']) && isset($user_session['SESSION']->shibboleth_session_id)){
-
-                    // If there is a match, delete file
-                    if ($user_session['SESSION']->shibboleth_session_id == $SessionID){
-                        // Delete this session entry
-                        if (ADODB_Session::destroy($session_data->sesskey) !== true){
-                            return new SoapFault('LogoutError', 'Could not delete Moodle session entry in database.');
-                        }
-                    }
-                }
-            }
-        }
+/**
+ * Handles SOAP Back-channel logout notification
+ *
+ * @param string $spsessionid SP-provided Shibboleth Session ID
+ * @return SoapFault or void if everything was fine
+ */
+function LogoutNotification($spsessionid) {
+    $sessionclass = \core\session\manager::get_handler_class();
+    switch ($sessionclass) {
+        case '\core\session\file':
+            return \auth_shibboleth\helper::logout_file_session($spsessionid);
+        case '\core\session\database':
+            return \auth_shibboleth\helper::logout_db_session($spsessionid);
+        default:
+            throw new moodle_exception("Shibboleth logout not implemented for '$sessionclass'");
     }
-
-    // If now SoapFault was thrown the function will return OK as the SP assumes
-
-}
-
-/*****************************************************************************/
-
-// Same function as in adodb, but cannot be used for file session for some reason...
-function unserializesession($serialized_string) {
-    $variables = array();
-    $a = preg_split("/(\w+)\|/", $serialized_string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-    $counta = count($a);
-    for ($i = 0; $i < $counta; $i = $i+2) {
-            $variables[$a[$i]] = unserialize($a[$i+1]);
-    }
-    return $variables;
+    // If no SoapFault was thrown, the function will return OK as the SP assumes.
 }
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 fc62cfc..e61b89a 100644 (file)
@@ -730,9 +730,16 @@ abstract class backup_cron_automated_helper {
     protected static function is_course_modified($courseid, $since) {
         $logmang = get_log_manager();
         $readers = $logmang->get_readers('core\log\sql_reader');
-        $where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
         $params = array('courseid' => $courseid, 'since' => $since);
-        foreach ($readers as $reader) {
+
+        foreach ($readers as $readerpluginname => $reader) {
+            $where = "courseid = :courseid and timecreated > :since and crud <> 'r'";
+
+            // Prevent logs of prevous backups causing a false positive.
+            if ($readerpluginname != 'logstore_legacy') {
+                $where .= " and target <> 'course_backup'";
+            }
+
             if ($reader->get_events_select_count($where, $params)) {
                 return true;
             }
index 320daac..274e6dd 100644 (file)
@@ -27,6 +27,8 @@ defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
 require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
+require_once($CFG->dirroot . '/backup/util/interfaces/checksumable.class.php');
+require_once("$CFG->dirroot/backup/backup.class.php");
 
 /**
  * Unit tests for backup cron helper
@@ -320,6 +322,48 @@ class backup_cron_helper_testcase extends advanced_testcase {
         $this->assertArrayHasKey('1000432000', $backupfiles);
         $this->assertEquals('file3.mbz', $backupfiles['1000432000']);
     }
+
+    /**
+     * Test {@link backup_cron_automated_helper::is_course_modified}.
+     */
+    public function test_is_course_modified() {
+        $this->resetAfterTest();
+        $this->preventResetByRollback();
+
+        set_config('enabled_stores', 'logstore_standard', 'tool_log');
+        set_config('buffersize', 0, 'logstore_standard');
+        set_config('logguests', 1, 'logstore_standard');
+
+        $course = $this->getDataGenerator()->create_course();
+
+        // New courses should be backed up.
+        $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, 0));
+
+        $timepriortobackup = time();
+        $this->waitForSecond();
+        $otherarray = [
+            'format' => backup::FORMAT_MOODLE,
+            'mode' => backup::MODE_GENERAL,
+            'interactive' => backup::INTERACTIVE_YES,
+            'type' => backup::TYPE_1COURSE,
+        ];
+        $event = \core\event\course_backup_created::create([
+            'objectid' => $course->id,
+            'context'  => context_course::instance($course->id),
+            'other'    => $otherarray
+        ]);
+        $event->trigger();
+
+        // If the only action since last backup was a backup then no backup.
+        $this->assertFalse(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
+
+        $course->groupmode = SEPARATEGROUPS;
+        $course->groupmodeforce = true;
+        update_course($course);
+
+        // Updated courses should be backed up.
+        $this->assertTrue(testable_backup_cron_automated_helper::testable_is_course_modified($course->id, $timepriortobackup));
+    }
 }
 
 /**
@@ -340,4 +384,17 @@ class testable_backup_cron_automated_helper extends backup_cron_automated_helper
     public static function testable_get_backups_to_delete($backupfiles, $now) {
         return parent::get_backups_to_delete($backupfiles, $now);
     }
+
+    /**
+     * Provides access to protected method get_backups_to_remove.
+     *
+     * @param int $courseid course id to check
+     * @param int $since timestamp, from which to check
+     *
+     * @return bool true if the course was modified, false otherwise. This also returns false if no readers are enabled. This is
+     * intentional, since we cannot reliably determine if any modification was made or not.
+     */
+    public static function testable_is_course_modified($courseid, $since) {
+        return parent::is_course_modified($courseid, $since);
+    }
 }
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;
             }