Merge branch 'wip-MDL-61477-master-behat' of https://github.com/marinaglancy/moodle
authorJake Dallimore <jake@moodle.com>
Thu, 15 Mar 2018 08:44:02 +0000 (16:44 +0800)
committerJake Dallimore <jake@moodle.com>
Thu, 15 Mar 2018 08:44:02 +0000 (16:44 +0800)
232 files changed:
admin/category.php
admin/settings/plugins.php
admin/settings/privacy.php [new file with mode: 0644]
admin/settings/security.php
admin/settings/top.php
admin/tool/behat/renderer.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/tests/externallib_test.php
auth/classes/external.php
auth/email/classes/external.php
auth/ldap/auth.php
auth/oauth2/classes/auth.php
auth/tests/behat/validateagedigitalconsentmap.feature
auth/tests/external_test.php
auth/upgrade.txt
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/fixtures/question_category_34_format.mbz [new file with mode: 0644]
backup/moodle2/tests/fixtures/question_category_35_format.mbz [new file with mode: 0644]
backup/moodle2/tests/moodle2_test.php
badges/backpack_form.php
blocks/activity_modules/classes/privacy/provider.php [new file with mode: 0644]
blocks/activity_modules/lang/en/block_activity_modules.php
blocks/activity_results/classes/privacy/provider.php [new file with mode: 0644]
blocks/activity_results/lang/en/block_activity_results.php
blocks/admin_bookmarks/classes/privacy/provider.php [new file with mode: 0644]
blocks/admin_bookmarks/lang/en/block_admin_bookmarks.php
blocks/badges/classes/privacy/provider.php [new file with mode: 0644]
blocks/badges/lang/en/block_badges.php
blocks/blog_menu/classes/privacy/provider.php [new file with mode: 0644]
blocks/blog_menu/lang/en/block_blog_menu.php
blocks/blog_recent/classes/privacy/provider.php [new file with mode: 0644]
blocks/blog_recent/lang/en/block_blog_recent.php
blocks/blog_tags/classes/privacy/provider.php [new file with mode: 0644]
blocks/blog_tags/lang/en/block_blog_tags.php
blocks/calendar_month/classes/privacy/provider.php [new file with mode: 0644]
blocks/calendar_month/lang/en/block_calendar_month.php
blocks/calendar_upcoming/classes/privacy/provider.php [new file with mode: 0644]
blocks/calendar_upcoming/lang/en/block_calendar_upcoming.php
blocks/completionstatus/classes/privacy/provider.php [new file with mode: 0644]
blocks/completionstatus/lang/en/block_completionstatus.php
blocks/course_list/classes/privacy/provider.php [new file with mode: 0644]
blocks/course_list/lang/en/block_course_list.php
blocks/course_summary/classes/privacy/provider.php [new file with mode: 0644]
blocks/course_summary/lang/en/block_course_summary.php
blocks/feedback/classes/privacy/provider.php [new file with mode: 0644]
blocks/feedback/lang/en/block_feedback.php
blocks/globalsearch/classes/privacy/provider.php [new file with mode: 0644]
blocks/globalsearch/lang/en/block_globalsearch.php
blocks/glossary_random/classes/privacy/provider.php [new file with mode: 0644]
blocks/glossary_random/lang/en/block_glossary_random.php
blocks/html/classes/privacy/provider.php [new file with mode: 0644]
blocks/html/lang/en/block_html.php
blocks/html/tests/privacy_provider_test.php [new file with mode: 0644]
blocks/login/classes/privacy/provider.php [new file with mode: 0644]
blocks/login/lang/en/block_login.php
blocks/lp/classes/privacy/provider.php [new file with mode: 0644]
blocks/lp/lang/en/block_lp.php
blocks/mentees/classes/privacy/provider.php [new file with mode: 0644]
blocks/mentees/lang/en/block_mentees.php
blocks/mnet_hosts/classes/privacy/provider.php [new file with mode: 0644]
blocks/mnet_hosts/lang/en/block_mnet_hosts.php
blocks/myoverview/classes/privacy/provider.php [new file with mode: 0644]
blocks/myoverview/lang/en/block_myoverview.php
blocks/myprofile/classes/privacy/provider.php [new file with mode: 0644]
blocks/myprofile/lang/en/block_myprofile.php
blocks/navigation/classes/privacy/provider.php [new file with mode: 0644]
blocks/navigation/lang/en/block_navigation.php
blocks/news_items/classes/privacy/provider.php [new file with mode: 0644]
blocks/news_items/lang/en/block_news_items.php
blocks/online_users/classes/privacy/provider.php [new file with mode: 0644]
blocks/online_users/lang/en/block_online_users.php
blocks/participants/classes/privacy/provider.php [new file with mode: 0644]
blocks/participants/lang/en/block_participants.php
blocks/private_files/classes/privacy/provider.php [new file with mode: 0644]
blocks/private_files/lang/en/block_private_files.php
blocks/quiz_results/classes/privacy/provider.php [new file with mode: 0644]
blocks/quiz_results/lang/en/block_quiz_results.php
blocks/search_forums/classes/privacy/provider.php [new file with mode: 0644]
blocks/search_forums/lang/en/block_search_forums.php
blocks/section_links/classes/privacy/provider.php [new file with mode: 0644]
blocks/section_links/lang/en/block_section_links.php
blocks/selfcompletion/classes/privacy/provider.php [new file with mode: 0644]
blocks/selfcompletion/lang/en/block_selfcompletion.php
blocks/settings/classes/privacy/provider.php [new file with mode: 0644]
blocks/settings/lang/en/block_settings.php
blocks/site_main_menu/classes/privacy/provider.php [new file with mode: 0644]
blocks/site_main_menu/lang/en/block_site_main_menu.php
blocks/social_activities/classes/privacy/provider.php [new file with mode: 0644]
blocks/social_activities/lang/en/block_social_activities.php
blocks/tag_flickr/classes/privacy/provider.php [new file with mode: 0644]
blocks/tag_flickr/lang/en/block_tag_flickr.php
blocks/tag_youtube/classes/privacy/provider.php [new file with mode: 0644]
blocks/tag_youtube/lang/en/block_tag_youtube.php
blocks/tags/classes/privacy/provider.php [new file with mode: 0644]
blocks/tags/lang/en/block_tags.php
cohort/tests/behat/access_visible_cohorts.feature
comment/classes/privacy/provider.php [new file with mode: 0644]
comment/tests/privacy_test.php [new file with mode: 0644]
course/format/topics/db/upgrade.php
course/format/topics/version.php
course/format/weeks/db/upgrade.php
course/format/weeks/version.php
enrol/cohort/lib.php
enrol/database/lib.php
enrol/paypal/classes/util.php
enrol/paypal/ipn.php
enrol/paypal/lang/en/enrol_paypal.php
file.php
lang/en/admin.php
lang/en/auth.php
lang/en/comment.php [new file with mode: 0644]
lang/en/moodle.php
lang/en/plagiarism.php
lang/en/privacy.php [new file with mode: 0644]
lang/en/rating.php
lang/en/tag.php
lib/adminlib.php
lib/classes/component.php
lib/classes/form/persistent.php
lib/db/services.php
lib/dml/pgsql_native_moodle_database.php
lib/form/cohort.php
lib/form/recaptcha.php
lib/moodlelib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/recaptchalib_v2.php [new file with mode: 0644]
lib/templates/action_menu_trigger.mustache
lib/tests/component_test.php
lib/tests/fixtures/component_class_callback_example.php [new file with mode: 0644]
lib/tests/moodlelib_test.php
lib/tests/sample_questions_with_old_image_tag.ser [new file with mode: 0644]
lib/tests/sample_questions_with_old_image_tag.xml [new file with mode: 0644]
lib/tests/xmlize_test.php
lib/upgrade.txt
lib/xmlize.php
login/signup_form.php
message/output/popup/lib.php
mod/choice/classes/privacy/provider.php [new file with mode: 0644]
mod/choice/lang/en/choice.php
mod/choice/tests/privacy_provider_test.php [new file with mode: 0644]
mod/feedback/item/captcha/lib.php
mod/folder/classes/privacy/provider.php [new file with mode: 0644]
mod/folder/lang/en/folder.php
mod/label/classes/privacy/provider.php [new file with mode: 0644]
mod/label/lang/en/label.php
mod/lesson/locallib.php
mod/page/classes/privacy/provider.php [new file with mode: 0644]
mod/page/lang/en/page.php
mod/resource/classes/privacy/provider.php [new file with mode: 0644]
mod/resource/lang/en/resource.php
mod/url/classes/privacy/provider.php [new file with mode: 0644]
mod/url/lang/en/url.php
phpunit.xml.dist
plagiarism/classes/privacy/plagiarism_provider.php [new file with mode: 0644]
plagiarism/classes/privacy/provider.php [new file with mode: 0644]
privacy/classes/local/legacy_polyfill.php [new file with mode: 0644]
privacy/classes/local/metadata/collection.php [new file with mode: 0644]
privacy/classes/local/metadata/null_provider.php [new file with mode: 0644]
privacy/classes/local/metadata/provider.php [new file with mode: 0644]
privacy/classes/local/metadata/types/database_table.php [new file with mode: 0644]
privacy/classes/local/metadata/types/external_location.php [new file with mode: 0644]
privacy/classes/local/metadata/types/plugintype_link.php [new file with mode: 0644]
privacy/classes/local/metadata/types/subsystem_link.php [new file with mode: 0644]
privacy/classes/local/metadata/types/type.php [new file with mode: 0644]
privacy/classes/local/metadata/types/user_preference.php [new file with mode: 0644]
privacy/classes/local/request/approved_contextlist.php [new file with mode: 0644]
privacy/classes/local/request/content_writer.php [new file with mode: 0644]
privacy/classes/local/request/contextlist.php [new file with mode: 0644]
privacy/classes/local/request/contextlist_base.php [new file with mode: 0644]
privacy/classes/local/request/contextlist_collection.php [new file with mode: 0644]
privacy/classes/local/request/core_data_provider.php [new file with mode: 0644]
privacy/classes/local/request/core_user_data_provider.php [new file with mode: 0644]
privacy/classes/local/request/data_provider.php [new file with mode: 0644]
privacy/classes/local/request/helper.php [new file with mode: 0644]
privacy/classes/local/request/moodle_content_writer.php [new file with mode: 0644]
privacy/classes/local/request/plugin/provider.php [new file with mode: 0644]
privacy/classes/local/request/plugin/subplugin_provider.php [new file with mode: 0644]
privacy/classes/local/request/plugin/subsystem_provider.php [new file with mode: 0644]
privacy/classes/local/request/shared_data_provider.php [new file with mode: 0644]
privacy/classes/local/request/subsystem/plugin_provider.php [new file with mode: 0644]
privacy/classes/local/request/subsystem/provider.php [new file with mode: 0644]
privacy/classes/local/request/transform.php [new file with mode: 0644]
privacy/classes/local/request/user_preference_provider.php [new file with mode: 0644]
privacy/classes/local/request/writer.php [new file with mode: 0644]
privacy/classes/local/sitepolicy/default_handler.php [new file with mode: 0644]
privacy/classes/local/sitepolicy/handler.php [new file with mode: 0644]
privacy/classes/local/sitepolicy/manager.php [new file with mode: 0644]
privacy/classes/manager.php [new file with mode: 0644]
privacy/classes/privacy/provider.php [new file with mode: 0644]
privacy/classes/tests/provider_testcase.php [new file with mode: 0644]
privacy/classes/tests/request/approved_contextlist.php [new file with mode: 0644]
privacy/classes/tests/request/content_writer.php [new file with mode: 0644]
privacy/tests/approved_contextlist_test.php [new file with mode: 0644]
privacy/tests/collection_test.php [new file with mode: 0644]
privacy/tests/contextlist_base_test.php [new file with mode: 0644]
privacy/tests/contextlist_collection_test.php [new file with mode: 0644]
privacy/tests/contextlist_test.php [new file with mode: 0644]
privacy/tests/fixtures/logo.png [new file with mode: 0644]
privacy/tests/fixtures/mock_mod_with_user_data_provider.php [new file with mode: 0644]
privacy/tests/fixtures/mock_null_provider.php [new file with mode: 0644]
privacy/tests/fixtures/mock_plugin_subplugin_provider.php [new file with mode: 0644]
privacy/tests/fixtures/mock_provider.php [new file with mode: 0644]
privacy/tests/fixtures/mock_sitepolicy_handler.php [new file with mode: 0644]
privacy/tests/legacy_polyfill_test.php [new file with mode: 0644]
privacy/tests/manager_test.php [new file with mode: 0644]
privacy/tests/moodle_content_writer_test.php [new file with mode: 0644]
privacy/tests/request_helper_test.php [new file with mode: 0644]
privacy/tests/request_transform_test.php [new file with mode: 0644]
privacy/tests/sitepolicy_test.php [new file with mode: 0644]
privacy/tests/types_database_table_test.php [new file with mode: 0644]
privacy/tests/types_external_location_test.php [new file with mode: 0644]
privacy/tests/types_plugintype_link_test.php [new file with mode: 0644]
privacy/tests/types_subsystem_link_test.php [new file with mode: 0644]
privacy/tests/types_user_preference_test.php [new file with mode: 0644]
privacy/tests/writer_test.php [new file with mode: 0644]
question/classes/bank/view.php
rating/classes/phpunit/privacy_helper.php [new file with mode: 0644]
rating/classes/privacy/provider.php [new file with mode: 0644]
rating/tests/privacy_provider_test.php [new file with mode: 0644]
tag/classes/privacy/provider.php [new file with mode: 0644]
tag/tests/privacy_test.php [new file with mode: 0644]
theme/boost/templates/core/action_menu_trigger.mustache
user/classes/analytics/indicator/user_profile_set.php
user/classes/output/unified_filter.php
user/externallib.php
user/index.php
user/policy.php
user/renderer.php
version.php
webservice/recaptcha.php [new file with mode: 0644]

index c87022b..f15fafb 100644 (file)
@@ -75,7 +75,7 @@ if ($data = data_submitted() and confirm_sesskey()) {
 if ($PAGE->user_allowed_editing() && $adminediting != -1) {
     $USER->editing = $adminediting;
 }
-
+$buttons = null;
 if ($PAGE->user_allowed_editing()) {
     $url = clone($PAGE->url);
     if ($PAGE->user_is_editing()) {
@@ -129,7 +129,9 @@ if ($savebutton) {
 $visiblepathtosection = array_reverse($settingspage->visiblepath);
 $PAGE->set_title("$SITE->shortname: " . implode(": ",$visiblepathtosection));
 $PAGE->set_heading($SITE->fullname);
-$PAGE->set_button($buttons);
+if ($buttons) {
+    $PAGE->set_button($buttons);
+}
 
 echo $OUTPUT->header();
 
index 6abbe55..c55b1f1 100644 (file)
@@ -123,43 +123,6 @@ if ($hassiteconfig) {
     $temp->add($setting);
     $ADMIN->add('authsettings', $temp);
 
-    $options = array(
-        0 => get_string('no'),
-        1 => get_string('yes')
-    );
-    $url = new moodle_url('/admin/settings.php?section=supportcontact');
-    $url = $url->out();
-    $setting = new admin_setting_configselect('agedigitalconsentverification',
-        new lang_string('agedigitalconsentverification', 'admin'),
-        new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
-    $setting->set_force_ltr(true);
-    $temp->add($setting);
-
-    $setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
-        new lang_string('ageofdigitalconsentmap', 'admin'),
-        new lang_string('ageofdigitalconsentmap_desc', 'admin'),
-        // See {@link https://gdpr-info.eu/art-8-gdpr/}.
-        implode(PHP_EOL, [
-            '*, 16',
-            'AT, 14',
-            'CZ, 13',
-            'DE, 14',
-            'DK, 13',
-            'ES, 13',
-            'FI, 15',
-            'GB, 13',
-            'HU, 14',
-            'IE, 13',
-            'LT, 16',
-            'LU, 16',
-            'NL, 16',
-            'PL, 13',
-            'SE, 13',
-        ]),
-        PARAM_RAW
-    );
-    $temp->add($setting);
-
     $temp = new admin_externalpage('authtestsettings', get_string('testsettings', 'core_auth'), new moodle_url("/auth/test_settings.php"), 'moodle/site:config', true);
     $ADMIN->add('authsettings', $temp);
 
diff --git a/admin/settings/privacy.php b/admin/settings/privacy.php
new file mode 100644 (file)
index 0000000..87f8c52
--- /dev/null
@@ -0,0 +1,80 @@
+<?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 privacy and policies links to admin tree.
+ *
+ * @package   core_privacy
+ * @copyright 2018 Marina Glancy
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+    // Privacy settings.
+    $temp = new admin_settingpage('privacysettings', new lang_string('privacysettings', 'admin'));
+
+    $options = array(
+        0 => get_string('no'),
+        1 => get_string('yes')
+    );
+    $url = new moodle_url('/admin/settings.php?section=supportcontact');
+    $url = $url->out();
+    $setting = new admin_setting_configselect('agedigitalconsentverification',
+        new lang_string('agedigitalconsentverification', 'admin'),
+        new lang_string('agedigitalconsentverification_desc', 'admin', $url), 0, $options);
+    $setting->set_force_ltr(true);
+    $temp->add($setting);
+
+    $setting = new admin_setting_agedigitalconsentmap('agedigitalconsentmap',
+        new lang_string('ageofdigitalconsentmap', 'admin'),
+        new lang_string('ageofdigitalconsentmap_desc', 'admin'),
+        // See {@link https://gdpr-info.eu/art-8-gdpr/}.
+        implode(PHP_EOL, [
+            '*, 16',
+            'AT, 14',
+            'CZ, 13',
+            'DE, 14',
+            'DK, 13',
+            'ES, 13',
+            'FI, 15',
+            'GB, 13',
+            'HU, 14',
+            'IE, 13',
+            'LT, 16',
+            'LU, 16',
+            'NL, 16',
+            'PL, 13',
+            'SE, 13',
+        ]),
+        PARAM_RAW
+    );
+    $temp->add($setting);
+
+    $ADMIN->add('privacy', $temp);
+
+    // Policy settings.
+    $temp = new admin_settingpage('policysettings', new lang_string('policysettings', 'admin'));
+    $temp->add(new admin_settings_sitepolicy_handler_select('sitepolicyhandler', new lang_string('sitepolicyhandler', 'core_admin'),
+        new lang_string('sitepolicyhandler_desc', 'core_admin')));
+    $temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'core_admin'),
+        new lang_string('sitepolicy_help', 'core_admin'), '', PARAM_RAW));
+    $temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'core_admin'),
+        new lang_string('sitepolicyguest_help', 'core_admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
+
+    $ADMIN->add('privacy', $temp);
+}
index 56b727a..a6ac61f 100644 (file)
@@ -54,8 +54,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
                        3600 => new lang_string('numminutes', '', 60))));
 
     $temp->add(new admin_setting_configcheckbox('extendedusernamechars', new lang_string('extendedusernamechars', 'admin'), new lang_string('configextendedusernamechars', 'admin'), 0));
