Merge branch 'wip-MDL-56635-master' of git://github.com/marinaglancy/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 4 Apr 2018 03:27:11 +0000 (11:27 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 4 Apr 2018 03:27:11 +0000 (11:27 +0800)
684 files changed:
.eslintignore
.stylelintignore
.travis.yml
admin/category.php
admin/roles/assign.php
admin/settings/development.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/cohortroles/classes/api.php
admin/tool/customlang/locallib.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/uploaduser/picture_form.php
admin/tool/usertours/classes/manager.php
admin/tool/usertours/classes/privacy/provider.php [new file with mode: 0644]
admin/tool/usertours/classes/step.php
admin/tool/usertours/lang/en/tool_usertours.php
admin/tool/usertours/tests/privcacy_provider_test.php [new file with mode: 0644]
admin/user.php
analytics/classes/local/target/base.php
analytics/tests/course_test.php
auth/classes/digital_consent.php [new file with mode: 0644]
auth/classes/external.php
auth/classes/form/verify_age_location_form.php [new file with mode: 0644]
auth/classes/output/digital_minor_page.php [new file with mode: 0644]
auth/classes/output/verify_age_location_page.php [new file with mode: 0644]
auth/email/auth.php
auth/email/classes/external.php
auth/email/tests/behat/behat_auth_email.php [new file with mode: 0644]
auth/email/tests/behat/signup.feature [new file with mode: 0644]
auth/ldap/auth.php
auth/ldap/lang/en/auth_ldap.php
auth/oauth2/classes/auth.php
auth/tests/behat/validateagedigitalconsentmap.feature [new file with mode: 0644]
auth/tests/behat/verifyageofconsent.feature [new file with mode: 0644]
auth/tests/digital_consent_test.php [new file with mode: 0644]
auth/tests/external_test.php
auth/upgrade.txt
backup/moodle2/backup_plan_builder.class.php
backup/moodle2/backup_qtype_extrafields_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_plan_builder.class.php
backup/moodle2/restore_qtype_extrafields_plugin.class.php [new file with mode: 0644]
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
backup/util/plan/base_task.class.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/backup/moodle1/lib.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/block_myprofile.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/rss_client/block_rss_client.php
blocks/rss_client/managefeeds.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
cache/stores/memcache/lang/en/cachestore_memcache.php
calendar/classes/local/event/data_access/event_vault.php
calendar/externallib.php
calendar/lib.php
calendar/type/gregorian/classes/privacy/provider.php [new file with mode: 0644]
calendar/type/gregorian/lang/en/calendartype_gregorian.php
calendar/view.php
cohort/lib.php
cohort/tests/behat/access_visible_cohorts.feature
cohort/tests/externallib_test.php
comment/classes/privacy/provider.php [new file with mode: 0644]
comment/tests/privacy_test.php [new file with mode: 0644]
completion/classes/external.php
completion/tests/externallib_test.php
composer.json
composer.lock
course/classes/search/section.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
course/moodleform_mod.php
course/tests/behat/course_controls.feature
course/tests/behat/paged_course_navigation.feature
course/tests/search_test.php
enrol/cohort/lib.php
enrol/database/lib.php
enrol/database/tests/sync_test.php
enrol/lti/classes/task/sync_grades.php
enrol/manual/ajax.php
enrol/paypal/classes/util.php
enrol/paypal/ipn.php
enrol/paypal/lang/en/enrol_paypal.php
enrol/self/bulkchangeforms.php [new file with mode: 0644]
enrol/self/classes/deleteselectedusers_form.php [new file with mode: 0644]
enrol/self/classes/deleteselectedusers_operation.php [new file with mode: 0644]
enrol/self/classes/editselectedusers_form.php [new file with mode: 0644]
enrol/self/classes/editselectedusers_operation.php [new file with mode: 0644]
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/locallib.php
file.php
files/renderer.php
grade/export/lib.php
group/index.php
install.php
install/lang/af/admin.php
install/lang/an/error.php [new file with mode: 0644]
install/lang/an/install.php [new file with mode: 0644]
install/lang/de_ch/langconfig.php [new file with mode: 0644]
install/lang/es/install.php
install/lang/pt/error.php
install/lang/pt_br/install.php
install/lang/sk/install.php
iplookup/tests/geoip_test.php
lang/en/admin.php
lang/en/auth.php
lang/en/backup.php
lang/en/cache.php
lang/en/comment.php [new file with mode: 0644]
lang/en/error.php
lang/en/grades.php
lang/en/message.php
lang/en/moodle.php
lang/en/plagiarism.php
lang/en/privacy.php [new file with mode: 0644]
lang/en/question.php
lang/en/rating.php
lang/en/repository.php
lang/en/role.php
lang/en/search.php
lang/en/tag.php
lib/adminlib.php
lib/amd/build/auto_rows.min.js
lib/amd/src/auto_rows.js
lib/behat/classes/partial_named_selector.php
lib/classes/component.php
lib/classes/event/course_module_completion_updated.php
lib/classes/event/message_deleted.php
lib/classes/event/message_sent.php
lib/classes/event/message_viewed.php
lib/classes/event/notification_sent.php [new file with mode: 0644]
lib/classes/event/notification_viewed.php [new file with mode: 0644]
lib/classes/form/persistent.php
lib/classes/message/manager.php
lib/classes/plugin_manager.php
lib/classes/scss.php
lib/classes/task/messaging_cleanup_task.php
lib/coursecatlib.php
lib/db/access.php
lib/db/caches.php
lib/db/install.xml
lib/db/services.php
lib/db/tag.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/deprecatedlib.php
lib/dml/mariadb_native_moodle_database.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/editor/atto/plugins/accessibilitychecker/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/accessibilitychecker/lang/en/atto_accessibilitychecker.php
lib/editor/atto/plugins/accessibilityhelper/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/accessibilityhelper/lang/en/atto_accessibilityhelper.php
lib/editor/atto/plugins/align/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/align/lang/en/atto_align.php
lib/editor/atto/plugins/backcolor/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/backcolor/lang/en/atto_backcolor.php
lib/editor/atto/plugins/bold/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/bold/lang/en/atto_bold.php
lib/editor/atto/plugins/charmap/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/charmap/lang/en/atto_charmap.php
lib/editor/atto/plugins/clear/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/clear/lang/en/atto_clear.php
lib/editor/atto/plugins/collapse/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/collapse/lang/en/atto_collapse.php
lib/editor/atto/plugins/emoticon/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/emoticon/lang/en/atto_emoticon.php
lib/editor/atto/plugins/equation/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/equation/lang/en/atto_equation.php
lib/editor/atto/plugins/fontcolor/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/fontcolor/lang/en/atto_fontcolor.php
lib/editor/atto/plugins/html/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/html/lang/en/atto_html.php
lib/editor/atto/plugins/image/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/editor/atto/plugins/indent/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/indent/lang/en/atto_indent.php
lib/editor/atto/plugins/italic/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/italic/lang/en/atto_italic.php
lib/editor/atto/plugins/link/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/link/lang/en/atto_link.php
lib/editor/atto/plugins/managefiles/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/managefiles/lang/en/atto_managefiles.php
lib/editor/atto/plugins/media/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/media/lang/en/atto_media.php
lib/editor/atto/plugins/noautolink/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/noautolink/lang/en/atto_noautolink.php
lib/editor/atto/plugins/orderedlist/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/orderedlist/lang/en/atto_orderedlist.php
lib/editor/atto/plugins/rtl/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/rtl/lang/en/atto_rtl.php
lib/editor/atto/plugins/strike/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/strike/lang/en/atto_strike.php
lib/editor/atto/plugins/subscript/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/subscript/lang/en/atto_subscript.php
lib/editor/atto/plugins/superscript/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/superscript/lang/en/atto_superscript.php
lib/editor/atto/plugins/table/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/table/lang/en/atto_table.php
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js
lib/editor/atto/plugins/table/yui/src/button/js/button.js
lib/editor/atto/plugins/title/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/title/lang/en/atto_title.php
lib/editor/atto/plugins/underline/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/underline/lang/en/atto_underline.php
lib/editor/atto/plugins/undo/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/undo/lang/en/atto_undo.php
lib/editor/atto/plugins/unorderedlist/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/plugins/unorderedlist/lang/en/atto_unorderedlist.php
lib/editor/tinymce/plugins/ctrlhelp/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/ctrlhelp/lang/en/tinymce_ctrlhelp.php
lib/editor/tinymce/plugins/managefiles/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/managefiles/lang/en/tinymce_managefiles.php
lib/editor/tinymce/plugins/moodleemoticon/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodleemoticon/lang/en/tinymce_moodleemoticon.php
lib/editor/tinymce/plugins/moodleimage/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodleimage/lang/en/tinymce_moodleimage.php
lib/editor/tinymce/plugins/moodlemedia/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodlemedia/lang/en/tinymce_moodlemedia.php
lib/editor/tinymce/plugins/moodlenolink/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodlenolink/lang/en/tinymce_moodlenolink.php
lib/editor/tinymce/plugins/pdw/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/pdw/lang/en/tinymce_pdw.php
lib/editor/tinymce/plugins/spellchecker/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/spellchecker/lang/en/tinymce_spellchecker.php
lib/editor/tinymce/plugins/wrap/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/plugins/wrap/lang/en/tinymce_wrap.php
lib/externallib.php
lib/form/cohort.php
lib/form/recaptcha.php
lib/listlib.php
lib/messagelib.php
lib/moodlelib.php
lib/outputcomponents.php
lib/outputlib.php
lib/outputrenderers.php
lib/phpunit/classes/message_sink.php
lib/phpunit/classes/phpmailer_sink.php
lib/phpunit/classes/util.php
lib/questionlib.php
lib/recaptchalib_v2.php [new file with mode: 0644]
lib/scssphp/Base/Range.php
lib/scssphp/Block.php
lib/scssphp/Colors.php
lib/scssphp/Compiler.php
lib/scssphp/Compiler/Environment.php
lib/scssphp/Exception/CompilerException.php
lib/scssphp/Exception/ParserException.php
lib/scssphp/Exception/RangeException.php
lib/scssphp/Exception/ServerException.php
lib/scssphp/Formatter.php
lib/scssphp/Formatter/Compact.php
lib/scssphp/Formatter/Compressed.php
lib/scssphp/Formatter/Crunched.php
lib/scssphp/Formatter/Debug.php
lib/scssphp/Formatter/Expanded.php
lib/scssphp/Formatter/Nested.php
lib/scssphp/Formatter/OutputBlock.php
lib/scssphp/Node.php
lib/scssphp/Node/Number.php
lib/scssphp/Parser.php
lib/scssphp/Server.php [deleted file]
lib/scssphp/SourceMap/Base64VLQEncoder.php [new file with mode: 0644]
lib/scssphp/SourceMap/SourceMapGenerator.php [new file with mode: 0644]
lib/scssphp/Type.php
lib/scssphp/Util.php
lib/scssphp/Version.php
lib/scssphp/moodle_readme.txt
lib/templates/action_menu_trigger.mustache
lib/templates/auth_digital_minor_page.mustache [new file with mode: 0644]
lib/templates/auth_verify_age_location_page.mustache [new file with mode: 0644]
lib/testing/generator/data_generator.php
lib/tests/component_test.php
lib/tests/coursecatlib_test.php
lib/tests/fixtures/component_class_callback_example.php [new file with mode: 0644]
lib/tests/message_test.php
lib/tests/messagelib_test.php
lib/tests/moodlelib_test.php
lib/tests/questionlib_test.php
lib/tests/sample_questions.ser [new file with mode: 0644]
lib/tests/sample_questions.xml [new file with mode: 0644]
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/sample_questions_wrong.xml [new file with mode: 0644]
lib/tests/scss_test.php
lib/tests/upgradelib_test.php
lib/tests/xmlize_test.php [new file with mode: 0644]
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/xmldb/xmldb_structure.php
lib/xmlize.php
login/digital_minor.php [new file with mode: 0644]
login/lib.php
login/signup.php
login/signup_form.php
login/verify_age_location.php [new file with mode: 0644]
media/player/videojs/styles.css
message/classes/api.php
message/classes/helper.php
message/classes/search/base_message.php
message/classes/search/message_received.php
message/classes/search/message_sent.php
message/externallib.php
message/index.php
message/lib.php
message/output/airnotifier/classes/privacy/provider.php [new file with mode: 0644]
message/output/airnotifier/lang/en/message_airnotifier.php
message/output/airnotifier/tests/privacy_test.php [new file with mode: 0644]
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/build/notification_repository.min.js
message/output/popup/amd/src/notification_popover_controller.js
message/output/popup/amd/src/notification_repository.js
message/output/popup/classes/api.php
message/output/popup/classes/output/popup_notification.php
message/output/popup/db/install.xml
message/output/popup/externallib.php
message/output/popup/lib.php
message/output/popup/message_output_popup.php
message/output/popup/tests/base.php
message/output/popup/tests/behat/message_popover_unread.feature
message/output/popup/version.php
message/tests/api_test.php
message/tests/events_test.php
message/tests/externallib_test.php
message/tests/messagelib_test.php
message/tests/search_received_test.php
message/tests/search_sent_test.php
message/upgrade.txt
mod/assign/tests/locallib_test.php
mod/book/edit.php
mod/choice/classes/privacy/provider.php [new file with mode: 0644]
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/report.php
mod/choice/tests/behat/modify_choice.feature
mod/choice/tests/privacy_provider_test.php [new file with mode: 0644]
mod/data/classes/search/entry.php
mod/data/tests/search_test.php
mod/feedback/classes/responses_table.php
mod/feedback/item/captcha/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/lang/en/feedback.php
mod/folder/classes/privacy/provider.php [new file with mode: 0644]
mod/folder/download_folder.php
mod/folder/lang/en/folder.php
mod/forum/classes/search/post.php
mod/forum/tests/search_test.php
mod/glossary/tests/behat/import_entries.feature
mod/glossary/view.php
mod/label/classes/privacy/provider.php [new file with mode: 0644]
mod/label/lang/en/label.php
mod/lesson/locallib.php
mod/lesson/pagetypes/branchtable.php
mod/lesson/pagetypes/matching.php
mod/lesson/pagetypes/multichoice.php
mod/lesson/pagetypes/numerical.php
mod/lesson/pagetypes/shortanswer.php
mod/lesson/pagetypes/truefalse.php
mod/lesson/tests/behat/lesson_exit_enter_clusters.feature [new file with mode: 0644]
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/service/memberships/classes/local/resources/contextmemberships.php [moved from mod/lti/service/memberships/classes/local/resource/contextmemberships.php with 99% similarity]
mod/lti/service/memberships/classes/local/resources/linkmemberships.php [moved from mod/lti/service/memberships/classes/local/resource/linkmemberships.php with 98% similarity]
mod/lti/service/memberships/classes/local/service/memberships.php
mod/lti/service/memberships/db/renamedclasses.php [new file with mode: 0644]
mod/lti/service/profile/classes/local/resources/profile.php [moved from mod/lti/service/profile/classes/local/resource/profile.php with 98% similarity]
mod/lti/service/profile/classes/local/service/profile.php
mod/lti/service/profile/db/renamedclasses.php [new file with mode: 0644]
mod/lti/service/toolproxy/classes/local/resources/toolproxy.php [moved from mod/lti/service/toolproxy/classes/local/resource/toolproxy.php with 98% similarity]
mod/lti/service/toolproxy/classes/local/service/toolproxy.php
mod/lti/service/toolproxy/db/renamedclasses.php [new file with mode: 0644]
mod/lti/service/toolsettings/classes/local/resources/contextsettings.php [moved from mod/lti/service/toolsettings/classes/local/resource/contextsettings.php with 97% similarity]
mod/lti/service/toolsettings/classes/local/resources/linksettings.php [moved from mod/lti/service/toolsettings/classes/local/resource/linksettings.php with 96% similarity]
mod/lti/service/toolsettings/classes/local/resources/systemsettings.php [moved from mod/lti/service/toolsettings/classes/local/resource/systemsettings.php with 99% similarity]
mod/lti/service/toolsettings/classes/local/service/toolsettings.php
mod/lti/service/toolsettings/db/renamedclasses.php [new file with mode: 0644]
mod/page/classes/privacy/provider.php [new file with mode: 0644]
mod/page/lang/en/page.php
mod/quiz/addrandom.php
mod/quiz/addrandomform.php
mod/quiz/attemptlib.php
mod/quiz/backup/moodle2/backup_quiz_stepslib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/classes/form/randomquestion_form.php [new file with mode: 0644]
mod/quiz/classes/local/structure/slot_random.php [new file with mode: 0644]
mod/quiz/classes/output/edit_renderer.php
mod/quiz/classes/structure.php
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/editrandom.php [new file with mode: 0644]
mod/quiz/lang/en/quiz.php
mod/quiz/locallib.php
mod/quiz/report/overview/db/install.xml
mod/quiz/report/overview/db/upgrade.php
mod/quiz/report/overview/version.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/editing_add_random.feature [new file with mode: 0644]
mod/quiz/tests/external_test.php
mod/quiz/tests/fixtures/random_by_tag_quiz.mbz [new file with mode: 0644]
mod/quiz/tests/locallib_test.php
mod/quiz/tests/tags_test.php [new file with mode: 0644]
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/resource/classes/privacy/provider.php [new file with mode: 0644]
mod/resource/lang/en/resource.php
mod/scorm/lib.php
mod/url/classes/privacy/provider.php [new file with mode: 0644]
mod/url/lang/en/url.php
mod/wiki/classes/search/collaborative_page.php
mod/wiki/tests/search_test.php
mod/workshop/locallib.php
mod/workshop/tests/behat/workshop_late_submission.feature [new file with mode: 0644]
phpunit.xml.dist
pix/t/tag.svg [new file with mode: 0644]
pix/t/tags.svg [new file with mode: 0644]
plagiarism/classes/privacy/legacy_polyfill.php [new file with mode: 0644]
plagiarism/classes/privacy/plagiarism_provider.php [new file with mode: 0644]
plagiarism/classes/privacy/provider.php [new file with mode: 0644]
plagiarism/tests/privacy_legacy_polyfill_test.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/amd/build/edit_tags.min.js
question/amd/build/repository.min.js
question/amd/src/edit_tags.js
question/amd/src/repository.js
question/behaviour/manualgraded/behaviour.php
question/behaviour/rendererbase.php
question/category.php
question/category_class.php
question/classes/bank/random_question_loader.php
question/classes/bank/search/tag_condition.php
question/classes/bank/tags_action_column.php
question/classes/bank/view.php
question/classes/external.php
question/editlib.php
question/engine/bank.php
question/engine/tests/helpers.php
question/format.php
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
question/lib.php
question/question.php
question/tests/behat/move_question_categories.feature [new file with mode: 0644]
question/tests/externallib_test.php
question/tests/generator/lib.php
question/type/calculatedsimple/tests/helper.php
question/type/ddwtos/tests/questiontype_test.php
question/type/edit_question_form.php
question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php
question/type/essay/db/install.xml
question/type/essay/db/upgrade.php
question/type/essay/edit_essay_form.php
question/type/essay/lang/en/qtype_essay.php
question/type/essay/question.php
question/type/essay/questiontype.php
question/type/essay/renderer.php
question/type/essay/tests/behat/file_type_restriction.feature [new file with mode: 0644]
question/type/essay/tests/fixtures/testquestion.moodle.xml
question/type/essay/tests/helper.php
question/type/essay/tests/walkthrough_test.php
question/type/essay/version.php
question/type/gapselect/tests/edit_form_test.php
question/type/gapselect/tests/helper.php
question/type/gapselect/tests/questiontype_test.php
question/type/gapselect/tests/walkthrough_test.php
question/type/questionbase.php
question/type/random/lang/en/qtype_random.php
question/type/random/questiontype.php
question/type/random/tests/helper.php
question/type/shortanswer/backup/moodle2/backup_qtype_shortanswer_plugin.class.php
question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
question/type/tags_form.php
question/type/upgrade.txt
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]
report/log/classes/table_log.php
report/loglive/classes/table_log.php
report/security/locallib.php
repository/filepicker.js
repository/onedrive/pix/icon.png
repository/onedrive/pix/icon.svg [new file with mode: 0644]
search/classes/base_mod.php
search/classes/document.php
search/classes/engine.php
search/classes/manager.php
search/classes/output/form/search.php
search/engine/simpledb/classes/engine.php [new file with mode: 0644]
search/engine/simpledb/db/install.php [new file with mode: 0644]
search/engine/simpledb/db/install.xml [new file with mode: 0644]
search/engine/simpledb/db/uninstall.php [new file with mode: 0644]
search/engine/simpledb/lang/en/search_simpledb.php [new file with mode: 0644]
search/engine/simpledb/tests/engine_test.php [new file with mode: 0644]
search/engine/simpledb/version.php [moved from message/output/popup/db/events.php with 68% similarity]
search/engine/solr/classes/engine.php
search/engine/solr/classes/schema.php
search/engine/solr/lang/en/search_solr.php
search/engine/solr/tests/engine_test.php
search/index.php
search/tests/engine_test.php
search/tests/fixtures/mock_search_engine.php
search/tests/manager_test.php
search/upgrade.txt
tag/classes/area.php
tag/classes/privacy/provider.php [new file with mode: 0644]
tag/classes/tag.php
tag/tests/privacy_test.php [new file with mode: 0644]
tag/tests/taglib_test.php
theme/boost/classes/privacy/provider.php [new file with mode: 0644]
theme/boost/lang/en/theme_boost.php
theme/boost/scss/moodle/forms.scss
theme/boost/templates/block_search_forums/search_form.mustache
theme/boost/templates/core/action_menu_trigger.mustache
theme/boost/templates/core/auth_digital_minor_page.mustache [new file with mode: 0644]
theme/boost/templates/core/auth_verify_age_location_page.mustache [new file with mode: 0644]
theme/boost/templates/core/filemanager_modal_generallayout.mustache
theme/boost/tests/scss_test.php [new file with mode: 0644]
theme/bootstrapbase/classes/privacy/provider.php [new file with mode: 0644]
theme/bootstrapbase/lang/en/theme_bootstrapbase.php
theme/clean/classes/privacy/provider.php [new file with mode: 0644]
theme/clean/lang/en/theme_clean.php
theme/more/classes/privacy/provider.php [new file with mode: 0644]
theme/more/lang/en/theme_more.php
user/classes/analytics/indicator/user_profile_set.php
user/classes/output/unified_filter.php
user/classes/participants_table.php
user/edit.php
user/edit_form.php
user/editadvanced.php
user/editadvanced_form.php
user/externallib.php
user/index.php
user/lib.php
user/policy.php
user/renderer.php
user/tests/behat/name_fields.feature
version.php
webservice/lib.php
webservice/recaptcha.php [new file with mode: 0644]

