Merge branch 'MDL-62397-master' of git://github.com/andrewnicols/moodle
authorDavid Monllao <davidm@moodle.com>
Fri, 11 May 2018 11:14:05 +0000 (13:14 +0200)
committerDavid Monllao <davidm@moodle.com>
Fri, 11 May 2018 11:14:05 +0000 (13:14 +0200)
616 files changed:
admin/roles/classes/privacy/provider.php [new file with mode: 0644]
admin/roles/tests/privacy_test.php [new file with mode: 0644]
admin/settings/users.php
admin/tool/analytics/version.php
admin/tool/assignmentupgrade/version.php
admin/tool/availabilityconditions/version.php
admin/tool/behat/version.php
admin/tool/capability/version.php
admin/tool/cohortroles/version.php
admin/tool/customlang/version.php
admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/expired_user_contexts.php
admin/tool/dataprivacy/classes/output/data_requests_page.php
admin/tool/dataprivacy/classes/output/my_data_requests_page.php
admin/tool/dataprivacy/classes/output/renderer.php
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/styles.css
admin/tool/dataprivacy/templates/contact_dpo.mustache
admin/tool/dataprivacy/templates/data_registry.mustache
admin/tool/dataprivacy/templates/data_requests.mustache
admin/tool/dataprivacy/templates/my_data_requests.mustache
admin/tool/dataprivacy/templates/purposes.mustache
admin/tool/dataprivacy/tests/api_test.php
admin/tool/dataprivacy/tests/expired_contexts_test.php
admin/tool/dataprivacy/version.php
admin/tool/dbtransfer/version.php
admin/tool/filetypes/version.php
admin/tool/generator/version.php
admin/tool/health/version.php
admin/tool/httpsreplace/version.php
admin/tool/innodb/version.php
admin/tool/installaddon/version.php
admin/tool/langimport/version.php
admin/tool/log/store/database/version.php
admin/tool/log/store/legacy/version.php
admin/tool/log/store/standard/version.php
admin/tool/log/version.php
admin/tool/lp/version.php
admin/tool/lpimportcsv/version.php
admin/tool/lpmigrate/version.php
admin/tool/messageinbound/version.php
admin/tool/mobile/version.php
admin/tool/monitor/version.php
admin/tool/multilangupgrade/version.php
admin/tool/oauth2/version.php
admin/tool/phpunit/version.php
admin/tool/policy/classes/output/page_viewdoc.php
admin/tool/policy/lang/en/tool_policy.php
admin/tool/policy/tests/behat/acceptances.feature
admin/tool/policy/tests/behat/managepolicies.feature
admin/tool/policy/tests/privacy_provider_test.php
admin/tool/policy/version.php
admin/tool/profiling/version.php
admin/tool/recyclebin/version.php
admin/tool/replace/version.php
admin/tool/spamcleaner/version.php
admin/tool/task/version.php
admin/tool/templatelibrary/version.php
admin/tool/unsuproles/version.php
admin/tool/uploadcourse/version.php
admin/tool/uploaduser/version.php
admin/tool/usertours/version.php
admin/tool/xmldb/version.php
auth/cas/version.php
auth/db/version.php
auth/email/version.php
auth/ldap/version.php
auth/lti/version.php
auth/manual/version.php
auth/mnet/classes/privacy/provider.php
auth/mnet/lang/en/auth_mnet.php
auth/mnet/tests/privacy_provider_test.php [new file with mode: 0644]
auth/mnet/version.php
auth/nologin/version.php
auth/none/version.php
auth/oauth2/lang/en/auth_oauth2.php
auth/oauth2/version.php
auth/shibboleth/version.php
auth/webservice/version.php
availability/condition/completion/version.php
availability/condition/date/version.php
availability/condition/grade/version.php
availability/condition/group/version.php
availability/condition/grouping/version.php
availability/condition/profile/version.php
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-debug.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form.js
availability/yui/src/form/js/form.js
backup/backup.class.php
backup/tests/privacy_provider_test.php [new file with mode: 0644]
backup/util/ui/classes/privacy/provider.php [new file with mode: 0644]
badges/criteria/award_criteria.php
blocks/activity_modules/version.php
blocks/activity_results/version.php
blocks/admin_bookmarks/version.php
blocks/badges/version.php
blocks/blog_menu/version.php
blocks/blog_recent/version.php
blocks/blog_tags/version.php
blocks/calendar_month/version.php
blocks/calendar_upcoming/version.php
blocks/comments/version.php
blocks/community/version.php
blocks/completionstatus/version.php
blocks/course_list/version.php
blocks/course_summary/version.php
blocks/edit_form.php
blocks/feedback/version.php
blocks/globalsearch/version.php
blocks/glossary_random/version.php
blocks/html/version.php
blocks/login/version.php
blocks/lp/version.php
blocks/mentees/version.php
blocks/mnet_hosts/version.php
blocks/myoverview/version.php
blocks/myprofile/version.php
blocks/navigation/version.php
blocks/news_items/version.php
blocks/online_users/version.php
blocks/participants/version.php
blocks/private_files/version.php
blocks/quiz_results/version.php
blocks/recent_activity/version.php
blocks/rss_client/version.php
blocks/search_forums/version.php
blocks/section_links/version.php
blocks/selfcompletion/version.php
blocks/settings/version.php
blocks/site_main_menu/version.php
blocks/social_activities/version.php
blocks/tag_flickr/classes/privacy/provider.php
blocks/tag_flickr/lang/en/block_tag_flickr.php
blocks/tag_flickr/version.php
blocks/tag_youtube/version.php
blocks/tags/version.php
blog/classes/privacy/provider.php
cache/locks/file/version.php
cache/stores/apcu/version.php
cache/stores/file/version.php
cache/stores/memcache/version.php
cache/stores/memcached/version.php
cache/stores/mongodb/version.php
cache/stores/redis/version.php
cache/stores/session/version.php
cache/stores/static/version.php
calendar/type/gregorian/version.php
competency/classes/privacy/provider.php
course/classes/privacy/provider.php [new file with mode: 0644]
course/format/singleactivity/version.php
course/format/social/version.php
course/format/topics/version.php
course/format/weeks/version.php
course/tests/privacy_test.php [new file with mode: 0644]
dataformat/csv/version.php
dataformat/excel/version.php
dataformat/html/version.php
dataformat/json/version.php
dataformat/ods/version.php
enrol/category/version.php
enrol/classes/privacy/provider.php [new file with mode: 0644]
enrol/cohort/lang/en/enrol_cohort.php
enrol/cohort/version.php
enrol/database/lang/en/enrol_database.php
enrol/database/version.php
enrol/flatfile/lang/en/enrol_flatfile.php
enrol/flatfile/version.php
enrol/guest/lang/en/enrol_guest.php
enrol/guest/version.php
enrol/imsenterprise/lang/en/enrol_imsenterprise.php
enrol/imsenterprise/version.php
enrol/ldap/version.php
enrol/lti/version.php
enrol/manual/version.php
enrol/meta/lang/en/enrol_meta.php
enrol/meta/version.php
enrol/mnet/version.php
enrol/paypal/classes/privacy/provider.php [new file with mode: 0644]
enrol/paypal/lang/en/enrol_paypal.php
enrol/paypal/tests/privacy_provider_test.php [new file with mode: 0644]
enrol/paypal/version.php
enrol/self/lang/en/enrol_self.php
enrol/self/version.php
enrol/tests/privacy_test.php [new file with mode: 0644]
files/converter/googledrive/version.php
files/converter/unoconv/version.php
filter/activitynames/version.php
filter/algebra/version.php
filter/censor/version.php
filter/data/version.php
filter/emailprotect/version.php
filter/emoticon/version.php
filter/glossary/version.php
filter/mathjaxloader/version.php
filter/mediaplugin/version.php
filter/multilang/version.php
filter/tex/version.php
filter/tidy/version.php
filter/urltolink/version.php
grade/export/ods/version.php
grade/export/txt/version.php
grade/export/xls/version.php
grade/export/xml/version.php
grade/grading/classes/privacy/gradingform_legacy_polyfill.php [new file with mode: 0644]
grade/grading/classes/privacy/gradingform_provider.php [new file with mode: 0644]
grade/grading/classes/privacy/provider.php [new file with mode: 0644]
grade/grading/form/guide/classes/privacy/provider.php [new file with mode: 0644]
grade/grading/form/guide/lang/en/gradingform_guide.php
grade/grading/form/guide/tests/privacy_test.php [new file with mode: 0644]
grade/grading/form/guide/version.php
grade/grading/form/rubric/classes/privacy/provider.php [new file with mode: 0644]
grade/grading/form/rubric/lang/en/gradingform_rubric.php
grade/grading/form/rubric/version.php
grade/grading/tests/privacy_legacy_polyfill_test.php [new file with mode: 0644]
grade/grading/tests/privacy_test.php [new file with mode: 0644]
grade/import/csv/version.php
grade/import/direct/version.php
grade/import/xml/version.php
grade/report/grader/version.php
grade/report/history/version.php
grade/report/outcomes/version.php
grade/report/overview/version.php
grade/report/singleview/version.php
grade/report/user/version.php
group/classes/privacy/provider.php [new file with mode: 0644]
group/tests/privacy_provider_test.php [new file with mode: 0644]
index.php
lang/en/backup.php
lang/en/badges.php
lang/en/completion.php
lang/en/course.php [new file with mode: 0644]
lang/en/enrol.php
lang/en/files.php
lang/en/grading.php
lang/en/group.php
lang/en/notes.php
lang/en/portfolio.php
lang/en/role.php
lang/en/search.php
lang/en/user.php [new file with mode: 0644]
lib/antivirus/clamav/version.php
lib/classes/event/notification_sent.php
lib/classes/output/icon_system_fontawesome.php
lib/db/upgrade.php
lib/editor/atto/db/upgrade.php
lib/editor/atto/plugins/accessibilitychecker/version.php
lib/editor/atto/plugins/accessibilityhelper/version.php
lib/editor/atto/plugins/align/version.php
lib/editor/atto/plugins/backcolor/version.php
lib/editor/atto/plugins/bold/version.php
lib/editor/atto/plugins/charmap/version.php
lib/editor/atto/plugins/clear/version.php
lib/editor/atto/plugins/collapse/version.php
lib/editor/atto/plugins/emoticon/version.php
lib/editor/atto/plugins/equation/version.php
lib/editor/atto/plugins/fontcolor/version.php
lib/editor/atto/plugins/html/version.php
lib/editor/atto/plugins/image/lang/en/atto_image.php
lib/editor/atto/plugins/image/version.php
lib/editor/atto/plugins/indent/version.php
lib/editor/atto/plugins/italic/version.php
lib/editor/atto/plugins/link/version.php
lib/editor/atto/plugins/managefiles/version.php
lib/editor/atto/plugins/media/lang/en/atto_media.php
lib/editor/atto/plugins/media/version.php
lib/editor/atto/plugins/noautolink/version.php
lib/editor/atto/plugins/orderedlist/version.php
lib/editor/atto/plugins/recordrtc/lang/en/atto_recordrtc.php
lib/editor/atto/plugins/recordrtc/lib.php
lib/editor/atto/plugins/recordrtc/pix/i/audiortc.png
lib/editor/atto/plugins/recordrtc/pix/i/videortc.png
lib/editor/atto/plugins/recordrtc/version.php
lib/editor/atto/plugins/rtl/version.php
lib/editor/atto/plugins/strike/version.php
lib/editor/atto/plugins/subscript/version.php
lib/editor/atto/plugins/superscript/version.php
lib/editor/atto/plugins/table/version.php
lib/editor/atto/plugins/title/version.php
lib/editor/atto/plugins/underline/version.php
lib/editor/atto/plugins/undo/version.php
lib/editor/atto/plugins/unorderedlist/version.php
lib/editor/atto/settings.php
lib/editor/atto/version.php
lib/editor/textarea/version.php
lib/editor/tinymce/plugins/ctrlhelp/version.php
lib/editor/tinymce/plugins/managefiles/version.php
lib/editor/tinymce/plugins/moodleemoticon/version.php
lib/editor/tinymce/plugins/moodleimage/version.php
lib/editor/tinymce/plugins/moodlemedia/version.php
lib/editor/tinymce/plugins/moodlenolink/version.php
lib/editor/tinymce/plugins/pdw/version.php
lib/editor/tinymce/plugins/spellchecker/version.php
lib/editor/tinymce/plugins/wrap/version.php
lib/editor/tinymce/version.php
lib/mlbackend/php/version.php
lib/mlbackend/python/version.php
lib/moodlelib.php
lib/portfolio/exporter.php
lib/portfoliolib.php
lib/upgrade.txt
media/player/html5audio/version.php
media/player/html5video/version.php
media/player/swf/version.php
media/player/videojs/version.php
media/player/vimeo/version.php
media/player/youtube/version.php
message/output/airnotifier/version.php
message/output/email/version.php
message/output/jabber/version.php
message/output/popup/version.php
message/tests/events_test.php
mnet/service/enrol/classes/privacy/provider.php [new file with mode: 0644]
mnet/service/enrol/lang/en/mnetservice_enrol.php
mnet/service/enrol/tests/privacy_test.php [new file with mode: 0644]
mnet/service/enrol/version.php
mod/assign/classes/privacy/provider.php
mod/assign/feedback/comments/version.php
mod/assign/feedback/editpdf/classes/privacy/provider.php
mod/assign/feedback/editpdf/version.php
mod/assign/feedback/file/version.php
mod/assign/feedback/offline/version.php
mod/assign/locallib.php
mod/assign/submission/comments/version.php
mod/assign/submission/file/version.php
mod/assign/submission/onlinetext/version.php
mod/assign/tests/portfolio_caller_test.php [new file with mode: 0644]
mod/assign/tests/privacy_test.php
mod/assign/version.php
mod/assignment/type/offline/version.php
mod/assignment/type/online/version.php
mod/assignment/type/upload/version.php
mod/assignment/type/uploadsingle/version.php
mod/assignment/version.php
mod/book/tool/exportimscp/version.php
mod/book/tool/importhtml/version.php
mod/book/tool/print/version.php
mod/book/version.php
mod/chat/lang/en/chat.php
mod/chat/version.php
mod/choice/version.php
mod/data/field/checkbox/version.php
mod/data/field/date/version.php
mod/data/field/file/version.php
mod/data/field/latlong/version.php
mod/data/field/menu/version.php
mod/data/field/multimenu/version.php
mod/data/field/number/version.php
mod/data/field/picture/version.php
mod/data/field/radiobutton/version.php
mod/data/field/text/version.php
mod/data/field/textarea/version.php
mod/data/field/url/version.php
mod/data/preset/imagegallery/version.php
mod/data/version.php
mod/feedback/version.php
mod/folder/version.php
mod/forum/classes/message/inbound/reply_handler.php
mod/forum/classes/privacy/provider.php [new file with mode: 0644]
mod/forum/classes/privacy/subcontext_info.php [new file with mode: 0644]
mod/forum/db/install.xml
mod/forum/db/upgrade.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/locallib.php
mod/forum/post.php
mod/forum/rsslib.php
mod/forum/tests/externallib_test.php
mod/forum/tests/helper.php [new file with mode: 0644]
mod/forum/tests/mail_test.php
mod/forum/tests/portfolio_caller_test.php [new file with mode: 0644]
mod/forum/tests/privacy_provider_test.php [new file with mode: 0644]
mod/forum/tests/subscriptions_test.php
mod/forum/version.php
mod/glossary/classes/privacy/provider.php [new file with mode: 0644]
mod/glossary/lang/en/glossary.php
mod/glossary/tests/privacy_provider_test.php [new file with mode: 0644]
mod/glossary/version.php
mod/imscp/version.php
mod/label/version.php
mod/lesson/lang/en/lesson.php
mod/lesson/version.php
mod/lti/service/gradebookservices/version.php
mod/lti/service/memberships/version.php
mod/lti/service/profile/version.php
mod/lti/service/toolproxy/version.php
mod/lti/service/toolsettings/version.php
mod/lti/version.php
mod/page/version.php
mod/quiz/accessrule/delaybetweenattempts/version.php
mod/quiz/accessrule/ipaddress/version.php
mod/quiz/accessrule/numattempts/version.php
mod/quiz/accessrule/offlineattempts/version.php
mod/quiz/accessrule/openclosedate/version.php
mod/quiz/accessrule/password/version.php
mod/quiz/accessrule/safebrowser/version.php
mod/quiz/accessrule/securewindow/version.php
mod/quiz/accessrule/timelimit/version.php
mod/quiz/addrandomform.php
mod/quiz/amd/build/add_random_form.min.js
mod/quiz/amd/src/add_random_form.js
mod/quiz/report/grading/version.php
mod/quiz/report/overview/version.php
mod/quiz/report/responses/version.php
mod/quiz/report/statistics/version.php
mod/quiz/version.php
mod/resource/version.php
mod/scorm/report/basic/version.php
mod/scorm/report/graphs/version.php
mod/scorm/report/interactions/version.php
mod/scorm/report/objectives/version.php
mod/scorm/version.php
mod/survey/version.php
mod/upgrade.txt
mod/url/version.php
mod/wiki/classes/privacy/provider.php [new file with mode: 0644]
mod/wiki/lang/en/wiki.php
mod/wiki/tests/generator/lib.php
mod/wiki/tests/privacy_test.php [new file with mode: 0644]
mod/wiki/version.php
mod/workshop/allocation/manual/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/allocation/manual/lang/en/workshopallocation_manual.php
mod/workshop/allocation/manual/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/allocation/manual/version.php
mod/workshop/allocation/random/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/allocation/random/lang/en/workshopallocation_random.php
mod/workshop/allocation/random/version.php
mod/workshop/allocation/scheduled/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/allocation/scheduled/lang/en/workshopallocation_scheduled.php
mod/workshop/allocation/scheduled/version.php
mod/workshop/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/classes/privacy/workshopform_legacy_polyfill.php [new file with mode: 0644]
mod/workshop/classes/privacy/workshopform_provider.php [new file with mode: 0644]
mod/workshop/db/install.xml
mod/workshop/db/upgrade.php
mod/workshop/eval/best/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/eval/best/lang/en/workshopeval_best.php
mod/workshop/eval/best/version.php
mod/workshop/form/accumulative/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/form/accumulative/lang/en/workshopform_accumulative.php
mod/workshop/form/accumulative/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/form/accumulative/version.php
mod/workshop/form/comments/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/form/comments/lang/en/workshopform_comments.php
mod/workshop/form/comments/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/form/comments/version.php
mod/workshop/form/numerrors/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/form/numerrors/lang/en/workshopform_numerrors.php
mod/workshop/form/numerrors/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/form/numerrors/version.php
mod/workshop/form/rubric/classes/privacy/provider.php [new file with mode: 0644]
mod/workshop/form/rubric/lang/en/workshopform_rubric.php
mod/workshop/form/rubric/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/form/rubric/version.php
mod/workshop/lang/en/workshop.php
mod/workshop/tests/generator/lib.php
mod/workshop/tests/privacy_provider_test.php [new file with mode: 0644]
mod/workshop/version.php
phpunit.xml.dist
pix/e/insert_edit_video.png
pix/e/insert_edit_video.svg
portfolio/boxnet/version.php
portfolio/classes/privacy/provider.php
portfolio/download/version.php
portfolio/flickr/version.php
portfolio/googledocs/version.php
portfolio/mahara/version.php
portfolio/picasa/version.php
portfolio/tests/privacy_provider_test.php
privacy/classes/local/request/context_aware_provider.php [new file with mode: 0644]
privacy/classes/local/request/contextlist.php
privacy/classes/local/request/moodle_content_writer.php
privacy/classes/manager.php
privacy/classes/tests/request/content_writer.php
privacy/tests/moodle_content_writer_test.php
privacy/tests/provider_test.php
privacy/tests/tests_content_writer_test.php
question/behaviour/adaptive/version.php
question/behaviour/adaptivenopenalty/version.php
question/behaviour/deferredcbm/version.php
question/behaviour/deferredfeedback/version.php
question/behaviour/immediatecbm/version.php
question/behaviour/immediatefeedback/version.php
question/behaviour/informationitem/version.php
question/behaviour/interactive/version.php
question/behaviour/interactivecountback/version.php
question/behaviour/manualgraded/version.php
question/behaviour/missing/version.php
question/format/aiken/version.php
question/format/blackboard_six/version.php
question/format/examview/version.php
question/format/gift/version.php
question/format/missingword/version.php
question/format/multianswer/version.php
question/format/webct/version.php
question/format/xhtml/version.php
question/format/xml/version.php
question/type/calculated/datasetitems_form.php
question/type/calculated/edit_calculated_form.php
question/type/calculated/question.php
question/type/calculated/questiontype.php
question/type/calculated/tests/formula_validation_test.php
question/type/calculated/tests/questiontype_test.php
question/type/calculated/version.php
question/type/calculatedmulti/db/upgradelib.php
question/type/calculatedmulti/edit_calculatedmulti_form.php
question/type/calculatedmulti/questiontype.php
question/type/calculatedmulti/version.php
question/type/calculatedsimple/version.php
question/type/ddimageortext/lang/en/qtype_ddimageortext.php
question/type/ddimageortext/version.php
question/type/ddmarker/lang/en/qtype_ddmarker.php
question/type/ddmarker/version.php
question/type/ddwtos/lang/en/qtype_ddwtos.php
question/type/ddwtos/version.php
question/type/description/version.php
question/type/essay/lang/en/qtype_essay.php
question/type/essay/version.php
question/type/gapselect/version.php
question/type/match/lang/en/qtype_match.php
question/type/match/version.php
question/type/missingtype/lang/en/qtype_missingtype.php
question/type/missingtype/version.php
question/type/multianswer/version.php
question/type/multichoice/lang/en/qtype_multichoice.php
question/type/multichoice/version.php
question/type/numerical/version.php
question/type/random/version.php
question/type/randomsamatch/lang/en/qtype_randomsamatch.php
question/type/randomsamatch/version.php
question/type/shortanswer/lang/en/qtype_shortanswer.php
question/type/shortanswer/version.php
question/type/truefalse/version.php
report/backups/version.php
report/competency/version.php
report/completion/version.php
report/configlog/version.php
report/courseoverview/version.php
report/eventlist/version.php
report/insights/version.php
report/log/version.php
report/loglive/version.php
report/outline/version.php
report/participation/version.php
report/performance/version.php
report/progress/version.php
report/questioninstances/version.php
report/security/version.php
report/stats/classes/privacy/provider.php
report/stats/lang/en/report_stats.php
report/stats/tests/privacy_test.php [new file with mode: 0644]
report/stats/version.php
report/usersessions/version.php
repository/areafiles/version.php
repository/boxnet/version.php
repository/coursefiles/version.php
repository/dropbox/version.php
repository/equella/version.php
repository/filesystem/version.php
repository/flickr/version.php
repository/flickr_public/version.php
repository/googledocs/version.php
repository/local/version.php
repository/merlot/version.php
repository/onedrive/version.php
repository/picasa/version.php
repository/recent/version.php
repository/s3/version.php
repository/skydrive/version.php
repository/upload/version.php
repository/url/version.php
repository/user/version.php
repository/webdav/version.php
repository/wikimedia/version.php
repository/youtube/version.php
search/classes/privacy/provider.php [new file with mode: 0644]
search/engine/simpledb/classes/privacy/provider.php [new file with mode: 0644]
search/engine/simpledb/lang/en/search_simpledb.php
search/engine/simpledb/tests/privacy_test.php [new file with mode: 0644]
search/engine/simpledb/version.php
search/engine/solr/classes/privacy/provider.php [new file with mode: 0644]
search/engine/solr/lang/en/search_solr.php
search/engine/solr/tests/privacy_test.php [new file with mode: 0644]
search/engine/solr/version.php
search/tests/fixtures/mock_search_area.php
tag/classes/tests/privacy_helper.php [new file with mode: 0644]
theme/boost/scss/moodle/bs4alphacompat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/undo.scss
theme/boost/templates/columns2.mustache
theme/boost/templates/core/auth_digital_minor_page.mustache
theme/boost/templates/core/auth_verify_age_location_page.mustache
theme/boost/templates/core/paging_bar.mustache
theme/boost/templates/core_form/element-date_selector-inline.mustache
theme/boost/templates/core_form/element-date_selector.mustache
theme/boost/templates/footer.mustache
theme/boost/version.php
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/version.php
theme/clean/version.php
theme/more/version.php
user/classes/privacy/provider.php [new file with mode: 0644]
user/profile/field/checkbox/version.php
user/profile/field/datetime/version.php
user/profile/field/menu/version.php
user/profile/field/text/version.php
user/profile/field/textarea/version.php
user/tests/privacy_test.php [new file with mode: 0644]
version.php
webservice/rest/version.php
webservice/soap/version.php
webservice/xmlrpc/version.php