-    $temp->add(new admin_setting_configtext('sitepolicy', new lang_string('sitepolicy', 'admin'), new lang_string('sitepolicy_help', 'admin'), '', PARAM_RAW));
-    $temp->add(new admin_setting_configtext('sitepolicyguest', new lang_string('sitepolicyguest', 'admin'), new lang_string('sitepolicyguest_help', 'admin'), (isset($CFG->sitepolicy) ? $CFG->sitepolicy : ''), PARAM_RAW));
+
     $temp->add(new admin_setting_configcheckbox('extendedusernamechars', new lang_string('extendedusernamechars', 'admin'), new lang_string('configextendedusernamechars', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('keeptagnamecase', new lang_string('keeptagnamecase','admin'),new lang_string('configkeeptagnamecase', 'admin'),'1'));
 
index 17621cb..a049ce9 100644 (file)
@@ -29,6 +29,7 @@ $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empt
 $ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
 $ADMIN->add('root', new admin_category('language', new lang_string('language')));
 $ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
+$ADMIN->add('root', new admin_category('privacy', new lang_string('privacyandpolicies', 'admin')));
 $ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
 $ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
 $ADMIN->add('root', new admin_category('frontpage', new lang_string('frontpage','admin')));
index 9bcda1f..bf2b7a1 100644 (file)
@@ -139,7 +139,7 @@ class tool_behat_renderer extends plugin_renderer_base {
         $msg = get_string('wrongbehatsetup', 'tool_behat', $a);
 
         // Error box including generic error string + specific error msg.
-        $html .= $this->output->box_start('box errorbox');
+        $html .= $this->output->box_start('box errorbox alert alert-danger');
         $html .= html_writer::tag('div', $msg);
         $html .= $this->output->box_end();
 
index e70910b..4841569 100644 (file)
@@ -149,6 +149,8 @@ class api {
             'maintenancemessage' => $maintenancemessage,
             'mobilecssurl' => !empty($CFG->mobilecssurl) ? $CFG->mobilecssurl : '',
             'tool_mobile_disabledfeatures' => get_config('tool_mobile', 'disabledfeatures'),
+            'country' => clean_param($CFG->country, PARAM_NOTAGS),
+            'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(),
         );
 
         $typeoflogin = get_config('tool_mobile', 'typeoflogin');
@@ -180,6 +182,12 @@ class api {
             $settings['identityproviders'] = $identityprovidersdata;
         }
 
+        // If age is verified, return also the admin contact details.
+        if ($settings['agedigitalconsentverification']) {
+            $settings['supportname'] = clean_param($CFG->supportname, PARAM_NOTAGS);
+            $settings['supportemail'] = clean_param($CFG->supportemail, PARAM_EMAIL);
+        }
+
         return $settings;
     }
 
@@ -223,7 +231,9 @@ class api {
         }
 
         if (empty($section) or $section == 'sitepolicies') {
-            $settings->sitepolicy = $CFG->sitepolicy;
+            $manager = new \core_privacy\local\sitepolicy\manager();
+            $settings->sitepolicy = ($sitepolicy = $manager->get_embed_url()) ? $sitepolicy->out(false) : '';
+            $settings->sitepolicyhandler = $CFG->sitepolicyhandler;
             $settings->disableuserimages = $CFG->disableuserimages;
         }
 
index d54d748..65a5ccf 100644 (file)
@@ -161,6 +161,13 @@ class external extends external_api {
                     ),
                     'Identity providers', VALUE_OPTIONAL
                 ),
+                'country' => new external_value(PARAM_NOTAGS, 'Default site country', VALUE_OPTIONAL),
+                'agedigitalconsentverification' => new external_value(PARAM_BOOL, 'Whether age digital consent verification
+                    is enabled.', VALUE_OPTIONAL),
+                'supportname' => new external_value(PARAM_NOTAGS, 'Site support contact name
+                    (only if age verification is enabled).', VALUE_OPTIONAL),
+                'supportemail' => new external_value(PARAM_EMAIL, 'Site support contact email
+                    (only if age verification is enabled).', VALUE_OPTIONAL),
                 'warnings' => new external_warnings(),
             )
         );
index ff1f3c1..19483ee 100644 (file)
@@ -86,6 +86,8 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
             'mobilecssurl' => '',
             'tool_mobile_disabledfeatures' => '',
             'launchurl' => "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php",
+            'country' => $CFG->country,
+            'agedigitalconsentverification' => \core_auth\digital_consent::is_age_digital_consent_verification_enabled(),
             'warnings' => array()
         );
         $this->assertEquals($expected, $result);
@@ -98,12 +100,16 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         set_config('logo', 'mock.png', 'core_admin');
         set_config('logocompact', 'mock.png', 'core_admin');
         set_config('forgottenpasswordurl', 'mailto:fake@email.zy'); // Test old hack.
+        set_config('agedigitalconsentverification', 1);
 
         list($authinstructions, $notusedformat) = external_format_text($authinstructions, FORMAT_MOODLE, $context->id);
         $expected['registerauth'] = 'email';
         $expected['authinstructions'] = $authinstructions;
         $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
         $expected['forgottenpasswordurl'] = ''; // Expect empty when it's not an URL.
+        $expected['agedigitalconsentverification'] = true;
+        $expected['supportname'] = $CFG->supportname;
+        $expected['supportemail'] = $CFG->supportemail;
 
         if ($logourl = $OUTPUT->get_logo_url()) {
             $expected['logourl'] = $logourl->out(false);
@@ -150,6 +156,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
             array('name' => 'newsitems', 'value' => $SITE->newsitems),
             array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage),
             array('name' => 'sitepolicy', 'value' => $mysitepolicy),
+            array('name' => 'sitepolicyhandler', 'value' => ''),
             array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages),
             array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
             array('name' => 'tool_mobile_forcelogout', 'value' => 0),
index bc21cbc..7c08349 100644 (file)
@@ -287,4 +287,53 @@ class core_auth_external extends external_api {
             )
         );
     }
+
+    /**
+     * Describes the parameters for is_age_digital_consent_verification_enabled.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.3
+     */
+    public static function is_age_digital_consent_verification_enabled_parameters() {
+        return new external_function_parameters(array());
+    }
+
+    /**
+     * Checks if age digital consent verification is enabled.
+     *
+     * @return array status (true if digital consent verification is enabled, false otherwise.)
+     * @since Moodle 3.3
+     * @throws moodle_exception
+     */
+    public static function is_age_digital_consent_verification_enabled() {
+        global $PAGE;
+
+        $context = context_system::instance();
+        $PAGE->set_context($context);
+
+        $status = false;
+        // Check if verification is enabled.
+        if (\core_auth\digital_consent::is_age_digital_consent_verification_enabled()) {
+            $status = true;
+        }
+
+        return array(
+            'status' => $status
+        );
+    }
+
+    /**
+     * Describes the is_age_digital_consent_verification_enabled return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.3
+     */
+    public static function is_age_digital_consent_verification_enabled_returns() {
+        return new external_single_structure(
+            array(
+                'status' => new external_value(PARAM_BOOL, 'True if digital consent verification is enabled,
+                    false otherwise.')
+            )
+        );
+    }
 }