index 7d19e9c..8e67b3e 100644 (file)
@@ -41,7 +41,6 @@ lib/html2text/
 lib/markdown/
 lib/recaptchalib.php
 lib/xhprof/
-lib/xmlize.php
 lib/horde/
 lib/requirejs/
 lib/amd/src/loglevel.js
index c35c560..38e84cd 100644 (file)
@@ -42,7 +42,6 @@ lib/html2text/
 lib/markdown/
 lib/recaptchalib.php
 lib/xhprof/
-lib/xmlize.php
 lib/horde/
 lib/requirejs/
 lib/amd/src/loglevel.js
index 9e436f5..a1217e8 100644 (file)
@@ -248,7 +248,7 @@ script:
     - >
       if [ "$TASK" = 'PHPUNIT' ];
       then
-        vendor/bin/phpunit;
+        vendor/bin/phpunit --fail-on-risky --disallow-test-output --verbose;
       fi
 
     - >
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 889a2d1..eada1e9 100644 (file)
@@ -245,7 +245,7 @@ if ($roleid) {
     $select = new single_select($PAGE->url, 'roleid', $nameswithcounts, $roleid, null);
     $select->label = get_string('assignanotherrole', 'core_role');
     echo $OUTPUT->render($select);
-    echo '<p><a href="' . $PAGE->url . '">' . get_string('backtoallroles', 'core_role') . '</a></p>';
+    echo '<p><a href="' . $url . '">' . get_string('backtoallroles', 'core_role') . '</a></p>';
     echo '</div>';
 
 } else if (empty($assignableroles)) {
index 80a8047..41d779f 100644 (file)
@@ -14,6 +14,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
     $temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
 
+    $temp->add(new admin_setting_configexecutable('pathtosassc', new lang_string('pathtosassc', 'admin'), new lang_string('pathtosassc_help', 'admin'), ''));
+
     $ADMIN->add('experimental', $temp);
 
     // "debugging" settingpage
index c55b1f1..9a2202f 100644 (file)
@@ -556,7 +556,7 @@ if ($hassiteconfig) {
     // Search engine selection.
     $temp->add(new admin_setting_heading('searchengineheading', new lang_string('searchengine', 'admin'), ''));
     $temp->add(new admin_setting_configselect('searchengine',
-                                new lang_string('selectsearchengine', 'admin'), '', 'solr', $engines));
+                                new lang_string('selectsearchengine', 'admin'), '', 'simpledb', $engines));
     $temp->add(new admin_setting_heading('searchoptionsheading', new lang_string('searchoptions', 'admin'), ''));
     $temp->add(new admin_setting_configcheckbox('searchindexwhendisabled',
             new lang_string('searchindexwhendisabled', 'admin'), new lang_string('searchindexwhendisabled_desc', 'admin'),
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 8357e70..b3f0e54 100644 (file)
@@ -167,7 +167,7 @@ class api {
                 $params['roleid'] = $roleid;
                 $params['userid'] = $userid;
 
-                $sql = 'SELECT u.id AS userid, ra.id, ctx.id AS contextid
+                $sql = 'SELECT DISTINCT u.id AS userid, ra.id, ctx.id AS contextid
                           FROM {user} u
                           JOIN {cohort_members} cm ON u.id = cm.userid
                           JOIN {context} ctx ON u.id = ctx.instanceid AND ctx.contextlevel = :usercontext
index 02ae558..83e879a 100644 (file)
@@ -159,6 +159,11 @@ class tool_customlang_utils {
                         $needsupdate = true;
                         $current[$stringid]->local          = $stringlocal;
                         $current[$stringid]->timecustomized = $now;
+                    } else if (isset($currentlocal) && $stringlocal !== $currentlocal) {
+                        // If local string has been removed, we need to remove also the old local value from DB.
+                        $needsupdate = true;
+                        $current[$stringid]->local          = null;
+                        $current[$stringid]->timecustomized = $now;
                     }
 
                     if ($needsupdate) {
index e70910b..160c4f0 100644 (file)
@@ -149,6 +149,13 @@ 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(),
+            'autolang' => $CFG->autolang,
+            'lang' => clean_param($CFG->lang, PARAM_LANG),  // Avoid breaking WS because of incorrect package langs.
+            'langmenu' => $CFG->langmenu,
+            'langlist' => $CFG->langlist,
+            'locale' => $CFG->locale,
         );
 
         $typeoflogin = get_config('tool_mobile', 'typeoflogin');
@@ -180,6 +187,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 +236,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..7df769b 100644 (file)
@@ -161,6 +161,19 @@ 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),
+                'autolang' => new external_value(PARAM_INT, 'Whether to detect default language
+                    from browser setting.', VALUE_OPTIONAL),
+                'lang' => new external_value(PARAM_LANG, 'Default language for the site.', VALUE_OPTIONAL),
+                'langmenu' => new external_value(PARAM_INT, 'Whether the language menu should be displayed.', VALUE_OPTIONAL),
+                'langlist' => new external_value(PARAM_RAW, 'Languages on language menu.', VALUE_OPTIONAL),
+                'locale' => new external_value(PARAM_RAW, 'Sitewide locale.', VALUE_OPTIONAL),
                 'warnings' => new external_warnings(),
             )
         );
index 1196f63..d3ddbec 100644 (file)
@@ -87,5 +87,5 @@ $string['smartappbanners'] = 'App Banners';
 $string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
 $string['remoteaddons'] = 'Remote add-ons';
 $string['typeoflogin'] = 'Type of login';
-$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins. If using SSO, autologinguests should be disabled.';
+$string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
 $string['getmoodleonyourmobile'] = 'Get the mobile app';
index ff1f3c1..e61657c 100644 (file)
@@ -86,6 +86,13 @@ 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(),
+            'autolang' => $CFG->autolang,
+            'lang' => $CFG->lang,
+            'langmenu' => $CFG->langmenu,
+            'langlist' => $CFG->langlist,
+            'locale' => $CFG->locale,
             'warnings' => array()
         );
         $this->assertEquals($expected, $result);