diff --git a/admin/roles/classes/privacy/provider.php b/admin/roles/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..ee8bcea
--- /dev/null
@@ -0,0 +1,382 @@
+<?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 core_role.
+ *
+ * @package    core_role
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_role\privacy;
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+
+/**
+ * Privacy provider for core_role.
+ *
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements
+    \core_privacy\local\metadata\provider,
+    \core_privacy\local\request\subsystem\provider,
+    \core_privacy\local\request\subsystem\plugin_provider,
+    \core_privacy\local\request\user_preference_provider {
+
+    /**
+     * Get information about the user data stored by this plugin.
+     *
+     * @param  collection $collection An object for storing metadata.
+     * @return collection The metadata.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $rolecapabilities = [
+            'roleid' => 'privacy:metadata:role_capabilities:roleid',
+            'capability' => 'privacy:metadata:role_capabilities:capability',
+            'permission' => 'privacy:metadata:role_capabilities:permission',
+            'timemodified' => 'privacy:metadata:role_capabilities:timemodified',
+            'modifierid' => 'privacy:metadata:role_capabilities:modifierid'
+        ];
+        $roleassignments = [
+            'roleid' => 'privacy:metadata:role_assignments:roleid',
+            'userid' => 'privacy:metadata:role_assignments:userid',
+            'timemodified' => 'privacy:metadata:role_assignments:timemodified',
+            'modifierid' => 'privacy:metadata:role_assignments:modifierid',
+            'component' => 'privacy:metadata:role_assignments:component',
+            'itemid' => 'privacy:metadata:role_assignments:itemid'
+        ];
+        $collection->add_database_table('role_capabilities', $rolecapabilities,
+            'privacy:metadata:role_capabilities:tableexplanation');
+        $collection->add_database_table('role_assignments', $roleassignments,
+            'privacy:metadata:role_assignments:tableexplanation');
+
+        $collection->add_user_preference('definerole_showadvanced',
+            'privacy:metadata:preference:showadvanced');
+
+        return $collection;
+    }
+    /**
+     * Export all user preferences for the plugin.
+     *
+     * @param   int         $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        $showadvanced = get_user_preferences('definerole_showadvanced', null, $userid);
+        if ($showadvanced !== null) {
+            writer::export_user_preference('core_role',
+                'definerole_showadvanced',
+                transform::yesno($showadvanced),
+                get_string('privacy:metadata:preference:showadvanced', 'core_role')
+            );
+        }
+    }
+    /**
+     * Return all contexts for this userid.
+     *
+     * @param  int $userid The user ID.
+     * @return contextlist The list of context IDs.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        global $DB;
+
+        $contextlist = new contextlist();
+
+        // The role_capabilities table contains user data.
+        $contexts = [
+            CONTEXT_SYSTEM,
+            CONTEXT_USER,
+            CONTEXT_COURSECAT,
+            CONTEXT_COURSE,
+            CONTEXT_MODULE,
+            CONTEXT_BLOCK
+        ];
+        list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+        $sql = "SELECT ctx.id
+                  FROM {context} ctx
+                  JOIN {role_capabilities} rc
+                    ON rc.contextid = ctx.id
+                   AND ((ctx.contextlevel {$insql} AND rc.modifierid = :modifierid)
+                    OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))";
+        $params = [
+            'modifierid' => $userid,
+            'contextlevel' => CONTEXT_USER,
+            'userid' => $userid
+         ];
+        $params += $inparams;
+
+        $contextlist->add_from_sql($sql, $params);
+
+        // The role_assignments table contains user data.
+        $contexts = [
+            CONTEXT_SYSTEM,
+            CONTEXT_USER,
+            CONTEXT_COURSECAT,
+            CONTEXT_COURSE,
+            CONTEXT_MODULE,
+            CONTEXT_BLOCK
+        ];
+        list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+        $params = [
+            'userid' => $userid,
+            'modifierid' => $userid
+         ];
+        $params += $inparams;
+        $sql = "SELECT ctx.id
+                  FROM {role_assignments} ra
+                  JOIN {context} ctx
+                    ON ctx.id = ra.contextid
+                   AND ctx.contextlevel {$insql}
+                 WHERE (ra.userid = :userid
+                    OR ra.modifierid = :modifierid)
+                   AND ra.component != 'tool_cohortroles'";
+        $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 list of approved contexts for a user.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        if (empty($contextlist)) {
+             return;
+        }
+
+        $rolesnames = self::get_roles_name();
+        $userid = $contextlist->get_user()->id;
+        $ctxfields = \context_helper::get_preload_record_columns_sql('ctx');
+        list($insql, $inparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
+
+        // Role Assignments export data.
+        $contexts = [
+            CONTEXT_SYSTEM,
+            CONTEXT_USER,
+            CONTEXT_COURSECAT,
+            CONTEXT_COURSE,
+            CONTEXT_MODULE,
+            CONTEXT_BLOCK
+        ];
+        list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+        $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, $ctxfields
+                  FROM {role_assignments} ra
+                  JOIN {context} ctx
+                    ON ctx.id = ra.contextid
+                   AND ctx.contextlevel {$inctxsql}
+                   AND (ra.userid = :userid OR ra.modifierid = :modifierid)
+                   AND ra.component != 'tool_cohortroles'
+                  JOIN {role} r
+                    ON r.id = ra.roleid
+                 WHERE ctx.id {$insql}";
+        $params = ['userid' => $userid, 'modifierid' => $userid];
+        $params += $inparams;
+        $params += $ctxparams;
+        $assignments = $DB->get_recordset_sql($sql, $params);
+        foreach ($assignments as $assignment) {
+            \context_helper::preload_from_record($assignment);
+            $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
+                'timemodified' => transform::datetime($assignment->timemodified),
+                'userid' => transform::user($assignment->userid),
+                'modifierid' => transform::user($assignment->modifierid)
+            ];
+        }
+        $assignments->close();
+        if (!empty($alldata)) {
+            array_walk($alldata, function($roledata, $contextid) {
+                $context = \context::instance_by_id($contextid);
+                array_walk($roledata, function($data, $rolename) use ($context) {
+                    writer::with_context($context)->export_data(
+                            [get_string('privacy:metadata:role_assignments', 'core_role'), $rolename],
+                            (object)$data);
+                });
+            });
+            unset($alldata);
+        }
+
+        // Role Capabilities export data.
+        $strpermissions = self::get_permissions_name();
+        $contexts = [
+            CONTEXT_SYSTEM,
+            CONTEXT_USER,
+            CONTEXT_COURSECAT,
+            CONTEXT_COURSE,
+            CONTEXT_MODULE,
+            CONTEXT_BLOCK
+        ];
+        list($inctxsql, $ctxparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
+        $sql = "SELECT rc.id, rc.contextid, rc.capability, rc.permission, rc.timemodified, rc.roleid, $ctxfields
+                  FROM {context} ctx
+                  JOIN {role_capabilities} rc
+                    ON rc.contextid = ctx.id
+                   AND ((ctx.contextlevel {$inctxsql} AND rc.modifierid = :modifierid)
+                    OR (ctx.contextlevel = :contextlevel AND ctx.instanceid = :userid))
+                 WHERE ctx.id {$insql}";
+        $params = [
+            'modifierid' => $userid,
+            'contextlevel' => CONTEXT_USER,
+            'userid' => $userid
+         ];
+        $params += $inparams;
+        $params += $ctxparams;
+        $capabilities = $DB->get_recordset_sql($sql, $params);
+        foreach ($capabilities as $capability) {
+            \context_helper::preload_from_record($capability);
+            $alldata[$capability->contextid][$rolesnames[$capability->roleid]][] = (object)[
+                'timemodified' => transform::datetime($capability->timemodified),
+                'capability' => $capability->capability,
+                'permission' => $strpermissions[$capability->permission]
+            ];
+        }
+        $capabilities->close();
+        if (!empty($alldata)) {
+            array_walk($alldata, function($capdata, $contextid) {
+                $context = \context::instance_by_id($contextid);
+                array_walk($capdata, function($data, $rolename) use ($context) {
+                    writer::with_context($context)->export_data(
+                            [get_string('privacy:metadata:role_capabilities', 'core_role'), $rolename],
+                            (object)$data);
+                });
+            });
+        }
+    }
+    /**
+     * Exports the data relating to tool_cohortroles component on role assignments by
+     * Assign user roles to cohort feature.
+     *
+     * @param  int $userid The user ID.
+     */
+    public static function export_user_role_to_cohort(int $userid) {
+        global $DB;
+
+        $rolesnames = self::get_roles_name();
+        $sql = "SELECT ra.id, ra.contextid, ra.roleid, ra.userid, ra.timemodified, ra.modifierid, r.id as roleid
+                  FROM {role_assignments} ra
+                  JOIN {context} ctx
+                    ON ctx.id = ra.contextid
+                   AND ctx.contextlevel = :contextlevel
+                   AND ra.component = 'tool_cohortroles'
+                  JOIN {role} r
+                    ON r.id = ra.roleid
+                 WHERE ctx.instanceid = :instanceid
+                    OR ra.userid = :userid";
+        $params = ['userid' => $userid, 'instanceid' => $userid, 'contextlevel' => CONTEXT_USER];
+        $assignments = $DB->get_recordset_sql($sql, $params);
+        foreach ($assignments as $assignment) {
+            $alldata[$assignment->contextid][$rolesnames[$assignment->roleid]][] = (object)[
+                'timemodified' => transform::datetime($assignment->timemodified),
+                'userid' => transform::user($assignment->userid),
+                'modifierid' => transform::user($assignment->modifierid)
+            ];
+        }
+        $assignments->close();
+        if (!empty($alldata)) {
+            array_walk($alldata, function($roledata, $contextid) {
+                $context = \context::instance_by_id($contextid);
+                array_walk($roledata, function($data, $rolename) use ($context) {
+                    writer::with_context($context)->export_related_data(
+                            [get_string('privacy:metadata:role_cohortroles', 'core_role'), $rolename], 'cohortroles',
+                            (object)$data);
+                });
+            });
+        }
+    }
+    /**
+     * Delete all user data for this context.
+     *
+     * @param  \context $context The context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        global $DB;
+
+        // Don't remove data from role_capabilities.
+        // Because this data affects the whole Moodle, there are override capabilities.
+        // Don't belong to the modifier user.
+
+        // Remove data from role_assignments.
+        if (empty($context)) {
+            return;
+        }
+        $DB->delete_records('role_assignments', ['contextid' => $context->id]);
+    }
+    /**
+     * Delete all user data for this user only.
+     *
+     * @param  approved_contextlist $contextlist The list of approved contexts for a user.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        global $DB;
+
+        // Don't remove data from role_capabilities.
+        // Because this data affects the whole Moodle, there are override capabilities.
+        // Don't belong to the modifier user.
+
+        // Remove data from role_assignments.
+        if (empty($contextlist->count())) {
+            return;
+        }
+        $userid = $contextlist->get_user()->id;
+        foreach ($contextlist->get_contexts() as $context) {
+            // Only delete the roles assignments where the user is assigned in all contexts.
+            $DB->delete_records('role_assignments', ['userid' => $userid, 'contextid' => $context->id]);
+        }
+    }
+    /**
+     * Delete user entries in role_assignments related to the feature
+     * Assign user roles to cohort feature.
+     *
+     * @param  int $userid The user ID.
+     */
+    public static function delete_user_role_to_cohort(int $userid) {
+        global $DB;
+
+        // Delete entries where userid is a mentor by tool_cohortroles.
+        $DB->delete_records('role_assignments', ['userid' => $userid, 'component' => 'tool_cohortroles']);
+    }
+    /**
+     * Get all the localised roles name in a simple array.
+     *
+     * @return array Array of name of the roles by roleid.
+     */
+    protected static function get_roles_name() {
+        $roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
+        $rolesnames = array();
+        foreach ($roles as $role) {
+            $rolesnames[$role->id] = $role->localname;
+        }
+        return $rolesnames;
+    }
+    /**
+     * Get all the permissions name in a simple array.
+     *
+     * @return array Array of permissions name.
+     */
+    protected static function get_permissions_name() {
+        $strpermissions = array(
+            CAP_INHERIT => get_string('inherit', 'role'),
+            CAP_ALLOW => get_string('allow', 'role'),
+            CAP_PREVENT => get_string('prevent', 'role'),
+            CAP_PROHIBIT => get_string('prohibit', 'role')
+        );
+        return $strpermissions;
+    }
+}
\ No newline at end of file
diff --git a/admin/roles/tests/privacy_test.php b/admin/roles/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..bffc919
--- /dev/null
@@ -0,0 +1,477 @@
+<?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 test for core_role
+ *
+ * @package    core_role
+ * @category   test
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+use \core_role\privacy\provider;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\writer;
+use \core_privacy\tests\provider_testcase;
+use \core_privacy\local\request\transform;
+use \tool_cohortroles\api;
+
+/**
+ * Privacy test for core_role
+ *
+ * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_role_privacy_testcase extends provider_testcase {
+    /**
+     * Test to check export_user_preferences.
+     * returns user preferences data.
+     */
+    public function test_export_user_preferences() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        $showadvanced = 1;
+        set_user_preference('definerole_showadvanced', $showadvanced);
+        provider::export_user_preferences($user->id);
+        $writer = writer::with_context(\context_system::instance());
+        $prefs = $writer->get_user_preferences('core_role');
+        $this->assertEquals(transform::yesno($showadvanced), transform::yesno($prefs->definerole_showadvanced->value));
+        $this->assertEquals(get_string('privacy:metadata:preference:showadvanced', 'core_role'),
+            $prefs->definerole_showadvanced->description);
+    }
+    /**
+     * Check all contexts are returned if there is any user data for this user.
+     */
+    public function test_get_contexts_for_userid() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $user = $this->getDataGenerator()->create_user();
+        $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext2 = \context_user::instance($user2->id);
+        $course = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecat = $this->getDataGenerator()->create_category();
+        $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+        $cmcontext = \context_module::instance($cm->cmid);
+        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+        $cmcontext2 = \context_module::instance($page->cmid);
+        $coursecontext = \context_course::instance($course->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+        $coursecatcontext = \context_coursecat::instance($coursecat->id);
+        $systemcontext = \context_system::instance();
+        $block = $this->getDataGenerator()->create_block('online_users');
+        $blockcontext = \context_block::instance($block->id);
+
+        $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+        $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+
+        // Role assignments, where the user is assigned.
+        role_assign($student->id, $user->id, $cmcontext2->id);
+        role_assign($student->id, $user->id, $coursecontext2->id);
+        role_assign($student->id, $user->id, $blockcontext->id);
+        role_assign($manager->id, $user->id, $usercontext2->id);
+        // Role assignments, where the user makes assignments.
+        $this->setUser($user);
+        role_assign($student->id, $user2->id, $coursecontext->id);
+        role_assign($manager->id, $user2->id, $coursecatcontext->id);
+        role_assign($manager->id, $user2->id, $systemcontext->id);
+
+        // Role capabilities.
+        $this->setUser($user);
+        $result = assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $cmcontext->id);
+
+        $contextlist = provider::get_contexts_for_userid($user->id)->get_contextids();
+        $this->assertCount(8, $contextlist);
+        $this->assertTrue(in_array($cmcontext->id, $contextlist));
+    }
+
+    /**
+     * Test that user data is exported correctly.
+     */
+    public function test_export_user_data() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext2 = \context_user::instance($user2->id);
+        $course = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $coursecat = $this->getDataGenerator()->create_category();
+        $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+        $cmcontext = \context_module::instance($cm->cmid);
+        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
+        $cmcontext2 = \context_module::instance($page->cmid);
+        $coursecontext = \context_course::instance($course->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+        $coursecatcontext = \context_coursecat::instance($coursecat->id);
+        $systemcontext = \context_system::instance();
+        $block = $this->getDataGenerator()->create_block('online_users');
+        $blockcontext = \context_block::instance($block->id);
+
+        $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+        $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+        $rolesnames = self::get_roles_name();
+
+        $subcontextstudent = [
+            get_string('privacy:metadata:role_assignments', 'core_role'),
+            $rolesnames[$student->id]
+        ];
+        $subcontextmanager = [
+            get_string('privacy:metadata:role_assignments', 'core_role'),
+            $rolesnames[$manager->id]
+        ];
+        $subcontextrc = [
+            get_string('privacy:metadata:role_capabilities', 'core_role'),
+            $rolesnames[$student->id]
+        ];
+
+        // Test over role assignments.
+        // Where the user is assigned.
+        role_assign($student->id, $user->id, $cmcontext2->id);
+        role_assign($student->id, $user->id, $coursecontext2->id);
+        role_assign($student->id, $user->id, $blockcontext->id);
+        role_assign($manager->id, $user->id, $usercontext2->id);
+        // Where the user makes assignments.
+        $this->setUser($user);
+        role_assign($manager->id, $user2->id, $coursecatcontext->id);
+        role_assign($manager->id, $user2->id, $systemcontext->id);
+
+        // Test overridable roles in module, course, category, user, system and block.
+        assign_capability('moodle/backup:backupactivity', CAP_ALLOW, $student->id, $cmcontext->id, true);
+        assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $coursecontext->id, true);
+        assign_capability('moodle/category:manage', CAP_ALLOW, $student->id, $coursecatcontext->id, true);
+        assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $systemcontext->id, true);
+        assign_capability('moodle/block:edit', CAP_ALLOW, $student->id, $blockcontext->id, true);
+        assign_capability('moodle/competency:evidencedelete', CAP_ALLOW, $student->id, $usercontext2->id, true);
+
+        // Retrieve the user's context ids.
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
+
+        $strpermissions = array(
+            CAP_INHERIT => get_string('inherit', 'role'),
+            CAP_ALLOW => get_string('allow', 'role'),
+            CAP_PREVENT => get_string('prevent', 'role'),
+            CAP_PROHIBIT => get_string('prohibit', 'role')
+        );
+        // Retrieve role capabilities and role assignments.
+        provider::export_user_data($approvedcontextlist);
+        foreach ($contextlist as $context) {
+            $writer = writer::with_context($context);
+            $this->assertTrue($writer->has_any_data());
+            if ($context->contextlevel == CONTEXT_MODULE) {
+                if ($data = $writer->get_data($subcontextstudent)) {
+                    $this->assertEquals($user->id, reset($data)->userid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/backup:backupactivity', reset($data)->capability);
+                    $this->assertEquals($strpermissions[CAP_ALLOW], reset($data)->permission);
+                }
+            }
+            if ($context->contextlevel == CONTEXT_COURSE) {
+                if ($data = $writer->get_data($subcontextstudent)) {
+                    $this->assertEquals($user->id, reset($data)->userid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
+                }
+            }
+            if ($context->contextlevel == CONTEXT_COURSECAT) {
+                if ($data = $writer->get_data($subcontextmanager)) {
+                    $this->assertEquals($user->id, reset($data)->modifierid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/category:manage', reset($data)->capability);
+                }
+            }
+            if ($context->contextlevel == CONTEXT_SYSTEM) {
+                if ($data = $writer->get_data($subcontextmanager)) {
+                    $this->assertEquals($user->id, reset($data)->modifierid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/backup:backupcourse', reset($data)->capability);
+                }
+            }
+            if ($context->contextlevel == CONTEXT_BLOCK) {
+                if ($data = $writer->get_data($subcontextstudent)) {
+                    $this->assertEquals($user->id, reset($data)->userid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/block:edit', reset($data)->capability);
+                }
+            }
+            if ($context->contextlevel == CONTEXT_USER) {
+                if ($data = $writer->get_data($subcontextmanager)) {
+                    $this->assertEquals($user->id, reset($data)->userid);
+                }
+                if ($data = $writer->get_data($subcontextrc)) {
+                    $this->assertEquals('moodle/competency:evidencedelete', reset($data)->capability);
+                }
+            }
+        }
+    }
+    /**
+     * Test for provider::delete_data_for_all_users_in_context().
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext2 = \context_user::instance($user2->id);
+        $user3 = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+        $coursecat = $this->getDataGenerator()->create_category();
+        $coursecatcontext = \context_coursecat::instance($coursecat->id);
+        $systemcontext = \context_system::instance();
+        $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+        $cmcontext = \context_module::instance($cm->cmid);
+        $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+        $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+        $block = $this->getDataGenerator()->create_block('online_users');
+        $blockcontext = \context_block::instance($block->id);
+
+        // Role assignments CONTEXT_COURSE.
+        role_assign($student->id, $user->id, $coursecontext->id);
+        role_assign($student->id, $user2->id, $coursecontext->id);
+        role_assign($student->id, $user3->id, $coursecontext->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
+        $this->assertEquals(3, $count);
+        // Role assignments CONTEXT_COURSECAT.
+        role_assign($student->id, $user2->id, $coursecatcontext->id);
+        role_assign($student->id, $user3->id, $coursecatcontext->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+        $this->assertEquals(2, $count);
+        // Role assignments CONTEXT_SYSTEM.
+        role_assign($student->id, $user->id, $systemcontext->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+        $this->assertEquals(1, $count);
+        // Role assignments CONTEXT_MODULE.
+        role_assign($student->id, $user->id, $cmcontext->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+        $this->assertEquals(1, $count);
+        // Role assigments CONTEXT_BLOCK.
+        role_assign($student->id, $user->id, $blockcontext->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $blockcontext->id]);
+        $this->assertEquals(1, $count);
+        // Role assigments CONTEXT_USER.
+        role_assign($manager->id, $user->id, $usercontext2->id);
+        $count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
+        $this->assertEquals(1, $count);
+
+        // Delete data based on CONTEXT_COURSE context.
+        provider::delete_data_for_all_users_in_context($coursecontext);
+        // After deletion, the role_assignments entries for this context should have been deleted.
+        $count = $DB->count_records('role_assignments', ['contextid' => $coursecontext->id]);
+        $this->assertEquals(0, $count);
+        // Check it is not removing data on other contexts.
+        $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+        $this->assertEquals(2, $count);
+        $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+        $this->assertEquals(1, $count);
+        $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+        $this->assertEquals(1, $count);
+        // Delete data based on CONTEXT_COURSECAT context.
+        provider::delete_data_for_all_users_in_context($coursecatcontext);
+        // After deletion, the role_assignments entries for this context should have been deleted.
+        $count = $DB->count_records('role_assignments', ['contextid' => $coursecatcontext->id]);
+        $this->assertEquals(0, $count);
+        // Delete data based on CONTEXT_SYSTEM context.
+        provider::delete_data_for_all_users_in_context($systemcontext);
+        // After deletion, the role_assignments entries for this context should have been deleted.
+        $count = $DB->count_records('role_assignments', ['contextid' => $systemcontext->id]);
+        $this->assertEquals(0, $count);
+        // Delete data based on CONTEXT_MODULE context.
+        provider::delete_data_for_all_users_in_context($cmcontext);
+        // After deletion, the role_assignments entries for this context should have been deleted.
+        $count = $DB->count_records('role_assignments', ['contextid' => $cmcontext->id]);
+        $this->assertEquals(0, $count);
+        // Delete data based on CONTEXT_BLOCK context.
+        provider::delete_data_for_all_users_in_context($usercontext2);
+        // After deletion, the role_assignments entries for this context should have been deleted.
+        $count = $DB->count_records('role_assignments', ['contextid' => $usercontext2->id]);
+        $this->assertEquals(0, $count);
+    }
+    /**
+     * Test for provider::delete_data_for_user().
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext2 = \context_user::instance($user2->id);
+        $user3 = $this->getDataGenerator()->create_user();
+        $usercontext3 = \context_user::instance($user3->id);
+        $course = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $coursecontext = \context_course::instance($course->id);
+        $coursecontext2 = \context_course::instance($course2->id);
+        $coursecontext3 = \context_course::instance($course3->id);
+        $coursecat = $this->getDataGenerator()->create_category();
+        $coursecatcontext = \context_coursecat::instance($coursecat->id);
+        $systemcontext = \context_system::instance();
+        $cm = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
+        $cmcontext = \context_module::instance($cm->cmid);
+        $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
+        $manager = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
+        $block = $this->getDataGenerator()->create_block('online_users');
+        $blockcontext = \context_block::instance($block->id);
+
+        // Role assignments, Where the user is assigned.
+        role_assign($student->id, $user->id, $coursecontext->id);
+        role_assign($student->id, $user->id, $coursecontext2->id);
+        role_assign($student->id, $user->id, $coursecatcontext->id);
+        role_assign($student->id, $user->id, $cmcontext->id);
+        role_assign($student->id, $user->id, $systemcontext->id);
+        role_assign($student->id, $user->id, $blockcontext->id);
+        role_assign($manager->id, $user->id, $usercontext2->id);
+        role_assign($manager->id, $user->id, $usercontext3->id);
+        $count = $DB->count_records('role_assignments', ['userid' => $user->id]);
+        $this->assertEquals(8, $count);
+        // Role assignments, where the user makes assignments.
+        $this->setUser($user);
+        role_assign($student->id, $user2->id, $coursecontext3->id);
+        role_assign($student->id, $user3->id, $coursecontext3->id);
+        $count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
+        $this->assertEquals(2, $count);
+
+        $contextlist = provider::get_contexts_for_userid($user->id);
+        $approvedcontextlist = new approved_contextlist($user, 'core_role', $contextlist->get_contextids());
+        provider::delete_data_for_user($approvedcontextlist);
+        // After deletion, the role_assignments assigned to the user should have been deleted.
+        $count = $DB->count_records('role_assignments', ['userid' => $user->id]);
+        $this->assertEquals(0, $count);
+        // After deletion, the role_assignments assigned by the user should not have been deleted.
+        $count = $DB->count_records('role_assignments', ['modifierid' => $user->id]);
+        $this->assertEquals(2, $count);
+    }
+    /**
+     * Export for a user with a key against a script where no instance is specified.
+     */
+    public function test_export_user_role_to_cohort() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        // Assign user roles to cohort.
+        $user = $this->getDataGenerator()->create_user();
+        $contextuser = \context_user::instance($user->id);
+        $teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
+        $cohort = $this->getDataGenerator()->create_cohort();
+        $userassignover = $this->getDataGenerator()->create_user();
+        $contextuserassignover = \context_user::instance($userassignover->id);
+        cohort_add_member($cohort->id, $userassignover->id);
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $user->id,
+            'roleid' => $teacher->id,
+            'cohortid' => $cohort->id
+        );
+        api::create_cohort_role_assignment($params);
+        api::sync_all_cohort_roles();
+        $rolesnames = self::get_roles_name();
+        $subcontextteacher = [
+            get_string('privacy:metadata:role_cohortroles', 'core_role'),
+            $rolesnames[$teacher->id]
+        ];
+        // Test User is assigned role teacher to cohort.
+        provider::export_user_role_to_cohort($user->id);
+        $writer = writer::with_context($contextuserassignover);
+        $this->assertTrue($writer->has_any_data());
+        $exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
+        $this->assertEquals($user->id, reset($exported)->userid);
+
+        // Test User is member of a cohort which User2 is assigned to role to this cohort.
+        $user2 = $this->getDataGenerator()->create_user();
+        $cohort2 = $this->getDataGenerator()->create_cohort();
+        cohort_add_member($cohort2->id, $user->id);
+        $params = (object) array(
+            'userid' => $user2->id,
+            'roleid' => $teacher->id,
+            'cohortid' => $cohort2->id
+        );
+        api::create_cohort_role_assignment($params);
+        api::sync_all_cohort_roles();
+        provider::export_user_role_to_cohort($user->id);
+        $writer = writer::with_context($contextuser);
+        $this->assertTrue($writer->has_any_data());
+        $exported = $writer->get_related_data($subcontextteacher, 'cohortroles');
+        $this->assertEquals($user2->id, reset($exported)->userid);
+    }
+    /**
+     * Test for provider::delete_user_role_to_cohort().
+     */
+    public function test_delete_user_role_to_cohort() {
+        global $DB;
+
+        $this->resetAfterTest();
+        $this->setAdminUser();
+        // Assign user roles to cohort.
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+        $teacher = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
+        $cohort = $this->getDataGenerator()->create_cohort();
+        cohort_add_member($cohort->id, $user2->id);
+        cohort_add_member($cohort->id, $user3->id);
+        cohort_add_member($cohort->id, $user4->id);
+        $this->setAdminUser();
+        $params = (object) array(
+            'userid' => $user->id,
+            'roleid' => $teacher->id,
+            'cohortid' => $cohort->id
+        );
+        api::create_cohort_role_assignment($params);
+        api::sync_all_cohort_roles();
+
+        $count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
+        $this->assertEquals(3, $count);
+
+        provider::delete_user_role_to_cohort($user->id);
+        $count = $DB->count_records('role_assignments', ['userid' => $user->id, 'component' => 'tool_cohortroles']);
+        $this->assertEquals(0, $count);
+    }
+    /**
+     * Supoort function to get all the localised roles name
+     * in a simple array for testing.
+     *
+     * @return array Array of name of the roles by roleid.
+     */
+    protected static function get_roles_name() {
+        $roles = role_fix_names(get_all_roles(), \context_system::instance(), ROLENAME_ORIGINAL);
+        $rolesnames = array();
+        foreach ($roles as $role) {
+            $rolesnames[$role->id] = $role->localname;
+        }
+        return $rolesnames;
+    }
+}
\ No newline at end of file
index 465a9d7..3adbf0b 100644 (file)
@@ -228,27 +228,17 @@ if ($hassiteconfig) {
     $setting->set_force_ltr(true);
     $temp->add($setting);
 
+    // See {@link https://gdpr-info.eu/art-8-gdpr/}.
+    $ageofdigitalconsentmap = implode(PHP_EOL, [
+        '*, 16',
+        'AT, 14',
+        'ES, 14',
+        'US, 13'
+    ]);
     $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',
-        ]),
+        $ageofdigitalconsentmap,
         PARAM_RAW
     );
     $temp->add($setting);