index a36ceb3..a51aa69 100644 (file)
@@ -88,8 +88,12 @@ class auth_email_external extends external_api {
         if (!empty($CFG->passwordpolicy)) {
             $result['passwordpolicy'] = print_password_policy();
         }
-        if (!empty($CFG->sitepolicy)) {
-            $result['sitepolicy'] = $CFG->sitepolicy;
+        $manager = new \core_privacy\local\sitepolicy\manager();
+        if ($sitepolicy = $manager->get_embed_url()) {
+            $result['sitepolicy'] = $sitepolicy->out(false);
+        }
+        if (!empty($CFG->sitepolicyhandler)) {
+            $result['sitepolicyhandler'] = $CFG->sitepolicyhandler;
         }
         if (!empty($CFG->defaultcity)) {
             $result['defaultcity'] = $CFG->defaultcity;
@@ -112,11 +116,11 @@ class auth_email_external extends external_api {
         }
 
         if (signup_captcha_enabled()) {
-            require_once($CFG->libdir . '/recaptchalib.php');
-            // We return the public key, maybe we want to use the javascript api to get the image.
+            // With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey.
+            // For now include placeholders for the v1 paramaters to support older mobile app versions.
             $result['recaptchapublickey'] = $CFG->recaptchapublickey;
             list($result['recaptchachallengehash'], $result['recaptchachallengeimage'], $result['recaptchachallengejs']) =
-                recaptcha_get_challenge_hash_and_urls(RECAPTCHA_API_SECURE_SERVER, $CFG->recaptchapublickey);
+                array('', '', '');
         }
 
         $result['warnings'] = array();
@@ -138,6 +142,7 @@ class auth_email_external extends external_api {
                 ),
                 'passwordpolicy' => new external_value(PARAM_RAW, 'Password policy', VALUE_OPTIONAL),
                 'sitepolicy' => new external_value(PARAM_RAW, 'Site policy', VALUE_OPTIONAL),
+                'sitepolicyhandler' => new external_value(PARAM_PLUGIN, 'Site policy handler', VALUE_OPTIONAL),
                 'defaultcity' => new external_value(PARAM_NOTAGS, 'Default city', VALUE_OPTIONAL),
                 'country' => new external_value(PARAM_ALPHA, 'Default country', VALUE_OPTIONAL),
                 'profilefields' => new external_multiple_structure(
@@ -287,7 +292,8 @@ class auth_email_external extends external_api {
         $data = $params;
         $data['email2'] = $data['email'];
         // Force policy agreed if a site policy is set. The client is responsible of implementing the interface check.
-        if (!empty($CFG->sitepolicy)) {
+        $manager = new \core_privacy\local\sitepolicy\manager();
+        if (!$manager->is_defined()) {
             $data['policyagreed'] = 1;
         }
         unset($data['recaptcharesponse']);
@@ -307,11 +313,11 @@ class auth_email_external extends external_api {
 
         // Validate recaptcha.
         if (signup_captcha_enabled()) {
-            require_once($CFG->libdir . '/recaptchalib.php');
-            $response = recaptcha_check_answer($CFG->recaptchaprivatekey, getremoteaddr(), $params['recaptchachallengehash'],
-                                               $params['recaptcharesponse'], true);
-            if (!$response->is_valid) {
-                $errors['recaptcharesponse'] = $response->error;
+            require_once($CFG->libdir . '/recaptchalib_v2.php');
+            $response = recaptcha_check_response(RECAPTCHA_VERIFY_URL, $CFG->recaptchaprivatekey,
+                                                 getremoteaddr(), $params['recaptcharesponse']);
+            if (!$response['isvalid']) {
+                $errors['recaptcharesponse'] = $response['error'];
             }
         }
 
index e7b5eb7..024fe2f 100644 (file)
@@ -164,17 +164,15 @@ class auth_plugin_ldap extends auth_plugin_base {
         //
         $key = sesskey();
         if (!empty($this->config->ntlmsso_enabled) && $key === $password) {
-            $cf = get_cache_flags($this->pluginconfig.'/ntlmsess');
+            $sessusername = get_cache_flag($this->pluginconfig.'/ntlmsess', $key);
             // We only get the cache flag if we retrieve it before
             // it expires (AUTH_NTLMTIMEOUT seconds).
-            if (!isset($cf[$key]) || $cf[$key] === '') {
+            if (empty($sessusername)) {
                 return false;
             }
 
-            $sessusername = $cf[$key];
             if ($username === $sessusername) {
                 unset($sessusername);
-                unset($cf);
 
                 // Check that the user is inside one of the configured LDAP contexts
                 $validuser = false;
@@ -1711,11 +1709,10 @@ class auth_plugin_ldap extends auth_plugin_base {
         global $CFG, $USER, $SESSION;
 
         $key = sesskey();
-        $cf = get_cache_flags($this->pluginconfig.'/ntlmsess');
-        if (!isset($cf[$key]) || $cf[$key] === '') {
+        $username = get_cache_flag($this->pluginconfig.'/ntlmsess', $key);
+        if (empty($username)) {
             return false;
         }
-        $username   = $cf[$key];
 
         // Here we want to trigger the whole authentication machinery
         // to make sure no step is bypassed...
index 1761768..67f1679 100644 (file)
@@ -403,7 +403,20 @@ class auth extends \auth_plugin_base {
         if (!empty($linkedlogin) && empty($linkedlogin->get('confirmtoken'))) {
             $mappeduser = get_complete_user_data('id', $linkedlogin->get('userid'));
 
-            if ($mappeduser && $mappeduser->confirmed) {
+            if ($mappeduser && $mappeduser->suspended) {
+                $failurereason = AUTH_LOGIN_SUSPENDED;
+                $event = \core\event\user_login_failed::create([
+                    'userid' => $mappeduser->id,
+                    'other' => [
+                        'username' => $userinfo['username'],
+                        'reason' => $failurereason
+                    ]
+                ]);
+                $event->trigger();
+                $SESSION->loginerrormsg = get_string('invalidlogin');
+                $client->log_out();
+                redirect(new moodle_url('/login/index.php'));
+            } else if ($mappeduser && $mappeduser->confirmed) {
                 $userinfo = (array) $mappeduser;
                 $userwasmapped = true;
             } else {
index 00f5165..9fe1753 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 "Manage authentication" node in "Site administration > Plugins > Authentication"
+    And I navigate to "Privacy settings" node in "Site administration > Privacy and policies"
 
   Scenario: Admin provides valid value for 'Age of digital consent'.
     Given I set the field "s__agedigitalconsentmap" to multiline:
index f85f9a6..84ffa1f 100644 (file)
@@ -89,4 +89,30 @@ class core_auth_external_testcase extends externallib_advanced_testcase {
         $this->expectExceptionMessage(get_string('invalidconfirmdata', 'error'));
         $result = core_auth_external::confirm_user($username, 'zzZZzz');
     }
+
+    /**
+     * Test age digital consent not enabled.
+     */
+    public function test_age_digital_consent_verification_is_not_enabled() {
+        global $CFG;
+
+        $CFG->agedigitalconsentverification = 0;
+        $result = core_auth_external::is_age_digital_consent_verification_enabled();
+        $result = external_api::clean_returnvalue(
+            core_auth_external::is_age_digital_consent_verification_enabled_returns(), $result);
+        $this->assertFalse($result['status']);
+    }
+
+    /**
+     * Test age digital consent is enabled.
+     */
+    public function test_age_digital_consent_verification_is_enabled() {
+        global $CFG;
+
+        $CFG->agedigitalconsentverification = 1;
+        $result = core_auth_external::is_age_digital_consent_verification_enabled();
+        $result = external_api::clean_returnvalue(
+            core_auth_external::is_age_digital_consent_verification_enabled_returns(), $result);
+        $this->assertTrue($result['status']);
+    }
 }
index 70c49d5..138cbbc 100644 (file)
@@ -5,6 +5,8 @@ information provided here is intended especially for developers.
 
 * The auth_db and auth_ldap plugins' implementations of update_user_record() have been removed and both now
   call the new implementation added in the base class.
+* Self registration plugins should use core_privacy\local\sitepolicy\manager instead of directly checking
+  $CFG->sitepolicy , especially in custom signup forms. See https://docs.moodle.org/dev/Site_policy_handler
 
 === 3.3 ===
 
index 6e050ab..b96a02e 100644 (file)
@@ -4409,8 +4409,7 @@ class restore_create_categories_and_questions extends restore_structure_step {
         if ($backuprelease < 3.5 || $backupbuild < 20180205) {
             $before35 = true;
         }
-        if (empty($mapping->info->parent) &&
-                ($before35 || $mapping->info->contextlevel == CONTEXT_MODULE)) {
+        if (empty($mapping->info->parent) && $before35) {
             $top = question_get_top_category($data->contextid, true);
             $data->parent = $top->id;
         }
diff --git a/backup/moodle2/tests/fixtures/question_category_34_format.mbz b/backup/moodle2/tests/fixtures/question_category_34_format.mbz
new file mode 100644 (file)
index 0000000..140fdeb
Binary files /dev/null and b/backup/moodle2/tests/fixtures/question_category_34_format.mbz differ
diff --git a/backup/moodle2/tests/fixtures/question_category_35_format.mbz b/backup/moodle2/tests/fixtures/question_category_35_format.mbz
new file mode 100644 (file)
index 0000000..69ecf54
Binary files /dev/null and b/backup/moodle2/tests/fixtures/question_category_35_format.mbz differ
index 0589755..971a35d 100644 (file)
@@ -955,4 +955,68 @@ class core_backup_moodle2_testcase extends advanced_testcase {
         $this->assertEquals($restoredforumcontext->id, $requests[2]->contextid);
         $this->assertEquals('', $requests[2]->searcharea);
     }
+
+    /**
+     * The Question category hierarchical structure was changed in Moodle 3.5.
+     * From 3.5, all question categories in each context are a child of a single top level question category for that context.
+     * This test ensures that both Moodle 3.4 and 3.5 backups can still be correctly restored.
+     */
+    public function test_restore_question_category_34_35() {
+        global $DB, $USER, $CFG;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        $backupfiles = array('question_category_34_format', 'question_category_35_format');
+
+        foreach ($backupfiles as $backupfile) {
+            // Extract backup file.
+            $backupid = $backupfile;
+            $backuppath = $CFG->tempdir . '/backup/' . $backupid;
+            check_dir_exists($backuppath);
+            get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
+                    __DIR__ . "/fixtures/$backupfile.mbz", $backuppath);
+
+            // Do restore to new course with default settings.
+            $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
+            $newcourseid = restore_dbops::create_new_course(
+                    'Test fullname', 'Test shortname', $categoryid);
+            $rc = new restore_controller($backupid, $newcourseid,
+                    backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
+                    backup::TARGET_NEW_COURSE);
+
+            $this->assertTrue($rc->execute_precheck());
+            $rc->execute_plan();
+            $rc->destroy();
+
+            // Get information about the resulting course and check that it is set up correctly.
+            $modinfo = get_fast_modinfo($newcourseid);
+            $quizzes = array_values($modinfo->get_instances_of('quiz'));
+            $contexts = $quizzes[0]->context->get_parent_contexts(true);
+
+            $topcategorycount = [];
+            foreach ($contexts as $context) {
+                $cats = $DB->get_records('question_categories', array('contextid' => $context->id), 'parent', 'id, name, parent');
+
+                // Make sure all question categories that were inside the backup file were restored correctly.
+                if ($context->contextlevel == CONTEXT_COURSE) {
+                    $this->assertEquals(['top', 'Default for C101'], array_column($cats, 'name'));
+                } else if ($context->contextlevel == CONTEXT_MODULE) {
+                    $this->assertEquals(['top', 'Default for Q1'], array_column($cats, 'name'));
+                }
+
+                $topcategorycount[$context->id] = 0;
+                foreach ($cats as $cat) {
+                    if (!$cat->parent) {
+                        $topcategorycount[$context->id]++;
+                    }
+                }
+
+                // Make sure there is a single top level category in this context.
+                if ($cats) {
+                    $this->assertEquals(1, $topcategorycount[$context->id]);
+                }
+            }
+        }
+    }
 }
index 417c960..e1b5195 100644 (file)
@@ -61,10 +61,10 @@ class edit_backpack_form extends moodleform {
                 array('class' => 'notconnected', 'id' => 'connection-status'));
             $mform->addElement('static', 'status', get_string('status'), $status);
             $mform->addElement('hidden', 'email', $this->_customdata['email']);
-            $mform->setType('email', PARAM_RAW_TRIMMED);
+            $mform->setType('email', PARAM_EMAIL);
             $mform->hardFreeze(['email']);
-            $status = html_writer::tag('span', $this->_customdata['email'], []);
-            $mform->addElement('static', 'emailverify', get_string('email'), $status);
+            $emailverify = html_writer::tag('span', s($this->_customdata['email']), []);
+            $mform->addElement('static', 'emailverify', get_string('email'), $emailverify);
             $buttonarray = [];
             $buttonarray[] = &$mform->createElement('submit', 'submitbutton',
                                                     get_string('backpackconnectionresendemail', 'badges'));
@@ -80,7 +80,7 @@ class edit_backpack_form extends moodleform {
             $mform->addElement('text', 'email', get_string('email'), 'maxlength="100" size="30"');
             $mform->addHelpButton('email', 'backpackemail', 'badges');
             $mform->addRule('email', get_string('required'), 'required', null, 'client');
-            $mform->setType('email', PARAM_RAW_TRIMMED);
+            $mform->setType('email', PARAM_EMAIL);
             $this->add_action_buttons(false, get_string('backpackconnectionconnect', 'badges'));
         }
     }
diff --git a/blocks/activity_modules/classes/privacy/provider.php b/blocks/activity_modules/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..7f8b315
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_activity_modules.
+ *
+ * @package    block_activity_modules
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_activity_modules\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_activity_modules implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index e20fa02..38f388a 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['activity_modules:addinstance'] = 'Add a new activities block';
 $string['pluginname'] = 'Activities';
+$string['privacy:metadata'] = 'The Activites block only shows data stored in other locations.';
diff --git a/blocks/activity_results/classes/privacy/provider.php b/blocks/activity_results/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..d68134e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_activity_results.
+ *
+ * @package    block_activity_results
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_activity_results\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_activity_results implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 8a9b631..23a2b10 100644 (file)
@@ -65,3 +65,4 @@ $string['worstgrade'] = 'The lowest grade:';
 $string['worstgrades'] = 'The {$a} lowest grades:';
 $string['worstgroupgrade'] = 'The group with the lowest average:';
 $string['worstgroupgrades'] = 'The {$a} groups with the lowest average:';
+$string['privacy:metadata'] = 'The Activites results block only shows data stored in other locations.';
diff --git a/blocks/admin_bookmarks/classes/privacy/provider.php b/blocks/admin_bookmarks/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..ac5ef87
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_admin_bookmarks.
+ *
+ * @package    block_admin_bookmarks
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_admin_bookmarks\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_admin_bookmarks implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 21bca58..956319b 100644 (file)
@@ -25,3 +25,4 @@
 $string['admin_bookmarks:addinstance'] = 'Add a new admin bookmarks block';
 $string['admin_bookmarks:myaddinstance'] = 'Add a new admin bookmarks block to Dashboard';
 $string['pluginname'] = 'Admin bookmarks';
+$string['privacy:metadata'] = 'The Admin bookmarks block only shows data stored in other locations.';
diff --git a/blocks/badges/classes/privacy/provider.php b/blocks/badges/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..bf9721c
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_badges.
+ *
+ * @package    block_badges
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_badges\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_badges implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index ba4adaf..df44819 100644 (file)
@@ -28,3 +28,4 @@ $string['numbadgestodisplay'] = 'Number of latest badges to display';
 $string['nothingtodisplay'] = 'You have no badges to display';
 $string['badges:addinstance'] = 'Add a new My latest badges block';
 $string['badges:myaddinstance'] = 'Add a new My latest badges block to Dashboard';
+$string['privacy:metadata'] = 'The Badges block only shows data stored in other locations.';
diff --git a/blocks/blog_menu/classes/privacy/provider.php b/blocks/blog_menu/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..8850872
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_blog_menu.
+ *
+ * @package    block_blog_menu
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_blog_menu\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_blog_menu implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index d39550e..da6f57d 100644 (file)
@@ -25,3 +25,4 @@
 
 $string['blog_menu:addinstance'] = 'Add a new blog menu block';
 $string['pluginname'] = 'Blog menu';
+$string['privacy:metadata'] = 'The Blog menu block only shows data stored in other locations.';
diff --git a/blocks/blog_recent/classes/privacy/provider.php b/blocks/blog_recent/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..2b33898
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_blog_recent.
+ *
+ * @package    block_blog_recent
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_blog_recent\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_blog_recent implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 2da369c..021c491 100644 (file)
@@ -28,3 +28,4 @@ $string['norecentblogentries'] = 'No recent entries';
 $string['numentriestodisplay'] = 'Number of recent entries to display';
 $string['pluginname'] = 'Recent blog entries';
 $string['recentinterval'] = 'Interval of time considered "recent"';
+$string['privacy:metadata'] = 'The Recent blog entries block only shows data stored in other locations.';
diff --git a/blocks/blog_tags/classes/privacy/provider.php b/blocks/blog_tags/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..8235cd8
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_blog_tags.
+ *
+ * @package    block_blog_tags
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_blog_tags\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_blog_tags implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index e5c6bc2..298ffe6 100644 (file)
@@ -25,3 +25,4 @@
 $string['blog_tags:addinstance'] = 'Add a new blog tags block';
 $string['pluginname'] = 'Blog tags';
 $string['configtitle'] = 'Blog tags block title';
+$string['privacy:metadata'] = 'The Blog tags block only shows data stored in other locations.';
diff --git a/blocks/calendar_month/classes/privacy/provider.php b/blocks/calendar_month/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..0ff00af
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_calendar_month.
+ *
+ * @package    block_calendar_month
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_calendar_month\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_calendar_month implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index f92ff5b..38aeda7 100644 (file)
@@ -25,3 +25,4 @@
 $string['calendar_month:addinstance'] = 'Add a new calendar block';
 $string['calendar_month:myaddinstance'] = 'Add a new calendar block to Dashboard';
 $string['pluginname'] = 'Calendar';
+$string['privacy:metadata'] = 'The Calendar block only displays existing calendar data.';
diff --git a/blocks/calendar_upcoming/classes/privacy/provider.php b/blocks/calendar_upcoming/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..ae4f01a
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_calendar_upcoming.
+ *
+ * @package    block_calendar_upcoming
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_calendar_upcoming\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_calendar_upcoming implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index dc8adc3..f565520 100644 (file)
@@ -26,3 +26,4 @@ $string['calendar_upcoming:addinstance'] = 'Add a new upcoming events block';
 $string['calendar_upcoming:myaddinstance'] = 'Add a new upcoming events block to Dashboard';
 $string['gotocalendar'] = 'Go to calendar...';
 $string['pluginname'] = 'Upcoming events';
+$string['privacy:metadata'] = 'The Upcoming calendar events block only displays existing calendar data.';
diff --git a/blocks/completionstatus/classes/privacy/provider.php b/blocks/completionstatus/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..97fd398
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_completionstatus.
+ *
+ * @package    block_completionstatus
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_completionstatus\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_completionstatus implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 01c0dcc..dc93b6f 100644 (file)
@@ -29,3 +29,4 @@ $string['firstofsecond'] = '{$a->first} of {$a->second}';
 $string['pluginname'] = 'Course completion status';
 $string['requirement'] = 'Requirement';
 $string['returntocourse'] = 'Return to course';
+$string['privacy:metadata'] = 'The Course completion status block only shows information about course completion and does not store any data of its own.';
diff --git a/blocks/course_list/classes/privacy/provider.php b/blocks/course_list/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..37066fd
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_course_list.
+ *
+ * @package    block_course_list
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_course_list\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_course_list implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 1b0e36a..1cd9244 100644 (file)
@@ -31,3 +31,4 @@ $string['course_list:myaddinstance'] = 'Add a new courses block to Dashboard';
 $string['hideallcourseslink'] = 'Hide \'All courses\' link';
 $string['owncourses'] = 'Admin user sees own courses';
 $string['pluginname'] = 'Courses';
+$string['privacy:metadata'] = 'The Courses block only shows data about courses and does not store any data itself.';
diff --git a/blocks/course_summary/classes/privacy/provider.php b/blocks/course_summary/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..4023e49
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_course_summary.
+ *
+ * @package    block_course_summary
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_course_summary\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_course_summary implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index e5e69be..4534d40 100644 (file)
@@ -26,3 +26,4 @@
 $string['coursesummary'] = 'Course summary';
 $string['course_summary:addinstance'] = 'Add a new course/site summary block';
 $string['pluginname'] = 'Course/site summary';
+$string['privacy:metadata'] = 'The Course and site summaryblock only shows information about courses and does not store data itself.';
diff --git a/blocks/feedback/classes/privacy/provider.php b/blocks/feedback/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..157e25e
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_feedback.
+ *
+ * @package    block_feedback
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_feedback\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_feedback implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 4999f02..e9163f9 100644 (file)
@@ -25,3 +25,4 @@
 $string['feedback'] = 'Feedback';
 $string['feedback:addinstance'] = 'Add a new feedback block';
 $string['pluginname'] = 'Feedback';
+$string['privacy:metadata'] = 'The Feedback block only shows data stored in other locations.';
diff --git a/blocks/globalsearch/classes/privacy/provider.php b/blocks/globalsearch/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..0c57f73
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_globalsearch.
+ *
+ * @package    block_globalsearch
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_globalsearch\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_globalsearch implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 8a0635b..6fd2131 100644 (file)
@@ -25,3 +25,4 @@
 $string['globalsearch:addinstance'] = 'Add a new global search block';
 $string['globalsearch:myaddinstance'] = 'Add a new global search block to Dashboard';
 $string['pluginname'] = 'Global search';
+$string['privacy:metadata'] = 'The Global search block only shows data stored in other locations.';
diff --git a/blocks/glossary_random/classes/privacy/provider.php b/blocks/glossary_random/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..d1c64d4
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_glossary_random.
+ *
+ * @package    block_glossary_random
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_glossary_random\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_glossary_random implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 7f909e8..1c58f6f 100644 (file)
@@ -45,3 +45,4 @@ $string['title'] = 'Title';
 $string['type'] = 'How a new entry is chosen';
 $string['viewglossary'] = 'View all entries';
 $string['whichfooter'] = 'You can display links to actions of the glossary this block is associated with. The block will only display links to actions which are enabled for that glossary.';
+$string['privacy:metadata'] = 'The Random glossary entry block only shows data stored in other locations.';
diff --git a/blocks/html/classes/privacy/provider.php b/blocks/html/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..003562b
--- /dev/null
@@ -0,0 +1,183 @@
+<?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 block_html.
+ *
+ * @package    block_html
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_html\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\helper;
+use \core_privacy\local\request\deletion_criteria;
+use \core_privacy\local\metadata\collection;
+
+/**
+ * Privacy Subsystem implementation for block_html.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        // The block_html block stores user provided data.
+        \core_privacy\local\metadata\provider,
+
+        // The block_html block provides data directly to core.
+        \core_privacy\local\request\plugin\provider {
+
+    /**
+     * Returns information about how block_html stores its data.
+     *
+     * @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->link_subsystem('block', 'privacy:metadata:block');
+
+        return $collection;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param   int         $userid     The user to search.
+     * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
+     */
+    public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
+        // This block doesn't know who information is stored against unless it
+        // is at the user context.
+        $contextlist = new \core_privacy\local\request\contextlist();
+
+        $sql = "SELECT c.id
+                  FROM {block_instances} b
+            INNER JOIN {context} c ON c.instanceid = b.id AND c.contextlevel = :contextblock
+            INNER JOIN {context} bpc ON bpc.id = b.parentcontextid
+                 WHERE b.blockname = 'html'
+                   AND bpc.contextlevel = :contextuser
+                   AND bpc.instanceid = :userid";
+
+        $params = [
+            'contextblock' => CONTEXT_BLOCK,
+            'contextuser' => CONTEXT_USER,
+            'userid' => $userid,
+        ];
+
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export all user data for the specified user, in the specified contexts.
+     *
+     * @param   approved_contextlist    $contextlist    The approved contexts to export information for.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        $user = $contextlist->get_user();
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+
+        $sql = "SELECT
+                    c.id AS contextid,
+                    bi.*
+                  FROM {context} c
+            INNER JOIN {block_instances} bi ON bi.id = c.instanceid AND c.contextlevel = :contextlevel
+                 WHERE bi.blockname = 'html'
+                   AND(
+                    c.id {$contextsql}
+                )
+        ";
+
+        $params = [
+            'contextlevel' => CONTEXT_BLOCK,
+        ];
+        $params += $contextparams;
+
+        $instances = $DB->get_recordset_sql($sql, $params);
+        foreach ($instances as $instance) {
+            $context = \context_block::instance($instance->id);
+            $block = block_instance('html', $instance);
+            if (empty($block->config)) {
+                // Skip this block. It has not been configured.
+                continue;
+            }
+
+            $html = writer::with_context($context)
+                ->rewrite_pluginfile_urls([], 'block_html', 'content', null, $block->config->text);
+
+            // Default to FORMAT_HTML which is what will have been used before the
+            // editor was properly implemented for the block.
+            $format = isset($block->config->format) ? $block->config->format : FORMAT_HTML;
+
+            $filteropt = (object) [
+                'overflowdiv' => true,
+                'noclean' => true,
+            ];
+            $html = format_text($html, $format, $filteropt);
+
+            $data = helper::get_context_data($context, $user);
+            helper::export_context_files($context, $user);
+            $data->title = $block->config->title;
+            $data->content = $html;
+
+            writer::with_context($context)->export_data([], $data);
+        }
+        $instances->close();
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param   context                 $context   The specific context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        // The only way to delete data for the html block is to delete the block instance itself.
+        blocks_delete_instance(static::get_instance_from_context($context));
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * @param   approved_contextlist    $contextlist    The approved contexts and user information to delete information for.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        // The only way to delete data for the html block is to delete the block instance itself.
+        foreach ($contextlist as $context) {
+            blocks_delete_instance(static::get_instance_from_context($context));
+        }
+    }
+
+    /**
+     * Get the block instance record for the specified context.
+     *
+     * @param   \context_block $context The context to fetch
+     * @return  \stdClass
+     */
+    protected static function get_instance_from_context(\context_block $context) {
+        global $DB;
+
+        return $DB->get_record('block_instances', ['id' => $context->instanceid]);
+    }
+}
index c51692a..6688816 100644 (file)
@@ -33,3 +33,4 @@ $string['html:myaddinstance'] = 'Add a new HTML block to Dashboard';
 $string['newhtmlblock'] = '(new HTML block)';
 $string['pluginname'] = 'HTML';
 $string['search:content'] = 'HTML block content';
+$string['privacy:metadata:block'] = 'The HTML block stores all of its data within the block subsystem.';
diff --git a/blocks/html/tests/privacy_provider_test.php b/blocks/html/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..94d38ca
--- /dev/null
@@ -0,0 +1,344 @@
+<?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 the block_html implementation of the privacy API.
+ *
+ * @package    block_html
+ * @category   test
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\approved_contextlist;
+use \block_html\privacy\provider;
+
+/**
+ * Unit tests for the block_html implementation of the privacy API.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class block_html_privacy_testcase extends \core_privacy\tests\provider_testcase {
+    /**
+     * Get the list of standard format options for comparison.
+     *
+     * @return \stdClass
+     */
+    protected function get_format_options() {
+        return (object) [
+            'overflowdiv' => true,
+            'noclean' => true,
+        ];
+    }
+
+    /**
+     * Creates an HTML block on a user.
+     *
+     * @param   string  $title
+     * @param   string  $body
+     * @param   string  $format
+     * @return  \block_instance
+     */
+    protected function create_user_block($title, $body, $format) {
+        global $USER;
+
+        $configdata = (object) [
+            'title' => $title,
+            'text' => [
+                'itemid' => 19,
+                'text' => $body,
+                'format' => $format,
+            ],
+        ];
+
+        $this->create_block($this->construct_user_page($USER));
+        $block = $this->get_last_block_on_page($this->construct_user_page($USER));
+        $block = block_instance('html', $block->instance);
+        $block->instance_config_save((object) $configdata);
+
+        return $block;
+    }
+
+    /**
+     * Creates an HTML block on a course.
+     *
+     * @param   \stdClass $course
+     * @param   string  $title
+     * @param   string  $body
+     * @param   string  $format
+     * @return  \block_instance
+     */
+    protected function create_course_block($course, $title, $body, $format) {
+        global $USER;
+
+        $configdata = (object) [
+            'title' => $title,
+            'text' => [
+                'itemid' => 19,
+                'text' => $body,
+                'format' => $format,
+            ],
+        ];
+
+        $this->create_block($this->construct_course_page($course));
+        $block = $this->get_last_block_on_page($this->construct_course_page($course));
+        $block = block_instance('html', $block->instance);
+        $block->instance_config_save((object) $configdata);
+
+        return $block;
+    }
+
+    /**
+     * Creates an HTML block on a page.
+     *
+     * @param \page $page Page
+     */
+    protected function create_block($page) {
+        $page->blocks->add_block_at_end_of_default_region('html');
+    }
+
+    /**
+     * Get the last block on the page.
+     *
+     * @param \page $page Page
+     * @return \block_html Block instance object
+     */
+    protected function get_last_block_on_page($page) {
+        $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
+        $block = end($blocks);
+
+        return $block;
+    }
+
+    /**
+     * Constructs a Page object for the User Dashboard.
+     *
+     * @param   \stdClass       $user User to create Dashboard for.
+     * @return  \moodle_page
+     */
+    protected function construct_user_page(\stdClass $user) {
+        $page = new \moodle_page();
+        $page->set_context(\context_user::instance($user->id));
+        $page->set_pagelayout('mydashboard');
+        $page->set_pagetype('my-index');
+        $page->blocks->load_blocks();
+        return $page;
+    }
+
+    /**
+     * Constructs a Page object for the User Dashboard.
+     *
+     * @param   \stdClass       $course Course to create Dashboard for.
+     * @return  \moodle_page
+     */
+    protected function construct_course_page(\stdClass $course) {
+        $page = new \moodle_page();
+        $page->set_context(\context_course::instance($course->id));
+        $page->set_pagelayout('standard');
+        $page->set_pagetype('course-view');
+        $page->set_course($course);
+        $page->blocks->load_blocks();
+        return $page;
+    }
+
+    /**
+     * Test that a block on the dashboard is exported.
+     */
+    public function test_user_block() {
+        $this->resetAfterTest();
+
+        $title = 'Example title';
+        $content = 'Example content';
+        $format = FORMAT_PLAIN;
+
+        // Test setup.
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        $block = $this->create_user_block($title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+
+        // Get the contexts.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // Only the user context should be returned.
+        $this->assertCount(1, $contextlist);
+        $this->assertEquals($context, $contextlist->current());
+
+        // Export the data.
+        $this->export_context_data_for_user($user->id, $context, 'block_html');
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        $this->assertTrue($writer->has_any_data());
+
+        // Check the data.
+        $data = $writer->get_data([]);
+        $this->assertInstanceOf('stdClass', $data);
+        $this->assertEquals($title, $data->title);
+        $this->assertEquals(format_text($content, $format, $this->get_format_options()), $data->content);
+
+        // Delete the context.
+        provider::delete_data_for_all_users_in_context($context);
+
+        // Re-fetch the contexts - it should no longer be returned.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $this->assertCount(0, $contextlist);
+    }
+
+    /**
+     * Test that a block on the dashboard which is not configured is _not_ exported.
+     */
+    public function test_user_block_unconfigured() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $title = 'Example title';
+        $content = 'Example content';
+        $format = FORMAT_PLAIN;
+
+        // Test setup.
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        $block = $this->create_user_block($title, $content, $format);
+        $block->instance->configdata = '';
+        $DB->update_record('block_instances', $block->instance);
+        $block = block_instance('html', $block->instance);
+
+        $context = \context_block::instance($block->instance->id);
+
+        // Get the contexts.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // Only the user context should be returned.
+        $this->assertCount(1, $contextlist);
+        $this->assertEquals($context, $contextlist->current());
+
+        // Export the data.
+        $this->export_context_data_for_user($user->id, $context, 'block_html');
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        $this->assertFalse($writer->has_any_data());
+    }
+
+    /**
+     * Test that a block on the dashboard is exported.
+     */
+    public function test_user_multiple_blocks_exported() {
+        $this->resetAfterTest();
+
+        $title = 'Example title';
+        $content = 'Example content';
+        $format = FORMAT_PLAIN;
+
+        // Test setup.
+        $blocks = [];
+        $contexts = [];
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $block = $this->create_user_block($title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+        $contexts[$context->id] = $context;
+
+        $block = $this->create_user_block($title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+        $contexts[$context->id] = $context;
+
+        // Get the contexts.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // There are now two blocks on the user context.
+        $this->assertCount(2, $contextlist);
+        foreach ($contextlist as $context) {
+            $this->assertTrue(isset($contexts[$context->id]));
+        }
+
+        // Turn them into an approved_contextlist.
+        $approvedlist = new approved_contextlist($user, 'block_html', $contextlist->get_contextids());
+
+        // Delete using delete_data_for_user.
+        provider::delete_data_for_user($approvedlist);
+
+        // Re-fetch the contexts - it should no longer be returned.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $this->assertCount(0, $contextlist);
+    }
+
+    /**
+     * Test that a block on the dashboard is not exported.
+     */
+    public function test_course_blocks_not_exported() {
+        $this->resetAfterTest();
+
+        $title = 'Example title';
+        $content = 'Example content';
+        $format = FORMAT_PLAIN;
+
+        // Test setup.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $this->setUser($user);
+
+        $block = $this->create_course_block($course, $title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+
+        // Get the contexts.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // No blocks should be returned.
+        $this->assertCount(0, $contextlist);
+    }
+
+    /**
+     * Test that a block on the dashboard is exported.
+     */
+    public function test_mixed_multiple_blocks_exported() {
+        $this->resetAfterTest();
+
+        $title = 'Example title';
+        $content = 'Example content';
+        $format = FORMAT_PLAIN;
+
+        // Test setup.
+        $contexts = [];
+
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $this->setUser($user);
+
+        $block = $this->create_course_block($course, $title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+
+        $block = $this->create_user_block($title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+        $contexts[$context->id] = $context;
+
+        $block = $this->create_user_block($title, $content, $format);
+        $context = \context_block::instance($block->instance->id);
+        $contexts[$context->id] = $context;
+
+        // Get the contexts.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // There are now two blocks on the user context.
+        $this->assertCount(2, $contextlist);
+        foreach ($contextlist as $context) {
+            $this->assertTrue(isset($contexts[$context->id]));
+        }
+    }
+}
diff --git a/blocks/login/classes/privacy/provider.php b/blocks/login/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..3978b4f
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_login.
+ *
+ * @package    block_login
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_login\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_login implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 9237cab..bf0f6d5 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['login:addinstance'] = 'Add a new login block';
 $string['pluginname'] = 'Login';
+$string['privacy:metadata'] = 'The Login block only provides a way to login and does not store any data itself.';
diff --git a/blocks/lp/classes/privacy/provider.php b/blocks/lp/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..602af6f
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_lp.
+ *
+ * @package    block_lp
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_lp\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_lp implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 0c0ba9a..520f710 100644 (file)
@@ -34,3 +34,4 @@ $string['planstoreview'] = 'Plans to review';
 $string['pluginname'] = 'Learning plans';
 $string['viewmore'] = 'View more...';
 $string['viewotherplans'] = 'View other plans...';
+$string['privacy:metadata'] = 'The Learning plans block only shows data stored in other locations.';
diff --git a/blocks/mentees/classes/privacy/provider.php b/blocks/mentees/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..ca86561
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_mentees.
+ *
+ * @package    block_mentees
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_mentees\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_mentees implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index d4af85f..df160cd 100644 (file)
@@ -29,3 +29,4 @@ $string['mentees:addinstance'] = 'Add a new mentees block';
 $string['mentees:myaddinstance'] = 'Add a new mentees block to Dashboard';
 $string['newmenteesblock'] = '(new Mentees block)';
 $string['pluginname'] = 'Mentees';
+$string['privacy:metadata'] = 'The Mentees block only shows data stored in other locations.';
diff --git a/blocks/mnet_hosts/classes/privacy/provider.php b/blocks/mnet_hosts/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..e70f864
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_mnet_hosts.
+ *
+ * @package    block_mnet_hosts
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_mnet_hosts\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_mnet_hosts implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index e70fe34..4275b75 100644 (file)
@@ -29,3 +29,4 @@ $string['mnet_hosts:addinstance'] = 'Add a new network servers block';
 $string['mnet_hosts:myaddinstance'] = 'Add a new network servers block to Dashboard';
 $string['pluginname'] = 'Network servers';
 $string['server'] = 'Server';
+$string['privacy:metadata'] = 'The Network servers block only allows interaction with Network servers and neither stores or exports data itself.';
diff --git a/blocks/myoverview/classes/privacy/provider.php b/blocks/myoverview/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..9f46f60
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_myoverview.
+ *
+ * @package    block_myoverview
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_myoverview\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_myoverview implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 2176ec5..c762db0 100644 (file)
@@ -44,3 +44,4 @@ $string['sortbydates'] = 'Sort by dates';
 $string['timeline'] = 'Timeline';
 $string['viewcourse'] = 'View course';
 $string['viewcoursename'] = 'View course {$a}';
+$string['privacy:metadata'] = 'The Course overview block only shows data stored in other locations.';
diff --git a/blocks/myprofile/classes/privacy/provider.php b/blocks/myprofile/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..160c186
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_myprofile.
+ *
+ * @package    block_myprofile
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_myprofile\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_myprofile implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index fb71798..24a7767 100644 (file)
@@ -45,6 +45,7 @@ $string['myprofile:addinstance'] = 'Add a new logged in user block';
 $string['myprofile:myaddinstance'] = 'Add a new logged in user block to Dashboard';
 $string['myprofile_settings'] = 'Visible user information';
 $string['pluginname'] = 'Logged in user';
+$string['privacy:metadata'] = 'The Logged in users block only shows information about logged in users and does not store data itself.';
 
 // Deprecated since Moodle 3.2.
 $string['display_un'] = 'Display name';
diff --git a/blocks/navigation/classes/privacy/provider.php b/blocks/navigation/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..032a554
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_navigation.
+ *
+ * @package    block_navigation
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_navigation\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_navigation implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 9876e6e..89c95e6 100644 (file)
@@ -39,3 +39,4 @@ $string['trimmoderight'] = 'Trim characters from the right';
 $string['trimmodeleft'] = 'Trim characters from the left';
 $string['trimmodecenter'] = 'Trim characters from the center';
 $string['trimlength'] = 'How many characters to trim to';
+$string['privacy:metadata'] = 'The Navigation block only shows data stored in other locations.';
diff --git a/blocks/news_items/classes/privacy/provider.php b/blocks/news_items/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..d4b883b
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_news_items.
+ *
+ * @package    block_news_items
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_news_items\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_news_items implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index ac124e2..8287d4f 100644 (file)
@@ -25,3 +25,4 @@
 $string['news_items:addinstance'] = 'Add a new latest announcements block';
 $string['news_items:myaddinstance'] = 'Add a new latest announcements block to Dashboard';
 $string['pluginname'] = 'Latest announcements';
+$string['privacy:metadata'] = 'The Latest announcements block only shows data stored in the forum and does not store data itself.';
diff --git a/blocks/online_users/classes/privacy/provider.php b/blocks/online_users/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..50d0280
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_online_users.
+ *
+ * @package    block_online_users
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_online_users\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_online_users implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index e5a79e3..ff79ce5 100644 (file)
@@ -33,3 +33,4 @@ $string['online_users:viewlist'] = 'View list of online users';
 $string['periodnminutes'] = 'last {$a} minutes';
 $string['pluginname'] = 'Online users';
 $string['timetosee'] = 'Remove after inactivity (minutes)';
+$string['privacy:metadata'] = 'The Online users block only shows data stored in other locations.';
diff --git a/blocks/participants/classes/privacy/provider.php b/blocks/participants/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..efcffd1
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_participants.
+ *
+ * @package    block_participants
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_participants\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_participants implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index bdddf5f..20eb2dd 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['participants:addinstance'] = 'Add a new people block';
 $string['pluginname'] = 'People';
+$string['privacy:metadata'] = 'The People block only shows data stored in other locations.';
diff --git a/blocks/private_files/classes/privacy/provider.php b/blocks/private_files/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..147ab44
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_private_files.
+ *
+ * @package    block_private_files
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_private_files\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_private_files implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 6adb60b..31a0066 100644 (file)
@@ -26,4 +26,4 @@ $string['pluginname'] = 'Private files';
 $string['privatefiles'] = 'Private files';
 $string['private_files:addinstance'] = 'Add a new private files block';
 $string['private_files:myaddinstance'] = 'Add a new private files block to Dashboard';
-
+$string['privacy:metadata'] = 'The Private files block only provides a view of, and link to, the user private files.';
diff --git a/blocks/quiz_results/classes/privacy/provider.php b/blocks/quiz_results/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..0994665
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_quiz_results.
+ *
+ * @package    block_quiz_results
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_quiz_results\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_quiz_results implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 7f8447b..f822a2f 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['pluginname'] = 'Quiz results (disabled)';
 $string['quiz_results:addinstance'] = 'Add a new quiz results block';
+$string['privacy:metadata'] = 'The Quiz results block only shows data stored in other locations.';
diff --git a/blocks/search_forums/classes/privacy/provider.php b/blocks/search_forums/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..090f960
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_search_forums.
+ *
+ * @package    block_search_forums
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_search_forums\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_search_forums implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 1e4b32e..c87b740 100644 (file)
@@ -25,3 +25,4 @@
 $string['advancedsearch'] = 'Advanced search';
 $string['pluginname'] = 'Search forums';
 $string['search_forums:addinstance'] = 'Add a new search forums block';
+$string['privacy:metadata'] = 'The Search forums block only shows data stored in other locations.';
diff --git a/blocks/section_links/classes/privacy/provider.php b/blocks/section_links/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..83bb444
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_section_links.
+ *
+ * @package    block_section_links
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_section_links\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_section_links implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 77cf69f..b267898 100644 (file)
@@ -36,3 +36,4 @@ $string['pluginname'] = 'Section links';
 $string['section_links:addinstance'] = 'Add a new section links block';
 $string['topics'] = 'Topics';
 $string['weeks'] = 'Weeks';
+$string['privacy:metadata'] = 'The Section links block only shows data stored in other locations.';
diff --git a/blocks/selfcompletion/classes/privacy/provider.php b/blocks/selfcompletion/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..c96a187
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_selfcompletion.
+ *
+ * @package    block_selfcompletion
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_selfcompletion\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_selfcompletion implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 5d5f340..2561b6a 100644 (file)
@@ -27,3 +27,4 @@ $string['completecourse'] = 'Complete course';
 $string['pluginname'] = 'Self completion';
 $string['selfcompletionnotenabled'] = 'The self completion criteria has not been enabled for this course';
 $string['selfcompletion:addinstance'] = 'Add a new self completion block';
+$string['privacy:metadata'] = 'The Self completion block only shows data stored in other locations.';
diff --git a/blocks/settings/classes/privacy/provider.php b/blocks/settings/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..808dda4
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_settings.
+ *
+ * @package    block_settings
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_settings\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_settings implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 05b94af..80e95b2 100644 (file)
@@ -28,3 +28,4 @@ $string['enabledock'] = 'Allow the user to dock this block';
 $string['pluginname'] = 'Administration';
 $string['settings:addinstance'] = 'Add a new administration block';
 $string['settings:myaddinstance'] = 'Add a new administration block to Dashboard';
+$string['privacy:metadata'] = 'The Administration block only shows data stored in other locations.';
diff --git a/blocks/site_main_menu/classes/privacy/provider.php b/blocks/site_main_menu/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..b9e6991
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_site_main_menu.
+ *
+ * @package    block_site_main_menu
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_site_main_menu\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_site_main_menu implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 7168054..aba47e3 100644 (file)
@@ -25,3 +25,4 @@
 
 $string['pluginname'] = 'Main menu';
 $string['site_main_menu:addinstance'] = 'Add a new main menu block';
+$string['privacy:metadata'] = 'The Main menu block only shows data stored in other locations.';
diff --git a/blocks/social_activities/classes/privacy/provider.php b/blocks/social_activities/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..cad5ba3
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_social_activities.
+ *
+ * @package    block_social_activities
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_social_activities\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_social_activities implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index c8a467a..3ec6b65 100644 (file)
@@ -24,3 +24,4 @@
 
 $string['pluginname'] = 'Social activities';
 $string['social_activities:addinstance'] = 'Add a new social activities block';
+$string['privacy:metadata'] = 'The Social activities block only shows data stored in other locations.';
diff --git a/blocks/tag_flickr/classes/privacy/provider.php b/blocks/tag_flickr/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..08ac391
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_tag_flickr.
+ *
+ * @package    block_tag_flickr
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_tag_flickr\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_tag_flickr implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 8a1e4a4..660cd4a 100644 (file)
@@ -37,3 +37,4 @@ $string['pluginname'] = 'Flickr';
 $string['relevance'] = 'Relevance';
 $string['sortby'] = 'Sort by';
 $string['tag_flickr:addinstance'] = 'Add a new flickr block';
+$string['privacy:metadata'] = 'The Flickr block only shows data stored in other locations.';
diff --git a/blocks/tag_youtube/classes/privacy/provider.php b/blocks/tag_youtube/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..e4b1c08
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_tag_youtube.
+ *
+ * @package    block_tag_youtube
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_tag_youtube\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_tag_youtube implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 50df4cc..3b169ae 100644 (file)
@@ -47,3 +47,4 @@ $string['scienceandtech'] = 'Science &amp; Tech';
 $string['sports'] = 'Sports';
 $string['tag_youtube:addinstance'] = 'Add a new YouTube block';
 $string['travel'] = 'Travel &amp; Places';
+$string['privacy:metadata'] = 'The Youtube block only shows data stored in other locations.';
diff --git a/blocks/tags/classes/privacy/provider.php b/blocks/tags/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..8ce5159
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for block_tags.
+ *
+ * @package    block_tags
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace block_tags\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy Subsystem for block_tags implementing null_provider.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\null_provider {
+
+    /**
+     * Get the language string identifier with the component's language
+     * file to explain why this plugin stores no data.
+     *
+     * @return  string
+     */
+    public static function get_reason() : string {
+        return 'privacy:metadata';
+    }
+}
index 457dcfe..afacdd0 100644 (file)
@@ -38,4 +38,4 @@ $string['taggeditemscontext'] = 'Tagged items context';
 $string['taggeditemscontext_help'] = 'You can limit the tag cloud to the tags that are present in the current course category, course or module';
 $string['tags:addinstance'] = 'Add a new tags block';
 $string['tags:myaddinstance'] = 'Add a new tags block to Dashboard';
-
+$string['privacy:metadata'] = 'The Tags block only shows data stored in other locations.';
index 946bd04..203673b 100644 (file)
@@ -44,18 +44,20 @@ Feature: Access visible and hidden cohorts
       | user    | course | role           |
       | teacher | C1     | editingteacher |
 
+  @javascript
   Scenario: Teacher can see visible cohorts defined in the above contexts
     When I log in as "teacher"
     And I am on "Course 1" course homepage
     And I navigate to "Enrolment methods" node in "Course administration > Users"
     And I select "Cohort sync" from the "Add method" singleselect
-    Then the "Cohort" select box should contain "Cohort in category 1"
-    And the "Cohort" select box should contain "System cohort"
-    And the "Cohort" select box should not contain "Cohort hidden in category 1"
-    And the "Cohort" select box should not contain "System hidden cohort"
-    And the "Cohort" select box should not contain "Cohort in category 2"
-    And the "Cohort" select box should contain "Cohort empty in category 1"
-    And the "Cohort" select box should contain "System empty cohort"
+    And I open the autocomplete suggestions list
+    Then "Cohort in category 1" "autocomplete_suggestions" should exist
+    And "System cohort" "autocomplete_suggestions" should exist
+    And "Cohort hidden in category 1" "autocomplete_suggestions" should not exist
+    And "System hidden cohort" "autocomplete_suggestions" should not exist
+    And "Cohort in category 2" "autocomplete_suggestions" should not exist
+    And "Cohort empty in category 1" "autocomplete_suggestions" should exist
+    And "System empty cohort" "autocomplete_suggestions" should exist
     And I set the field "Cohort" to "System cohort"
     And I press "Add method"
     And I am on "Course 1" course homepage
@@ -72,18 +74,20 @@ Feature: Access visible and hidden cohorts
     And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
     And the "Select members from cohort" select box should not contain "System empty cohort"
 
+  @javascript
   Scenario: System manager can see all cohorts defined in the above contexts
     When I log in as "user1"
     And I am on "Course 1" course homepage
     And I navigate to "Enrolment methods" node in "Course administration > Users"
     And I select "Cohort sync" from the "Add method" singleselect
-    Then the "Cohort" select box should contain "Cohort in category 1"
-    And the "Cohort" select box should contain "System cohort"
-    And the "Cohort" select box should contain "Cohort hidden in category 1"
-    And the "Cohort" select box should contain "System hidden cohort"
-    And the "Cohort" select box should not contain "Cohort in category 2"
-    And the "Cohort" select box should contain "Cohort empty in category 1"
-    And the "Cohort" select box should contain "System empty cohort"
+    And I open the autocomplete suggestions list
+    Then "Cohort in category 1" "autocomplete_suggestions" should exist
+    And "System cohort" "autocomplete_suggestions" should exist
+    And "Cohort hidden in category 1" "autocomplete_suggestions" should exist
+    And "System hidden cohort" "autocomplete_suggestions" should exist
+    And "Cohort in category 2" "autocomplete_suggestions" should not exist
+    And "Cohort empty in category 1" "autocomplete_suggestions" should exist
+    And "System empty cohort" "autocomplete_suggestions" should exist
     And I set the field "Cohort" to "System cohort"
     And I press "Add method"
     And I navigate to "Enrolled users" node in "Course administration > Users"
@@ -98,18 +102,20 @@ Feature: Access visible and hidden cohorts
     And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
     And the "Select members from cohort" select box should not contain "System empty cohort"
 
+  @javascript
   Scenario: Category manager can see all cohorts defined in his category and visible cohorts defined above
     When I log in as "user2"
     And I am on "Course 1" course homepage
     And I navigate to "Enrolment methods" node in "Course administration > Users"
     And I select "Cohort sync" from the "Add method" singleselect
-    Then the "Cohort" select box should contain "Cohort in category 1"
-    And the "Cohort" select box should contain "System cohort"
-    And the "Cohort" select box should contain "Cohort hidden in category 1"
-    And the "Cohort" select box should not contain "System hidden cohort"
-    And the "Cohort" select box should not contain "Cohort in category 2"
-    And the "Cohort" select box should contain "Cohort empty in category 1"
-    And the "Cohort" select box should contain "System empty cohort"
+    And I open the autocomplete suggestions list
+    Then "Cohort in category 1" "autocomplete_suggestions" should exist
+    And "System cohort" "autocomplete_suggestions" should exist
+    And "Cohort hidden in category 1" "autocomplete_suggestions" should exist
+    And "System hidden cohort" "autocomplete_suggestions" should not exist
+    And "Cohort in category 2" "autocomplete_suggestions" should not exist
+    And "Cohort empty in category 1" "autocomplete_suggestions" should exist
+    And "System empty cohort" "autocomplete_suggestions" should exist
     And I set the field "Cohort" to "System cohort"
     And I press "Add method"
     And I navigate to "Enrolled users" node in "Course administration > Users"
diff --git a/comment/classes/privacy/provider.php b/comment/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..0a26788
--- /dev/null
@@ -0,0 +1,130 @@
+<?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 class for requesting user data.
+ *
+ * @package    core_comment
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_comment\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\transform;
+
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    core_comment
+ * @copyright  2018 Adrian Greeve <adrian@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\subsystem\plugin_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_database_table('comments', [
+                'content' => 'privacy:metadata:comment:content',
+                'timecreated' => 'privacy:metadata:comment:timecreated',
+                'userid' => 'privacy:metadata:comment:userid',
+            ], 'privacy:metadata:comment');
+
+        return $collection;
+    }
+
+    /**
+     * Writes user data to the writer for the user to download.
+     *
+     * @param  array  $context Contexts to run through and return data.
+     * @param  string $component The component that is calling this function
+     * @param  string $commentarea The comment area related to the component
+     * @param  int    $itemid An identifier for a group of comments
+     * @param  array  $subcontext The sub-context in which to export this data
+     * @param  bool   $onlyforthisuser  Only return the comments this user made.
+     */
+    public static function export_comments($context, $component, $commentarea, $itemid, $subcontext, $onlyforthisuser = true) {
+
+        $data = new \stdClass;
+        $data->context   = $context;
+        $data->area      = $commentarea;
+        $data->itemid    = $itemid;
+        $data->component = $component;
+
+        $commentobject = new \comment($data);
+        $commentobject->set_view_permission(true);
+        $comments = $commentobject->get_comments(0);
+        $subcontext[] = get_string('commentsubcontext', 'core_comment');
+
+        $comments = array_filter($comments, function($comment) use ($onlyforthisuser) {
+            global $USER;
+
+            return (!$onlyforthisuser || $comment->userid == $USER->id);
+        });
+
+        $comments = array_map(function($comment) {
+            return (object) [
+                'content' => $comment->content,
+                'time' => transform::datetime($comment->timecreated),
+                'userid' => transform::user($comment->userid),
+            ];
+        }, $comments);
+
+        if (!empty($comments)) {
+            \core_privacy\local\request\writer::with_context($context)
+                ->export_data($subcontext, (object) [
+                    'comments' => $comments,
+                ]);
+        }
+    }
+
+    /**
+     * Deletes all comments for a specified context.
+     *
+     * @param  \context $context Details about which context to delete comments for.
+     */
+    public static function delete_comments_for_all_users_in_context(\context $context) {
+        global $DB;
+        $DB->delete_records('comments', ['contextid' => $context->id]);
+    }
+
+    /**
+     * Deletes all records for a user from a list of approved contexts.
+     *
+     * @param  \core_privacy\local\request\approved_contextlist $contextlist Contains the user ID and a list of contexts to be
+     * deleted from.
+     */
+    public static function delete_comments_for_user(\core_privacy\local\request\approved_contextlist $contextlist) {
+        global $DB;
+
+        $userid = $contextlist->get_user()->id;
+        $contextids = implode(',', $contextlist->get_contextids());
+        $params = ['userid' => $userid];
+        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+        $params += $inparams;
+
+        $select = "userid = :userid and contextid $insql";
+        $DB->delete_records_select('comments', $select, $params);
+    }
+}
diff --git a/comment/tests/privacy_test.php b/comment/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..1f15394
--- /dev/null
@@ -0,0 +1,191 @@
+<?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 core_comment.
+ *
+ * @package    core_comment
+ * @category   test
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/comment/locallib.php');
+require_once($CFG->dirroot . '/comment/lib.php');
+
+use \core_privacy\tests\provider_testcase;
+
+/**
+ * Unit tests for comment/classes/privacy/policy
+ *
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_comment_privacy_testcase extends provider_testcase {
+
+    /**
+     * Check the exporting of comments for a user id in a context.
+     */
+    public function test_export_comments() {
+        $this->resetAfterTest(true);
+        $course = $this->getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+
+        $comment = $this->get_comment_object($context, $course);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        // Add comments.
+        $comments = [];
+        $firstcomment = 'This is the first comment';
+        $this->setUser($user1);
+        $comment->add($firstcomment);
+        $comments[$user1->id] = $firstcomment;
+
+        $secondcomment = 'From the second user';
+        $this->setUser($user2);
+        $comment->add($secondcomment);
+        $comments[$user2->id] = $secondcomment;
+
+        // Retrieve comments only for user1.
+        $this->setUser($user1);
+        $writer = \core_privacy\local\request\writer::with_context($context);
+        \core_comment\privacy\provider::export_comments($context, 'block_comments', 'page_comments', 0, []);
+
+        $data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
+        $exportedcomments = $data->comments;
+
+        // There is only one comment made by this user.
+        $this->assertCount(1, $exportedcomments);
+        $comment = reset($exportedcomments);
+        $this->assertEquals($comments[$user1->id], format_string($comment->content, FORMAT_PLAIN));
+
+        // Retrieve comments from any user.
+        \core_comment\privacy\provider::export_comments($context, 'block_comments', 'page_comments', 0, [], false);
+
+        $data = $writer->get_data([get_string('commentsubcontext', 'core_comment')]);
+        $exportedcomments = $data->comments;
+
+        // The whole conversation is two comments.
+        $this->assertCount(2, $exportedcomments);
+        foreach ($exportedcomments as $comment) {
+            $this->assertEquals($comments[$comment->userid], format_string($comment->content, FORMAT_PLAIN));
+        }
+    }
+
+    /**
+     * Tests the deletion of all comments in a context.
+     */
+    public function test_delete_comments_for_all_users_in_context() {
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
+        $coursecontext1 = context_course::instance($course1->id);
+        $coursecontext2 = context_course::instance($course2->id);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $comment1 = $this->get_comment_object($coursecontext1, $course1);
+        $comment2 = $this->get_comment_object($coursecontext2, $course2);
+
+        $this->setUser($user1);
+        $comment1->add('First comment for user 1 on comment 1');
+        $comment2->add('First comment for user 1 on comment 2');
+        $this->setUser($user2);
+        $comment1->add('First comment for user 2 on comment 1');
+        $comment2->add('First comment for user 2 on comment 2');
+
+        // Delete only for the first context. All records in the comments table for this context should be removed.
+        \core_comment\privacy\provider::delete_comments_for_all_users_in_context($coursecontext1);
+        // No records left here.
+        $this->assertCount(0, $comment1->get_comments());
+        // All of the records are left intact here.
+        $this->assertCount(2, $comment2->get_comments());
+
+    }
+
+    /**
+     * Tests deletion of comments for a specified user and contexts.
+     */
+    public function test_delete_comments_for_user() {
+        $this->resetAfterTest();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+
+        $coursecontext1 = context_course::instance($course1->id);
+        $coursecontext2 = context_course::instance($course2->id);
+        $coursecontext3 = context_course::instance($course3->id);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+
+        $comment1 = $this->get_comment_object($coursecontext1, $course1);
+        $comment2 = $this->get_comment_object($coursecontext2, $course2);
+        $comment3 = $this->get_comment_object($coursecontext3, $course3);
+
+        $this->setUser($user1);
+        $comment1->add('First comment for user 1');
+        $comment2->add('User 1 comment in second comment');
+
+        $this->setUser($user2);
+        $comment2->add('User two replied in comment two');
+        $comment3->add('Comment three for user 2.');
+
+        // Delete the comments for user 1.
+        $approvedcontextlist = new core_privacy\tests\request\approved_contextlist($user1, 'block_comments',
+                [$coursecontext1->id, $coursecontext2->id]);
+        \core_comment\privacy\provider::delete_comments_for_user($approvedcontextlist);
+
+        // No comments left in comments 1 as only user 1 commented there.
+        $this->assertCount(0, $comment1->get_comments());
+        // Only user 2 comments left in comments 2.
+        $comment2comments = $comment2->get_comments();
+        $this->assertCount(1, $comment2comments);
+        $this->assertEquals($user2->id, $comment2comments[0]->userid);
+        // Nothing changed here as user 1 did not leave a comment.
+        $comment3comments = $comment3->get_comments();
+        $this->assertCount(1, $comment3comments);
+        $this->assertEquals($user2->id, $comment3comments[0]->userid);
+    }
+
+    /**
+     * Creates a comment object
+     *
+     * @param  context $context A context object.
+     * @param  stdClass $course A course object.
+     * @return comment The comment object.
+     */
+    protected function get_comment_object($context, $course) {
+        // Comment on course page.
+        $args = new stdClass;
+        $args->context = $context;
+        $args->course = $course;
+        $args->area = 'page_comments';
+        $args->itemid = 0;
+        $args->component = 'block_comments';
+        $comment = new comment($args);
+        $comment->set_post_permission(true);
+        return $comment;
+    }
+}
index 34841e1..3f35b53 100644 (file)
@@ -49,5 +49,15 @@ function xmldb_format_topics_upgrade($oldversion) {
     // Automatically generated Moodle v3.4.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2018030900) {
+
+        // During upgrade to Moodle 3.3 it could happen that general section (section 0) became 'invisible'.
+        // It should always be visible.
+        $DB->execute("UPDATE {course_sections} SET visible=1 WHERE visible=0 AND section=0 AND course IN
+        (SELECT id FROM {course} WHERE format=?)", ['topics']);
+
+        upgrade_plugin_savepoint(true, 2018030900, 'format', 'topics');
+    }
+
     return true;
 }
index ea6ecd4..aa7210f 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2018030900;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017110800;        // Requires this Moodle version.
 $plugin->component = 'format_topics';    // Full name of the plugin (used for diagnostics).
index 1600c73..a68f713 100644 (file)
@@ -85,5 +85,15 @@ function xmldb_format_weeks_upgrade($oldversion) {
     // Automatically generated Moodle v3.4.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2018030900) {
+
+        // During upgrade to Moodle 3.3 it could happen that general section (section 0) became 'invisible'.
+        // It should always be visible.
+        $DB->execute("UPDATE {course_sections} SET visible=1 WHERE visible=0 AND section=0 AND course IN
+        (SELECT id FROM {course} WHERE format=?)", ['weeks']);
+
+        upgrade_plugin_savepoint(true, 2018030900, 'format', 'weeks');
+    }
+
     return true;
 }
index dfe4e0c..3f1bb72 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2018030900;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017110800;        // Requires this Moodle version.
 $plugin->component = 'format_weeks';    // Full name of the plugin (used for diagnostics).
index a44b813..dfe7d82 100644 (file)
@@ -437,8 +437,9 @@ class enrol_cohort_plugin extends enrol_plugin {
         $options = $this->get_status_options();
         $mform->addElement('select', 'status', get_string('status', 'enrol_cohort'), $options);
 
-        $options = $this->get_cohort_options($instance, $coursecontext);
-        $mform->addElement('select', 'customint1', get_string('cohort', 'cohort'), $options);
+        $options = ['contextid' => $coursecontext->id, 'multiple' => false];
+        $mform->addElement('cohort', 'customint1', get_string('cohort', 'cohort'), $options);
+
         if ($instance->id) {
             $mform->setConstant('customint1', $instance->customint1);
             $mform->hardFreeze('customint1', $instance->customint1);
index c888169..11500fb 100644 (file)
@@ -782,6 +782,7 @@ class enrol_database_plugin extends enrol_plugin {
                 $template->groupmodeforce = $courseconfig->groupmodeforce;
                 $template->visible        = $courseconfig->visible;
                 $template->lang           = $courseconfig->lang;
+                $template->enablecompletion = $courseconfig->enablecompletion;
                 $template->groupmodeforce = $courseconfig->groupmodeforce;
                 $template->startdate      = usergetmidnight(time());
                 if ($courseconfig->courseenddateenabled) {
index 6f91abd..fcb8f3c 100644 (file)
@@ -81,6 +81,10 @@ final class util {
             }
             error_log($logerrmsg);
 
+            if (http_response_code() == 200) {
+                http_response_code(500);
+            }
+
             exit(0);
         };
     }
index 4c1f713..a5b09a5 100644 (file)
@@ -32,6 +32,7 @@
 // comment out when debugging or better look into error log!
 define('NO_DEBUG_DISPLAY', true);
 
+// @codingStandardsIgnoreLine This script does not require login.
 require("../../config.php");
 require_once("lib.php");
 require_once($CFG->libdir.'/eventslib.php');
@@ -42,9 +43,16 @@ require_once($CFG->libdir . '/filelib.php');
 // the custom handler just logs exceptions and stops.
 set_exception_handler(\enrol_paypal\util::get_exception_handler());
 
+// Make sure we are enabled in the first place.
+if (!enrol_is_enabled('paypal')) {
+    http_response_code(503);
+    throw new moodle_exception('errdisabled', 'enrol_paypal');
+}
+
 /// Keep out casual intruders
 if (empty($_POST) or !empty($_GET)) {
-    print_error("Sorry, you can not use the script that way.");
+    http_response_code(400);
+    throw new moodle_exception('invalidrequest', 'core_error');
 }
 
 /// Read all the data from PayPal and get it ready for later;
@@ -57,11 +65,27 @@ $req = 'cmd=_notify-validate';
 $data = new stdClass();
 
 foreach ($_POST as $key => $value) {
+    if ($key !== clean_param($key, PARAM_ALPHANUMEXT)) {
+        throw new moodle_exception('invalidrequest', 'core_error', '', null, $key);
+    }
+    if (is_array($value)) {
+        throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Unexpected array param: '.$key);
+    }
     $req .= "&$key=".urlencode($value);
     $data->$key = fix_utf8($value);
 }
 
+if (empty($data->custom)) {
+    throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Missing request param: custom');
+}
+
 $custom = explode('-', $data->custom);
+unset($data->custom);
+
+if (empty($custom) || count($custom) < 3) {
+    throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Invalid value of the request param: custom');
+}
+
 $data->userid           = (int)$custom[0];
 $data->courseid         = (int)$custom[1];
 $data->instanceid       = (int)$custom[2];
@@ -69,36 +93,13 @@ $data->payment_gross    = $data->mc_gross;
 $data->payment_currency = $data->mc_currency;
 $data->timeupdated      = time();
 
-// Required for message_send.
-$PAGE->set_context(context_system::instance());
-
-/// get the user and course records
+$user = $DB->get_record("user", array("id" => $data->userid), "*", MUST_EXIST);
+$course = $DB->get_record("course", array("id" => $data->courseid), "*", MUST_EXIST);
+$context = context_course::instance($course->id, MUST_EXIST);
 
-if (! $user = $DB->get_record("user", array("id"=>$data->userid))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid user id", $data);
-    die;
-}
-
-if (! $course = $DB->get_record("course", array("id"=>$data->courseid))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid course id", $data);
-    die;
-}
-
-if (! $context = context_course::instance($course->id, IGNORE_MISSING)) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid context id", $data);
-    die;
-}
-
-// Now that the course/context has been validated, we can set it. Not that it's wonderful
-// to set contexts more than once but system->course switches are accepted.
-// Required for message_send.
 $PAGE->set_context($context);
 
-if (! $plugin_instance = $DB->get_record("enrol", array("id"=>$data->instanceid, "status"=>0))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid instance id", $data);
-    die;
-}
-
+$plugin_instance = $DB->get_record("enrol", array("id" => $data->instanceid, "enrol" => "paypal", "status" => 0), "*", MUST_EXIST);
 $plugin = enrol_get_plugin('paypal');
 
 /// Open a connection back to PayPal to validate the data
@@ -113,10 +114,9 @@ $options = array(
 $location = "https://$paypaladdr/cgi-bin/webscr";
 $result = $c->post($location, $req, $options);
 
-if (!$result) {  /// Could not connect to PayPal - FAIL
-    echo "<p>Error: could not access paypal.com</p>";
-    \enrol_paypal\util::message_paypal_error_to_admin("Could not access paypal.com to verify payment", $data);
-    die;
+if ($c->get_errno()) {
+    throw new moodle_exception('errpaypalconnect', 'enrol_paypal', '', array('url' => $paypaladdr, 'result' => $result),
+        json_encode($data));
 }
 
 /// Connection is OK, so now we post the data to validate it
@@ -318,8 +318,6 @@ if (strlen($result) > 0) {
 
     } else if (strcmp ($result, "INVALID") == 0) { // ERROR
         $DB->insert_record("enrol_paypal", $data, false);
-        \enrol_paypal\util::message_paypal_error_to_admin("Received an invalid payment notification!! (Fake payment?)", $data);
+        throw new moodle_exception('erripninvalid', 'enrol_paypal', '', null, json_encode($data));
     }
 }
-
-exit;
index aa9c7a5..80abf18 100644 (file)
@@ -39,6 +39,9 @@ $string['enrolperiod_desc'] = 'Default length of time that the enrolment is vali
 $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited.';
 $string['enrolstartdate'] = 'Start date';
 $string['enrolstartdate_help'] = 'If enabled, users can be enrolled from this date onward only.';
+$string['errdisabled'] = 'PayPal plugin is disabled and does not handle payment notifications.';
+$string['erripninvalid'] = 'Instant payment notification has not been verified by PayPal.';
+$string['errpaypalconnect'] = 'Could not connect to {$a->url} to verify the instant payment notification: {$a->result}';
 $string['expiredaction'] = 'Enrolment expiry action';
 $string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['mailadmins'] = 'Notify admin';
index 61e5632..2799c31 100644 (file)
--- a/file.php
+++ b/file.php
@@ -68,7 +68,7 @@ if ($course->id != SITEID) {
     require_login($course, true, null, false);
 
 } else if ($CFG->forcelogin) {
-    if (!empty($CFG->sitepolicy)
+    if (empty($CFG->sitepolicyhandler) and !empty($CFG->sitepolicy)
         and ($CFG->sitepolicy == $CFG->wwwroot.'/file.php/'.$relativepath
              or $CFG->sitepolicy == $CFG->wwwroot.'/file.php?file=/'.$relativepath)) {
         //do not require login for policy file
index 8eead64..eebf74f 100644 (file)
@@ -305,8 +305,8 @@ $string['configproxypassword'] = 'Password needed to access internet through pro
 $string['configproxyport'] = 'If this server needs to use a proxy computer, then provide the proxy port here.';
 $string['configproxytype'] = 'Type of web proxy (PHP5 and cURL extension required for SOCKS5 support).';
 $string['configproxyuser'] = 'Username needed to access internet through proxy if required, empty if none (PHP cURL extension required).';
-$string['configrecaptchaprivatekey'] = 'String of characters (private key) used to communicate between your Moodle server and the recaptcha server. ReCAPTCHA keys can be obtained from <a target="_blank" href="https://www.google.com/recaptcha">Google reCAPTCHA</a>.';
-$string['configrecaptchapublickey'] = 'String of characters (public key) used to display the reCAPTCHA element in the signup form. ReCAPTCHA keys can be obtained from <a target="_blank" href="https://www.google.com/recaptcha">Google reCAPTCHA</a>.';
+$string['configrecaptchaprivatekey'] = 'String of characters (secret key) used to communicate between your Moodle server and the recaptcha server. ReCAPTCHA keys can be obtained from <a target="_blank" href="https://www.google.com/recaptcha">Google reCAPTCHA</a>.';
+$string['configrecaptchapublickey'] = 'String of characters (site key) used to display the reCAPTCHA element in the signup form. ReCAPTCHA keys can be obtained from <a target="_blank" href="https://www.google.com/recaptcha">Google reCAPTCHA</a>.';
 $string['configrequestcategoryselection'] = 'Allow the selection of a category when requesting a course.';
 $string['configrequestedstudentname'] = 'Word for student used in requested courses';
 $string['configrequestedstudentsname'] = 'Word for students used in requested courses';
@@ -860,6 +860,9 @@ $string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}
 $string['pluginschecktodo'] = 'You must solve all the plugin requirements before proceeding to install this Moodle version!';
 $string['pluginsoverview'] = 'Plugins overview';
 $string['pluginsoverviewsee'] = 'See <a href="{$a->url}">plugins overview</a> page for more details.';
+$string['policysettings'] = 'Policy settings';
+$string['privacyandpolicies'] = 'Privacy and policies';
+$string['privacysettings'] = 'Privacy settings';
 $string['profilecategory'] = 'Category';
 $string['profilecategoryname'] = 'Category name (must be unique)';
 $string['profilecategorynamenotunique'] = 'This category name is already in use';
@@ -1053,6 +1056,10 @@ $string['sitemaintenancewarning2'] = 'Your site is currently in maintenance mode
 $string['sitepolicies'] = 'Site policies';
 $string['sitepolicy'] = 'Site policy URL';
 $string['sitepolicy_help'] = 'If you have a site policy that all registered users must see and agree to before using this site, then specify the URL to it here, otherwise leave this field blank. This setting can contain any public URL.';
+$string['sitepolicyhandler'] = 'Site policy handler';
+$string['sitepolicyhandler_desc'] = 'Select the component to handle user agreements to site policies. The default core handler provides a simple functionality controlled by the two other settings `sitepolicy` and `sitepolicyguest`. Alternative handlers may be provided by additional plugins and offer more advanced control of site policies.';
+$string['sitepolicyhandlercore'] = 'Default (core)';
+$string['sitepolicyhandlerplugin'] = '{$a->name} ({$a->component})';
 $string['sitepolicyguest'] = 'Site policy URL for guests';
 $string['sitepolicyguest_help'] = 'If you have a site policy that all guests must see and agree to before using this site, then specify the URL to it here, otherwise leave this field blank. This setting can contain any public URL. Note: access of not-logged-in users may be prevented with forcelogin setting.';
 $string['sitesectionhelp'] = 'If selected, a topic section will be displayed on the site\'s front page.';
index d5f70cb..c5185e2 100644 (file)
@@ -103,6 +103,7 @@ $string['forgottenpasswordurl'] = 'Forgotten password URL';
 $string['getanaudiocaptcha'] = 'Get an audio CAPTCHA';
 $string['getanimagecaptcha'] = 'Get an image CAPTCHA';
 $string['getanothercaptcha'] = 'Get another CAPTCHA';
+$string['getrecaptchaapi'] = 'To use reCAPTCHA you must get an API key from <a href=\'https://www.google.com/recaptcha/admin\'>https://www.google.com/recaptcha/admin</a>';
 $string['guestloginbutton'] = 'Guest login button';
 $string['changepassword'] = 'Change password URL';
 $string['changepasswordhelp'] = 'URL of lost password recovery page, which will be sent to users in an email. Note that this setting will have no effect if a forgotten password URL is set in the authentication common settings.';
@@ -139,9 +140,9 @@ $string['pluginnotenabled'] = 'Authentication plugin \'{$a}\' is not enabled.';
 $string['pluginnotinstalled'] = 'Authentication plugin \'{$a}\' is not installed.';
 $string['potentialidps'] = 'Log in using your account on:';
 $string['recaptcha'] = 'reCAPTCHA';
-$string['recaptcha_help'] = 'The CAPTCHA is for preventing abuse from automated programs. Simply enter the words in the box, in order and separated by a space.
+$string['recaptcha_help'] = 'The CAPTCHA is for preventing abuse from automated programs. Follow the instructions to verify you are a person. This could be a box to check, characters presented in an image you must enter or a set of images to select from.
 
-If you are not sure what the words are, you can try getting another CAPTCHA or an audio CAPTCHA.';
+If you are not sure what the images are, you can try getting another CAPTCHA or an audio CAPTCHA.';
 $string['recaptcha_link'] = 'auth/email';
 $string['security_question'] = 'Security question';
 $string['selfregistration'] = 'Self registration';
diff --git a/lang/en/comment.php b/lang/en/comment.php
new file mode 100644 (file)
index 0000000..2d1bdf4
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'moodle', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package   core
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['commentsubcontext'] = 'Comments';
+$string['privacy:metadata:comment'] = 'Stores comments of users.';
+$string['privacy:metadata:comment:content'] = 'Stores the text of the comment.';
+$string['privacy:metadata:comment:timecreated'] = 'Time a comment was created.';
+$string['privacy:metadata:comment:userid'] = 'The user who made the comment.';
index 3d14d66..21b3c60 100644 (file)
@@ -1217,7 +1217,7 @@ $string['missinglastname'] = 'Missing surname';
 $string['missingname'] = 'Missing name';
 $string['missingnewpassword'] = 'Missing new password';
 $string['missingpassword'] = 'Missing password';
-$string['missingrecaptchachallengefield'] = 'Missing reCAPTCHA challenge field';
+$string['missingrecaptchachallengefield'] = 'Failed reCAPTCHA challenge, try again.';
 $string['missingreqreason'] = 'Missing reason';
 $string['missingshortname'] = 'Missing short name';
 $string['missingshortsitename'] = 'Missing short site name';
@@ -2073,6 +2073,7 @@ $string['userselectorpreserveselected'] = 'Keep selected users, even if they no
 $string['userselectorsearchanywhere'] = 'Match the search text anywhere in the displayed fields';
 $string['usersnew'] = 'New users';
 $string['usersnoaccesssince'] = 'Inactive for more than';
+$string['userpreferences'] = 'User preferences';
 $string['userswithfiles'] = 'Users with files';
 $string['useruploadtype'] = 'User upload type: {$a}';
 $string['userzones'] = 'User zones';
index 723b28d..f9cac3a 100644 (file)
@@ -29,3 +29,4 @@ $string['configenableplagiarism'] = 'This will allow administrators to configure
 $string['manageplagiarism'] = 'Manage plagiarism plugins';
 $string['nopluginsinstalled'] = 'No plagiarism plugins are installed.';
 $string['plagiarism'] = 'Plagiarism';
+$string['privacy:metadata:plagiarism'] = 'The plagiarism subsystem acts as a channel, passing requests from plugins to the various plagiarism plugins.';
diff --git a/lang/en/privacy.php b/lang/en/privacy.php
new file mode 100644 (file)
index 0000000..073daac
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'privacy', language 'en', branch 'master'
+ *
+ * @package   core_privacy
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['privacy:metadata'] = 'The privacy subsystem does not store any data of its own and is designed to act as a channel between components and the interface used to describe, export, and remove their data.';
index 864c428..4e03d2f 100644 (file)
@@ -54,4 +54,9 @@ $string['ratingtime'] = 'Restrict ratings to items with dates in this range:';
 $string['ratings'] = 'Ratings';
 $string['rolewarning'] = 'Roles with permission to rate';
 $string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the administration block.';
-$string['scaleselectionrequired'] = 'When selecting a ratings aggregate type you must also select to use either a scale or set a maximum points.';
\ No newline at end of file
+$string['scaleselectionrequired'] = 'When selecting a ratings aggregate type you must also select to use either a scale or set a maximum points.';
+$string['privacy:metadata:rating'] = 'The user-entered rating is stored alongside a mapping of the item which was rated.';
+$string['privacy:metadata:rating:userid'] = 'The user who made the rating.';
+$string['privacy:metadata:rating:rating'] = 'The numeric rating that the user entered.';
+$string['privacy:metadata:rating:timecreated'] = 'The time that the rating was first made.';
+$string['privacy:metadata:rating:timemodified'] = 'The time that the rating was last updated.';
index fea1031..ecc3252 100644 (file)
@@ -92,6 +92,19 @@ $string['noresultsfor'] = 'No results for "{$a}"';
 $string['nothingtoupdate'] = 'Nothing to update';
 $string['owner'] = 'Owner';
 $string['prevpage'] = 'Back';
+$string['privacy:metadata:tag'] = 'The details of each unique tag are stored alongside their description and other related information';
+$string['privacy:metadataetag:name'] = 'The name of the tag - this is the normalised version of the name.';
+$string['privacy:metadata:tag:rawname'] = 'The name of the tag - this is the display name.';
+$string['privacy:metadata:tag:description'] = 'The description of the tag.';
+$string['privacy:metadata:tag:flag'] = 'Whether a tag has been flagged as inappropriate.';
+$string['privacy:metadata:tag:timemodified'] = 'The last time that the tag was last modified.';
+$string['privacy:metadata:tag:userid'] = 'The user who first created the tag.';
+$string['privacy:metadata:taginstance'] = 'The link between each tag and where it is used.';
+$string['privacy:metadata:taginstance:tagid'] = 'The link to the tag.';
+$string['privacy:metadata:taginstance:ordering'] = 'The relative order of this tag.';
+$string['privacy:metadata:taginstance:timecreated'] = 'The time that this tag was linked to the target.';
+$string['privacy:metadata:taginstance:timemodified'] = 'The time that this tag was modified for the target.';
+$string['privacy:metadata:taginstance:tiuserid'] = 'Where shared content can be individually tagged by users, the owner of the tag instance is stored.';
 $string['ptags'] = 'User defined tags (Comma separated)';
 $string['relatedblogs'] = 'Most recent blog entries';
 $string['relatedtags'] = 'Related tags';
index 0e0deff..30f756a 100644 (file)
@@ -10626,3 +10626,46 @@ class admin_setting_agedigitalconsentmap extends admin_setting_configtextarea {
         return true;
     }
 }
+
+/**
+ * Selection of plugins that can work as site policy handlers
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2018 Marina Glancy
+ */
+class admin_settings_sitepolicy_handler_select extends admin_setting_configselect {
+
+    /**
+     * Constructor
+     * @param string $name unique ascii name, either 'mysetting' for settings that in config, or 'myplugin/mysetting'
+     *        for ones in config_plugins.
+     * @param string $visiblename localised
+     * @param string $description long localised info
+     * @param string $defaultsetting
+     */
+    public function __construct($name, $visiblename, $description, $defaultsetting = '') {
+        parent::__construct($name, $visiblename, $description, $defaultsetting, null);
+    }
+
+    /**
+     * Lazy-load the available choices for the select box
+     */
+    public function load_choices() {
+        if (during_initial_install()) {
+            return false;
+        }
+        if (is_array($this->choices)) {
+            return true;
+        }
+
+        $this->choices = ['' => new lang_string('sitepolicyhandlercore', 'core_admin')];
+        $manager = new \core_privacy\local\sitepolicy\manager();
+        $plugins = $manager->get_all_handlers();
+        foreach ($plugins as $pname => $unused) {
+            $this->choices[$pname] = new lang_string('sitepolicyhandlerplugin', 'core_admin',
+                ['name' => new lang_string('pluginname', $pname), 'component' => $pname]);
+        }
+
+        return true;
+    }
+}
index 2009ade..a7b804e 100644 (file)
@@ -470,6 +470,7 @@ $cache = '.var_export($cache, true).';
             'plagiarism'  => $CFG->dirroot.'/plagiarism',
             'plugin'      => null,
             'portfolio'   => $CFG->dirroot.'/portfolio',
+            'privacy'     => $CFG->dirroot . '/privacy',
             'publish'     => $CFG->dirroot.'/course/publish',
             'question'    => $CFG->dirroot.'/question',
             'rating'      => $CFG->dirroot.'/rating',
index 8d99533..0a146cc 100644 (file)
@@ -80,9 +80,10 @@ abstract class persistent extends moodleform {
      * @param string $target
      * @param mixed $attributes
      * @param bool $editable
+     * @param array $ajaxformdata
      */
     public function __construct($action = null, $customdata = null, $method = 'post', $target = '',
-                                $attributes = null, $editable = true) {
+                                $attributes = null, $editable = true, $ajaxformdata=null) {
         if (empty(static::$persistentclass)) {
             throw new coding_exception('Static property $persistentclass must be set.');
         } else if (!is_subclass_of(static::$persistentclass, 'core\\persistent')) {
@@ -106,7 +107,7 @@ abstract class persistent extends moodleform {
         $this->persistent->from_record($persistendata);
 
         unset($customdata['persistent']);
-        parent::__construct($action, $customdata, $method, $target, $attributes, $editable);
+        parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata);
 
         // Load the defaults.
         $this->set_data($this->get_default_data());
index 8cdb154..35ba7f9 100644 (file)
@@ -55,6 +55,15 @@ $functions = array(
         'methodname'  => 'is_minor',
         'description' => 'Requests a check if a user is a digital minor.',
         'type'        => 'read',
+        'ajax'          => true,
+        'loginrequired' => false,
+    ),
+    'core_auth_is_age_digital_consent_verification_enabled' => array(
+        'classname'   => 'core_auth_external',
+        'methodname'  => 'is_age_digital_consent_verification_enabled',
+        'description' => 'Checks if age digital consent verification is enabled.',
+        'type'        => 'read',
+        'ajax'          => true,
         'loginrequired' => false,
     ),
     'core_badges_get_user_badges' => array(
index b6114e5..ae75501 100644 (file)
@@ -351,13 +351,15 @@ class pgsql_native_moodle_database extends moodle_database {
 
         if ($result) {
             while ($row = pg_fetch_assoc($result)) {
-                if (!preg_match('/CREATE (|UNIQUE )INDEX ([^\s]+) ON '.$tablename.' USING ([^\s]+) \(([^\)]+)\)/i', $row['indexdef'], $matches)) {
+                // The index definition could be generated schema-qualifying the target table name
+                // for safety, depending on the pgsql version (CVE-2018-1058).
+                if (!preg_match('/CREATE (|UNIQUE )INDEX ([^\s]+) ON (|'.$row['schemaname'].'\.)'.$tablename.' USING ([^\s]+) \(([^\)]+)\)/i', $row['indexdef'], $matches)) {
                     continue;
                 }
-                if ($matches[4] === 'id') {
+                if ($matches[5] === 'id') {
                     continue;
                 }
-                $columns = explode(',', $matches[4]);
+                $columns = explode(',', $matches[5]);
                 foreach ($columns as $k=>$column) {
                     $column = trim($column);
                     if ($pos = strpos($column, ' ')) {
index 1ed4fe9..d0ce561 100644 (file)
@@ -27,6 +27,7 @@
 
 global $CFG;
 require_once($CFG->libdir . '/form/autocomplete.php');
+require_once($CFG->dirroot . '/cohort/lib.php');
 
 /**
  * Form field type for choosing a cohort.
index 87b3d2a..2eb7326 100644 (file)
@@ -46,9 +46,6 @@ class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templata
     /** @var string html for help button, if empty then no help */
     var $_helpbutton='';
 
-    /** @var bool if true, recaptcha will be servered from https */
-    var $_https=false;
-
     /**
      * constructor
      *
@@ -58,14 +55,8 @@ class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templata
      *              or an associative array
      */
     public function __construct($elementName = null, $elementLabel = null, $attributes = null) {
-        global $CFG;
         parent::__construct($elementName, $elementLabel, $attributes);
         $this->_type = 'recaptcha';
-        if (is_https()) {
-            $this->_https = true;
-        } else {
-            $this->_https = false;
-        }
     }
 
     /**
@@ -79,49 +70,15 @@ class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templata
     }
 
     /**
-     * Returns the recaptcha element in HTML
+     * Returns the reCAPTCHA element in HTML
      *
-     * @return string
+     * @return string The HTML to render
      */
-    function toHtml() {
-        global $CFG, $PAGE;
-        require_once $CFG->libdir . '/recaptchalib.php';
-
-        $recaptureoptions = Array('theme'=>'custom', 'custom_theme_widget'=>'recaptcha_widget');
-        $html = html_writer::script(js_writer::set_variable('RecaptchaOptions', $recaptureoptions));
-
-        $attributes = $this->getAttributes();
-        if (empty($attributes['error_message'])) {
-            $attributes['error_message'] = null;
-            $this->setAttributes($attributes);
-        }
-        $error = $attributes['error_message'];
-        unset($attributes['error_message']);
-
-        $strincorrectpleasetryagain = get_string('incorrectpleasetryagain', 'auth');
-        $strenterthewordsabove = get_string('enterthewordsabove', 'auth');
-        $strenterthenumbersyouhear = get_string('enterthenumbersyouhear', 'auth');
-        $strgetanothercaptcha = get_string('getanothercaptcha', 'auth');
-        $strgetanaudiocaptcha = get_string('getanaudiocaptcha', 'auth');
-        $strgetanimagecaptcha = get_string('getanimagecaptcha', 'auth');
-
-        $html .= '
-<div id="recaptcha_widget" style="display:none">
-
-<div id="recaptcha_image"></div>
-<div class="recaptcha_only_if_incorrect_sol" style="color:red">' . $strincorrectpleasetryagain . '</div>
-
-<span class="recaptcha_only_if_image"><label for="recaptcha_response_field">' . $strenterthewordsabove . '</label></span>
-<span class="recaptcha_only_if_audio"><label for="recaptcha_response_field">' . $strenterthenumbersyouhear . '</label></span>
-
-<input type="text" id="recaptcha_response_field" name="recaptcha_response_field" class="text-ltr" />
-<input type="hidden" name="recaptcha_element" value="dummyvalue" /> <!-- Dummy value to fool formslib -->
-<div><a href="javascript:Recaptcha.reload()">' . $strgetanothercaptcha . '</a></div>
-<div class="recaptcha_only_if_image"><a href="javascript:Recaptcha.switch_type(\'audio\')">' . $strgetanaudiocaptcha . '</a></div>
-<div class="recaptcha_only_if_audio"><a href="javascript:Recaptcha.switch_type(\'image\')">' . $strgetanimagecaptcha . '</a></div>
-</div>';
+    public function toHtml() {
+        global $CFG;
+        require_once($CFG->libdir . '/recaptchalib_v2.php');
 
-        return $html . recaptcha_get_html($CFG->recaptchapublickey, $error, $this->_https);
+        return recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey);
     }
 
     /**
@@ -134,25 +91,22 @@ class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templata
     }
 
     /**
-     * Checks input and challenged field
+     * Checks recaptcha response with Google.
      *
-     * @param string $challenge_field recaptcha shown  to user
-     * @param string $response_field input value by user
+     * @param string $responsestr
      * @return bool
      */
-    function verify($challenge_field, $response_field) {
+    public function verify($responsestr) {
         global $CFG;
-        require_once $CFG->libdir . '/recaptchalib.php';
-        $response = recaptcha_check_answer($CFG->recaptchaprivatekey,
-                                           getremoteaddr(),
-                                           $challenge_field,
-                                           $response_field,
-                                           $this->_https);
-        if (!$response->is_valid) {
+        require_once($CFG->libdir . '/recaptchalib_v2.php');
+
+        $response = recaptcha_check_response(RECAPTCHA_VERIFY_URL, $CFG->recaptchaprivatekey,
+                                           getremoteaddr(), $responsestr);
+        if (!$response['isvalid']) {
             $attributes = $this->getAttributes();
-            $attributes['error_message'] = $response->error;
+            $attributes['error_message'] = $response['error'];
             $this->setAttributes($attributes);
-            return $response->error;
+            return $response['error'];
         }
         return true;
     }
index 61a64d0..a8259ae 100644 (file)
@@ -439,6 +439,11 @@ define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
 /** True if module uses the question bank */
 define('FEATURE_USES_QUESTIONS', 'usesquestions');
 
+/**
+ * Maximum filename char size
+ */
+define('MAX_FILENAME_SIZE', 90);
+
 /** Unspecified module archetype */
 define('MOD_ARCHETYPE_OTHER', 0);
 /** Resource-like type module */
@@ -986,6 +991,21 @@ function clean_param($param, $type) {
             if ($param === '.' || $param === '..') {
                 $param = '';
             }
+            // Extract a part of the filename if it's char size exceeds MAX_FILENAME_SIZE.
+            // If the filename is too long, the file cannot be created on the filesystem due to exceeding max byte size.
+            // Limiting the filename to a certain size (considering multibyte characters) will prevent this.
+            if (core_text::strlen($param) > MAX_FILENAME_SIZE) {
+                // Exclude extension if present in filename.
+                $mimetypes = get_mimetypes_array();
+                $extension = pathinfo($param, PATHINFO_EXTENSION);
+                if ($extension && !empty($mimetypes[$extension])) {
+                    $basename = pathinfo($param, PATHINFO_FILENAME);
+                    $param = core_text::substr($basename, 0, MAX_FILENAME_SIZE);
+                    $param .= '.' . $extension;
+                } else {
+                    $param = core_text::substr($param, 0, MAX_FILENAME_SIZE);
+                }
+            }
             return $param;
 
         case PARAM_PATH:
@@ -2695,24 +2715,24 @@ function require_login($courseorid = null, $autologinguest = true, $cm = null, $
         return;
     }
 
+    // Scripts have a chance to declare that $USER->policyagreed should not be checked.
+    // This is mostly for places where users are actually accepting the policies, to avoid the redirect loop.
+    if (!defined('NO_SITEPOLICY_CHECK')) {
+        define('NO_SITEPOLICY_CHECK', false);
+    }
+
     // Check that the user has agreed to a site policy if there is one - do not test in case of admins.
-    if (!$USER->policyagreed and !is_siteadmin()) {
-        if (!empty($CFG->sitepolicy) and !isguestuser()) {
+    // Do not test if the script explicitly asked for skipping the site policies check.
+    if (!$USER->policyagreed && !is_siteadmin() && !NO_SITEPOLICY_CHECK) {
+        $manager = new \core_privacy\local\sitepolicy\manager();
+        if ($policyurl = $manager->get_redirect_url(isguestuser())) {
             if ($preventredirect) {
-                throw new moodle_exception('sitepolicynotagreed', 'error', '', $CFG->sitepolicy);
+                throw new moodle_exception('sitepolicynotagreed', 'error', '', $policyurl->out());
             }
             if ($setwantsurltome) {
                 $SESSION->wantsurl = qualified_me();
             }
-            redirect($CFG->wwwroot .'/user/policy.php');
-        } else if (!empty($CFG->sitepolicyguest) and isguestuser()) {
-            if ($preventredirect) {
-                throw new moodle_exception('sitepolicynotagreed', 'error', '', $CFG->sitepolicyguest);
-            }
-            if ($setwantsurltome) {
-                $SESSION->wantsurl = qualified_me();
-            }
-            redirect($CFG->wwwroot .'/user/policy.php');
+            redirect($policyurl);
         }
     }
 
@@ -7716,6 +7736,37 @@ function component_callback_exists($component, $function) {
     return false;
 }
 
+/**
+ * Call the specified callback method on the provided class.
+ *
+ * If the callback returns null, then the default value is returned instead.
+ * If the class does not exist, then the default value is returned.
+ *
+ * @param   string      $classname The name of the class to call upon.
+ * @param   string      $methodname The name of the staticically defined method on the class.
+ * @param   array       $params The arguments to pass into the method.
+ * @param   mixed       $default The default value.
+ * @return  mixed       The return value.
+ */
+function component_class_callback($classname, $methodname, array $params, $default = null) {
+    if (!class_exists($classname)) {
+        return $default;
+    }
+
+    if (!method_exists($classname, $methodname)) {
+        return $default;
+    }
+
+    $fullfunction = $classname . '::' . $methodname;
+    $result = call_user_func_array($fullfunction, $params);
+
+    if (null === $result) {
+        return $default;
+    } else {
+        return $result;
+    }
+}
+
 /**
  * Checks whether a plugin supports a specified feature.
  *
index c696d60..db773a6 100644 (file)
@@ -4139,6 +4139,12 @@ class action_menu implements renderable, templatable {
      */
     public $menutrigger = '';
 
+    /**
+     * Any extra classes for toggling to the secondary menu.
+     * @var triggerextraclasses
+     */
+    public $triggerextraclasses = '';
+
     /**
      * Place the action menu before all other actions.
      * @var prioritise
@@ -4178,8 +4184,16 @@ class action_menu implements renderable, templatable {
         }
     }
 
-    public function set_menu_trigger($trigger) {
+    /**
+     * Sets the menu trigger text.
+     *
+     * @param string $trigger The text
+     * @param string $extraclasses Extra classes to style the secondary menu toggle.
+     * @return null
+     */
+    public function set_menu_trigger($trigger, $extraclasses = '') {
         $this->menutrigger = $trigger;
+        $this->triggerextraclasses = $extraclasses;
     }
 
     /**
@@ -4457,6 +4471,7 @@ class action_menu implements renderable, templatable {
         $actionicon = $this->actionicon;
         if (!empty($this->menutrigger)) {
             $primary->menutrigger = $this->menutrigger;
+            $primary->triggerextraclasses = $this->triggerextraclasses;
         } else {
             $primary->title = get_string('actions');
             $actionicon = new pix_icon('t/edit_menu', '', 'moodle', ['class' => 'iconsmall actionmenu', 'title' => '']);
index 6a5800d..b088e09 100644 (file)
@@ -2797,7 +2797,7 @@ EOD;
             $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>';
             //It is usually not possible to recover from errors triggered during installation, you may need to create a new database or use a different database prefix for new installation.
         }
-        $output .= $this->box($message, 'errorbox', null, array('data-rel' => 'fatalerror'));
+        $output .= $this->box($message, 'errorbox alert alert-danger', null, array('data-rel' => 'fatalerror'));
 
         if ($CFG->debugdeveloper) {
             if (!empty($debuginfo)) {
diff --git a/lib/recaptchalib_v2.php b/lib/recaptchalib_v2.php
new file mode 100644 (file)
index 0000000..b8e4837
--- /dev/null
@@ -0,0 +1,193 @@
+<?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 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 is a PHP library that handles calling reCAPTCHA v2.
+ *
+ *    - Documentation
+ *          {@link https://developers.google.com/recaptcha/docs/display}
+ *    - Get a reCAPTCHA API Key
+ *          {@link https://www.google.com/recaptcha/admin}
+ *    - Discussion group
+ *          {@link http://groups.google.com/group/recaptcha}
+ *
+ * @package core
+ * @copyright 2018 Jeff Webster
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The reCAPTCHA URL's
+ */
+define('RECAPTCHA_API_URL', 'https://www.google.com/recaptcha/api.js');
+define('RECAPTCHA_VERIFY_URL', 'https://www.google.com/recaptcha/api/siteverify');
+
+/**
+ * Returns the language code the reCAPTCHA element should use.
+ * Google reCAPTCHA uses different language codes than Moodle so we must convert.
+ * https://developers.google.com/recaptcha/docs/language
+ *
+ * @param string $lang Language to use. If not provided, get current language.
+ * @return string A language code
+ */
+function recaptcha_lang($lang = null) {
+
+    if (empty($lang)) {
+        $lang = current_language();
+    }
+
+    $glang = $lang;
+    switch ($glang) {
+        case 'en':
+            $glang = 'en-GB';
+            break;
+        case 'en_us':
+            $glang = 'en';
+            break;
+        case 'zh_cn':
+            $glang = 'zh-CN';
+            break;
+        case 'zh_tw':
+            $glang = 'zh-TW';
+            break;
+        case 'fr_ca':
+            $glang = 'fr-CA';
+            break;
+        case 'pt_br':
+            $glang = 'pt-BR';
+            break;
+        case 'he':
+            $glang = 'iw';
+            break;
+    }
+    // For any language code that didn't change reduce down to the base language.
+    if (($lang === $glang) and (strpos($lang, '_') !== false)) {
+        list($glang, $trash) = explode('_', $lang, 2);
+    }
+    return $glang;
+}
+
+/**
+ * Gets the challenge HTML
+ * This is called from the browser, and the resulting reCAPTCHA HTML widget
+ * is embedded within the HTML form it was called from.
+ *
+ * @param string $apiurl URL for reCAPTCHA API
+ * @param string $pubkey The public key for reCAPTCHA
+ * @param string $lang Language to use. If not provided, get current language.
+ * @return string - The HTML to be embedded in the user's form.
+ */
+function recaptcha_get_challenge_html($apiurl, $pubkey, $lang = null) {
+    global $CFG, $PAGE;
+
+    // To use reCAPTCHA you must have an API key.
+    if ($pubkey === null || $pubkey === '') {
+        return get_string('getrecaptchaapi', 'auth');
+    }
+
+    $jscode = "
+        var recaptchacallback = function() {
+            grecaptcha.render('recaptcha_element', {
+              'sitekey' : '$pubkey'
+            });
+        }";
+
+    $lang = recaptcha_lang($lang);
+    $apicode = "\n<script type=\"text/javascript\" ";
+    $apicode .= "src=\"$apiurl?onload=recaptchacallback&render=explicit&hl=$lang\" async defer>";
+    $apicode .= "</script>\n";
+
+    $return = html_writer::script($jscode, '');
+    $return .= html_writer::div('', 'recaptcha_element', array('id' => 'recaptcha_element'));
+    $return .= $apicode;
+
+    return $return;
+}
+
+/**
+ * Calls an HTTP POST function to verify if the user's response was correct
+ *
+ * @param string $verifyurl URL for reCAPTCHA verification
+ * @param string $privkey The private key for reCAPTCHA
+ * @param string $remoteip The user's IP
+ * @param string $response The response from reCAPTCHA
+ * @return ReCaptchaResponse
+ */
+function recaptcha_check_response($verifyurl, $privkey, $remoteip, $response) {
+    global $CFG;
+    require_once($CFG->libdir.'/filelib.php');
+
+    // Check response - isvalid boolean, error string.
+    $checkresponse = array('isvalid' => false, 'error' => 'check-not-started');
+
+    // To use reCAPTCHA you must have an API key.
+    if ($privkey === null || $privkey === '') {
+        $checkresponse['isvalid'] = false;
+        $checkresponse['error'] = 'no-apikey';
+        return $checkresponse;
+    }
+
+    // For security reasons, you must pass the remote ip to reCAPTCHA.
+    if ($remoteip === null || $remoteip === '') {
+        $checkresponse['isvalid'] = false;
+        $checkresponse['error'] = 'no-remoteip';
+        return $checkresponse;
+    }
+
+    // Discard spam submissions.
+    if ($response === null || strlen($response) === 0) {
+        $checkresponse['isvalid'] = false;
+        $checkresponse['error'] = 'incorrect-captcha-sol';
+        return $checkresponse;
+    }
+
+    $params = array('secret' => $privkey, 'remoteip' => $remoteip, 'response' => $response);
+    $curl = new curl();
+    $curlresponse = $curl->post($verifyurl, $params);
+
+    if ($curl->get_errno() === 0) {
+        $curldata = json_decode($curlresponse);
+
+        if (isset($curldata->success) && $curldata->success === true) {
+            $checkresponse['isvalid'] = true;
+            $checkresponse['error'] = '';
+        } else {
+            $checkresponse['isvalid'] = false;
+            $checkresponse['error'] = $curldata->{error-codes};
+        }
+    } else {
+        $checkresponse['isvalid'] = false;
+        $checkresponse['error'] = 'check-failed';
+    }
+    return $checkresponse;
+}
+
index 9328510..fce1a8b 100644 (file)
@@ -23,7 +23,8 @@
     {
         "instance": "1",
         "title": "Trigger me!",
-        "menutrigger": true
+        "menutrigger": true,
+        "triggerextraclasses": ""
     }
 }}
-<a href="#" class="toggle-display {{#menutrigger}}textmenu{{/menutrigger}}" id="action-menu-toggle-{{instance}}" title="{{title}}" role="menuitem">{{{actiontext}}}{{{menutrigger}}}{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#rawicon}}{{{.}}}{{/rawicon}}{{#menutrigger}}<b class="caret"></b>{{/menutrigger}}</a>
+<a href="#" class="{{triggerextraclasses}} toggle-display {{#menutrigger}}textmenu{{/menutrigger}}" id="action-menu-toggle-{{instance}}" title="{{title}}" role="menuitem">{{{actiontext}}}{{{menutrigger}}}{{#icon}}{{#pix}}{{key}}, {{component}}, {{title}}{{/pix}}{{/icon}}{{#rawicon}}{{{.}}}{{/rawicon}}{{#menutrigger}}<b class="caret"></b>{{/menutrigger}}</a>
index 24b0b6d..78206a6 100644 (file)
@@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase {
      * this is defined here to annoy devs that try to add more without any thinking,
      * always verify that it does not collide with any existing add-on modules and subplugins!!!
      */
-    const SUBSYSTEMCOUNT = 67;
+    const SUBSYSTEMCOUNT = 68;
 
     public function setUp() {
         $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
diff --git a/lib/tests/fixtures/component_class_callback_example.php b/lib/tests/fixtures/component_class_callback_example.php
new file mode 100644 (file)
index 0000000..cf88df6
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Fixtures for component_class_callback tests.
+ *
+ * @package    core
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class fixture for component_class_callback.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_component_class_callback_example {
+    /**
+     * Function which returns the input value.
+     *
+     * @param   mixed   $output
+     * @return  mixed
+     */
+    public static function method_returns_value($output) {
+        return $output;
+    }
+
+    /**
+     * Function which returns all args.
+     *
+     * @return  mixed
+     */
+    public static function method_returns_all_params() {
+        return count(func_get_args());
+    }
+}
+
+/**
+ * Class fixture for component_class_callback which extends another class.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class test_component_class_callback_child_example extends test_component_class_callback_example {
+}
index faa63fb..454d9cc 100644 (file)
@@ -682,6 +682,13 @@ class core_moodlelib_testcase extends advanced_testcase {
         $this->assertSame(' . .dontltrim.me', clean_param(' . .dontltrim.me', PARAM_FILE));
         $this->assertSame('here is a tab.txt', clean_param("here is a tab\t.txt", PARAM_FILE));
         $this->assertSame('here is a linebreak.txt', clean_param("here is a line\r\nbreak.txt", PARAM_FILE));
+        // Test filename that contains more than 90 characters.
+        $filename = 'sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium totam rem';
+        $this->assertSame('sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laud',
+            clean_param($filename, PARAM_FILE));
+        // Filename contains extension.
+        $this->assertSame('sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laud.zip',
+            clean_param($filename . '.zip', PARAM_FILE));
 
         // The following behaviours have been maintained although they seem a little odd.
         $this->assertSame('funnything', clean_param('funny:thing', PARAM_FILE));
@@ -3592,4 +3599,130 @@ class core_moodlelib_testcase extends advanced_testcase {
         $a = array('aggregatesonly' => [51, 34], 'gradesonly' => [21, 45, 78]);
         $this->assertEquals($a, unserialize_array(serialize($a)));
     }
+
+    /**
+     * Test that the component_class_callback returns the correct default value when the class was not found.
+     *
+     * @dataProvider component_class_callback_default_provider
+     * @param $default
+     */
+    public function test_component_class_callback_not_found($default) {
+        $this->assertSame($default, component_class_callback('thisIsNotTheClassYouWereLookingFor', 'anymethod', [], $default));
+    }
+
+    /**
+     * Test that the component_class_callback returns the correct default value when the class was not found.
+     *
+     * @dataProvider component_class_callback_default_provider
+     * @param $default
+     */
+    public function test_component_class_callback_method_not_found($default) {
+        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
+
+        $this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'this_is_not_the_method_you_were_looking_for', ['abc'], $default));
+    }
+
+    /**
+     * Test that the component_class_callback returns the default when the method returned null.
+     *
+     * @dataProvider component_class_callback_default_provider
+     * @param $default
+     */
+    public function test_component_class_callback_found_returns_null($default) {
+        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
+
+        $this->assertSame($default, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [null], $default));
+        $this->assertSame($default, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [null], $default));
+    }
+
+    /**
+     * Test that the component_class_callback returns the expected value and not the default when there was a value.
+     *
+     * @dataProvider component_class_callback_data_provider
+     * @param $default
+     */
+    public function test_component_class_callback_found_returns_value($value) {
+        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
+
+        $this->assertSame($value, component_class_callback(test_component_class_callback_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
+        $this->assertSame($value, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_value', [$value], 'This is not the value you were looking for'));
+    }
+
+    /**
+     * Test that the component_class_callback handles multiple params correctly.
+     *
+     * @dataProvider component_class_callback_multiple_params_provider
+     * @param $default
+     */
+    public function test_component_class_callback_found_accepts_multiple($params, $count) {
+        require_once(__DIR__ . '/fixtures/component_class_callback_example.php');
+
+        $this->assertSame($count, component_class_callback(test_component_class_callback_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
+        $this->assertSame($count, component_class_callback(test_component_class_callback_child_example::class, 'method_returns_all_params', $params, 'This is not the value you were looking for'));
+    }
+
+    /**
+     * Data provider with list of default values for user in component_class_callback tests.
+     *
+     * @return array
+     */
+    public function component_class_callback_default_provider() {
+        return [
+            'null' => [null],
+            'empty string' => [''],
+            'string' => ['This is a string'],
+            'int' => [12345],
+            'stdClass' => [(object) ['this is my content']],
+            'array' => [['a' => 'b',]],
+        ];
+    }
+
+    /**
+     * Data provider with list of default values for user in component_class_callback tests.
+     *
+     * @return array
+     */
+    public function component_class_callback_data_provider() {
+        return [
+            'empty string' => [''],
+            'string' => ['This is a string'],
+            'int' => [12345],
+            'stdClass' => [(object) ['this is my content']],
+            'array' => [['a' => 'b',]],
+        ];
+    }
+
+    /**
+     * Data provider with list of default values for user in component_class_callback tests.
+     *
+     * @return array
+     */
+    public function component_class_callback_multiple_params_provider() {
+        return [
+            'empty array' => [
+                [],
+                0,
+            ],
+            'string value' => [
+                ['one'],
+                1,
+            ],
+            'string values' => [
+                ['one', 'two'],
+                2,
+            ],
+            'arrays' => [
+                [[], []],
+                2,
+            ],
+            'nulls' => [
+                [null, null, null, null],
+                4,
+            ],
+            'mixed' => [
+                ['a', 1, null, (object) [], []],
+                5,
+            ],
+        ];
+    }
 }
diff --git a/lib/tests/sample_questions_with_old_image_tag.ser b/lib/tests/sample_questions_with_old_image_tag.ser
new file mode 100644 (file)
index 0000000..959d302
--- /dev/null
@@ -0,0 +1,13 @@
+a:1:{s:4:"quiz";a:2:{s:1:"@";a:0:{}s:1:"#";a:1:{s:8:"question";a:3:{i:0;a:2:{s:1:"@";a:1:{s:4:"type";s:8:"category";}s:1:"#";a:1:{s:8:"category";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:42:"$course$/Default for Test images in backup";}}}}}}}i:1;a:2:{s:1:"@";a:1:{s:4:"type";s:5:"cloze";}s:1:"#";a:6:{s:4:"name";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:25:"cloze question with image";}}}}}s:12:"questiontext";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:650:"This is a multianswer question with an image in the old &quot;image&quot; field that was used in moodle 1.9.<br />Match the following cities with the correct state:<br />* San Francisco: {1:MULTICHOICE:=California#OK~Arizona#Wrong}<br />* Tucson: {1:MULTICHOICE:California#Wrong~%100%Arizona#OK}<br />* Los Angeles: {1:MULTICHOICE:=California#OK~Arizona#Wrong}<br />* Phoenix: {1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}<br />The capital of France is {1:SHORTANSWER:%100%Paris#Congratulations!<br />~%50%Marseille#No, that is the second largest city in France (after<br />Paris).~*#Wrong answer. The capital of France is Paris, of course.}.<br />";}}}}}s:5:"image";a:1:{i:0;a:1:{s:1:"#";s:10:"moodle.png";}}s:12:"image_base64";a:1:{i:0;a:1:{s:1:"#";s:815:"
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAHzSURBVDhPxZFbSJNhGIDXERsVGbKawWJaoyAYGeqNYgdl6zenrhhUlB0I6iJEbUl0WF1UUK2DYhDkGghu7Z+HraisPLAsu+0ygi66kCioqwg2t6f///YhzAVBXfTc/HzP9/J8L/wG/pG8QDw+TE+Xn/O+dva07MW5vZayys3yNp/ZwPPRBPuOHqGisordDc00Ki42bqhAjQ3Jid8jAvoL9vIt4qs463G73VRtrcFiseDvuSYG+TwB7y/zfdzHpzeDWachAjeuXyWkPqZ21zZ2Nrioa/JQarOjlK8jes7Eh/s18GwHRMxkgka+3lvEi1ubcgM63Zea6W5v5ESLB4fDwRVvNam+eRBey3TAxqObNl76rXwLFvAlsIyhC2W5gZ+JJmYixQS9q8RZZzpghwEr/R2LpYGPIYWZqJnYSWNugCkPPDDx1JddTyeZqCetasMdS6WB9OQB7Xet5kmbITeQntoP/YWEfSZxFoy5SA2YCXeukAJ+vD5MMl7Cw9YF2UAkGhIXmYSbZLgI9WKxOOukx52kVAvq6eXS6HOHyAyuRz0lN5hlwg19hcTOWqXQ0FxaXZPnGC4V7s+BsToRUM8YpdA2eHswG/DO2eDV7WpGWg30dpZIA5Nddkba5nP3+Epp4F2vwqh3CXeOLZyzwV/wvwPwC58bSNEdAkhaAAAAAElFTkSuQmCC
+
+    ";}}s:15:"generalfeedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}s:14:"shuffleanswers";a:1:{i:0;a:1:{s:1:"#";s:1:"0";}}}}i:2;a:2:{s:1:"@";a:1:{s:4:"type";s:11:"multichoice";}s:1:"#";a:15:{s:4:"name";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:14:"mcq with image";}}}}}s:12:"questiontext";a:1:{i:0;a:2:{s:1:"@";a:1:{s:6:"format";s:4:"html";}s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:107:"This is a multichoice question with an image in the old &quot;image&quot; field that was used in Moodle 1.9";}}}}}s:5:"image";a:1:{i:0;a:1:{s:1:"#";s:10:"moodle.png";}}s:12:"image_base64";a:1:{i:0;a:1:{s:1:"#";s:815:"
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAHzSURBVDhPxZFbSJNhGIDXERsVGbKawWJaoyAYGeqNYgdl6zenrhhUlB0I6iJEbUl0WF1UUK2DYhDkGghu7Z+HraisPLAsu+0ygi66kCioqwg2t6f///YhzAVBXfTc/HzP9/J8L/wG/pG8QDw+TE+Xn/O+dva07MW5vZayys3yNp/ZwPPRBPuOHqGisordDc00Ki42bqhAjQ3Jid8jAvoL9vIt4qs463G73VRtrcFiseDvuSYG+TwB7y/zfdzHpzeDWachAjeuXyWkPqZ21zZ2Nrioa/JQarOjlK8jes7Eh/s18GwHRMxkgka+3lvEi1ubcgM63Zea6W5v5ESLB4fDwRVvNam+eRBey3TAxqObNl76rXwLFvAlsIyhC2W5gZ+JJmYixQS9q8RZZzpghwEr/R2LpYGPIYWZqJnYSWNugCkPPDDx1JddTyeZqCetasMdS6WB9OQB7Xet5kmbITeQntoP/YWEfSZxFoy5SA2YCXeukAJ+vD5MMl7Cw9YF2UAkGhIXmYSbZLgI9WKxOOukx52kVAvq6eXS6HOHyAyuRz0lN5hlwg19hcTOWqXQ0FxaXZPnGC4V7s+BsToRUM8YpdA2eHswG/DO2eDV7WpGWg30dpZIA5Nddkba5nP3+Epp4F2vwqh3CXeOLZyzwV/wvwPwC58bSNEdAkhaAAAAAElFTkSuQmCC
+
+    ";}}s:15:"generalfeedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}s:12:"defaultgrade";a:1:{i:0;a:1:{s:1:"#";s:1:"1";}}s:7:"penalty";a:1:{i:0;a:1:{s:1:"#";s:3:"0.1";}}s:6:"hidden";a:1:{i:0;a:1:{s:1:"#";s:1:"0";}}s:14:"shuffleanswers";a:2:{i:0;a:1:{s:1:"#";s:1:"1";}i:1;a:1:{s:1:"#";s:4:"true";}}s:6:"single";a:1:{i:0;a:1:{s:1:"#";s:4:"true";}}s:15:"correctfeedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}s:24:"partiallycorrectfeedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}s:17:"incorrectfeedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}s:15:"answernumbering";a:1:{i:0;a:1:{s:1:"#";s:3:"abc";}}s:6:"answer";a:3:{i:0;a:2:{s:1:"@";a:1:{s:8:"fraction";s:1:"0";}s:1:"#";a:2:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:22:"
+wrong answer
+        ";}}s:8:"feedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}}}i:1;a:2:{s:1:"@";a:1:{s:8:"fraction";s:1:"0";}s:1:"#";a:2:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:30:"
+another wrong answer
+        ";}}s:8:"feedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}}}i:2;a:2:{s:1:"@";a:1:{s:8:"fraction";s:3:"100";}s:1:"#";a:2:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:22:"
+right answer
+        ";}}s:8:"feedback";a:1:{i:0;a:1:{s:1:"#";a:1:{s:4:"text";a:1:{i:0;a:1:{s:1:"#";s:0:"";}}}}}}}}}}}}}}
\ No newline at end of file
diff --git a/lib/tests/sample_questions_with_old_image_tag.xml b/lib/tests/sample_questions_with_old_image_tag.xml
new file mode 100644 (file)
index 0000000..852e5bc
--- /dev/null
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<quiz>
+
+
+<!-- question: 0  -->
+  <question type="category">
+    <category>
+        <text>$course$/Default for Test images in backup</text>
+
+    </category>
+  </question>
+
+
+
+<!-- question: 5  -->
+  <question type="cloze">
+    <name><text>cloze question with image</text>
+</name>
+    <questiontext>
+<text><![CDATA[This is a multianswer question with an image in the old &quot;image&quot; field that was used in moodle 1.9.<br />Match the following cities with the correct state:<br />* San Francisco: {1:MULTICHOICE:=California#OK~Arizona#Wrong}<br />* Tucson: {1:MULTICHOICE:California#Wrong~%100%Arizona#OK}<br />* Los Angeles: {1:MULTICHOICE:=California#OK~Arizona#Wrong}<br />* Phoenix: {1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}<br />The capital of France is {1:SHORTANSWER:%100%Paris#Congratulations!<br />~%50%Marseille#No, that is the second largest city in France (after<br />Paris).~*#Wrong answer. The capital of France is Paris, of course.}.<br />]]></text>
+    </questiontext>
+<image>moodle.png</image>
+    <image_base64>
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAHzSURBVDhPxZFbSJNhGIDXERsVGbKawWJaoyAYGeqNYgdl6zenrhhUlB0I6iJEbUl0WF1UUK2DYhDkGghu7Z+HraisPLAsu+0ygi66kCioqwg2t6f///YhzAVBXfTc/HzP9/J8L/wG/pG8QDw+TE+Xn/O+dva07MW5vZayys3yNp/ZwPPRBPuOHqGisordDc00Ki42bqhAjQ3Jid8jAvoL9vIt4qs463G73VRtrcFiseDvuSYG+TwB7y/zfdzHpzeDWachAjeuXyWkPqZ21zZ2Nrioa/JQarOjlK8jes7Eh/s18GwHRMxkgka+3lvEi1ubcgM63Zea6W5v5ESLB4fDwRVvNam+eRBey3TAxqObNl76rXwLFvAlsIyhC2W5gZ+JJmYixQS9q8RZZzpghwEr/R2LpYGPIYWZqJnYSWNugCkPPDDx1JddTyeZqCetasMdS6WB9OQB7Xet5kmbITeQntoP/YWEfSZxFoy5SA2YCXeukAJ+vD5MMl7Cw9YF2UAkGhIXmYSbZLgI9WKxOOukx52kVAvq6eXS6HOHyAyuRz0lN5hlwg19hcTOWqXQ0FxaXZPnGC4V7s+BsToRUM8YpdA2eHswG/DO2eDV7WpGWg30dpZIA5Nddkba5nP3+Epp4F2vwqh3CXeOLZyzwV/wvwPwC58bSNEdAkhaAAAAAElFTkSuQmCC
+
+    </image_base64>
+    <generalfeedback>
+<text></text>
+    </generalfeedback>
+    <shuffleanswers>0</shuffleanswers>
+</question>
+
+
+
+<!-- question: 4  -->
+  <question type="multichoice">
+    <name><text>mcq with image</text>
+</name>
+    <questiontext format="html">
+<text><![CDATA[This is a multichoice question with an image in the old &quot;image&quot; field that was used in Moodle 1.9]]></text>
+    </questiontext>
+    <image>moodle.png</image>
+    <image_base64>
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAHzSURBVDhPxZFbSJNhGIDXERsVGbKawWJaoyAYGeqNYgdl6zenrhhUlB0I6iJEbUl0WF1UUK2DYhDkGghu7Z+HraisPLAsu+0ygi66kCioqwg2t6f///YhzAVBXfTc/HzP9/J8L/wG/pG8QDw+TE+Xn/O+dva07MW5vZayys3yNp/ZwPPRBPuOHqGisordDc00Ki42bqhAjQ3Jid8jAvoL9vIt4qs463G73VRtrcFiseDvuSYG+TwB7y/zfdzHpzeDWachAjeuXyWkPqZ21zZ2Nrioa/JQarOjlK8jes7Eh/s18GwHRMxkgka+3lvEi1ubcgM63Zea6W5v5ESLB4fDwRVvNam+eRBey3TAxqObNl76rXwLFvAlsIyhC2W5gZ+JJmYixQS9q8RZZzpghwEr/R2LpYGPIYWZqJnYSWNugCkPPDDx1JddTyeZqCetasMdS6WB9OQB7Xet5kmbITeQntoP/YWEfSZxFoy5SA2YCXeukAJ+vD5MMl7Cw9YF2UAkGhIXmYSbZLgI9WKxOOukx52kVAvq6eXS6HOHyAyuRz0lN5hlwg19hcTOWqXQ0FxaXZPnGC4V7s+BsToRUM8YpdA2eHswG/DO2eDV7WpGWg30dpZIA5Nddkba5nP3+Epp4F2vwqh3CXeOLZyzwV/wvwPwC58bSNEdAkhaAAAAAElFTkSuQmCC
+
+    </image_base64>
+    <generalfeedback>
+<text></text>
+    </generalfeedback>
+    <defaultgrade>1</defaultgrade>
+    <penalty>0.1</penalty>
+    <hidden>0</hidden>
+    <shuffleanswers>1</shuffleanswers>
+    <single>true</single>
+    <shuffleanswers>true</shuffleanswers>
+    <correctfeedback>      <text></text>
+</correctfeedback>
+    <partiallycorrectfeedback>      <text></text>
+</partiallycorrectfeedback>
+    <incorrectfeedback>      <text></text>
+</incorrectfeedback>
+    <answernumbering>abc</answernumbering>
+      <answer fraction="0">
+        <text>
+wrong answer
+        </text>
+      <feedback>
+          <text>
+
+          </text>
+      </feedback>
+    </answer>
+      <answer fraction="0">
+        <text>
+another wrong answer
+        </text>
+      <feedback>
+          <text>
+
+          </text>
+      </feedback>
+    </answer>
+      <answer fraction="100">
+        <text>
+right answer
+        </text>
+      <feedback>
+          <text>
+
+          </text>
+      </feedback>
+    </answer>
+</question>
+
+
+</quiz>
\ No newline at end of file
index f6dceb4..554f165 100644 (file)
@@ -56,4 +56,16 @@ class core_xmlize_testcase extends basic_testcase {
         $this->expectExceptionMessage('Error parsing XML: Mismatched tag at line 18, char 23');
         $xmlnew = xmlize($xml, 1, "UTF-8", true);
     }
+
+    /**
+     * Test an XML import using legacy question data with old image tag.
+     */
+    public function test_xmlimport_of_sample_question_with_old_image_tag() {
+        $xml = file_get_contents(__DIR__ . '/sample_questions_with_old_image_tag.xml');
+        $serialised = file_get_contents(__DIR__ . '/sample_questions_with_old_image_tag.ser');
+
+        // Compare the legacy representation in its serialized state and after unserialization.
+        $this->assertEquals($serialised, serialize(xmlize($xml)));
+        $this->assertEquals(unserialize($serialised), xmlize($xml));
+    }
 }
index eb69954..174efd9 100644 (file)
@@ -15,6 +15,13 @@ information provided here is intended especially for developers.
   - notify()
 * XMLDB now validates the PATH attribute on every install.xml file. Both the XMLDB editor and installation will fail
   when a problem is detected with it. Please ensure your plugins contain correct directory relative paths.
+* Add recaptchalib_v2.php for support of reCAPTCHA v2.
+* Plugins can define class 'PLUGINNAME\privacy\local\sitepolicy\handler' if they implement an alternative mechanisms for
+  site policies managements and agreements. Administrators can define which component is to be used for handling site
+  policies and agreements. See https://docs.moodle.org/dev/Site_policy_handler
+* Scripts can define a constant NO_SITEPOLICY_CHECK and set it to true before requiring the main config.php file. It
+  will make the require_login() skipping the test for the user's policyagreed status. This is useful for plugins that
+  act as a site policy handler.
 
 === 3.4 ===
 
index ef54a6e..78cf82b 100644 (file)
@@ -137,7 +137,7 @@ class core_xml_parser {
         $level = &$this->level;
         if (!empty($name)) {
             if (empty($current[$level])) {
-                $current[$level] = null;
+                $current[$level] = '';
             } else if (array_key_exists(0, $current[$level])) {
                 if (count($current[$level]) == 1) {
                     $current[$level] = $current[$level][0]; // We remove array index if we only have a single entry.
index 325594b..ca975aa 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -98,13 +97,10 @@ class login_signup_form extends moodleform implements renderable, templatable {
             $mform->closeHeaderBefore('recaptcha_element');
         }
 
-        if (!empty($CFG->sitepolicy)) {
-            $mform->addElement('header', 'policyagreement', get_string('policyagreement'), '');
-            $mform->setExpanded('policyagreement');
-            $mform->addElement('static', 'policylink', '', '<a href="'.$CFG->sitepolicy.'" onclick="this.target=\'_blank\'">'.get_String('policyagreementclick').'</a>');
-            $mform->addElement('checkbox', 'policyagreed', get_string('policyaccept'));
-            $mform->addRule('policyagreed', get_string('policyagree'), 'required', null, 'client');
-        }
+        // Add "Agree to sitepolicy" controls. By default it is a link to the policy text and a checkbox but
+        // it can be implemented differently in custom sitepolicy handlers.
+        $manager = new \core_privacy\local\sitepolicy\manager();
+        $manager->signup_form($mform);
 
         // buttons
         $this->add_action_buttons(true, get_string('createaccount'));
@@ -121,15 +117,22 @@ class login_signup_form extends moodleform implements renderable, templatable {
         }
     }
 
-    function validation($data, $files) {
+    /**
+     * Validate user supplied data on the signup form.
+     *
+     * @param array $data array of ("fieldname"=>value) of submitted data
+     * @param array $files array of uploaded files "element_name"=>tmp_file_path
+     * @return array of "element_name"=>"error_description" if there are errors,
+     *         or an empty array if everything is OK (true allowed for backwards compatibility too).
+     */
+    public function validation($data, $files) {
         $errors = parent::validation($data, $files);
 
         if (signup_captcha_enabled()) {
-            $recaptcha_element = $this->_form->getElement('recaptcha_element');
-            if (!empty($this->_form->_submitValues['recaptcha_challenge_field'])) {
-                $challenge_field = $this->_form->_submitValues['recaptcha_challenge_field'];
-                $response_field = $this->_form->_submitValues['recaptcha_response_field'];
-                if (true !== ($result = $recaptcha_element->verify($challenge_field, $response_field))) {
+            $recaptchaelement = $this->_form->getElement('recaptcha_element');
+            if (!empty($this->_form->_submitValues['g-recaptcha-response'])) {
+                $response = $this->_form->_submitValues['g-recaptcha-response'];
+                if (!$recaptchaelement->verify($response)) {
                     $errors['recaptcha_element'] = get_string('incorrectpleasetryagain', 'auth');
                 }
             } else {
@@ -140,7 +143,6 @@ class login_signup_form extends moodleform implements renderable, templatable {
         $errors += signup_validate_data($data, $files);
 
         return $errors;
-
     }
 
     /**
index 0993084..4c4f910 100644 (file)
@@ -36,7 +36,8 @@ function message_popup_render_navbar_output(\renderer_base $renderer) {
     // Early bail out conditions.
     if (!isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
         get_user_preferences('auth_forcepasswordchange') ||
-        ($CFG->sitepolicy && !$USER->policyagreed && !is_siteadmin())) {
+        (!$USER->policyagreed && !is_siteadmin() &&
+            ($manager = new \core_privacy\local\sitepolicy\manager()) && $manager->is_defined())) {
         return '';
     }
 
diff --git a/mod/choice/classes/privacy/provider.php b/mod/choice/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..229efc9
--- /dev/null
@@ -0,0 +1,209 @@
+<?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 mod_choice.
+ *
+ * @package    mod_choice
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_choice\privacy;
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\contextlist;
+use core_privacy\local\request\deletion_criteria;
+use core_privacy\local\request\helper;
+use core_privacy\local\request\writer;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Implementation of the privacy subsystem plugin provider for the choice activity module.
+ *
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+        // This plugin stores personal data.
+        \core_privacy\local\metadata\provider,
+
+        // This plugin is a core_user_data_provider.
+        \core_privacy\local\request\plugin\provider {
+    /**
+     * Return the fields which contain personal data.
+     *
+     * @param collection $items a reference to the collection to use to store the metadata.
+     * @return collection the updated collection of metadata items.
+     */
+    public static function get_metadata(collection $items) : collection {
+        $items->add_database_table(
+            'choice_answers',
+            [
+                'choiceid' => 'privacy:metadata:choice_answers:choiceid',
+                'optionid' => 'privacy:metadata:choice_answers:optionid',
+                'userid' => 'privacy:metadata:choice_answers:userid',
+                'timemodified' => 'privacy:metadata:choice_answers:timemodified',
+            ],
+            'privacy:metadata:choice_answers'
+        );
+
+        return $items;
+    }
+
+    /**
+     * Get the list of contexts that contain user information for the specified user.
+     *
+     * @param int $userid the userid.
+     * @return contextlist the list of contexts containing user info for the user.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        // Fetch all choice answers.
+        $sql = "SELECT c.id
+                  FROM {context} c
+            INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
+            INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
+            INNER JOIN {choice} ch ON ch.id = cm.instance
+            INNER JOIN {choice_options} co ON co.choiceid = ch.id
+            INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id
+                 WHERE ca.userid = :userid";
+
+        $params = [
+            'modname'       => 'choice',
+            'contextlevel'  => CONTEXT_MODULE,
+            'userid'        => $userid,
+        ];
+        $contextlist = new contextlist();
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export personal data for the given approved_contextlist. User and context information is contained within the contextlist.
+     *
+     * @param approved_contextlist $contextlist a list of contexts approved for export.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        $user = $contextlist->get_user();
+
+        list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+
+        $sql = "SELECT cm.id AS cmid,
+                       co.text as answer,
+                       ca.timemodified
+                  FROM {context} c
+            INNER JOIN {course_modules} cm ON cm.id = c.instanceid
+            INNER JOIN {choice} ch ON ch.id = cm.instance
+            INNER JOIN {choice_options} co ON co.choiceid = ch.id
+            INNER JOIN {choice_answers} ca ON ca.optionid = co.id AND ca.choiceid = ch.id
+                 WHERE c.id {$contextsql}
+                       AND ca.userid = :userid
+              ORDER BY cm.id";
+
+        $params = ['userid' => $user->id] + $contextparams;
+
+        // Reference to the choice activity seen in the last iteration of the loop. By comparing this with the current record, and
+        // because we know the results are ordered, we know when we've moved to the answers for a new choice activity and therefore
+        // when we can export the complete data for the last activity.
+        $lastcmid = null;
+
+        $choiceanswers = $DB->get_recordset_sql($sql, $params);
+        foreach ($choiceanswers as $choiceanswer) {
+            // If we've moved to a new choice, then write the last choice data and reinit the choice data array.
+            if ($lastcmid != $choiceanswer->cmid) {
+                if (!empty($choicedata)) {
+                    $context = \context_module::instance($lastcmid);
+                    self::export_choice_data_for_user($choicedata, $context, $user);
+                }
+                $choicedata = [
+                    'answer' => [],
+                    'timemodified' => \core_privacy\local\request\transform::datetime($choiceanswer->timemodified),
+                ];
+            }
+            $choicedata['answer'][] = $choiceanswer->answer;
+            $lastcmid = $choiceanswer->cmid;
+        }
+        $choiceanswers->close();
+
+        // The data for the last activity won't have been written yet, so make sure to write it now!
+        if (!empty($choicedata)) {
+            $context = \context_module::instance($lastcmid);
+            self::export_choice_data_for_user($choicedata, $context, $user);
+        }
+    }
+
+    /**
+     * Export the supplied personal data for a single choice activity, along with any generic data or area files.
+     *
+     * @param array $choicedata the personal data to export for the choice.
+     * @param \context_module $context the context of the choice.
+     * @param \stdClass $user the user record
+     */
+    protected static function export_choice_data_for_user(array $choicedata, \context_module $context, \stdClass $user) {
+        // Fetch the generic module data for the choice.
+        $contextdata = helper::get_context_data($context, $user);
+
+        // Merge with choice data and write it.
+        $contextdata = (object)array_merge((array)$contextdata, $choicedata);
+        writer::with_context($context)->export_data([], $contextdata);
+
+        // Write generic module intro files.
+        helper::export_context_files($context, $user);
+    }
+
+    /**
+     * Delete all data for all users in the specified context.
+     *
+     * @param \context $context the context to delete in.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        global $DB;
+
+        if (empty($context)) {
+            return;
+        }
+        $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
+        $DB->delete_records('choice_answers', ['choiceid' => $instanceid]);
+    }
+
+    /**
+     * Delete all user data for the specified user, in the specified contexts.
+     *
+     * @param approved_contextlist $contextlist a list of contexts approved for deletion.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        global $DB;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        $userid = $contextlist->get_user()->id;
+        foreach ($contextlist->get_contexts() as $context) {
+            $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST);
+            $DB->delete_records('choice_answers', ['choiceid' => $instanceid, 'userid' => $userid]);
+        }
+    }
+}
index 3a732cb..ee568e2 100644 (file)
@@ -109,6 +109,11 @@ $string['pluginadministration'] = 'Choice administration';
 $string['pluginname'] = 'Choice';
 $string['previewonly'] = 'This is just a preview of the available options for this activity. You will not be able to submit your choice until {$a}.';
 $string['privacy'] = 'Privacy of results';
+$string['privacy:metadata:choice_answers'] = 'Information about the user\'s chosen answer(s) for a given choice activity';
+$string['privacy:metadata:choice_answers:choiceid'] = 'The ID of the choice activity the user is providing answer for';
+$string['privacy:metadata:choice_answers:optionid'] = 'The ID of option that the user selected';
+$string['privacy:metadata:choice_answers:userid'] = 'The ID of the user answering this choice activity';
+$string['privacy:metadata:choice_answers:timemodified'] = 'The timestamp indicating when the choice was modified by the user';
 $string['publish'] = 'Publish results';
 $string['publishafteranswer'] = 'Show results to students after they answer';
 $string['publishafterclose'] = 'Show results to students only after the choice is closed';
diff --git a/mod/choice/tests/privacy_provider_test.php b/mod/choice/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..44a7e62
--- /dev/null
@@ -0,0 +1,225 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy provider tests.
+ *
+ * @package    mod_choice
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\deletion_criteria;
+use mod_choice\privacy\provider;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy provider tests class.
+ *
+ * @package    mod_choice
+ * @copyright  2018 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_choice_privacy_provider_testcase extends \core_privacy\tests\provider_testcase {
+    /** @var stdClass The student object. */
+    protected $student;
+
+    /** @var stdClass The choice object. */
+    protected $choice;
+
+    /** @var stdClass The course object. */
+    protected $course;
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function setUp() {
+        $this->resetAfterTest();
+
+        global $DB;
+        $generator = $this->getDataGenerator();
+        $course = $generator->create_course();
+        $options = ['fried rice', 'spring rolls', 'sweet and sour pork', 'satay beef', 'gyouza'];
+        $params = [
+            'course' => $course->id,
+            'option' => $options,
+            'name' => 'First Choice Activity',
+            'showpreview' => 0
+        ];
+
+        $plugingenerator = $generator->get_plugin_generator('mod_choice');
+        // The choice activity the user will answer.
+        $choice = $plugingenerator->create_instance($params);
+        // Create another choice activity.
+        $plugingenerator->create_instance($params);
+        $cm = get_coursemodule_from_instance('choice', $choice->id);
+
+        // Create a student which will make a choice.
+        $student = $generator->create_user();
+        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+        $generator->enrol_user($student->id,  $course->id, $studentrole->id);
+
+        $choicewithoptions = choice_get_choice($choice->id);
+        $optionids = array_keys($choicewithoptions->option);
+
+        choice_user_submit_response($optionids[2], $choice, $student->id, $course, $cm);
+        $this->student = $student;
+        $this->choice = $choice;
+        $this->course = $course;
+    }
+
+    /**
+     * Test for provider::get_metadata().
+     */
+    public function test_get_metadata() {
+        $collection = new collection('mod_choice');
+        $newcollection = provider::get_metadata($collection);
+        $itemcollection = $newcollection->get_collection();
+        $this->assertCount(1, $itemcollection);
+
+        $table = reset($itemcollection);
+        $this->assertEquals('choice_answers', $table->get_name());
+
+        $privacyfields = $table->get_privacy_fields();
+        $this->assertArrayHasKey('choiceid', $privacyfields);
+        $this->assertArrayHasKey('optionid', $privacyfields);
+        $this->assertArrayHasKey('userid', $privacyfields);
+        $this->assertArrayHasKey('timemodified', $privacyfields);
+
+        $this->assertEquals('privacy:metadata:choice_answers', $table->get_summary());
+    }
+
+    /**
+     * Test for provider::get_contexts_for_userid().
+     */
+    public function test_get_contexts_for_userid() {
+        $cm = get_coursemodule_from_instance('choice', $this->choice->id);
+
+        $contextlist = provider::get_contexts_for_userid($this->student->id);
+        $this->assertCount(1, $contextlist);
+        $contextforuser = $contextlist->current();
+        $cmcontext = context_module::instance($cm->id);
+        $this->assertEquals($cmcontext->id, $contextforuser->id);
+    }
+
+    /**
+     * Test for provider::export_user_data().
+     */
+    public function test_export_for_context() {
+        $cm = get_coursemodule_from_instance('choice', $this->choice->id);
+        $cmcontext = context_module::instance($cm->id);
+
+        // Export all of the data for the context.
+        $this->export_context_data_for_user($this->student->id, $cmcontext, 'mod_choice');
+        $writer = \core_privacy\local\request\writer::with_context($cmcontext);
+        $this->assertTrue($writer->has_any_data());
+    }
+
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $choice = $this->choice;
+        $generator = $this->getDataGenerator();
+        $cm = get_coursemodule_from_instance('choice', $this->choice->id);
+
+        // Create another student who will answer the choice activity.
+        $student = $generator->create_user();
+        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+        $generator->enrol_user($student->id, $this->course->id, $studentrole->id);
+
+        $choicewithoptions = choice_get_choice($choice->id);
+        $optionids = array_keys($choicewithoptions->option);
+
+        choice_user_submit_response($optionids[1], $choice, $student->id, $this->course, $cm);
+
+        // Before deletion, we should have 2 responses.
+        $count = $DB->count_records('choice_answers', ['choiceid' => $choice->id]);
+        $this->assertEquals(2, $count);
+
+        // Delete data based on context.
+        $cmcontext = context_module::instance($cm->id);
+        provider::delete_data_for_all_users_in_context($cmcontext);
+
+        // After deletion, the choice answers for that choice activity should have been deleted.
+        $count = $DB->count_records('choice_answers', ['choiceid' => $choice->id]);
+        $this->assertEquals(0, $count);
+    }
+
+    /**
+     * Test for provider::delete_data_for_user().
+     */
+    public function test_delete_data_for_user_() {
+        global $DB;
+
+        $choice = $this->choice;
+        $generator = $this->getDataGenerator();
+        $cm1 = get_coursemodule_from_instance('choice', $this->choice->id);
+
+        // Create a second choice activity.
+        $options = ['Boracay', 'Camiguin', 'Bohol', 'Cebu', 'Coron'];
+        $params = [
+            'course' => $this->course->id,
+            'option' => $options,
+            'name' => 'Which do you think is the best island in the Philippines?',
+            'showpreview' => 0
+        ];
+        $plugingenerator = $generator->get_plugin_generator('mod_choice');
+        $choice2 = $plugingenerator->create_instance($params);
+        $plugingenerator->create_instance($params);
+        $cm2 = get_coursemodule_from_instance('choice', $choice2->id);
+
+        // Make a selection for the first student for the 2nd choice activity.
+        $choicewithoptions = choice_get_choice($choice2->id);
+        $optionids = array_keys($choicewithoptions->option);
+        choice_user_submit_response($optionids[2], $choice2, $this->student->id, $this->course, $cm2);
+
+        // Create another student who will answer the first choice activity.
+        $otherstudent = $generator->create_user();
+        $studentrole = $DB->get_record('role', ['shortname' => 'student']);
+        $generator->enrol_user($otherstudent->id, $this->course->id, $studentrole->id);
+
+        $choicewithoptions = choice_get_choice($choice->id);
+        $optionids = array_keys($choicewithoptions->option);
+
+        choice_user_submit_response($optionids[1], $choice, $otherstudent->id, $this->course, $cm1);
+
+        // Before deletion, we should have 2 responses.
+        $count = $DB->count_records('choice_answers', ['choiceid' => $choice->id]);
+        $this->assertEquals(2, $count);
+
+        $context1 = context_module::instance($cm1->id);
+        $context2 = context_module::instance($cm2->id);
+        $contextlist = new \core_privacy\local\request\approved_contextlist($this->student, 'choice',
+                                                                            [$context1->id, $context2->id]);
+        provider::delete_data_for_user($contextlist);
+
+        // After deletion, the choice answers for the first student should have been deleted.
+        $count = $DB->count_records('choice_answers', ['choiceid' => $choice->id, 'userid' => $this->student->id]);
+        $this->assertEquals(0, $count);
+
+        // Confirm that we only have one choice answer available.
+        $choiceanswers = $DB->get_records('choice_answers');
+        $this->assertCount(1, $choiceanswers);
+        $lastresponse = reset($choiceanswers);
+        // And that it's the other student's response.
+        $this->assertEquals($otherstudent->id, $lastresponse->userid);
+    }
+}
index 8c44deb..47a07ea 100644 (file)
@@ -25,13 +25,13 @@ class feedback_item_captcha extends feedback_item_base {
 
         $editurl = new moodle_url('/mod/feedback/edit.php', array('id'=>$cm->id));
 
-        //ther are no settings for recaptcha
+        // There are no settings for recaptcha.
         if (isset($item->id) AND $item->id > 0) {
             notice(get_string('there_are_no_settings_for_recaptcha', 'feedback'), $editurl->out());
             exit;
         }
 
-        //only one recaptcha can be in a feedback
+        // Only one recaptcha can be in a feedback.
         $params = array('feedback' => $feedback->id, 'typ' => $this->type);
         if ($DB->record_exists('feedback_item', $params)) {
             notice(get_string('only_one_captcha_allowed', 'feedback'), $editurl->out());
@@ -39,7 +39,7 @@ class feedback_item_captcha extends feedback_item_base {
         }
 
         $this->item = $item;
-        $this->item_form = true; //dummy
+        $this->item_form = true; // Dummy.
 
         $lastposition = $DB->count_records('feedback_item', array('feedback'=>$feedback->id));
 
@@ -140,16 +140,13 @@ class feedback_item_captcha extends feedback_item_base {
         $form->add_validation_rule(function($values, $files) use ($item, $form) {
             $elementname = $item->typ . '_' . $item->id . 'recaptcha';
             $recaptchaelement = $form->get_form_element($elementname);
-            if (empty($values['recaptcha_response_field'])) {
+            if (empty($values['g-recaptcha-response'])) {
                 return array($elementname => get_string('required'));
-            } else if (!empty($values['recaptcha_challenge_field'])) {
-                $challengefield = $values['recaptcha_challenge_field'];
-                $responsefield = $values['recaptcha_response_field'];
-                if (true !== ($result = $recaptchaelement->verify($challengefield, $responsefield))) {
+            } else {
+                $response = $values['g-recaptcha-response'];
+                if (true !== ($result = $recaptchaelement->verify($response))) {
                     return array($elementname => $result);
                 }
-            } else {
-                return array($elementname => get_string('missingrecaptchachallengefield'));
             }
             return true;
         });
@@ -164,7 +161,7 @@ class feedback_item_captcha extends feedback_item_base {
     public function get_hasvalue() {
         global $CFG;
 
-        //is recaptcha configured in moodle?
+        // Is recaptcha configured in moodle?
         if (empty($CFG->recaptchaprivatekey) OR empty($CFG->recaptchapublickey)) {
             return 0;
         }
@@ -196,9 +193,10 @@ class feedback_item_captcha extends feedback_item_base {
             return null;
         }
 
-        require_once($CFG->libdir . '/recaptchalib.php');
-        // We return the public key, maybe we want to use the javascript api to get the image.
-        $data = recaptcha_get_challenge_hash_and_urls(RECAPTCHA_API_SECURE_SERVER, $CFG->recaptchapublickey);
+        // With reCAPTCHA v2 the captcha will be rendered by the mobile client using just the publickey.
+        // For now include placeholders for the v1 paramaters to support older mobile app versions.
+        // recaptchachallengehash, recaptchachallengeimage and recaptchachallengejs.
+        $data = array('', '', '');
         $data[] = $CFG->recaptchapublickey;
         return json_encode($data);
     }
diff --git a/mod/folder/classes/privacy/provider.php b/mod/folder/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..7ed91f3
--- /dev/null
@@ -0,0 +1,44 @@
+<?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 mod_folder.
+ *
+ * @package    mod_folder
+ * @copyright  2018 Zig Tan <zig@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_folder\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The mod_folder module does not store any data.
+ *
+ * @copyright  2018 Zig Tan <zig@moodle.com>