@@ -98,12 +105,20 @@ 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);
+        set_config('autolang', 1);
+        set_config('lang', 'a_b');  // Set invalid lang.
 
         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;
+        $expected['autolang'] = '1';
+        $expected['lang'] = ''; // Expect empty because it was set to an invalid lang.
 
         if ($logourl = $OUTPUT->get_logo_url()) {
             $expected['logourl'] = $logourl->out(false);
@@ -150,6 +165,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 24e6ba7..77359eb 100644 (file)
@@ -37,7 +37,7 @@ class admin_uploadpicture_form extends moodleform {
 
 
         $options = array();
-        $options['accepted_types'] = array('archive');
+        $options['accepted_types'] = array('.zip');
         $mform->addElement('filepicker', 'userpicturesfile', get_string('file'), 'size="40"', $options);
         $mform->addRule('userpicturesfile', null, 'required');
 
index b481d2b..15f142c 100644 (file)
@@ -819,7 +819,7 @@ class manager {
         $existingtourrecords->close();
 
         foreach ($shippedtours as $filename => $version) {
-            $filepath = $CFG->dirroot . '/admin/tool/usertours/tours/' . $filename;
+            $filepath = $CFG->dirroot . "/{$CFG->admin}/tool/usertours/tours/" . $filename;
             $tourjson = file_get_contents($filepath);
             $tour = self::import_tour_from_json($tourjson);
 
diff --git a/admin/tool/usertours/classes/privacy/provider.php b/admin/tool/usertours/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..4441ba8
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy Subsystem implementation for tool_usertours.
+ *
+ * @package    tool_usertours
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_usertours\privacy;
+
+use \core_privacy\local\request\writer;
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\transform;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Implementation of the privacy subsystem plugin provider for the user tours feature.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    // This plugin has data.
+    \core_privacy\local\metadata\provider,
+
+    // This plugin has some sitewide user preferences to export.
+    \core_privacy\local\request\user_preference_provider
+{
+
+    /**
+     * Returns meta data about this system.
+     *
+     * @param   collection     $itemcollection The initialised item collection to add items to.
+     * @return  collection     A listing of user data stored through this system.
+     */
+    public static function get_metadata(collection $items) : collection {
+        // There are several user preferences.
+        $items->add_user_preference(\tool_usertours\tour::TOUR_REQUESTED_BY_USER, 'privacy:metadata:preference:requested');
+        $items->add_user_preference(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER, 'privacy:metadata:preference:completed');
+
+        return $items;
+    }
+
+    /**
+     * Store all user preferences for the plugin.
+     *
+     * @param   int         $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        $preferences = get_user_preferences();
+        foreach ($preferences as $name => $value) {
+            $descriptionidentifier = null;
+            $tourid = null;
+            if (strpos($name, \tool_usertours\tour::TOUR_REQUESTED_BY_USER) === 0) {
+                $descriptionidentifier = 'privacy:request:preference:requested';
+                $tourid = substr($name, strlen(\tool_usertours\tour::TOUR_REQUESTED_BY_USER));
+            } else if (strpos($name, \tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER) === 0) {
+                $descriptionidentifier = 'privacy:request:preference:completed';
+                $tourid = substr($name, strlen(\tool_usertours\tour::TOUR_LAST_COMPLETED_BY_USER));
+            }
+
+            if ($descriptionidentifier !== null) {
+                $time = transform::datetime($value);
+                $tour = \tool_usertours\tour::instance($tourid);
+
+                writer::export_user_preference(
+                    'tool_usertours',
+                    $name,
+                    $time,
+                    get_string($descriptionidentifier, 'tool_usertours', (object) [
+                        'name' => $tour->get_name(),
+                        'time' => $time,
+                    ])
+                );
+            }
+        }
+    }
+}
index 6049e8f..4819ac0 100644 (file)
@@ -483,10 +483,9 @@ class step {
             $record = $this->to_record();
             unset($record->id);
             $this->id = $DB->insert_record('tool_usertours_steps', $record);
+            $this->get_tour()->reset_step_sortorder();
         }
 
-        $this->get_tour()->reset_step_sortorder();
-
         $this->reload();
 
         // Notify of a change to the step configuration.
index 5d8dd8a..4c4333c 100644 (file)
@@ -175,3 +175,7 @@ $string['tour2_title_addingblocks'] = 'Adding blocks';
 $string['tour2_content_addingblocks'] = 'You can add blocks to this page using this button. However, think carefully about including any blocks on your pages. Blocks are not shown on the Moodle Mobile app, so for the best user experience it is better to make sure your course works well without any blocks.';
 $string['tour2_title_end'] = 'End of tour';
 $string['tour2_content_end'] = 'This is the end of your user tour. It won\'t show again unless you reset it using the link in the footer. The site admin can also create further tours for this site if required.';
+$string['privacy:metadata:preference:requested'] = 'The time that a user last manually requested a user tour.';
+$string['privacy:metadata:preference:completed'] = 'The time that a user last completed a user tour.';
+$string['privacy:request:preference:requested'] = 'You last requested the "{$a->name}" user tour on {$a->time}';
+$string['privacy:request:preference:completed'] = 'You last marked the "{$a->name}" user tour as completed on {$a->time}';
diff --git a/admin/tool/usertours/tests/privcacy_provider_test.php b/admin/tool/usertours/tests/privcacy_provider_test.php
new file mode 100644 (file)
index 0000000..8cfc81a
--- /dev/null
@@ -0,0 +1,115 @@
+<?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\metadata\collection;
+use \core_privacy\local\request\writer;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\deletion_criteria;
+use \tool_usertours\tour;
+use \tool_usertours\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 tool_usertours_privacy_testcase extends \core_privacy\tests\provider_testcase {
+
+    /**
+     * Ensure that get_metadata exports valid content.
+     */
+    public function test_get_metadata() {
+        $items = new collection('tool_usertours');
+        $result = provider::get_metadata($items);
+        $this->assertSame($items, $result);
+        $this->assertInstanceOf(collection::class, $result);
+    }
+
+    /**
+     * Ensure that export_user_preferences returns no data if the user has completed no tours.
+     */
+    public function test_export_user_preferences_no_pref() {
+        $user = \core_user::get_user_by_username('admin');
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+
+        $this->assertFalse($writer->has_any_data());
+    }
+
+    /**
+     * Ensure that export_user_preferences returns request completion data.
+     */
+    public function test_export_user_preferences_completed() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $alltours = $DB->get_records('tool_usertours_tours');
+        $tourdata = reset($alltours);
+
+        $user = \core_user::get_user_by_username('admin');
+        $tour = tour::instance($tourdata->id);
+        $tour->mark_user_completed();
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+
+        $this->assertTrue($writer->has_any_data());
+        $prefs = $writer->get_user_preferences('tool_usertours');
+
+        $this->assertCount(1, (array) $prefs);
+    }
+
+    /**
+     * Ensure that export_user_preferences returns request completion data.
+     */
+    public function test_export_user_preferences_requested() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+
+        $alltours = $DB->get_records('tool_usertours_tours');
+        $tourdata = reset($alltours);
+
+        $user = \core_user::get_user_by_username('admin');
+        $tour = tour::instance($tourdata->id);
+        $tour->mark_user_completed();
+        $tour->request_user_reset();
+        provider::export_user_preferences($user->id);
+
+        $writer = writer::with_context(\context_system::instance());
+
+        $this->assertTrue($writer->has_any_data());
+        $prefs = $writer->get_user_preferences('tool_usertours');
+
+        $this->assertCount(2, (array) $prefs);
+    }
+}
index ac0f5f9..0caf98f 100644 (file)
 
     // Carry on with the user listing
     $context = context_system::instance();
-    $extracolumns = get_extra_user_fields($context);
+    // These columns are always shown in the users list.
+    $requiredcolumns = array('city', 'country', 'lastaccess');
+    // Extra columns containing the extra user fields, excluding the required columns (city and country, to be specific).
+    $extracolumns = get_extra_user_fields($context, $requiredcolumns);
     // Get all user name fields as an array.
     $allusernamefields = get_all_user_name_fields(false, null, null, null, true);
-    $columns = array_merge($allusernamefields, $extracolumns, array('city', 'country', 'lastaccess'));
+    $columns = array_merge($allusernamefields, $extracolumns, $requiredcolumns);
 
     foreach ($columns as $column) {
         $string[$column] = get_user_field_name($column);
 
     } else {
 
-        $countries = get_string_manager()->get_list_of_countries(false);
+        $countries = get_string_manager()->get_list_of_countries(true);
         if (empty($mnethosts)) {
             $mnethosts = $DB->get_records('mnet_host', null, 'id', 'id,wwwroot,name');
         }
index 3e84120..638b18b 100644 (file)
@@ -205,10 +205,10 @@ abstract class base extends \core_analytics\calculable {
                 $message->contexturlname = $message->subject;
                 $message->courseid = $coursecontext->instanceid;
 
-                $message->fullmessage = get_string('insightinfomessage', 'analytics', $insighturl->out());
+                $message->fullmessage = get_string('insightinfomessage', 'analytics', $insighturl->out(false));
                 $message->fullmessageformat = FORMAT_PLAIN;
                 $message->fullmessagehtml = get_string('insightinfomessagehtml', 'analytics', $insighturl->out());
-                $message->smallmessage = get_string('insightinfomessage', 'analytics', $insighturl->out());
+                $message->smallmessage = get_string('insightinfomessage', 'analytics', $insighturl->out(false));
                 $message->contexturl = $insighturl->out(false);
 
                 message_send($message);
index 0383c08..00cfea3 100644 (file)
@@ -36,7 +36,7 @@ class core_analytics_course_testcase extends advanced_testcase {
     public function setUp() {
         global $DB;
 
-        $this->course = $this->getDataGenerator()->create_course();
+        $this->course = $this->getDataGenerator()->create_course(['startdate' => 0]);
         $this->stu1 = $this->getDataGenerator()->create_user();
         $this->stu2 = $this->getDataGenerator()->create_user();
         $this->both = $this->getDataGenerator()->create_user();
diff --git a/auth/classes/digital_consent.php b/auth/classes/digital_consent.php
new file mode 100644 (file)
index 0000000..3f7e3af
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Contains helper class for digital consent.
+ *
+ * @package     core_auth
+ * @copyright   2018 Mihail Geshoski <mihail@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_auth;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Helper class for digital consent.
+ *
+ * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class digital_consent {
+
+    /**
+     * Returns true if age and location verification is enabled in the site.
+     *
+     * @return bool
+     */
+    public static function is_age_digital_consent_verification_enabled() {
+        global $CFG;
+
+        return !empty($CFG->agedigitalconsentverification);
+    }
+
+    /**
+     * Checks if a user is a digital minor.
+     *
+     * @param int $age
+     * @param string $country The country code (ISO 3166-2)
+     * @return bool
+     */
+    public static function is_minor($age, $country) {
+        global $CFG;
+
+        $ageconsentmap = $CFG->agedigitalconsentmap;
+        $agedigitalconsentmap = self::parse_age_digital_consent_map($ageconsentmap);
+
+        return array_key_exists($country, $agedigitalconsentmap) ?
+            $age < $agedigitalconsentmap[$country] : $age < $agedigitalconsentmap['*'];
+    }
+
+    /**
+     * Parse the agedigitalconsentmap setting into an array.
+     *
+     * @param  string $ageconsentmap The value of the agedigitalconsentmap setting
+     * @return array $ageconsentmapparsed
+     */
+    public static function parse_age_digital_consent_map($ageconsentmap) {
+
+        $ageconsentmapparsed = array();
+        $countries = get_string_manager()->get_list_of_countries(true);
+        $isdefaultvaluepresent = false;
+        $lines = preg_split('/\r|\n/', $ageconsentmap, -1, PREG_SPLIT_NO_EMPTY);
+        foreach ($lines as $line) {
+            $arr = explode(",", $line);
+            // Handle if there is more or less than one comma separator.
+            if (count($arr) != 2) {
+                throw new \moodle_exception('agedigitalconsentmapinvalidcomma', 'error', '', $line);
+            }
+            $country = trim($arr[0]);
+            $age = trim($arr[1]);
+            // Check if default.
+            if ($country == "*") {
+                $isdefaultvaluepresent = true;
+            }
+            // Handle if the presented value for country is not valid.
+            if ($country !== "*" && !array_key_exists($country, $countries)) {
+                throw new \moodle_exception('agedigitalconsentmapinvalidcountry', 'error', '', $country);
+            }
+            // Handle if the presented value for age is not valid.
+            if (!is_numeric($age)) {
+                throw new \moodle_exception('agedigitalconsentmapinvalidage', 'error', '', $age);
+            }
+            $ageconsentmapparsed[$country] = $age;
+        }
+        // Handle if a default value does not exist.
+        if (!$isdefaultvaluepresent) {
+            throw new \moodle_exception('agedigitalconsentmapinvaliddefault');
+        }
+
+        return $ageconsentmapparsed;
+    }
+}
index baa86d9..7c08349 100644 (file)
@@ -215,4 +215,125 @@ class core_auth_external extends external_api {
             )
         );
     }
+
+    /**
+     * Describes the parameters for the digital minor check.
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.4
+     */
+    public static function is_minor_parameters() {
+        return new external_function_parameters(
+            array(
+                'age' => new external_value(PARAM_INT, 'Age'),
+                'country' => new external_value(PARAM_ALPHA, 'Country of residence'),
+            )
+        );
+    }
+
+    /**
+     * Requests a check if a user is digital minor.
+     *
+     * @param  int $age User age
+     * @param  string $country Country of residence
+     * @return array status (true if the user is a minor, false otherwise)
+     * @since Moodle 3.4
+     * @throws moodle_exception
+     */
+    public static function is_minor($age, $country) {
+        global $CFG, $PAGE;
+        require_once($CFG->dirroot . '/login/lib.php');
+
+        $params = self::validate_parameters(
+            self::is_minor_parameters(),
+            array(
+                'age' => $age,
+                'country' => $country,
+            )
+        );
+
+        if (!array_key_exists($params['country'], get_string_manager()->get_list_of_countries())) {
+            throw new invalid_parameter_exception('Invalid value for country parameter (value: '.
+                $params['country'] .')');
+        }
+
+        $context = context_system::instance();
+        $PAGE->set_context($context);
+
+        // Check if verification of age and location (minor check) is enabled.
+        if (!\core_auth\digital_consent::is_age_digital_consent_verification_enabled()) {
+            throw new moodle_exception('nopermissions', 'error', '',
+                get_string('agelocationverificationdisabled', 'error'));
+        }
+
+        $status = \core_auth\digital_consent::is_minor($params['age'], $params['country']);
+
+        return array(
+            'status' => $status
+        );
+    }
+
+    /**
+     * Describes the is_minor return value.
+     *
+     * @return external_single_structure
+     * @since Moodle 3.4
+     */
+    public static function is_minor_returns() {
+        return new external_single_structure(
+            array(
+                'status' => new external_value(PARAM_BOOL, 'True if the user is considered to be a digital minor,
+                    false if not')
+            )
+        );
+    }
+
+    /**
+     * 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.')
+            )
+        );
+    }
 }
diff --git a/auth/classes/form/verify_age_location_form.php b/auth/classes/form/verify_age_location_form.php
new file mode 100644 (file)
index 0000000..d2a80e1
--- /dev/null
@@ -0,0 +1,62 @@
+<?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/>.
+
+/**
+ * Age and location verification mform.
+ *
+ * @package     core_auth
+ * @copyright   2018 Mihail Geshoski <mihail@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_auth\form;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+use moodleform;
+
+/**
+ * Age and location verification mform class.
+ *
+ * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class verify_age_location_form extends moodleform {
+    /**
+     * Defines the form fields.
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+
+        $mform->addElement('text', 'age', get_string('whatisyourage'), array('optional'  => false));
+        $mform->setType('age', PARAM_RAW);
+        $mform->addRule('age', null, 'required', null, 'client');
+        $mform->addRule('age', null, 'numeric', null, 'client');
+
+        $countries = get_string_manager()->get_list_of_countries();
+        $defaultcountry[''] = get_string('selectacountry');
+        $countries = array_merge($defaultcountry, $countries);
+        $mform->addElement('select', 'country', get_string('wheredoyoulive'), $countries);
+        $mform->addRule('country', null, 'required', null, 'client');
+        $mform->setDefault('country', $CFG->country);
+
+        $this->add_action_buttons(true, get_string('proceed'));
+    }
+}
diff --git a/auth/classes/output/digital_minor_page.php b/auth/classes/output/digital_minor_page.php
new file mode 100644 (file)
index 0000000..411893e
--- /dev/null
@@ -0,0 +1,63 @@
+<?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/>.
+
+/**
+ * Digital minor renderable.
+ *
+ * @package     core_auth
+ * @copyright   2018 Mihail Geshoski <mihail@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_auth\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use renderable;
+use renderer_base;
+use templatable;
+
+/**
+ * Digital minor renderable class.
+ *
+ * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class digital_minor_page implements renderable, templatable {
+
+    /**
+     * Export the page data for the mustache template.
+     *
+     * @param renderer_base $output renderer to be used to render the page elements.
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+        global $SITE, $CFG;
+
+        $sitename = format_string($SITE->fullname);
+        $supportname = $CFG->supportname;
+        $supportemail = $CFG->supportemail;
+
+        $context = [
+            'sitename' => $sitename,
+            'supportname' => $supportname,
+            'supportemail' => $supportemail,
+            'homelink' => new \moodle_url('/')
+        ];
+
+        return $context;
+    }
+}
diff --git a/auth/classes/output/verify_age_location_page.php b/auth/classes/output/verify_age_location_page.php
new file mode 100644 (file)
index 0000000..4d8b790
--- /dev/null
@@ -0,0 +1,81 @@
+<?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/>.
+
+/**
+ * Age and location verification renderable.
+ *
+ * @package     core_auth
+ * @copyright   2018 Mihail Geshoski <mihail@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_auth\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use renderable;
+use renderer_base;
+use templatable;
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Age and location verification renderable class.
+ *
+ * @copyright 2018 Mihail Geshoski <mihail@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class verify_age_location_page implements renderable, templatable {
+
+    /** @var mform The form object */
+    protected $form;
+
+    /** @var string Error message */
+    protected $errormessage;
+
+    /**
+     * Constructor
+     *
+     * @param mform $form The form object
+     * @param string $errormessage The error message.
+     */
+    public function __construct($form, $errormessage = null) {
+        $this->form = $form;
+        $this->errormessage = $errormessage;
+    }
+
+    /**
+     * Export the page data for the mustache template.
+     *
+     * @param renderer_base $output renderer to be used to render the page elements.
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+        global $SITE;
+
+        $sitename = format_string($SITE->fullname);
+        $formhtml = $this->form->render();
+        $error = $this->errormessage;
+
+        $context = [
+            'sitename' => $sitename,
+            'formhtml' => $formhtml,
+            'error'    => $error
+        ];
+
+        return $context;
+    }
+}
index f1cbc11..f207f03 100644 (file)
@@ -113,7 +113,7 @@ class auth_plugin_email extends auth_plugin_base {
      * @since Moodle 3.2
      */
     public function user_signup_with_confirmation($user, $notify=true, $confirmationurl = null) {
-        global $CFG, $DB;
+        global $CFG, $DB, $SESSION;
         require_once($CFG->dirroot.'/user/profile/lib.php');
         require_once($CFG->dirroot.'/user/lib.php');
 
@@ -130,6 +130,11 @@ class auth_plugin_email extends auth_plugin_base {
         // Save any custom profile field information.
         profile_save_data($user);
 
+        // Save wantsurl against user's profile, so we can return them there upon confirmation.
+        if (!empty($SESSION->wantsurl)) {
+            set_user_preference('auth_email_wantsurl', $SESSION->wantsurl, $user);
+        }
+
         // Trigger event.
         \core\event\user_created::create_from_userid($user->id)->trigger();
 
@@ -166,7 +171,7 @@ class auth_plugin_email extends auth_plugin_base {
      * @param string $confirmsecret
      */
     function user_confirm($username, $confirmsecret) {
-        global $DB;
+        global $DB, $SESSION;
         $user = get_complete_user_data('username', $username);
 
         if (!empty($user)) {
@@ -178,6 +183,13 @@ class auth_plugin_email extends auth_plugin_base {
 
             } else if ($user->secret == $confirmsecret) {   // They have provided the secret key to get in
                 $DB->set_field("user", "confirmed", 1, array("id"=>$user->id));
+
+                if ($wantsurl = get_user_preferences('auth_email_wantsurl', false, $user)) {
+                    // Ensure user gets returned to page they were trying to access before signing up.
+                    $SESSION->wantsurl = $wantsurl;
+                    unset_user_preference('auth_email_wantsurl', $user);
+                }
+
                 return AUTH_CONFIRM_OK;
             }
         } else {
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'];
             }
         }
 
diff --git a/auth/email/tests/behat/behat_auth_email.php b/auth/email/tests/behat/behat_auth_email.php
new file mode 100644 (file)
index 0000000..b3fe2fb
--- /dev/null
@@ -0,0 +1,54 @@
+<?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/>.
+
+/**
+ * Step definition for auth_email
+ *
+ * @package    auth_email
+ * @category   test
+ * @copyright  2018 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
+
+/**
+ * Step definition for auth_email.
+ *
+ * @package    auth_email
+ * @category   test
+ * @copyright  2018 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_auth_email extends behat_base {
+
+    /**
+     * Emulate clicking on confirmation link from the email
+     *
+     * @When /^I confirm email for "(?P<username>(?:[^"]|\\")*)"$/
+     *
+     * @param string $username
+     */
+    public function i_confirm_email_for($username) {
+        global $DB;
+        $secret = $DB->get_field('user', 'secret', ['username' => $username], MUST_EXIST);
+        $confirmationurl = new moodle_url('/login/confirm.php');
+        $confirmationpath = $confirmationurl->out_as_local_url(false);
+        $url = $confirmationpath .  '?' . 'data='. $secret .'/'. $username;
+
+        $this->getSession()->visit($this->locate_path($url));
+    }
+}
diff --git a/auth/email/tests/behat/signup.feature b/auth/email/tests/behat/signup.feature
new file mode 100644 (file)
index 0000000..7934510
--- /dev/null
@@ -0,0 +1,65 @@
+@auth @auth_email
+Feature: User must accept policy when logging in and signing up
+  In order to record user agreement to use the site
+  As a user
+  I need to be able to accept site policy during sign up
+
+  Scenario: Accept policy on sign up, no site policy
+    Given the following config values are set as admin:
+      | registerauth    | email |
+      | passwordpolicy  | 0     |
+    And I am on site homepage
+    And I follow "Log in"
+    When I press "Create new account"
+    Then I should not see "I understand and agree"
+    And I set the following fields to these values:
+      | Username      | user1                 |
+      | Password      | user1                 |
+      | Email address | user1@address.invalid |
+      | Email (again) | user1@address.invalid |
+      | First name    | User1                 |
+      | Surname       | L1                    |
+    And I press "Create my new account"
+    And I should see "Confirm your account"
+    And I should see "An email should have been sent to your address at user1@address.invalid"
+    And I confirm email for "user1"
+    And I should see "Thanks, User1 L1"
+    And I should see "Your registration has been confirmed"
+    And I open my profile in edit mode
+    And the field "First name" matches value "User1"
+    And I log out
+    # Confirm that user can login and browse the site (edit their profile).
+    And I log in as "user1"
+    And I open my profile in edit mode
+    And the field "First name" matches value "User1"
+
+  Scenario: Accept policy on sign up, with site policy
+    Given the following config values are set as admin:
+      | registerauth    | email              |
+      | passwordpolicy  | 0                  |
+      | sitepolicy      | https://moodle.org |
+    And I am on site homepage
+    And I follow "Log in"
+    When I press "Create new account"
+    Then the field "I understand and agree" matches value "0"
+    And I set the following fields to these values:
+      | Username      | user1                 |
+      | Password      | user1                 |
+      | Email address | user1@address.invalid |
+      | Email (again) | user1@address.invalid |
+      | First name    | User1                 |
+      | Surname       | L1                    |
+      | I understand and agree | 1            |
+    And I press "Create my new account"
+    And I should see "Confirm your account"
+    And I should see "An email should have been sent to your address at user1@address.invalid"
+    And I confirm email for "user1"
+    And I should see "Thanks, User1 L1"
+    And I should see "Your registration has been confirmed"
+    And I open my profile in edit mode
+    And the field "First name" matches value "User1"
+    And I log out
+    # Confirm that user is not asked to agree to site policy again after the next login.
+    And I log in as "user1"
+    And I open my profile in edit mode
+    And the field "First name" matches value "User1"
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 5629542..219996e 100644 (file)
@@ -113,7 +113,7 @@ $string['auth_ntlmsso_subnet'] = 'If set, it will only attempt SSO with clients
 $string['auth_ntlmsso_subnet_key'] = 'Subnet';
 $string['auth_ntlmsso_type_key'] = 'Authentication type';
 $string['auth_ntlmsso_type'] = 'The authentication method configured in the web server to authenticate the users (if in doubt, choose NTLM)';
-$string['cannotmaprole'] = 'The role "{$a->rolename}" can\'t be mapped because its short name "{$a->shortname}" is too long or contains hyphens. To allow it to be mapped, you need to reduce the short name to {$a->charlimit} characters or remove the hyphens. <a href="{$a->link}">Edit the role here</a>';
+$string['cannotmaprole'] = 'The role "{$a->rolename}" cannot be mapped because its short name "{$a->shortname}" is too long and/or contains hyphens. To allow it to be mapped, the short name needs to be reduced to a maximum of {$a->charlimit} characters and any hyphens removed. <a href="{$a->link}">Edit the role</a>';
 $string['connectingldap'] = "Connecting to LDAP server...\n";
 $string['connectingldapsuccess'] = "Connecting to your LDAP server was successful";
 $string['creatingtemptable'] = "Creating temporary table {\$a}\n";
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 {
diff --git a/auth/tests/behat/validateagedigitalconsentmap.feature b/auth/tests/behat/validateagedigitalconsentmap.feature
new file mode 100644 (file)
index 0000000..9fe1753
--- /dev/null
@@ -0,0 +1,68 @@
+@core @verify_age_location
+Feature: Test validation of 'Age of digital consent' setting.
+  In order to set the 'Age of digital consent' setting
+  As an admin
+  I need to provide valid data and valid format
+
+  Background:
+    Given I log in as "admin"
+    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:
+    """
+    *, 16
+    AT, 14
+    BE, 14
+    """
+    When I press "Save changes"
+    Then I should see "Changes saved"
+    And I should not see "Some settings were not changed due to an error."
+    And I should not see "The digital age of consent is not valid:"
+
+  Scenario: Admin provides invalid format for 'Age of digital consent'.
+    # Try to set a value with missing space separator
+    Given I set the field "s__agedigitalconsentmap" to multiline:
+    """
+    *16
+    AT, 14
+    BE, 14
+    """
+    When I press "Save changes"
+    Then I should not see "Changes saved"
+    And I should see "Some settings were not changed due to an error."
+    And I should see "The digital age of consent is not valid: \"*16\" has more or less than one comma separator."
+    # Try to set a value with missing default age of consent
+    When I set the field "s__agedigitalconsentmap" to multiline:
+    """
+    AT, 14
+    BE, 14
+    """
+    And I press "Save changes"
+    Then I should not see "Changes saved"
+    And I should see "Some settings were not changed due to an error."
+    And I should see "The digital age of consent is not valid: Default (*) value is missing."
+
+  Scenario: Admin provides invalid age of consent or country for 'Age of digital consent'.
+    # Try to set a value containing invalid age of consent
+    Given I set the field "s__agedigitalconsentmap" to multiline:
+    """
+    *, 16
+    AT, age
+    BE, 14
+    """
+    When I press "Save changes"
+    Then I should not see "Changes saved"
+    And I should see "Some settings were not changed due to an error."
+    And I should see "The digital age of consent is not valid: \"age\" is not a valid value for age."
+    # Try to set a value containing invalid country
+    When I set the field "s__agedigitalconsentmap" to multiline:
+    """
+    *, 16
+    COUNTRY, 14
+    BE, 14
+    """
+    And I press "Save changes"
+    Then I should not see "Changes saved"
+    And I should see "Some settings were not changed due to an error."
+    And I should see "The digital age of consent is not valid: \"COUNTRY\" is not a valid value for country."
diff --git a/auth/tests/behat/verifyageofconsent.feature b/auth/tests/behat/verifyageofconsent.feature
new file mode 100644 (file)
index 0000000..9f04967
--- /dev/null
@@ -0,0 +1,45 @@
+@core @verify_age_location
+Feature: Test the 'Digital age of consent verification' feature works.
+  In order to self-register on the site
+  As an user
+  I need be to be over the age of digital consent
+
+  Background:
+    Given the following config values are set as admin:
+      | registerauth | email |
+      | agedigitalconsentverification | 1 |
+
+  Scenario: User that is not considered a digital minor attempts to self-register on the site.
+    # Try to access the sign up page.
+    Given I am on homepage
+    When I click on "Log in" "link" in the ".logininfo" "css_element"
+    And I press "Create new account"
+    Then I should see "Age and location verification"
+    When I set the field "What is your age?" to "16"
+    And I set the field "In which country do you live?" to "DZ"
+    And I press "Proceed"
+    Then I should see "New account"
+    And I should see "Choose your username and password"
+    # Try to access the sign up page again.
+    When I press "Cancel"
+    And I press "Create new account"
+    Then I should see "New account"
+    And I should see "Choose your username and password"
+
+  Scenario: User that is considered a digital minor attempts to self-register on the site.
+    # Try to access the sign up page.
+    Given I am on homepage
+    When I click on "Log in" "link" in the ".logininfo" "css_element"
+    And I press "Create new account"
+    Then I should see "Age and location verification"
+    When I set the field "What is your age?" to "12"
+    And I set the field "In which country do you live?" to "AT"
+    And I press "Proceed"
+    Then I should see "You are considered to be a digital minor."
+    And I should see "To create an account on this site please have your parent/guardian contact the following person."
+    # Try to access the sign up page again.
+    When I click on "Back to the site home" "link"
+    And I click on "Log in" "link" in the ".logininfo" "css_element"
+    And I press "Create new account"
+    Then I should see "You are considered to be a digital minor."
+    And I should see "To create an account on this site please have your parent/guardian contact the following person."
diff --git a/auth/tests/digital_consent_test.php b/auth/tests/digital_consent_test.php
new file mode 100644 (file)
index 0000000..c9b2b90
--- /dev/null
@@ -0,0 +1,182 @@
+<?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 core_auth\digital_consent.
+ *
+ * @package    core_auth
+ * @copyright  2018 Mihail Geshoski
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Digital consent helper testcase.
+ *
+ * @package    core_auth
+ * @copyright  2018 Mihail Geshoski
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_auth_digital_consent_testcase extends advanced_testcase {
+
+    public function test_is_age_digital_consent_verification_enabled() {
+        global $CFG;
+        $this->resetAfterTest();
+
+        // Age of digital consent verification is enabled.
+        $CFG->agedigitalconsentverification = 0;
+
+        $isenabled = \core_auth\digital_consent::is_age_digital_consent_verification_enabled();
+        $this->assertFalse($isenabled);
+    }
+
+    public function test_is_minor() {
+        global $CFG;
+        $this->resetAfterTest();
+
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT, 14',
+            'CZ, 13',
+            'DE, 14',
+            'DK, 13',
+        ]);
+        $CFG->agedigitalconsentmap = $agedigitalconsentmap;
+
+        $usercountry1 = 'DK';
+        $usercountry2 = 'AU';
+        $userage1 = 12;
+        $userage2 = 14;
+        $userage3 = 16;
+
+        // Test country exists in agedigitalconsentmap and user age is below the particular digital minor age.
+        $isminor = \core_auth\digital_consent::is_minor($userage1, $usercountry1);
+        $this->assertTrue($isminor);
+        // Test country exists in agedigitalconsentmap and user age is above the particular digital minor age.
+        $isminor = \core_auth\digital_consent::is_minor($userage2, $usercountry1);
+        $this->assertFalse($isminor);
+        // Test country does not exists in agedigitalconsentmap and user age is below the particular digital minor age.
+        $isminor = \core_auth\digital_consent::is_minor($userage2, $usercountry2);
+        $this->assertTrue($isminor);
+        // Test country does not exists in agedigitalconsentmap and user age is above the particular digital minor age.
+        $isminor = \core_auth\digital_consent::is_minor($userage3, $usercountry2);
+        $this->assertFalse($isminor);
+    }
+
+    public function test_parse_age_digital_consent_map_valid_format() {
+
+        // Value of agedigitalconsentmap has a valid format.
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT, 14',
+            'BE, 13'
+        ]);
+
+        $ageconsentmapparsed = \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+
+        $this->assertEquals([
+            '*' => 16,
+            'AT' => 14,
+            'BE' => 13
+        ], $ageconsentmapparsed
+        );
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_missing_spaces() {
+
+        // Value of agedigitalconsentmap has an invalid format (missing space separator between values).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT14',
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvalidcomma', 'error', 'AT14'));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_missing_default_value() {
+
+        // Value of agedigitalconsentmap has an invalid format (missing default value).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            'BE, 16',
+            'AT, 14'
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvaliddefault', 'error'));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_invalid_country() {
+
+        // Value of agedigitalconsentmap has an invalid format (invalid value for country).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'TEST, 14'
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvalidcountry', 'error', 'TEST'));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_invalid_age_string() {
+
+        // Value of agedigitalconsentmap has an invalid format (string value for age).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT, ten'
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvalidage', 'error', 'ten'));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_missing_age() {
+
+        // Value of agedigitalconsentmap has an invalid format (missing value for age).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            'AT, '
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvalidage', 'error', ''));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+
+    public function test_parse_age_digital_consent_map_invalid_format_missing_country() {
+
+        // Value of agedigitalconsentmap has an invalid format (missing value for country).
+        $agedigitalconsentmap = implode(PHP_EOL, [
+            '*, 16',
+            ', 12'
+        ]);
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('agedigitalconsentmapinvalidcountry', 'error', ''));
+
+        \core_auth\digital_consent::parse_age_digital_consent_map($agedigitalconsentmap);
+    }
+}
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 c359096..c3c200a 100644 (file)
@@ -37,6 +37,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_default_block_task.class.ph
 require_once($CFG->dirroot . '/backup/moodle2/backup_xml_transformer.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_extrafields_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_gradingform_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_local_plugin.class.php');
diff --git a/backup/moodle2/backup_qtype_extrafields_plugin.class.php b/backup/moodle2/backup_qtype_extrafields_plugin.class.php
new file mode 100644 (file)
index 0000000..d7db6ff
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Defines backup_qtype_extrafields_plugin class
+ *
+ * @package    core_backup
+ * @copyright  2012 Oleg Sychev, Volgograd State Technical University
+ * @author     Valeriy Streltsov <vostreltsov@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/question/engine/bank.php');
+
+/**
+ * Class extending backup_qtype_plugin in order to use extra fields method
+ *
+ * See qtype_shortanswer for an example
+ *
+ * @copyright  2012 Oleg Sychev, Volgograd State Technical University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class backup_qtype_extrafields_plugin extends backup_qtype_plugin {
+
+    /**
+     * Returns the qtype information to attach to question element.
+     */
+    protected function define_question_plugin_structure() {
+        $qtypeobj = question_bank::get_qtype($this->pluginname);
+
+        // Define the virtual plugin element with the condition to fulfill.
+        $plugin = $this->get_plugin_element(null, '../../qtype', $qtypeobj->name());
+
+        // Create one standard named plugin element (the visible container).
+        $pluginwrapper = new backup_nested_element($this->get_recommended_name());
+
+        // Connect the visible container ASAP.
+        $plugin->add_child($pluginwrapper);
+
+        // This qtype uses standard question_answers, add them here
+        // to the tree before any other information that will use them.
+        $this->add_question_question_answers($pluginwrapper);
+        $answers = $pluginwrapper->get_child('answers');
+        $answer = $answers->get_child('answer');
+
+        // Extra question fields.
+        $extraquestionfields = $qtypeobj->extra_question_fields();
+        if (!empty($extraquestionfields)) {
+            $tablename = array_shift($extraquestionfields);
+            $child = new backup_nested_element($qtypeobj->name(), array('id'), $extraquestionfields);
+            $pluginwrapper->add_child($child);
+            $child->set_source_table($tablename, array($qtypeobj->questionid_column_name() => backup::VAR_PARENTID));
+        }
+
+        // Extra answer fields.
+        $extraanswerfields = $qtypeobj->extra_answer_fields();
+        if (!empty($extraanswerfields)) {
+            $tablename = array_shift($extraanswerfields);
+            $child = new backup_nested_element('extraanswerdata', array('id'), $extraanswerfields);
+            $answer->add_child($child);
+            $child->set_source_table($tablename, array('answerid' => backup::VAR_PARENTID));
+        }
+
+        // Don't need to annotate ids nor files.
+        return $plugin;
+    }
+}
index ae48ec9..02dc062 100644 (file)
@@ -36,6 +36,7 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_block_task.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_default_block_task.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_qtype_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/restore_qtype_extrafields_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_local_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_theme_plugin.class.php');
@@ -46,6 +47,7 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_gradingform_plugin.class.p
 require_once($CFG->dirroot . '/backup/moodle2/restore_enrol_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_extrafields_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_local_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_theme_plugin.class.php');
diff --git a/backup/moodle2/restore_qtype_extrafields_plugin.class.php b/backup/moodle2/restore_qtype_extrafields_plugin.class.php
new file mode 100644 (file)
index 0000000..e976f38
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Defines restore_qtype_extrafields_plugin class
+ *
+ * @package    core_backup
+ * @copyright  2012 Oleg Sychev, Volgograd State Technical University
+ * @author     Valeriy Streltsov <vostreltsov@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/question/engine/bank.php');
+
+/**
+ * Class extending restore_qtype_plugin in order to use extra fields method
+ *
+ * See qtype_shortanswer for an example
+ *
+ * @copyright  2012 Oleg Sychev, Volgograd State Technical University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class restore_qtype_extrafields_plugin extends restore_qtype_plugin {
+
+    /**
+     * Question type class for a particular question type
+     * @var question_type
+     */
+    protected $qtypeobj;
+
+    /**
+     * Constructor
+     *
+     * @param string $plugintype plugin type
+     * @param string $pluginname plugin name
+     * @param restore_step $step step
+     */
+    public function __construct($plugintype, $pluginname, $step) {
+        parent::__construct($plugintype, $pluginname, $step);
+        $this->qtypeobj = question_bank::get_qtype($this->pluginname);
+    }
+
+    /**
+     * Returns the paths to be handled by the plugin at question level.
+     */
+    protected function define_question_plugin_structure() {
+        $paths = array();
+
+        // This qtype uses question_answers, add them.
+        $this->add_question_question_answers($paths);
+
+        // Add own qtype stuff.
+        $elepath = $this->get_pathfor('/' . $this->qtypeobj->name());
+        $paths[] = new restore_path_element($this->qtypeobj->name(), $elepath);
+
+        $elepath = $this->get_pathfor('/answers/answer/extraanswerdata');
+        $paths[] = new restore_path_element('extraanswerdata', $elepath);
+
+        return $paths;
+    }
+
+    /**
+     * Processes the extra answer data
+     *
+     * @param array $data extra answer data
+     */
+    public function process_extraanswerdata($data) {
+        global $DB;
+
+        $extra = $this->qtypeobj->extra_answer_fields();
+        $tablename = array_shift($extra);
+
+        $oldquestionid = $this->get_old_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        if ($questioncreated) {
+            $data['answerid'] = $this->get_mappingid('question_answer', $data['id']);
+            $DB->insert_record($tablename, $data);
+        } else {
+            $DB->update_record($tablename, $data);
+        }
+    }
+
+    /**
+     * Process the qtype/... element.
+     *
+     * @param array $data question data
+     */
+    public function really_process_extra_question_fields($data) {
+        global $DB;
+
+        $oldid = $data['id'];
+
+        // Detect if the question is created or mapped.
+        $oldquestionid = $this->get_old_parentid('question');
+        $newquestionid = $this->get_new_parentid('question');
+        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
+
+        // If the question has been created by restore, we need to create its qtype_... too.
+        if ($questioncreated) {
+            $extraquestionfields = $this->qtypeobj->extra_question_fields();
+            $tablename = array_shift($extraquestionfields);
+
+            // Adjust some columns.
+            $qtfield = $this->qtypeobj->questionid_column_name();
+            $data[$qtfield] = $newquestionid;
+
+            // Insert record.
+            $newitemid = $DB->insert_record($tablename, $data);
+
+            // Create mapping.
+            $this->set_mapping($tablename, $oldid, $newitemid);
+        }
+    }
+}
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 19cb072..ce30c06 100644 (file)
@@ -209,7 +209,7 @@ abstract class base_task implements checksumable, executable, loggable {
         }
         // Everything has been destroyed recursively, now we can reset safely
         $this->steps = array();
-        $this->setting = array();
+        $this->settings = array();
         $this->plan = null;
     }
 
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.';
index 3830ba9..da215c2 100644 (file)
@@ -30,9 +30,13 @@ defined('MOODLE_INTERNAL') || die();
 class moodle1_block_html_handler extends moodle1_block_handler {
     private $fileman = null;
     protected function convert_configdata(array $olddata) {
+        global $CFG;
+        require_once($CFG->libdir . '/db/upgradelib.php');
         $instanceid = $olddata['id'];
         $contextid  = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']);
-        $configdata = unserialize(base64_decode($olddata['configdata']));
+        $decodeddata = base64_decode($olddata['configdata']);
+        list($updated, $configdata) = upgrade_fix_serialized_objects($decodeddata);
+        $configdata = unserialize($configdata);
 
         // get a fresh new file manager for this instance
         $this->fileman = $this->converter->get_file_manager($contextid, 'block_html');
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]);
+    }
+}