index 162e89c..42e81f0 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
index 20d06f1..63fd29d 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;
-$plugin->requires  = 2017110800;
+$plugin->version   = 2018051400;
+$plugin->requires  = 2018050800;
 $plugin->component = 'tool_assignmentupgrade';
-$plugin->dependencies = array('mod_assign' => 2017110800);
+$plugin->dependencies = array('mod_assign' => 2018050800);
index 0ed3384..a30d705 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
 $plugin->component = 'tool_availabilityconditions';
index 8066574..7771e76 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;   // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;   // Requires this Moodle version
+$plugin->version   = 2018051400;   // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;   // Requires this Moodle version
 $plugin->component = 'tool_behat'; // Full name of the plugin (used for diagnostics)
index d8980e4..9cfa8f7 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_capability'; // Full name of the plugin (used for diagnostics).
index 4e7de4a..663c515 100644 (file)
@@ -25,8 +25,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_cohortroles'; // Full name of the plugin (used for diagnostics).
 
 $plugin->dependencies = array(
index 4dfe9c2..400e4b9 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;
-$plugin->requires  = 2017110800;
+$plugin->version   = 2018051400;
+$plugin->requires  = 2018050800;
 $plugin->component = 'tool_customlang'; // Full name of the plugin (used for diagnostics)
index 09583b2..9054612 100644 (file)
@@ -478,6 +478,10 @@ class api {
     public static function update_purpose(stdClass $record) {
         self::check_can_manage_data_registry();
 
+        if (!isset($record->sensitivedatareasons)) {
+            $record->sensitivedatareasons = '';
+        }
+
         $purpose = new purpose($record->id);
         $purpose->from_record($record);
 
@@ -765,6 +769,7 @@ class api {
      * @param int $status the status to set the contexts to.
      */
     public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
+        $request = new data_request($requestid);
         foreach ($clcollection as $contextlist) {
             // Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it.
             $clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist);
@@ -773,6 +778,12 @@ class api {
 
             // Store the associated contexts in the contextlist.
             foreach ($contextlist->get_contextids() as $contextid) {
+                if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
+                    $context = \context::instance_by_id($contextid);
+                    if (($purpose = static::get_effective_context_purpose($context)) && !empty($purpose->get('protected'))) {
+                        continue;
+                    }
+                }
                 $context = new contextlist_context();
                 $context->set('contextid', $contextid)
                     ->set('contextlistid', $contextlistid)
@@ -869,6 +880,7 @@ class api {
                 }
                 $contexts = [];
             }
+
             $contexts[] = $record->contextid;
             $lastcomponent = $record->component;
         }
index 62d9485..e4b40e8 100644 (file)
@@ -115,12 +115,35 @@ class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
      * @return \context|false
      */
     protected function delete_expired_context(\core_privacy\manager $privacymanager, \tool_dataprivacy\expired_context $expiredctx) {
-        if (!$context = parent::delete_expired_context($privacymanager, $expiredctx)) {
+        $context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
+        if (!$context) {
+            api::delete_expired_context($expiredctx->get('contextid'));
             return false;
         }
 
-        // Delete the user.
+        if (!PHPUNIT_TEST) {
+            mtrace('Deleting context ' . $context->id . ' - ' .
+                shorten_text($context->get_context_name(true, true)));
+        }
+
+        // To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
+        // contexts for the user that may still exist and call delete_data_for_user().
         $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
+        $approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
+        $contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
+
+        foreach ($contextlistcollection as $contextlist) {
+            $approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
+                $user,
+                $contextlist->get_component(),
+                $contextlist->get_contextids()
+            ));
+        }
+
+        $privacymanager->delete_data_for_user($approvedlistcollection);
+        api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
+
+        // Delete the user.
         delete_user($user);
 
         return $context;
index 572617c..c1b2861 100644 (file)
@@ -73,6 +73,11 @@ class data_requests_page implements renderable, templatable {
         $data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
         $data->newdatarequesturl->param('manage', true);
 
+        if (!is_https()) {
+            $httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
+            $data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
+        }
+
         $requests = [];
         foreach ($this->requests as $request) {
             $requestid = $request->get('id');
index 0e0da54..6b90669 100644 (file)
@@ -70,6 +70,11 @@ class my_data_requests_page implements renderable, templatable {
         $data = new stdClass();
         $data->newdatarequesturl = new moodle_url('/admin/tool/dataprivacy/createdatarequest.php');
 
+        if (!is_https()) {
+            $httpwarningmessage = get_string('httpwarning', 'tool_dataprivacy');
+            $data->httpsite = array('message' => $httpwarningmessage, 'announce' => 1);
+        }
+
         $requests = [];
         foreach ($this->requests as $request) {
             $requestid = $request->get('id');
index 3199777..09bcb1c 100644 (file)
@@ -62,7 +62,8 @@ class renderer extends plugin_renderer_base {
     public function render_contact_dpo_link($replytoemail) {
         $params = [
             'data-action' => 'contactdpo',
-            'data-replytoemail' => $replytoemail
+            'data-replytoemail' => $replytoemail,
+            'class' => 'contactdpo'
         ];
         return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
     }
index 999ba5e..ea87d52 100644 (file)
@@ -46,7 +46,7 @@ $string['confirmapproval'] = 'Do you really want to approve this data request?';
 $string['confirmcontextdeletion'] = 'Do you really want to confirm the deletion of the selected contexts? This will also delete all of the user data for their respective sub-contexts.';
 $string['confirmdenial'] = 'Do you really want deny this data request?';
 $string['contactdataprotectionofficer'] = 'Contact Data Protection Officer';
-$string['contactdataprotectionofficer_desc'] = 'Enabling this feature will provide a link for users to contact the site\'s Data Protection Officer through this site. This link will be shown on their profile page, and on the site\'s privacy policy page, as well. The link leads to a form in which the user can make a data request to the Data Protection Officer.';
+$string['contactdataprotectionofficer_desc'] = 'If enabled, users will be able to contact the Data Protection Officer and make a data request via a link on their profile page.';
 $string['contextlevelname10'] = 'Site';
 $string['contextlevelname30'] = 'Users';
 $string['contextlevelname40'] = 'Course categories';
@@ -54,12 +54,12 @@ $string['contextlevelname50'] = 'Courses';
 $string['contextlevelname70'] = 'Activity modules';
 $string['contextlevelname80'] = 'Blocks';
 $string['contextpurposecategorysaved'] = 'Purpose and category saved.';
-$string['contactdpoviaprivacypolicy'] = 'Please contact the site\'s Data Protection Officer as described in the Privacy Policy';
+$string['contactdpoviaprivacypolicy'] = 'Please contact the Data Protection Officer as described in the privacy policy.';
 $string['createcategory'] = 'Create data category';
 $string['createpurpose'] = 'Create data purpose';
 $string['datadeletion'] = 'Data deletion';
 $string['datadeletionpagehelp'] = 'This page lists the contexts that are already past their retention period and need to be confirmed for user data deletion. Once the selected contexts have been confirmed for deletion, the user data related to these contexts will be deleted on the next execution of the "Delete expired contexts" scheduled task.';
-$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for children';
+$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
 $string['dataprivacy:managedatarequests'] = 'Manage data requests';
 $string['dataprivacy:managedataregistry'] = 'Manage data registry';
 $string['dataregistry'] = 'Data registry';
@@ -78,7 +78,7 @@ $string['deny'] = 'Deny';
 $string['denyrequest'] = 'Deny request';
 $string['download'] = 'Download';
 $string['dporolemapping'] = 'Data Protection Officer role mapping';
-$string['dporolemapping_desc'] = 'Select one or more roles that map to the Data Protection Officer role. Users with these roles will be able to manage data requests. This requires the selected role(s) to have the capability \'tool/dataprivacy:managedatarequests\'';
+$string['dporolemapping_desc'] = 'The Data Protection Officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a Data Protection Officer role mapping option.';
 $string['editcategories'] = 'Edit categories';
 $string['editcategory'] = 'Edit category';
 $string['editcategories'] = 'Edit categories';
@@ -136,6 +136,7 @@ $string['gdpr_art_9_2_i_name'] = 'Public health (GDPR Art. 9.2(i))';
 $string['gdpr_art_9_2_j_description'] = 'Processing is necessary for archiving purposes in the public interest, scientific or historical research purposes or statistical purposes in accordance with Article 89(1) based on Union or Member State law which shall be proportionate to the aim pursued, respect the essence of the right to data protection and provide for suitable and specific measures to safeguard the fundamental rights and the interests of the data subject';
 $string['gdpr_art_9_2_j_name'] = 'Public interest, or scientific/historical/statistical research (GDPR Art. 9.2(j))';
 $string['hide'] = 'Collapse all';
+$string['httpwarning'] = 'Any data downloaded from this site may not be encrypted. Please contact your system administrator and request that they install SSL on this site.';
 $string['inherit'] = 'Inherit';
 $string['lawfulbases'] = 'Lawful bases';
 $string['lawfulbases_help'] = 'Select at least one option that will serve as the lawful basis for processing personal data. For details on these lawful bases, please see <a href="https://gdpr-info.eu/art-6-gdpr/" target="_blank">GDPR Art. 6.1</a>';
@@ -181,7 +182,7 @@ $string['replyto'] = 'Reply to';
 $string['requestactions'] = 'Actions';
 $string['requestby'] = 'Requested by';
 $string['requestcomments'] = 'Comments';
-$string['requestcomments_help'] = 'Please feel free to provide more details about your request';
+$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
 $string['requestemailintro'] = 'You have received a data request:';
 $string['requestfor'] = 'Requesting for';
 $string['requeststatus'] = 'Status';
index bff49a7..a351052 100644 (file)
@@ -1,7 +1,9 @@
 .nav-pills .nav-pills {
     margin-left: 1rem;
 }
-
+.data-registry > .top-nav > * {
+    margin-right: 0.5rem;
+}
 /*Extra attribute selection to have preference over bs2's .moodle-actionmenu[data-enhance] */
 .data-registry > .top-nav > .singlebutton,
 .data-registry > .top-nav > .moodle-actionmenu[data-owner='dataregistry-actions'] {
     height: 70vh;
     overflow-y: scroll;
 }
+
+dd a.contactdpo {
+    /* Reverting dd's left margin */
+    margin-left: -10px;
+}
+
+.card dd a.contactdpo {
+    /* Reverting dd's left margin */
+    margin-left: inherit;
+}
index 886bf45..fa84f14 100644 (file)
@@ -36,7 +36,7 @@
     }
 }}
 <div class="container">
-    <div class="row">
+    <div class="row m-b-2">
         <label class="col-md-3 span3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
         <div class="col-md-9 span9 col-form-label">{{replytoemail}}</div>
     </div>
index 203510b..e4aa0c6 100644 (file)
@@ -33,7 +33,7 @@
     }
 }}
 <div class="data-registry">
-    <div class="top-nav">
+    <div class="top-nav d-flex">
         {{#defaultsbutton}}
             {{> core/action_link}}
         {{/defaultsbutton}}
index db2f2ae..beed5ac 100644 (file)
     }
 }}
 
+{{#httpsite}}
+    {{> core/notification_warning}}
+{{/httpsite}}
+
 <div data-region="datarequests">
     <div class="m-t-1 m-b-1">
         <a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
index c43ea97..1bdcbdb 100644 (file)
     }
 }}
 
+{{#httpsite}}
+    {{> core/notification_warning}}
+{{/httpsite}}
+
 <div data-region="datarequests">
     <div class="m-t-1 m-b-1">
         <a href="{{newdatarequesturl}}" class="btn btn-primary" data-action="new-request">
index ebdc10f..6e6c855 100644 (file)
@@ -67,7 +67,7 @@
     <table class="generaltable fullwidth">
         <caption class="accesshide">{{#str}}purposeslist, tool_dataprivacy{{/str}}</caption>
         <thead>
-            <tr style="display: flex;">
+            <tr>
                 <th scope="col" class="col-md-2">{{#str}}name{{/str}}</th>
                 <th scope="col" class="col-md-3">{{#str}}description{{/str}}</th>
                 <th scope="col" class="col-md-2">{{#str}}lawfulbases, tool_dataprivacy{{/str}}</th>
index 62d29c1..2589607 100644 (file)
@@ -1073,4 +1073,126 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
             [$module1, $module2]
         ];
     }
+
+    /**
+     * Test that delete requests filter out protected purpose contexts.
+     */
+    public function test_add_request_contexts_with_status_delete() {
+        $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
+        $contextids = $data->list->get_contextids();
+
+        $this->assertCount(1, $contextids);
+        $this->assertEquals($data->contexts->unprotected, $contextids);
+    }
+
+    /**
+     * Test that export requests don't filter out protected purpose contexts.
+     */
+    public function test_add_request_contexts_with_status_export() {
+        $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
+        $contextids = $data->list->get_contextids();
+
+        $this->assertCount(2, $contextids);
+        $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
+    }
+
+    /**
+     * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
+     *
+     * @param       int $type The type of request to create
+     * @return      \stdClass
+     */
+    protected function setup_test_add_request_contexts_with_status($type) {
+        $this->setAdminUser();
+
+        // User under test.
+        $s1 = $this->getDataGenerator()->create_user();
+
+        // Create three sample contexts.
+        // 1 which should not be returned; and
+        // 1 which will be returned and is not protected; and
+        // 1 which will be returned and is protected.
+
+        $c1 = $this->getDataGenerator()->create_course();
+        $c2 = $this->getDataGenerator()->create_course();
+        $c3 = $this->getDataGenerator()->create_course();
+
+        $ctx1 = \context_course::instance($c1->id);
+        $ctx2 = \context_course::instance($c2->id);
+        $ctx3 = \context_course::instance($c3->id);
+
+        $unprotected = api::create_purpose((object)[
+            'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
+        $protected = api::create_purpose((object) [
+            'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
+
+        $cat1 = api::create_category((object)['name' => 'a']);
+
+        // Set the defaults.
+        list($purposevar, $categoryvar) = data_registry::var_names_from_context(
+            \context_helper::get_class_for_level(CONTEXT_SYSTEM)
+        );
+        set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
+        set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
+
+        $contextinstance1 = api::set_context_instance((object) [
+                'contextid' => $ctx1->id,
+                'purposeid' => $unprotected->get('id'),
+                'categoryid' => $cat1->get('id'),
+            ]);
+
+        $contextinstance2 = api::set_context_instance((object) [
+                'contextid' => $ctx2->id,
+                'purposeid' => $unprotected->get('id'),
+                'categoryid' => $cat1->get('id'),
+            ]);
+
+        $contextinstance3 = api::set_context_instance((object) [
+                'contextid' => $ctx3->id,
+                'purposeid' => $protected->get('id'),
+                'categoryid' => $cat1->get('id'),
+            ]);
+
+        $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
+        $contextlist = new \core_privacy\local\request\contextlist();
+        $contextlist->set_component('tool_dataprivacy');
+        $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
+                'ctx2' => $ctx2->id,
+                'ctx3' => $ctx3->id,
+            ]);
+
+        $collection->add_contextlist($contextlist);
+
+        // Create the sample data request.
+        $datarequest = api::create_data_request($s1->id, $type);
+        $requestid = $datarequest->get('id');
+
+        // Add the full collection with contexts 2, and 3.
+        api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
+
+        // Mark it as approved.
+        api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
+
+        // Fetch the list.
+        $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
+
+        return (object) [
+            'contexts' => (object) [
+                'unused' => [
+                    $ctx1->id,
+                ],
+                'used' => [
+                    $ctx2->id,
+                    $ctx3->id,
+                ],
+                'unprotected' => [
+                    $ctx2->id,
+                ],
+                'protected' => [
+                    $ctx3->id,
+                ],
+            ],
+            'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
+        ];
+    }
 }
index 7e62cc4..1e434a7 100644 (file)
@@ -84,6 +84,22 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $this->getDataGenerator()->enrol_user($user3->id, $course2->id, 'student');
         $this->getDataGenerator()->enrol_user($user4->id, $course3->id, 'student');
 
+        // Add an activity and some data for user 2.
+        $assignmod = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
+        $data = (object) [
+            'assignment' => $assignmod->id,
+            'userid' => $user2->id,
+            'timecreated' => time(),
+            'timemodified' => time(),
+            'status' => 'new',
+            'groupid' => 0,
+            'attemptnumber' => 0,
+            'latest' => 1,
+        ];
+        $DB->insert_record('assign_submission', $data);
+        // We should have one record in the assign submission table.
+        $this->assertEquals(1, $DB->count_records('assign_submission'));
+
         // Users without lastaccess are skipped as well as users enroled in courses with no end date.
         $expired = new \tool_dataprivacy\expired_user_contexts();
         $numexpired = $expired->flag_expired();
@@ -109,6 +125,9 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
         $deleted = $expired->delete();
         $this->assertEquals(0, $deleted);
 
+        // No user data left in mod_assign.
+        $this->assertEquals(0, $DB->count_records('assign_submission'));
+
         // The user is deleted.
         $deleteduser = \core_user::get_user($user2->id, 'id, deleted', IGNORE_MISSING);
         $this->assertEquals(1, $deleteduser->deleted);
index 9d73fe1..3561d57 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2018040500;
-$plugin->requires  = 2018040500;        // Moodle 3.5dev (Build 2018031600) and upwards.
+$plugin->version   = 2018051400;
+$plugin->requires  = 2018050800;        // Moodle 3.5dev (Build 2018031600) and upwards.
 $plugin->component = 'tool_dataprivacy';
index 26baf54..9fa2f1e 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_dbtransfer'; // Full name of the plugin (used for diagnostics).
index 8910e8d..550536d 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
 $plugin->component = 'tool_filetypes';
index e27c676..4049283 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300;
-$plugin->requires = 2017110800;
+$plugin->version = 2018051400;
+$plugin->requires = 2018050800;
 $plugin->component = 'tool_generator';
index c2e4db6..a4ef47c 100644 (file)
@@ -25,8 +25,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_health'; // Full name of the plugin (used for diagnostics)
 
 $plugin->maturity  = MATURITY_ALPHA; // this version's maturity level
index f0a1df0..4a2e8cf 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_httpsreplace'; // Full name of the plugin (used for diagnostics).
index 5e80b83..cbc2912 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_innodb'; // Full name of the plugin (used for diagnostics)
index ff1f7ea..b0ba3ac 100644 (file)
@@ -24,6 +24,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component  = 'tool_installaddon';
-$plugin->version    = 2017111300;
-$plugin->requires   = 2017110800;
+$plugin->version    = 2018051400;
+$plugin->requires   = 2018050800;
 $plugin->maturity   = MATURITY_STABLE;
index fafa6ed..c34440f 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_langimport'; // Full name of the plugin (used for diagnostics)
index 27596c0..f3841d4 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
 $plugin->component = 'logstore_database'; // Full name of the plugin (used for diagnostics).
index 8eb6ff7..8d5b532 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
 $plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
index dc08ba9..b509968 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
 $plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
index 041c70e..b5a7d90 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_log'; // Full name of the plugin (used for diagnostics).
index 7477548..4436def 100644 (file)
@@ -25,6 +25,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
index 1cae087..47fc25c 100644 (file)
@@ -25,8 +25,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_lpimportcsv'; // Full name of the plugin (used for diagnostics).
-$plugin->dependencies = array('tool_lp' => 2017110800);
+$plugin->dependencies = array('tool_lp' => 2018050800);
 
index a293e26..7ef6bd0 100644 (file)
@@ -24,8 +24,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_lpmigrate'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(
     'tool_lp' => ANY_VERSION
index 0bf7da9..a13983f 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;
-$plugin->requires  = 2017110800;
+$plugin->version   = 2018051400;
+$plugin->requires  = 2018050800;
 $plugin->component = 'tool_messageinbound';
index 438a6d5..2457ecf 100644 (file)
@@ -23,9 +23,9 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(
-    'webservice_rest' => 2017110800
+    'webservice_rest' => 2018050800
 );
index c5239d8..5601b72 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2017111300;     // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800;     // Requires this Moodle version.
+$plugin->version   = 2018051400;     // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800;     // Requires this Moodle version.
 $plugin->component = 'tool_monitor'; // Full name of the plugin (used for diagnostics).
index 09000e0..6efabad 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_multilangupgrade'; // Full name of the plugin (used for diagnostics)
 
index fe05f0e..4f78e33 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_oauth2'; // Full name of the plugin (used for diagnostics).
 
index b0741a2..dbc7baf 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_phpunit'; // Full name of the plugin (used for diagnostics)
 
index b16a4e7..95613a7 100644 (file)
@@ -155,12 +155,13 @@ class page_viewdoc implements renderable, templatable {
         $data = (object) [
             'pluginbaseurl' => (new moodle_url('/admin/tool/policy'))->out(false),
             'returnurl' => $this->returnurl ? (new moodle_url($this->returnurl))->out(false) : null,
-            'editurl' => ($this->manage && $this->policy->status != policy_version::STATUS_ARCHIVED) ?
-                (new moodle_url('/admin/tool/policy/editpolicydoc.php',
-                ['policyid' => $this->policy->policyid, 'versionid' => $this->policy->id]))->out(false) : null,
             'numpolicy' => $this->numpolicy ? : null,
             'totalpolicies' => $this->totalpolicies ? : null,
         ];
+        if ($this->manage && $this->policy->status != policy_version::STATUS_ARCHIVED) {
+            $paramsurl = ['policyid' => $this->policy->policyid, 'versionid' => $this->policy->id];
+            $data->editurl = (new moodle_url('/admin/tool/policy/editpolicydoc.php', $paramsurl))->out(false);
+        }
 
         $data->policy = clone($this->policy);
 
index 32be8f9..efd944d 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['acceptanceacknowledgement'] = 'I acknowledge that I have received the user\'s request to consent on the abovementioned policy on behalf of the user.';
+$string['acceptanceacknowledgement'] = 'I acknowledge that I have received a request to give consent on behalf of user(s).';
 $string['acceptancecount'] = '{$a->agreedcount} of {$a->policiescount}';
 $string['acceptancenote'] = 'Remarks';
 $string['acceptancepolicies'] = 'Policies';
-$string['acceptancessavedsucessfully'] = 'The agreements has been saved successfully.';
+$string['acceptancessavedsucessfully'] = 'The agreements have been saved successfully.';
 $string['acceptancestatusoverall'] = 'Overall';
 $string['acceptanceusers'] = 'Users';
 $string['actions'] = 'Actions';
 $string['activate'] = 'Set status to "Active"';
 $string['activating'] = 'Activating a policy';
-$string['activateconfirm'] = '<p>You are about to activate policy <em>\'{$a->name}\'</em> and make the version <em>\'{$a->revision}\'</em> the current one.</p><p>All users will be required to accept this new policy version to be able to use the site.</p>';
+$string['activateconfirm'] = '<p>You are about to activate policy <em>\'{$a->name}\'</em> and make the version <em>\'{$a->revision}\'</em> the current one.</p><p>All users will be required to agree to this new policy version to be able to use the site.</p>';
 $string['activateconfirmyes'] = 'Activate';
 $string['agreed'] = 'Agreed';
 $string['agreedby'] = 'Agreed by';
-$string['agreedno'] = 'Not agreed';
-$string['agreednowithlink'] = 'Not agreed, click to agree to "{$a}"';
-$string['agreednowithlinkall'] = 'Not agreed, click to agree to all policies';
+$string['agreedno'] = 'Consent not given';
+$string['agreednowithlink'] = 'Consent not given; click to give consent on behalf of user for {$a}';
+$string['agreednowithlinkall'] = 'Consent not given; click to give consent on behalf of user for all policies';
 $string['agreedon'] = 'Agreed on';
 $string['agreedyes'] = 'Agreed';
-$string['agreedyesonbehalf'] = 'Agreed on behalf of';
-$string['agreedyesonbehalfwithlink'] = 'Agreed on behalf of, click to withdraw consent to "{$a}"';
-$string['agreedyesonbehalfwithlinkall'] = 'Agreed on behalf of, click to withdraw consent to all policies"';
-$string['agreedyeswithlink'] = 'Agreed, click to withdraw consent to "{$a}"';
-$string['agreedyeswithlinkall'] = 'Agreed, click to withdraw consent to all policies';
+$string['agreedyesonbehalf'] = 'Consent given on behalf of user';
+$string['agreedyesonbehalfwithlink'] = 'Consent given on behalf of user; click to withdraw user consent for {$a}';
+$string['agreedyesonbehalfwithlinkall'] = 'Consent given on behalf of user; click to withdraw user consent for all policies';
+$string['agreedyeswithlink'] = 'Consent given; click to withdraw user consent for {$a}';
+$string['agreedyeswithlinkall'] = 'Consent given; click to withdraw user consent for all policies';
 $string['agreepolicies'] = 'Please agree to the following policies';
 $string['backtotop'] = 'Back to top';
 $string['consentbulk'] = 'Consent';
-$string['consentdetails'] = 'Agree on behalf of the user';
+$string['consentdetails'] = 'Give consent on behalf of user';
 $string['consentpagetitle'] = 'Consent';
 $string['contactdpo'] = 'For questions regarding the policies please contact the Data Protection Officer.';
 $string['dataproc'] = 'Personal data processing';
@@ -61,7 +61,7 @@ $string['deleteconfirm'] = '<p>Are you sure you want to delete policy <em>\'{$a-
 $string['editingpolicydocument'] = 'Editing policy';
 $string['errorpolicyversionnotfound'] = 'There isn\'t any policy version with this identifier.';
 $string['errorsaveasdraft'] = 'Minor change can not be saved as draft';
-$string['errorusercantviewpolicyversion'] = 'The user hasn\'t access to this policy version.';
+$string['errorusercantviewpolicyversion'] = 'The user doesn\'t have access to this policy version.';
 $string['event_acceptance_created'] = 'User policy agreement created';
 $string['event_acceptance_updated'] = 'User policy agreement updated';
 $string['filtercapabilityno'] = 'Permission: Can not agree';
@@ -77,15 +77,15 @@ $string['filterpolicy'] = 'Policy: {$a}';
 $string['guestconsent:continue'] = 'Continue';
 $string['guestconsentmessage'] = 'If you continue browsing this website, you agree to our policies:';
 $string['iagree'] = 'I agree to the {$a}';
-$string['iagreetothepolicy'] = 'Agree';
+$string['iagreetothepolicy'] = 'Give consent on behalf of user';
 $string['inactivate'] = 'Set status to "Inactive"';
 $string['inactivating'] = 'Inactivating a policy';
-$string['inactivatingconfirm'] = '<p>You are about to inactivate policy <em>\'{$a->name}\'</em> version <em>\'{$a->revision}\'</em>.</p><p>The policy will not apply until some version is made the current one.</p>';
+$string['inactivatingconfirm'] = '<p>You are about to inactivate policy <em>\'{$a->name}\'</em> version <em>\'{$a->revision}\'</em>.</p>';
 $string['inactivatingconfirmyes'] = 'Inactivate';
 $string['invalidversionid'] = 'There is no policy with this identifier!';
-$string['irevokethepolicy'] = 'Withdraw consent';
+$string['irevokethepolicy'] = 'Withdraw user consent';
 $string['minorchange'] = 'Minor change';
-$string['minorchangeinfo'] = 'Minor changes do not amend the meaning of the policy text, terms or conditions. Users do not need to reconfirm their consent.';
+$string['minorchangeinfo'] = 'A minor change does not alter the meaning of the policy. Users are not required to agree to the policy again if the edit is marked as a minor change.';
 $string['managepolicies'] = 'Manage policies';
 $string['movedown'] = 'Move down';
 $string['moveup'] = 'Move up';
@@ -97,7 +97,7 @@ $string['nopermissiontoagreedocs'] = 'No permission to agree to the policies';
 $string['nopermissiontoagreedocs_desc'] = 'Sorry, you do not have the required permissions to agree to the policies.<br />You will not be able to use this site until the following policies are agreed:';
 $string['nopermissiontoagreedocsbehalf'] = 'No permission to agree to the policies on behalf of this user';
 $string['nopermissiontoagreedocsbehalf_desc'] = 'Sorry, you do not have the required permission to agree to the following policies on behalf of {$a}:';
-$string['nopermissiontoagreedocscontact'] = 'For further assistance, please contact the following person:';
+$string['nopermissiontoagreedocscontact'] = 'For further assistance, please contact';
 $string['nopermissiontoviewpolicyversion'] = 'You do not have permissions to view this policy version.';
 $string['nopolicies'] = 'There are no policies for registered users with an active version.';
 $string['selectpolicyandversion'] = 'Use the filter above to select policy and/or version';
@@ -105,7 +105,7 @@ $string['steppolicies'] = 'Policy {$a->numpolicy} out of {$a->totalpolicies}';
 $string['pluginname'] = 'Policies';
 $string['policiesagreements'] = 'Policies and agreements';
 $string['policy:accept'] = 'Agree to the policies';
-$string['policy:acceptbehalf'] = 'Agree to the policies on someone else\'s behalf';
+$string['policy:acceptbehalf'] = 'Give consent for policies on someone else\'s behalf';
 $string['policy:managedocs'] = 'Manage policies';
 $string['policy:viewacceptances'] = 'View user agreement reports';
 $string['policydocaudience'] = 'User consent';
@@ -124,30 +124,30 @@ $string['policydoctype0'] = 'Site policy';
 $string['policydoctype1'] = 'Privacy policy';
 $string['policydoctype2'] = 'Third parties policy';
 $string['policydoctype99'] = 'Other policy';
-$string['policyversionacceptedinbehalf'] = 'This policy version has been agreed to by another user on behalf of you.';
-$string['policyversionacceptedinotherlang'] = 'This policy version has been agreed to in a different language.';
+$string['policyversionacceptedinbehalf'] = 'Consent for this policy has been given on your behalf.';
+$string['policyversionacceptedinotherlang'] = 'Consent for this policy version has been given in a different language.';
 $string['previousversions'] = '{$a} previous versions';
-$string['privacy:metadata:acceptances'] = 'Information from policies agreements made by the users of this site.';
+$string['privacy:metadata:acceptances'] = 'Information from policy agreements made by site users';
 $string['privacy:metadata:acceptances:policyversionid'] = 'The ID of the accepted version policy.';
 $string['privacy:metadata:acceptances:userid'] = 'The ID of the user who agreed to the policy.';
 $string['privacy:metadata:acceptances:status'] = 'The status of the agreement: 0 if not accepted; 1 otherwise.';
 $string['privacy:metadata:acceptances:lang'] = 'The current language displayed when the policy is accepted.';
 $string['privacy:metadata:acceptances:usermodified'] = 'The ID of the user accepting the policy, if made on behalf of another user.';
-$string['privacy:metadata:acceptances:timecreated'] = 'The timestamp indicating when the agreement was made by the user.';
-$string['privacy:metadata:acceptances:timemodified'] = 'The timestamp indicating when the agreement was modified by the user.';
-$string['privacy:metadata:acceptances:note'] = 'Any comments added by the user who agree to policies on behalf of another.';
+$string['privacy:metadata:acceptances:timecreated'] = 'The time when the user agreed to the policy';
+$string['privacy:metadata:acceptances:timemodified'] = 'The time when the user modified their agreement';
+$string['privacy:metadata:acceptances:note'] = 'Any comments added by a user when giving consent on behalf of another user';
 $string['privacysettings'] = 'Privacy settings';
 $string['readpolicy'] = 'Please read our {$a}';
-$string['refertofullpolicytext'] = 'Please refer to the full {$a} text if you would like to review.';
-$string['revokeacknowledgement'] = 'I acknowledge that I have received the user\'s request to withdraw consent on the abovementioned policy on behalf of the user.';
-$string['revokedetails'] = 'Withdraw user\'s consent';
+$string['refertofullpolicytext'] = 'Please refer to the full {$a} if you would like to review the text.';
+$string['revokeacknowledgement'] = 'I acknowledge that I have received a request to withdraw consent on behalf of user(s).';
+$string['revokedetails'] = 'Withdraw user consent';
 $string['save'] = 'Save';
 $string['saveasdraft'] = 'Save as draft';
 $string['selectuser'] = 'Select user {$a}';
-$string['selectusersforconsent'] = 'Select users to give consent for';
-$string['settodraft'] = 'Create a new "Draft"';
+$string['selectusersforconsent'] = 'Select users to give consent on behalf of';
+$string['settodraft'] = 'Create a new draft';
 $string['status'] = 'Policy status';
-$string['statusinfo'] = 'An active policy will require consent from new users. Existing users will need to consent to this policy on their next logged in session.';
+$string['statusinfo'] = 'A policy with \'Active\' status requires users to give their consent, either when they first log in, or in the case of existing users when they next log in.';
 $string['status0'] = 'Draft';
 $string['status1'] = 'Active';
 $string['status2'] = 'Inactive';
index c7ce145..02588bd 100644 (file)
@@ -43,7 +43,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I navigate to "Users > Privacy and policies > User agreements" in site administration
     And "Agreed" "icon" should exist in the "User One" "table_row"
     And "Agreed" "icon" should exist in the "Max Manager" "table_row"
-    And "Not agreed" "icon" should exist in the "User Two" "table_row"
+    And "Consent not given" "icon" should exist in the "User Two" "table_row"
 
   Scenario: Agree on behalf of another user as a manager, single policy, javascript off
     Given I log in as "admin"
@@ -57,17 +57,17 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I press "Next"
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
-    And I click on "Not agreed" "link" in the "User One" "table_row"
-    Then I should see "Agree on behalf of the user"
+    And I click on "Consent not given" "link" in the "User One" "table_row"
+    Then I should see "Give consent on behalf of user"
     And I should see "User One"
     And I should see "This site policy"
-    And I should see "I acknowledge that I have received the user's request to consent on the abovementioned policy on behalf of the user."
+    And I should see "I acknowledge that I have received a request to give consent on behalf of user(s)."
     And I set the field "Remarks" to "Consent received from a parent"
-    And I press "Agree"
-    And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
+    And I press "Give consent on behalf of user"
+    And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
     And "Max Manager" "link" should exist in the "User One" "table_row"
     And "Consent received from a parent" "text" should exist in the "User One" "table_row"
-    And "Not agreed" "icon" should exist in the "User Two" "table_row"
+    And "Consent not given" "icon" should exist in the "User Two" "table_row"
 
   @javascript
   Scenario: Agree on behalf of another user as a manager, single policy, javascript on
@@ -82,17 +82,17 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I press "Next"
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
-    And I click on "Not agreed" "link" in the "User One" "table_row"
-    Then I should see "Agree on behalf of the user"
+    And I click on "Consent not given" "link" in the "User One" "table_row"
+    Then I should see "Give consent on behalf of user"
     And I should see "User One"
     And I should see "This site policy"
-    And I should see "I acknowledge that I have received the user's request to consent on the abovementioned policy on behalf of the user."
+    And I should see "I acknowledge that I have received a request to give consent on behalf of user(s)."
     And I set the field "Remarks" to "Consent received from a parent"
-    And I press "Agree"
-    And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
+    And I press "Give consent on behalf of user"
+    And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
     And "Max Manager" "link" should exist in the "User One" "table_row"
     And "Consent received from a parent" "text" should exist in the "User One" "table_row"
-    And "Not agreed" "icon" should exist in the "User Two" "table_row"
+    And "Consent not given" "icon" should exist in the "User Two" "table_row"
 
   Scenario: View acceptances made by users on their own, multiple policies
     Given I log in as "admin"
@@ -119,9 +119,9 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I press "Next"
     And I navigate to "Users > Privacy and policies > User agreements" in site administration
     And "Agreed" "icon" should exist in the "User One" "table_row"
-    And "Not agreed" "icon" should not exist in the "User One" "table_row"
+    And "Consent not given" "icon" should not exist in the "User One" "table_row"
     And "Agreed" "icon" should exist in the "Max Manager" "table_row"
-    And "Not agreed" "icon" should exist in the "User Two" "table_row"
+    And "Consent not given" "icon" should exist in the "User Two" "table_row"
     And "Agreed" "icon" should not exist in the "User Two" "table_row"
     And I click on "2 of 2" "link" in the "User One" "table_row"
     And "Agreed" "icon" should exist in the "This site policy" "table_row"
@@ -129,8 +129,8 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I am on site homepage
     And I navigate to "Users > Privacy and policies > User agreements" in site administration
     And I click on "0 of 2" "link" in the "User Two" "table_row"
-    And "Not agreed" "icon" should exist in the "This site policy" "table_row"
-    And "Not agreed" "icon" should exist in the "This privacy policy" "table_row"
+    And "Consent not given" "icon" should exist in the "This site policy" "table_row"
+    And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
 
   Scenario: Agree on behalf of another user as a manager, multiple policies, javascript off
     Given I log in as "admin"
@@ -149,20 +149,20 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I set the field "I agree to the This privacy policy" to "1"
     And I press "Next"
     And I navigate to "Users > Privacy and policies > User agreements" in site administration
-    And I click on "Not agreed, click to agree to \"This site policy\"" "link" in the "User One" "table_row"
-    Then I should see "Agree on behalf of the user"
+    And I click on "Consent not given; click to give consent on behalf of user for This site policy" "link" in the "User One" "table_row"
+    Then I should see "Give consent on behalf of user"
     And I should see "User One"
     And I should see "This site policy"
-    And I should see "I acknowledge that I have received the user's request to consent on the abovementioned policy on behalf of the user."
+    And I should see "I acknowledge that I have received a request to give consent on behalf of user(s)."
     And I set the field "Remarks" to "Consent received from a parent"
-    And I press "Agree"
-    And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
-    And "Not agreed, click to agree to \"This privacy policy\"" "icon" should exist in the "User One" "table_row"
+    And I press "Give consent on behalf of user"
+    And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
+    And "Consent not given; click to give consent on behalf of user for This privacy policy" "icon" should exist in the "User One" "table_row"
     And I click on "1 of 2" "link" in the "User One" "table_row"
-    And "Agreed on behalf of" "icon" should exist in the "This site policy" "table_row"
+    And "Consent given on behalf of user" "icon" should exist in the "This site policy" "table_row"
     And "Max Manager" "link" should exist in the "This site policy" "table_row"
     And "Consent received from a parent" "text" should exist in the "This site policy" "table_row"
-    And "Not agreed" "icon" should exist in the "This privacy policy" "table_row"
+    And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
 
   @javascript
   Scenario: Agree on behalf of another user as a manager, multiple policies, javascript on
@@ -182,20 +182,20 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I set the field "I agree to the This privacy policy" to "1"
     And I press "Next"
     And I navigate to "Users > Privacy and policies > User agreements" in site administration
-    And I click on "Not agreed, click to agree to \"This site policy\"" "link" in the "User One" "table_row"
-    Then I should see "Agree on behalf of the user"
+    And I click on "Consent not given; click to give consent on behalf of user for This site policy" "link" in the "User One" "table_row"
+    Then I should see "Give consent on behalf of user"
     And I should see "User One"
     And I should see "This site policy"
-    And I should see "I acknowledge that I have received the user's request to consent on the abovementioned policy on behalf of the user."
+    And I should see "I acknowledge that I have received a request to give consent on behalf of user(s)."
     And I set the field "Remarks" to "Consent received from a parent"
-    And I press "Agree"
-    And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
-    And "Not agreed, click to agree to \"This privacy policy\"" "icon" should exist in the "User One" "table_row"
+    And I press "Give consent on behalf of user"
+    And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
+    And "Consent not given; click to give consent on behalf of user for This privacy policy" "icon" should exist in the "User One" "table_row"
     And I click on "1 of 2" "link" in the "User One" "table_row"
-    And "Agreed on behalf of" "icon" should exist in the "This site policy" "table_row"
+    And "Consent given on behalf of user" "icon" should exist in the "This site policy" "table_row"
     And "Max Manager" "link" should exist in the "This site policy" "table_row"
     And "Consent received from a parent" "text" should exist in the "This site policy" "table_row"
-    And "Not agreed" "icon" should exist in the "This privacy policy" "table_row"
+    And "Consent not given" "icon" should exist in the "This privacy policy" "table_row"
 
   Scenario: Policies and agreements profile link visible for current user
     Given I log in as "user1"
@@ -247,15 +247,15 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I press "Continue"
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
-    And I click on "Not agreed" "link" in the "User One" "table_row"
-    Then I should see "Agree on behalf of the user"
+    And I click on "Consent not given" "link" in the "User One" "table_row"
+    Then I should see "Give consent on behalf of user"
     And I should see "User One"
     And I should see "This site policy"
-    And I should see "I acknowledge that I have received the user's request to consent on the abovementioned policy on behalf of the user."
+    And I should see "I acknowledge that I have received a request to give consent on behalf of user(s)."
     And I set the field "Remarks" to "Consent received from a parent"
-    And I press "Agree"
-    And "Agreed on behalf of" "icon" should exist in the "User One" "table_row"
+    And I press "Give consent on behalf of user"
+    And "Consent given on behalf of user" "icon" should exist in the "User One" "table_row"
     And "Max Manager" "link" should not exist in the "User One" "table_row"
     And "Admin User" "link" should exist in the "User One" "table_row"
     And "Consent received from a parent" "text" should exist in the "User One" "table_row"
-    And "Not agreed" "icon" should exist in the "User Two" "table_row"
+    And "Consent not given" "icon" should exist in the "User Two" "table_row"
index 7e97540..f25bd4d 100644 (file)
@@ -175,7 +175,7 @@ Feature: Manage policies
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I open the action menu in "Policy1" "table_row"
     And I click on "Set status to \"Active\"" "link" in the "Policy1" "table_row"
-    Then I should see "All users will be required to accept this new policy version to be able to use the site"
+    Then I should see "All users will be required to agree to this new policy version to be able to use the site."
     And I press "Continue"
     And the following should exist in the "tool-policy-managedocs-wrapper" table:
       | Name                           | Policy status | Version    | Agreements   |
@@ -195,13 +195,13 @@ Feature: Manage policies
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I open the action menu in "Policy1" "table_row"
     And I click on "Set status to \"Inactive\"" "link" in the "Policy1" "table_row"
-    Then I should see "The policy will not apply until some version is made the current one"
+    Then I should see "You are about to inactivate policy"
     And I press "Continue"
     And the following should exist in the "tool-policy-managedocs-wrapper" table:
       | Name                           | Policy status | Version    | Agreements   |
       | Policy1 Site policy, All users | Inactive      | v1         | 1 of 4 (25%) |
     And I open the action menu in "Policy1" "table_row"
-    And I click on "Create a new \"Draft\"" "link" in the "Policy1" "table_row"
+    And I click on "Create a new draft" "link" in the "Policy1" "table_row"
     And I set the field "Version" to "v2"
     And I set the field "Name" to "Policy2"
     And the field "status" matches value "0"
@@ -238,7 +238,7 @@ Feature: Manage policies
     And I click on "Set status to \"Inactive\"" "link" in the "Policy1" "table_row"
     And I press "Continue"
     And I open the action menu in "Policy1" "table_row"
-    And I click on "Create a new \"Draft\"" "link" in the "Policy1" "table_row"
+    And I click on "Create a new draft" "link" in the "Policy1" "table_row"
     And I set the field "Version" to "v2"
     And I set the field "Name" to "Policy2"
     And I set the field "Active" to "1"
index ce0d0a6..d392cc1 100644 (file)
@@ -112,7 +112,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
         // Create policies and agree to them as admin.
         $this->setAdminUser();
         $admin = fullclone($USER);
-        $admincontext = context_user::instance($admin->id);
+        $admincontext = \context_user::instance($admin->id);
         $CFG->sitepolicyhandler = 'tool_policy';
         $policy1 = $this->add_policy();
         api::make_current($policy1->get('id'));
@@ -122,7 +122,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
 
         // Agree to the policies for oneself.
         $this->setUser($this->user);
-        $usercontext = context_user::instance($this->user->id);
+        $usercontext = \context_user::instance($this->user->id);
         api::accept_policies([$policy1->get('id'), $policy2->get('id')]);
 
         // Request export for this user.
@@ -139,7 +139,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
 
         $writer = writer::with_context($usercontext);
         $datauser = $writer->get_related_data([get_string('userpoliciesagreements', 'tool_policy'), $this->user->id]);
-        $this->assertEquals(2, count($datauser));
+        $this->assertCount(2, (array) $datauser);
         $this->assertEquals($policy1->get('name'), $datauser['policyagreement-'.$policy1->get('id')]->name);
         $this->assertEquals($this->user->id, $datauser['policyagreement-'.$policy1->get('id')]->usermodified);
         $this->assertEquals($policy2->get('name'), $datauser['policyagreement-'.$policy2->get('id')]->name);
@@ -159,8 +159,8 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
         api::make_current($policy2->get('id'));
 
         // Agree to the policies for oneself and for another user.
-        $usercontext = context_user::instance($this->user->id);
-        $admincontext = context_user::instance($USER->id);
+        $usercontext = \context_user::instance($this->user->id);
+        $admincontext = \context_user::instance($USER->id);
         api::accept_policies([$policy1->get('id'), $policy2->get('id')]);
         api::accept_policies([$policy1->get('id'), $policy2->get('id')], $this->user->id, 'Mynote');
 
@@ -181,7 +181,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
 
         $writer = writer::with_context($usercontext);
         $datauser = $writer->get_related_data([get_string('userpoliciesagreements', 'tool_policy'), $this->user->id]);
-        $this->assertEquals(2, count($datauser));
+        $this->assertCount(2, (array) $datauser);
         $this->assertEquals($policy1->get('name'), $datauser['policyagreement-'.$policy1->get('id')]->name);
         $this->assertEquals($admin->id, $datauser['policyagreement-'.$policy1->get('id')]->usermodified);
         $this->assertEquals('Mynote', $datauser['policyagreement-'.$policy1->get('id')]->note);
@@ -200,7 +200,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
         // Admin can see all four agreements.
         $writer = writer::with_context($admincontext);
         $dataadmin = $writer->get_related_data([get_string('userpoliciesagreements', 'tool_policy'), $admin->id]);
-        $this->assertEquals(2, count($dataadmin));
+        $this->assertCount(2, (array) $dataadmin);
         $this->assertEquals($policy1->get('name'), $dataadmin['policyagreement-'.$policy1->get('id')]->name);
         $this->assertEquals($admin->id, $dataadmin['policyagreement-'.$policy1->get('id')]->usermodified);
         $this->assertEquals($policy2->get('name'), $dataadmin['policyagreement-'.$policy2->get('id')]->name);
@@ -208,7 +208,7 @@ class tool_policy_privacy_provider_testcase extends \core_privacy\tests\provider
 
         $writer = writer::with_context($usercontext);
         $datauser = $writer->get_related_data([get_string('userpoliciesagreements', 'tool_policy'), $this->user->id]);
-        $this->assertEquals(2, count($datauser));
+        $this->assertCount(2, (array) $datauser);
         $this->assertEquals($policy1->get('name'), $datauser['policyagreement-'.$policy1->get('id')]->name);
         $this->assertEquals($admin->id, $datauser['policyagreement-'.$policy1->get('id')]->usermodified);
         $this->assertEquals('Mynote', $datauser['policyagreement-'.$policy1->get('id')]->note);
index 159cb2a..e87145d 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2018032900;         // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2018032900;         // Requires this Moodle version.
+$plugin->version   = 2018051400;         // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800;         // Requires this Moodle version.
 $plugin->component = 'tool_policy';      // Full name of the plugin (used for diagnostics).
index c85b558..372332b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_profiling'; // Full name of the plugin (used for diagnostics)
index 82dc549..976bc91 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_recyclebin'; // Full name of the plugin (used for diagnostics).
index db0cf98..bb17971 100644 (file)
@@ -25,8 +25,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_replace'; // Full name of the plugin (used for diagnostics)
 
 $plugin->maturity  = MATURITY_ALPHA; // this version's maturity level
index c182791..67b7e81 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;
-$plugin->requires  = 2017110800;
+$plugin->version   = 2018051400;
+$plugin->requires  = 2018050800;
 $plugin->component = 'tool_spamcleaner'; // Full name of the plugin (used for diagnostics)
 
index f243e16..adb0081 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_task'; // Full name of the plugin (used for diagnostics)
 
index 6c2e44e..0a3ae84 100644 (file)
@@ -21,6 +21,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800; // Requires this Moodle version.
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800; // Requires this Moodle version.
 $plugin->component = 'tool_templatelibrary'; // Full name of the plugin (used for diagnostics).
index 5017a11..20e4245 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_unsuproles'; // Full name of the plugin (used for diagnostics)
 
index b23fe01..8573143 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;            // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800;            // Requires this Moodle version.
+$plugin->version   = 2018051400;            // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800;            // Requires this Moodle version.
 $plugin->component = 'tool_uploadcourse';   // Full name of the plugin (used for diagnostics).
index 5e83d26..671f015 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_uploaduser'; // Full name of the plugin (used for diagnostics)
 
index c2ede76..b9aa86b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;            // The current module version (Date: YYYYMMDDXX).
-$plugin->requires  = 2017110800;            // Requires this Moodle version.
+$plugin->version   = 2018051400;            // The current module version (Date: YYYYMMDDXX).
+$plugin->requires  = 2018050800;            // Requires this Moodle version.
 $plugin->component = 'tool_usertours';      // Full name of the plugin (used for diagnostics).
index 8b54f77..486ba9e 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800; // Requires this Moodle version
+$plugin->version   = 2018051400; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800; // Requires this Moodle version
 $plugin->component = 'tool_xmldb'; // Full name of the plugin (used for diagnostics)
 
index 5096c44..a572e5b 100644 (file)
@@ -26,8 +26,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_cas';        // Full name of the plugin (used for diagnostics)
 
-$plugin->dependencies = array('auth_ldap' => 2017110800);
+$plugin->dependencies = array('auth_ldap' => 2018050800);
index 5324ed8..a1acfba 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_db';         // Full name of the plugin (used for diagnostics)
index 2d3c4cd..d88f88d 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die;
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_email';      // Full name of the plugin (used for diagnostics)
index 5c9d370..4a98703 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_ldap';       // Full name of the plugin (used for diagnostics)
index 7e7cd26..bbbe28a 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires = 2017110800; // Requires this Moodle version.
+$plugin->version = 2018051400; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2018050800; // Requires this Moodle version.
 $plugin->component = 'auth_lti'; // Full name of the plugin (used for diagnostics).
index fc5be6a..d61bd51 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_manual';     // Full name of the plugin (used for diagnostics)
index 3c79a2d..dbe0f5d 100644 (file)
  * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
+
 namespace auth_mnet\privacy;
+
 defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+
 /**
- * Privacy Subsystem for auth_mnet implementing null_provider.
+ * Privacy provider for the mnet authentication
  *
  * @copyright  2018 Carlos Escobedo <carlos@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class provider implements \core_privacy\local\metadata\null_provider {
+class provider implements
+        \core_privacy\local\metadata\provider,
+        \core_privacy\local\request\plugin\provider {
     /**
-     * Get the language string identifier with the component's language
-     * file to explain why this plugin stores no data.
+     * Returns meta data about this system.
      *
-     * @return  string
+     * @param   collection $collection The initialised item collection to add items to.
+     * @return  collection     A listing of user data stored through this system.
      */
-    public static function get_reason() : string {
-        return 'privacy:metadata';
+    public static function get_metadata(collection $collection) : collection {
+
+        $sessionfields = [
+                'userid' => 'privacy:metadata:mnet_session:userid',
+                'username' => 'privacy:metadata:mnet_session:username',
+                'token' => 'privacy:metadata:mnet_session:token',
+                'mnethostid' => 'privacy:metadata:mnet_session:mnethostid',
+                'useragent' => 'privacy:metadata:mnet_session:useragent',
+                'expires' => 'privacy:metadata:mnet_session:expires'
+        ];
+
+        $collection->add_database_table('mnet_session', $sessionfields, 'privacy:metadata:mnet_session');
+
+        $logfields = [
+                'hostid' => 'privacy:metadata:mnet_log:hostid',
+                'remoteid' => 'privacy:metadata:mnet_log:remoteid',
+                'time' => 'privacy:metadata:mnet_log:time',
+                'userid' => 'privacy:metadata:mnet_log:userid',
+                'ip' => 'privacy:metadata:mnet_log:ip',
+                'course' => 'privacy:metadata:mnet_log:course',
+                'coursename' => 'privacy:metadata:mnet_log:coursename',
+                'module' => 'privacy:metadata:mnet_log:module',
+                'cmid' => 'privacy:metadata:mnet_log:cmid',
+                'action' => 'privacy:metadata:mnet_log:action',
+                'url' => 'privacy:metadata:mnet_log:url',
+                'info' => 'privacy:metadata:mnet_log:info'
+        ];
+
+        $collection->add_database_table('mnet_log', $logfields, 'privacy:metadata:mnet_log');
+
+        $externalfields = [
+                'address' => 'privacy:metadata:mnet_external:address',
+                'aim' => 'privacy:metadata:mnet_external:aim',
+                'alternatename' => 'privacy:metadata:mnet_external:alternatename',
+                'autosubscribe' => 'privacy:metadata:mnet_external:autosubscribe',
+                'calendartype' => 'privacy:metadata:mnet_external:calendartype',
+                'city' => 'privacy:metadata:mnet_external:city',
+                'country' => 'privacy:metadata:mnet_external:country',
+                'currentlogin' => 'privacy:metadata:mnet_external:currentlogin',
+                'department' => 'privacy:metadata:mnet_external:department',
+                'description' => 'privacy:metadata:mnet_external:description',
+                'email' => 'privacy:metadata:mnet_external:email',
+                'emailstop' => 'privacy:metadata:mnet_external:emailstop',
+                'firstaccess' => 'privacy:metadata:mnet_external:firstaccess',
+                'firstname' => 'privacy:metadata:mnet_external:firstname',
+                'firstnamephonetic' => 'privacy:metadata:mnet_external:firstnamephonetic',
+                'icq' => 'privacy:metadata:mnet_external:icq',
+                'id' => 'privacy:metadata:mnet_external:id',
+                'idnumber' => 'privacy:metadata:mnet_external:idnumber',
+                'imagealt' => 'privacy:metadata:mnet_external:imagealt',
+                'institution' => 'privacy:metadata:mnet_external:institution',
+                'lang' => 'privacy:metadata:mnet_external:lang',
+                'lastaccess' => 'privacy:metadata:mnet_external:lastaccess',
+                'lastlogin' => 'privacy:metadata:mnet_external:lastlogin',
+                'lastname' => 'privacy:metadata:mnet_external:lastname',
+                'lastnamephonetic' => 'privacy:metadata:mnet_external:lastnamephonetic',
+                'maildigest' => 'privacy:metadata:mnet_external:maildigest',
+                'maildisplay' => 'privacy:metadata:mnet_external:maildisplay',
+                'middlename' => 'privacy:metadata:mnet_external:middlename',
+                'msn' => 'privacy:metadata:mnet_external:msn',
+                'phone1' => 'privacy:metadata:mnet_external:phone1',
+                'pnone2' => 'privacy:metadata:mnet_external:phone2',
+                'picture' => 'privacy:metadata:mnet_external:picture',
+                'policyagreed' => 'privacy:metadata:mnet_external:policyagreed',
+                'skype' => 'privacy:metadata:mnet_external:skype',
+                'suspended' => 'privacy:metadata:mnet_external:suspended',
+                'timezone' => 'privacy:metadata:mnet_external:timezone',
+                'trackforums' => 'privacy:metadata:mnet_external:trackforums',
+                'trustbitmask' => 'privacy:metadata:mnet_external:trustbitmask',
+                'url' => 'privacy:metadata:mnet_external:url',
+                'username' => 'privacy:metadata:mnet_external:username',
+                'yahoo' => 'privacy:metadata:mnet_external:yahoo',
+        ];
+
+        $collection->add_external_location_link('moodle', $externalfields, 'privacy:metadata:external:moodle');
+
+        $collection->add_external_location_link('mahara', $externalfields, 'privacy:metadata:external:mahara');
+
+        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 list of contexts used in this plugin.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        $sql = "SELECT ctx.id
+                  FROM {mnet_log} ml
+                  JOIN {context} ctx ON ctx.instanceid = ml.userid AND ctx.contextlevel = :contextlevel
+                 WHERE ml.userid = :userid";
+        $params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
+
+        $contextlist = new contextlist();
+        $contextlist->add_from_sql($sql, $params);
+
+        return $contextlist;
+    }
+
+    /**
+     * Export all user data for the specified user, in the specified contexts, using the supplied exporter instance.
+     *
+     * @param   approved_contextlist $contextlist The approved contexts to export information for.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+
+        $context = \context_user::instance($contextlist->get_user()->id);
+
+        $sql = "SELECT ml.id, mh.wwwroot, mh.name, ml.remoteid, ml.time, ml.userid, ml.ip, ml.course,
+                       ml.coursename, ml.module, ml.cmid, ml.action, ml.url, ml.info
+                  FROM {mnet_log} ml
+                  JOIN {mnet_host} mh ON mh.id = ml.hostid
+                 WHERE ml.userid = :userid
+              ORDER BY mh.name, ml.coursename";
+        $params = ['userid' => $contextlist->get_user()->id];
+
+        $data = [];
+        $lastcourseid = null;
+
+        $logentries = $DB->get_recordset_sql($sql, $params);
+        foreach ($logentries as $logentry) {
+            $item = (object) [
+                    'time' => transform::datetime($logentry->time),
+                    'remoteid' => $logentry->remoteid,
+                    'ip' => $logentry->ip,
+                    'course' => $logentry->course,
+                    'coursename' => format_string($logentry->coursename),
+                    'module' => $logentry->module,
+                    'cmid' => $logentry->cmid,
+                    'action' => $logentry->action,
+                    'url' => $logentry->url,
+                    'info' => format_string($logentry->info)
+            ];
+
+            $item->externalhost =
+                    ($logentry->name == '') ? preg_replace('#^https?://#', '', $logentry->wwwroot) :
+                            preg_replace('#^https?://#', '', $logentry->name);
+
+            if ($lastcourseid && $lastcourseid != $logentry->course) {
+                $path = [get_string('pluginname', 'auth_mnet'), $data[0]->externalhost, $data[0]->coursename];
+                writer::with_context($context)->export_data($path, (object) $data);
+                $data = [];
+            }
+
+            $data[] = $item;
+            $lastcourseid = $logentry->course;
+        }
+        $logentries->close();
+
+        $path = [get_string('pluginname', 'auth_mnet'), $item->externalhost, $item->coursename];
+        writer::with_context($context)->export_data($path, (object) $data);
+    }
+
+    /**
+     * Delete all personal data for all users in the specified context.
+     *
+     * @param context $context Context to delete data from.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        global $DB;
+
+        if ($context->contextlevel != CONTEXT_USER) {
+            return;
+        }
+
+        $DB->delete_records('mnet_log', ['userid' => $context->instanceid]);
+    }
+
+    /**
+     * 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) {
+        global $DB;
+
+        if (empty($contextlist->count())) {
+            return;
+        }
+
+        foreach ($contextlist->get_contexts() as $context) {
+            if ($context->contextlevel != CONTEXT_USER) {
+                return;
+            }
+
+            // Because we only use user contexts the instance ID is the user ID.
+            $DB->delete_records('mnet_log', ['userid' => $context->instanceid]);
+        }
     }
 }
\ No newline at end of file
index 6fa6ff1..412d35b 100644 (file)
@@ -35,4 +35,67 @@ $string['sso_mnet_login_refused'] = 'Username {$a->user} is not permitted to log
 $string['sso_sp_description'] = 'Publish  this service to allow authenticated users from {$a} to access your site without having to re-login. <ul><li><em>Dependency</em>: You must also <strong>subscribe</strong> to the SSO (Identity Provider) service on {$a}.</li></ul><br />Subscribe to this service to allow your users to roam to the {$a} site without having to re-login there. <ul><li><em>Dependency</em>: You must also <strong>publish</strong> the SSO (Identity Provider) service to {$a}.</li></ul><br />';
 $string['sso_sp_name'] = 'SSO (Service Provider)';
 $string['pluginname'] = 'MNet authentication';
-$string['privacy:metadata'] = 'The MNet authentication plugin does not store any personal data.';
+$string['privacy:metadata:external:mahara'] = 'This plugin can send data externally to a linked Mahara application.';
+$string['privacy:metadata:external:moodle'] = 'This plugin can send data externally to a linked Moodle application.';
+$string['privacy:metadata:mnet_external:address'] = 'The address of the user.';
+$string['privacy:metadata:mnet_external:aim'] = 'The AIM identifier of the user.';
+$string['privacy:metadata:mnet_external:alternatename'] = 'An alternative name for the user.';
+$string['privacy:metadata:mnet_external:autosubscribe'] = 'A preference as to if the user should be auto-subscribed to forums the user posts in.';
+$string['privacy:metadata:mnet_external:calendartype'] = 'A user preference for the type of calendar to use.';
+$string['privacy:metadata:mnet_external:city'] = 'The city of the user.';
+$string['privacy:metadata:mnet_external:country'] = 'The country that the user is in.';
+$string['privacy:metadata:mnet_external:currentlogin'] = 'The current login for this user.';
+$string['privacy:metadata:mnet_external:department'] = 'The department that this user can be found in.';
+$string['privacy:metadata:mnet_external:description'] = 'General details about this user.';
+$string['privacy:metadata:mnet_external:email'] = 'An email address for contact.';
+$string['privacy:metadata:mnet_external:emailstop'] = 'A preference to stop email being sent to the user.';
+$string['privacy:metadata:mnet_external:firstaccess'] = 'The time that this user first accessed the site.';
+$string['privacy:metadata:mnet_external:firstname'] = 'The first name of the user.';
+$string['privacy:metadata:mnet_external:firstnamephonetic'] = 'The phonetic details about the user\'s first name.';
+$string['privacy:metadata:mnet_external:icq'] = 'The ICQ number of the user.';
+$string['privacy:metadata:mnet_external:id'] = 'The identifier for the user.';
+$string['privacy:metadata:mnet_external:idnumber'] = 'An identification number given by the institution.';
+$string['privacy:metadata:mnet_external:imagealt'] = 'Alternative text for the user\'s image.';
+$string['privacy:metadata:mnet_external:institution'] = 'The institution that this user is a member of.';
+$string['privacy:metadata:mnet_external:lang'] = 'A user preference for the language shown.';
+$string['privacy:metadata:mnet_external:lastaccess'] = 'The time that the user last accessed the site.';
+$string['privacy:metadata:mnet_external:lastlogin'] = 'The last login of this user.';
+$string['privacy:metadata:mnet_external:lastname'] = 'The surname of the user.';
+$string['privacy:metadata:mnet_external:lastnamephonetic'] = 'The phonetic details about the user\'s surname.';
+$string['privacy:metadata:mnet_external:maildigest'] = 'A setting for the mail digest for this user.';
+$string['privacy:metadata:mnet_external:maildisplay'] = 'A preference for the user about displaying their email address to other users.';
+$string['privacy:metadata:mnet_external:middlename'] = 'The middle name of the user.';
+$string['privacy:metadata:mnet_external:msn'] = 'The MSN identifier of the user.';
+$string['privacy:metadata:mnet_external:phone1'] = 'A phone number for the user.';
+$string['privacy:metadata:mnet_external:phone2'] = 'An additional phone number for the user.';
+$string['privacy:metadata:mnet_external:picture'] = 'The picture details associated with this user.';
+$string['privacy:metadata:mnet_external:policyagreed'] = 'A flag to determine if the user has agreed to the site policy.';
+$string['privacy:metadata:mnet_external:skype'] = 'The skype identifier of the user.';
+$string['privacy:metadata:mnet_external:suspended'] = 'A flag to show if the user has been suspended on this system.';
+$string['privacy:metadata:mnet_external:timezone'] = 'The timezone that the user resides in.';
+$string['privacy:metadata:mnet_external:trackforums'] = 'A preference for forums and tracking them.';
+$string['privacy:metadata:mnet_external:trustbitmask'] = 'The trust bit mask';
+$string['privacy:metadata:mnet_external:url'] = 'A URL related to this user.';
+$string['privacy:metadata:mnet_external:username'] = 'The username for this user.';
+$string['privacy:metadata:mnet_external:yahoo'] = 'The yahoo identifier of the user.';
+$string['privacy:metadata:mnet_log'] = 'Details of remote actions carried out by a local user logged in a remote system.';
+$string['privacy:metadata:mnet_log:action'] = 'Action carried out by the user.';
+$string['privacy:metadata:mnet_log:cmid'] = 'ID of the course module.';
+$string['privacy:metadata:mnet_log:course'] = 'Remote system course ID where the action occurred.';
+$string['privacy:metadata:mnet_log:coursename'] = 'Remote system course full name where the action occurred.';
+$string['privacy:metadata:mnet_log:hostid'] = 'Remote system MNet ID.';
+$string['privacy:metadata:mnet_log:info'] = 'Additional information about the action.';
+$string['privacy:metadata:mnet_log:ip'] = 'The IP address used at the time of the action occurred.';
+$string['privacy:metadata:mnet_log:module'] = 'Remote system module where the event the action occurred.';
+$string['privacy:metadata:mnet_log:remoteid'] = 'Remote ID of the user who carried out the action in the remote system.';
+$string['privacy:metadata:mnet_log:time'] = 'Time when the action occurred.';
+$string['privacy:metadata:mnet_log:url'] = 'Remote system URL where the action occurred.';
+$string['privacy:metadata:mnet_log:userid'] = 'Local ID of the user who carried out the action in the remote system.';
+$string['privacy:metadata:mnet_session'] = 'The details of each MNet user session in a remote system is stored temporarily.';
+$string['privacy:metadata:mnet_session:expires'] = 'Time when the session expires.';
+$string['privacy:metadata:mnet_session:mnethostid'] = 'Remote system MNet ID.';
+$string['privacy:metadata:mnet_session:token'] = 'Unique session identifier.';
+$string['privacy:metadata:mnet_session:useragent'] = 'String denoting the user agent being which is accessing the page.';
+$string['privacy:metadata:mnet_session:userid'] = 'ID of the user jumping to remote system.';
+$string['privacy:metadata:mnet_session:username'] = 'Username of the user jumping to remote system.';
+$string['unknownhost'] = 'Unknown host';
\ No newline at end of file
diff --git a/auth/mnet/tests/privacy_provider_test.php b/auth/mnet/tests/privacy_provider_test.php
new file mode 100644 (file)
index 0000000..2c7292b
--- /dev/null
@@ -0,0 +1,214 @@
+<?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 test for the authentication mnet
+ *
+ * @package    auth_mnet
+ * @category   test
+ * @copyright  2018 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \auth_mnet\privacy\provider;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\writer;
+use \core_privacy\tests\provider_testcase;
+use core_privacy\local\request\transform;
+
+/**
+ * Privacy test for the authentication mnet
+ *
+ * @package    auth_mnet
+ * @category   test
+ * @copyright  2018 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class auth_mnet_privacy_testcase extends provider_testcase {
+    /**
+     * Set up method.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+        $this->setAdminUser();
+    }
+
+    /**
+     * Check that a user context is returned if there is any user data for this user.
+     */
+    public function test_get_contexts_for_userid() {
+        global $DB;
+
+        $user = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+        $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+
+        // Insert mnet_log record.
+        $logrecord = new stdClass();
+        $logrecord->hostid = '';
+        $logrecord->remoteid = 65;
+        $logrecord->time = time();
+        $logrecord->userid = $user->id;
+
+        $DB->insert_record('mnet_log', $logrecord);
+
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // Check that we only get back one context.
+        $this->assertCount(1, $contextlist);
+
+        // Check that a context is returned is the expected.
+        $usercontext = \context_user::instance($user->id);
+        $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]);
+    }
+
+    /**
+     * Test that user data is exported correctly.
+     */
+    public function test_export_user_data() {
+        global $DB;
+
+        $user = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+
+        // Insert mnet_host record.
+        $hostrecord = new stdClass();
+        $hostrecord->wwwroot = 'https://external.moodle.com';
+        $hostrecord->name = 'External Moodle';
+        $hostrecord->public_key = '-----BEGIN CERTIFICATE-----';
+
+        $hostid = $DB->insert_record('mnet_host', $hostrecord);
+
+        // Insert mnet_log record.
+        $logrecord = new stdClass();
+        $logrecord->hostid = $hostid;
+        $logrecord->remoteid = 65;
+        $logrecord->time = time();
+        $logrecord->userid = $user->id;
+        $logrecord->course = 3;
+        $logrecord->coursename = 'test course';
+
+        $DB->insert_record('mnet_log', $logrecord);
+
+        $usercontext = \context_user::instance($user->id);
+
+        $writer = writer::with_context($usercontext);
+        $this->assertFalse($writer->has_any_data());
+        $approvedlist = new approved_contextlist($user, 'auth_mnet', [$usercontext->id]);
+        provider::export_user_data($approvedlist);
+
+        $data = $writer->get_data([get_string('pluginname', 'auth_mnet'), $hostrecord->name, $logrecord->coursename]);
+
+        $this->assertEquals($logrecord->remoteid, reset($data)->remoteid);
+        $this->assertEquals(transform::datetime($logrecord->time),  reset($data)->time);
+    }
+
+    /**
+     * Test deleting all user data for a specific context.
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $user1 = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+
+        // Insert mnet_log record.
+        $logrecord1 = new stdClass();
+        $logrecord1->hostid = '';
+        $logrecord1->remoteid = 65;
+        $logrecord1->time = time();
+        $logrecord1->userid = $user1->id;
+
+        $DB->insert_record('mnet_log', $logrecord1);
+
+        $user1context = \context_user::instance($user1->id);
+
+        $user2 = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+
+        // Insert mnet_log record.
+        $logrecord2 = new stdClass();
+        $logrecord2->hostid = '';
+        $logrecord2->remoteid = 65;
+        $logrecord2->time = time();
+        $logrecord2->userid = $user2->id;
+
+        $DB->insert_record('mnet_log', $logrecord2);
+
+        // Get all mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', array());
+        // There should be two.
+        $this->assertCount(2, $mnetlogrecords);
+
+        // Delete everything for the first user context.
+        provider::delete_data_for_all_users_in_context($user1context);
+
+        // Get all user1 mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', ['userid' => $user1->id]);
+        $this->assertCount(0, $mnetlogrecords);
+
+        // Get all mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', array());
+        // There should be one (user2).
+        $this->assertCount(1, $mnetlogrecords);
+    }
+
+    /**
+     * This should work identical to the above test.
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $user1 = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+
+        // Insert mnet_log record.
+        $logrecord1 = new stdClass();
+        $logrecord1->hostid = '';
+        $logrecord1->remoteid = 65;
+        $logrecord1->time = time();
+        $logrecord1->userid = $user1->id;
+
+        $DB->insert_record('mnet_log', $logrecord1);
+
+        $user1context = \context_user::instance($user1->id);
+
+        $user2 = $this->getDataGenerator()->create_user(['auth' => 'mnet']);
+
+        // Insert mnet_log record.
+        $logrecord2 = new stdClass();
+        $logrecord2->hostid = '';
+        $logrecord2->remoteid = 65;
+        $logrecord2->time = time();
+        $logrecord2->userid = $user2->id;
+
+        $DB->insert_record('mnet_log', $logrecord2);
+
+        // Get all mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', array());
+        // There should be two.
+        $this->assertCount(2, $mnetlogrecords);
+
+        // Delete everything for the first user.
+        $approvedlist = new approved_contextlist($user1, 'auth_mnet', [$user1context->id]);
+        provider::delete_data_for_user($approvedlist);
+
+        // Get all user1 mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', ['userid' => $user1->id]);
+        $this->assertCount(0, $mnetlogrecords);
+
+        // Get all mnet log records.
+        $mnetlogrecords = $DB->get_records('mnet_log', array());
+        // There should be one (user2).
+        $this->assertCount(1, $mnetlogrecords);
+    }
+}
index cd77e96..a827888 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017111300;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2017110800;        // Requires this Moodle version
+$plugin->version   = 2018051400;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2018050800;        // Requires this Moodle version
 $plugin->component = 'auth_mnet';       // Full name of the plugin (used for diagnostics)