Merge branch 'MDL-63528-master' of git://github.com/junpataleta/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 29 Oct 2018 10:18:02 +0000 (11:18 +0100)
committerDavid Monllao <davidm@moodle.com>
Mon, 29 Oct 2018 10:18:02 +0000 (11:18 +0100)
914 files changed:
.travis.yml
admin/cli/upgrade.php
admin/environment.xml
admin/filters.php
admin/message.php
admin/mnet/access_control.php
admin/mnet/delete.php
admin/mnet/index.php
admin/mnet/peers.php
admin/mnet/profilefields.php
admin/mnet/services.php
admin/mnet/testclient.php
admin/mnet/trustedhosts.php
admin/portfolio.php
admin/qtypes.php
admin/repository.php
admin/repositoryinstance.php
admin/roles/allow.php
admin/roles/classes/capability_table_with_risks.php
admin/roles/define.php
admin/roles/manage.php
admin/settings/analytics.php
admin/settings/appearance.php
admin/settings/courses.php
admin/settings/moodleservices.php [new file with mode: 0644]
admin/settings/server.php
admin/settings/top.php
admin/templates/setting_description.mustache [new file with mode: 0644]
admin/templates/settings_search_results.mustache
admin/tool/analytics/classes/output/form/edit_model.php
admin/tool/analytics/model.php
admin/tool/dataprivacy/classes/api.php
admin/tool/dataprivacy/classes/data_registry.php
admin/tool/dataprivacy/classes/expired_context.php
admin/tool/dataprivacy/classes/expired_contexts_manager.php
admin/tool/dataprivacy/classes/expired_course_related_contexts.php [deleted file]
admin/tool/dataprivacy/classes/expired_user_contexts.php [deleted file]
admin/tool/dataprivacy/classes/expiry_info.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/external.php
admin/tool/dataprivacy/classes/external/purpose_exporter.php
admin/tool/dataprivacy/classes/filtered_userlist.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/form/context_instance.php
admin/tool/dataprivacy/classes/form/purpose.php
admin/tool/dataprivacy/classes/metadata_registry.php
admin/tool/dataprivacy/classes/output/expired_contexts_table.php
admin/tool/dataprivacy/classes/output/renderer.php
admin/tool/dataprivacy/classes/output/summary_page.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/privacy/provider.php
admin/tool/dataprivacy/classes/purpose.php
admin/tool/dataprivacy/classes/purpose_override.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/task/delete_expired_contexts.php
admin/tool/dataprivacy/classes/task/expired_retention_period.php
admin/tool/dataprivacy/createdatarequest.php
admin/tool/dataprivacy/db/caches.php
admin/tool/dataprivacy/db/install.xml
admin/tool/dataprivacy/db/upgrade.php
admin/tool/dataprivacy/editpurpose.php
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/lib.php
admin/tool/dataprivacy/settings.php
admin/tool/dataprivacy/summary.php [new file with mode: 0644]
admin/tool/dataprivacy/templates/categories.mustache
admin/tool/dataprivacy/templates/purposes.mustache
admin/tool/dataprivacy/templates/summary.mustache [new file with mode: 0644]
admin/tool/dataprivacy/tests/api_test.php
admin/tool/dataprivacy/tests/behat/datadelete.feature
admin/tool/dataprivacy/tests/behat/dataexport.feature
admin/tool/dataprivacy/tests/behat/manage_categories.feature
admin/tool/dataprivacy/tests/behat/manage_data_requests.feature
admin/tool/dataprivacy/tests/behat/manage_defaults.feature
admin/tool/dataprivacy/tests/behat/manage_purposes.feature
admin/tool/dataprivacy/tests/data_registry_test.php [new file with mode: 0644]
admin/tool/dataprivacy/tests/expired_contexts_test.php
admin/tool/dataprivacy/tests/external_test.php
admin/tool/dataprivacy/tests/filtered_userlist_test.php [new file with mode: 0644]
admin/tool/dataprivacy/version.php
admin/tool/dbtransfer/dbexport.php
admin/tool/dbtransfer/index.php
admin/tool/health/index.php
admin/tool/httpsreplace/index.php
admin/tool/httpsreplace/tool.php
admin/tool/innodb/index.php
admin/tool/log/store/database/test_settings.php
admin/tool/mobile/classes/privacy/provider.php
admin/tool/mobile/settings.php
admin/tool/mobile/tests/privacy_provider_test.php
admin/tool/monitor/classes/privacy/provider.php
admin/tool/monitor/managerules.php
admin/tool/monitor/tests/privacy_test.php
admin/tool/policy/accept.php
admin/tool/policy/amd/build/acceptmodal.min.js
admin/tool/policy/amd/src/acceptmodal.js
admin/tool/policy/classes/acceptances_table.php
admin/tool/policy/classes/api.php
admin/tool/policy/classes/form/accept_policy.php
admin/tool/policy/classes/form/policydoc.php
admin/tool/policy/classes/output/acceptances.php
admin/tool/policy/classes/output/acceptances_filter.php
admin/tool/policy/classes/output/page_agreedocs.php
admin/tool/policy/classes/output/page_managedocs_list.php
admin/tool/policy/classes/output/page_nopermission.php
admin/tool/policy/classes/output/page_viewdoc.php
admin/tool/policy/classes/output/user_agreement.php
admin/tool/policy/classes/policy_version.php
admin/tool/policy/classes/privacy/local/sitepolicy/handler.php
admin/tool/policy/classes/privacy/provider.php
admin/tool/policy/classes/test/helper.php [new file with mode: 0644]
admin/tool/policy/db/caches.php [new file with mode: 0644]
admin/tool/policy/db/install.xml
admin/tool/policy/db/upgrade.php [new file with mode: 0644]
admin/tool/policy/index.php
admin/tool/policy/lang/en/tool_policy.php
admin/tool/policy/lib.php
admin/tool/policy/pix/agreed.png [moved from admin/tool/policy/pix/agreedyes.png with 100% similarity]
admin/tool/policy/pix/agreed.svg [moved from admin/tool/policy/pix/agreedyes.svg with 100% similarity]
admin/tool/policy/pix/agreedyesonbehalf.png [deleted file]
admin/tool/policy/pix/agreedyesonbehalf.svg [deleted file]
admin/tool/policy/pix/declined.png [moved from admin/tool/policy/pix/agreedno.png with 100% similarity]
admin/tool/policy/pix/declined.svg [moved from admin/tool/policy/pix/agreedno.svg with 100% similarity]
admin/tool/policy/pix/partial.png [new file with mode: 0644]
admin/tool/policy/pix/partial.svg [new file with mode: 0644]
admin/tool/policy/pix/pending.png [new file with mode: 0644]
admin/tool/policy/pix/pending.svg [new file with mode: 0644]
admin/tool/policy/templates/acceptances.mustache
admin/tool/policy/templates/page_agreedocs.mustache
admin/tool/policy/templates/page_managedocs_list.mustache
admin/tool/policy/templates/page_viewdoc.mustache
admin/tool/policy/templates/user_agreement.mustache
admin/tool/policy/tests/api_test.php
admin/tool/policy/tests/behat/acceptances.feature
admin/tool/policy/tests/behat/behat_tool_policy.php
admin/tool/policy/tests/behat/consent.feature
admin/tool/policy/tests/behat/managepolicies.feature
admin/tool/policy/tests/behat/optional.feature [new file with mode: 0644]
admin/tool/policy/tests/privacy_provider_test.php
admin/tool/policy/tests/sitepolicy_handler_test.php [new file with mode: 0644]
admin/tool/policy/version.php
admin/tool/spamcleaner/index.php
admin/tool/task/classes/run_from_cli.php [new file with mode: 0644]
admin/tool/task/lang/en/tool_task.php
admin/tool/task/renderer.php
admin/tool/task/schedule_task.php
admin/tool/task/tests/behat/run_task_now.feature [deleted file]
admin/tool/unsuproles/index.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/picture.php
admin/tool/usertours/tests/behat/create_tour.feature
admin/tool/usertours/tests/behat/tour_accessibility.feature
admin/tool/xmldb/actions/edit_table/edit_table.class.php
admin/tool/xmldb/actions/edit_xml_file/edit_xml_file.class.php
admin/tool/xmldb/amd/build/move.min.js [new file with mode: 0644]
admin/tool/xmldb/amd/src/move.js [new file with mode: 0644]
admin/tool/xmldb/classes/external.php [new file with mode: 0644]
admin/tool/xmldb/db/services.php [moved from blocks/myoverview/settings.php with 57% similarity]
admin/tool/xmldb/index.php
admin/tool/xmldb/styles_boost.css [new file with mode: 0644]
admin/tool/xmldb/version.php
admin/user/user_bulk_cohortadd.php
admin/user/user_bulk_confirm.php
admin/user/user_bulk_delete.php
admin/user/user_bulk_download.php
admin/user/user_bulk_forcepasswordchange.php
admin/user/user_bulk_message.php
admin/webservice/tokens.php
analytics/classes/manager.php
analytics/classes/model.php
analytics/tests/prediction_test.php
auth/mnet/classes/privacy/provider.php
auth/mnet/tests/privacy_provider_test.php
auth/oauth2/classes/privacy/provider.php
auth/oauth2/tests/privacy_provider_test.php
auth/test_settings.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
blocks/classes/external.php
blocks/comments/classes/privacy/provider.php
blocks/comments/tests/privacy_provider_test.php
blocks/community/classes/privacy/provider.php
blocks/community/tests/privacy_test.php
blocks/html/block_html.php
blocks/html/classes/privacy/provider.php
blocks/html/tests/privacy_provider_test.php
blocks/moodleblock.class.php
blocks/myoverview/amd/build/event_list.min.js [deleted file]
blocks/myoverview/amd/build/event_list_by_course.min.js [deleted file]
blocks/myoverview/amd/build/main.min.js [new file with mode: 0644]
blocks/myoverview/amd/build/paging_bar.min.js [deleted file]
blocks/myoverview/amd/build/paging_content.min.js [deleted file]
blocks/myoverview/amd/build/repository.min.js [new file with mode: 0644]
blocks/myoverview/amd/build/tab_preferences.min.js [deleted file]
blocks/myoverview/amd/build/view.min.js [new file with mode: 0644]
blocks/myoverview/amd/build/view_nav.min.js [new file with mode: 0644]
blocks/myoverview/amd/src/event_list.js [deleted file]
blocks/myoverview/amd/src/event_list_by_course.js [deleted file]
blocks/myoverview/amd/src/main.js [new file with mode: 0644]
blocks/myoverview/amd/src/paging_bar.js [deleted file]
blocks/myoverview/amd/src/paging_content.js [deleted file]
blocks/myoverview/amd/src/repository.js [new file with mode: 0644]
blocks/myoverview/amd/src/tab_preferences.js [deleted file]
blocks/myoverview/amd/src/view.js [new file with mode: 0644]
blocks/myoverview/amd/src/view_nav.js [new file with mode: 0644]
blocks/myoverview/block_myoverview.php
blocks/myoverview/classes/output/courses_view.php [deleted file]
blocks/myoverview/classes/output/main.php
blocks/myoverview/classes/privacy/provider.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lang/en/deprecated.txt [new file with mode: 0644]
blocks/myoverview/lib.php
blocks/myoverview/templates/course-action-menu.mustache [new file with mode: 0644]
blocks/myoverview/templates/course-event-list-item.mustache [deleted file]
blocks/myoverview/templates/course-event-list.mustache [deleted file]
blocks/myoverview/templates/course-paging-content-item.mustache [deleted file]
blocks/myoverview/templates/course-paging-content.mustache [deleted file]
blocks/myoverview/templates/course-summary.mustache [deleted file]
blocks/myoverview/templates/courses-view-by-status.mustache [deleted file]
blocks/myoverview/templates/courses-view.mustache
blocks/myoverview/templates/event-list-group.mustache [deleted file]
blocks/myoverview/templates/event-list-item.mustache [deleted file]
blocks/myoverview/templates/event-list.mustache [deleted file]
blocks/myoverview/templates/favourite-icon.mustache [new file with mode: 0644]
blocks/myoverview/templates/main.mustache
blocks/myoverview/templates/nav-display-selector.mustache [new file with mode: 0644]
blocks/myoverview/templates/nav-grouping-selector.mustache [new file with mode: 0644]
blocks/myoverview/templates/nav-sort-selector.mustache [new file with mode: 0644]
blocks/myoverview/templates/no-courses.mustache [moved from blocks/myoverview/templates/paging-bar-item.mustache with 57% similarity]
blocks/myoverview/templates/paging-bar.mustache [deleted file]
blocks/myoverview/templates/placeholder-course.mustache [moved from blocks/myoverview/templates/timeline-view-dates.mustache with 57% similarity]
blocks/myoverview/templates/progress-bar.mustache [moved from theme/bootstrapbase/templates/block_myoverview/paging-content-item.mustache with 60% similarity]
blocks/myoverview/templates/progress-chart.mustache [deleted file]
blocks/myoverview/templates/timeline-view-courses.mustache [deleted file]
blocks/myoverview/templates/timeline-view.mustache [deleted file]
blocks/myoverview/templates/view-cards.mustache [new file with mode: 0644]
blocks/myoverview/templates/view-list.mustache [new file with mode: 0644]
blocks/myoverview/templates/view-summary.mustache [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/myoverview/tests/behat/block_myoverview_favourite.feature [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_progress.feature
blocks/myoverview/tests/privacy_test.php
blocks/myoverview/version.php
blocks/myprofile/block_myprofile.php
blocks/myprofile/classes/output/myprofile.php [new file with mode: 0644]
blocks/myprofile/classes/output/renderer.php [new file with mode: 0644]
blocks/myprofile/styles.css
blocks/myprofile/templates/myprofile.mustache [new file with mode: 0644]
blocks/recent_activity/classes/privacy/provider.php
blocks/rss_client/classes/privacy/provider.php
blocks/rss_client/tests/privacy_test.php
blocks/tag_flickr/classes/privacy/provider.php
blocks/tests/externallib_test.php
blocks/timeline/amd/build/calendar_events_repository.min.js [moved from blocks/myoverview/amd/build/calendar_events_repository.min.js with 100% similarity]
blocks/timeline/amd/build/event_list.min.js [new file with mode: 0644]
blocks/timeline/amd/build/main.min.js [new file with mode: 0644]
blocks/timeline/amd/build/view.min.js [new file with mode: 0644]
blocks/timeline/amd/build/view_courses.min.js [new file with mode: 0644]
blocks/timeline/amd/build/view_dates.min.js [new file with mode: 0644]
blocks/timeline/amd/build/view_nav.min.js [new file with mode: 0644]
blocks/timeline/amd/src/calendar_events_repository.js [moved from blocks/myoverview/amd/src/calendar_events_repository.js with 96% similarity]
blocks/timeline/amd/src/event_list.js [new file with mode: 0644]
blocks/timeline/amd/src/main.js [new file with mode: 0644]
blocks/timeline/amd/src/view.js [new file with mode: 0644]
blocks/timeline/amd/src/view_courses.js [new file with mode: 0644]
blocks/timeline/amd/src/view_dates.js [new file with mode: 0644]
blocks/timeline/amd/src/view_nav.js [new file with mode: 0644]
blocks/timeline/block_timeline.php [new file with mode: 0644]
blocks/timeline/classes/output/main.php [new file with mode: 0644]
blocks/timeline/classes/output/renderer.php [new file with mode: 0644]
blocks/timeline/classes/privacy/provider.php [new file with mode: 0644]
blocks/timeline/db/access.php [new file with mode: 0644]
blocks/timeline/db/install.php [new file with mode: 0644]
blocks/timeline/lang/en/block_timeline.php [new file with mode: 0644]
blocks/timeline/lib.php [new file with mode: 0644]
blocks/timeline/pix/activities.svg [moved from blocks/myoverview/pix/activities.svg with 100% similarity]
blocks/timeline/pix/courses.svg [new file with mode: 0644]
blocks/timeline/templates/course-item-loading-placeholder.mustache [new file with mode: 0644]
blocks/timeline/templates/course-item.mustache [moved from blocks/myoverview/templates/course-item.mustache with 64% similarity]
blocks/timeline/templates/course-items.mustache [moved from theme/bootstrapbase/templates/block_myoverview/course-item.mustache with 63% similarity]
blocks/timeline/templates/event-list-content.mustache [moved from theme/bootstrapbase/templates/block_myoverview/event-list-group.mustache with 66% similarity]
blocks/timeline/templates/event-list-item.mustache [new file with mode: 0644]
blocks/timeline/templates/event-list-items.mustache [moved from blocks/myoverview/templates/course-event-list-items.mustache with 82% similarity]
blocks/timeline/templates/event-list.mustache [new file with mode: 0644]
blocks/timeline/templates/main.mustache [new file with mode: 0644]
blocks/timeline/templates/nav-day-filter.mustache [new file with mode: 0644]
blocks/timeline/templates/nav-view-selector.mustache [new file with mode: 0644]
blocks/timeline/templates/placeholder-event-list-item.mustache [new file with mode: 0644]
blocks/timeline/templates/view-courses.mustache [new file with mode: 0644]
blocks/timeline/templates/view-dates.mustache [new file with mode: 0644]
blocks/timeline/templates/view.mustache [new file with mode: 0644]
blocks/timeline/tests/behat/block_timeline_courses.feature [new file with mode: 0644]
blocks/timeline/tests/behat/block_timeline_dates.feature [new file with mode: 0644]
blocks/timeline/tests/privacy_test.php [new file with mode: 0644]
blocks/timeline/version.php [new file with mode: 0644]
blocks/upgrade.txt
blog/classes/external.php [new file with mode: 0644]
blog/classes/external/post_exporter.php [new file with mode: 0644]
blog/index.php
blog/lib.php
blog/tests/external_test.php [new file with mode: 0644]
cache/stores/memcached/classes/privacy/provider.php
cache/stores/mongodb/classes/privacy/provider.php
cache/stores/redis/classes/privacy/provider.php
cache/stores/session/classes/privacy/provider.php
calendar/classes/external/calendar_event_exporter.php
calendar/classes/external/event_exporter.php
calendar/classes/local/api.php
calendar/lib.php
calendar/templates/event_summary_body.mustache
calendar/tests/calendar_event_exporter_test.php
calendar/upgrade.txt
cohort/classes/privacy/provider.php
cohort/tests/privacy_test.php
comment/classes/privacy/provider.php
comment/index.php
comment/tests/privacy_test.php
completion/classes/privacy/provider.php
completion/tests/privacy_test.php
config-dist.php
course/amd/build/repository.min.js [new file with mode: 0644]
course/amd/src/repository.js [new file with mode: 0644]
course/classes/category.php
course/classes/external/course_summary_exporter.php
course/classes/list_element.php
course/classes/output/activity_navigation.php
course/classes/privacy/provider.php
course/edit.php
course/externallib.php
course/lib.php
course/moodleform_mod.php
course/pending.php
course/renderer.php
course/tests/behat/behat_course.php
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_change_visibility.feature
course/tests/behat/course_contact.feature [new file with mode: 0644]
course/tests/behat/course_creation.feature
course/tests/behat/course_resort.feature
course/tests/category_test.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/tests/privacy_test.php
course/upgrade.txt
enrol/category/tests/plugin_test.php
enrol/classes/privacy/provider.php
enrol/cohort/classes/privacy/provider.php
enrol/cohort/tests/privacy_test.php
enrol/externallib.php
enrol/flatfile/classes/privacy/provider.php
enrol/flatfile/tests/privacy_provider_test.php
enrol/lti/classes/privacy/provider.php
enrol/lti/tests/privacy_provider_test.php
enrol/meta/classes/privacy/provider.php
enrol/meta/tests/privacy_test.php
enrol/paypal/lib.php
enrol/test_settings.php
enrol/tests/behat/role_visibility.feature
enrol/tests/enrollib_test.php
enrol/tests/externallib_test.php
enrol/tests/privacy_test.php
enrol/upgrade.txt
favourites/classes/local/entity/favourite.php [new file with mode: 0644]
favourites/classes/local/repository/favourite_repository.php [new file with mode: 0644]
favourites/classes/local/repository/favourite_repository_interface.php [new file with mode: 0644]
favourites/classes/local/service/user_favourite_service.php [new file with mode: 0644]
favourites/classes/privacy/provider.php [new file with mode: 0644]
favourites/classes/service_factory.php [new file with mode: 0644]
favourites/tests/privacy_test.php [new file with mode: 0644]
favourites/tests/repository_test.php [new file with mode: 0644]
favourites/tests/service_test.php [new file with mode: 0644]
files/classes/privacy/provider.php
files/converter/classes/privacy/provider.php
files/converter/googledrive/classes/privacy/provider.php
files/tests/privacy_test.php [new file with mode: 0644]
filter/glossary/filter.php
filter/glossary/lang/en/filter_glossary.php
filter/glossary/tests/filter_test.php
filter/mathjaxloader/filter.php
filter/mathjaxloader/tests/filtermath_test.php [new file with mode: 0644]
filter/upgrade.txt
grade/edit/letter/index.php
grade/edit/tree/lib.php
grade/grading/classes/privacy/provider.php
grade/grading/form/guide/tests/privacy_test.php
grade/grading/tests/fixtures/marking_guide.php
grade/grading/tests/privacy_test.php
grade/report/history/classes/output/tablelog.php
grade/report/user/lib.php
grade/tests/behat/grade_category_validation.feature
grade/tests/behat/grade_grade_minmax_change.feature
grade/tests/behat/grade_item_validation.feature
grade/tests/behat/grade_scales_aggregation.feature
grade/tests/report_graderlib_test.php
group/classes/privacy/provider.php
group/group_form.php
group/lib.php
group/tests/lib_test.php
group/tests/privacy_provider_test.php
install/lang/af/moodle.php
install/lang/el/admin.php
install/lang/el/error.php
install/lang/el/install.php
install/lang/ru/admin.php
install/lang/sl/admin.php
lang/en/admin.php
lang/en/analytics.php
lang/en/bulkusers.php
lang/en/cache.php
lang/en/deprecated.txt
lang/en/favourites.php [new file with mode: 0644]
lang/en/group.php
lang/en/message.php
lang/en/moodle.php
lang/en/privacy.php
lang/en/question.php
lang/en/role.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/autoscroll.min.js [new file with mode: 0644]
lib/amd/build/dragdrop.min.js [new file with mode: 0644]
lib/amd/build/page_global.min.js [new file with mode: 0644]
lib/amd/build/paged_content.min.js [new file with mode: 0644]
lib/amd/build/paged_content_events.min.js
lib/amd/build/paged_content_factory.min.js
lib/amd/build/paged_content_pages.min.js
lib/amd/build/paged_content_paging_bar.min.js
lib/amd/build/paged_content_paging_bar_limit_selector.min.js [new file with mode: 0644]
lib/amd/build/paged_content_paging_dropdown.min.js
lib/amd/build/pending.min.js [new file with mode: 0644]
lib/amd/build/pubsub.min.js [new file with mode: 0644]
lib/amd/build/sortable_list.min.js [new file with mode: 0644]
lib/amd/build/templates.min.js
lib/amd/build/tree.min.js
lib/amd/build/user_date.min.js
lib/amd/src/autoscroll.js [new file with mode: 0644]
lib/amd/src/dragdrop.js [new file with mode: 0644]
lib/amd/src/page_global.js [new file with mode: 0644]
lib/amd/src/paged_content.js [new file with mode: 0644]
lib/amd/src/paged_content_events.js
lib/amd/src/paged_content_factory.js
lib/amd/src/paged_content_pages.js
lib/amd/src/paged_content_paging_bar.js
lib/amd/src/paged_content_paging_bar_limit_selector.js [new file with mode: 0644]
lib/amd/src/paged_content_paging_dropdown.js
lib/amd/src/pending.js [new file with mode: 0644]
lib/amd/src/pubsub.js [new file with mode: 0644]
lib/amd/src/sortable_list.js [new file with mode: 0644]
lib/amd/src/templates.js
lib/amd/src/tree.js
lib/amd/src/user_date.js
lib/badgeslib.php
lib/behat/behat_base.php
lib/behat/classes/util.php
lib/behat/lib.php
lib/blocklib.php
lib/classes/component.php
lib/classes/event/message_contact_blocked.php
lib/classes/event/message_contact_unblocked.php
lib/classes/event/message_deleted.php
lib/classes/event/message_user_blocked.php [new file with mode: 0644]
lib/classes/event/message_user_unblocked.php [new file with mode: 0644]
lib/classes/message/message.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/plugin_manager.php
lib/classes/plugininfo/mlbackend.php
lib/classes/privacy/provider.php
lib/classes/shutdown_manager.php
lib/classes/update/code_manager.php
lib/db/access.php
lib/db/caches.php
lib/db/install.xml
lib/db/messages.php
lib/db/services.php
lib/db/upgrade.php
lib/ddl/mssql_sql_generator.php
lib/ddl/oracle_sql_generator.php
lib/ddl/sql_generator.php
lib/ddl/tests/ddl_test.php
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/atto/plugins/emoticon/styles.css
lib/editor/atto/plugins/media/lang/en/atto_media.php
lib/editor/atto/plugins/media/lib.php
lib/editor/atto/plugins/media/tests/behat/media.feature
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js
lib/editor/atto/plugins/media/yui/src/button/js/button.js
lib/editor/tinymce/module.js
lib/editor/tinymce/readme_moodle.txt
lib/editor/tinymce/tiny_mce/3.5.11/themes/advanced/skins/moodle/content.css
lib/editor/tinymce/tiny_mce/3.5.11/themes/advanced/skins/moodle/dialog.css
lib/enrollib.php
lib/environmentlib.php
lib/externallib.php
lib/filelib.php
lib/filterlib.php
lib/grade/constants.php
lib/grade/grade_grade.php
lib/grade/grade_item.php
lib/grade/grade_object.php
lib/gradelib.php
lib/grouplib.php
lib/installlib.php
lib/javascript-static.js
lib/jquery/readme_moodle.txt
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/phpunit/bootstrap.php
lib/phpunit/classes/util.php
lib/questionlib.php
lib/requirejs/readme_moodle.txt
lib/templates/drag_handle.mustache [moved from blocks/myoverview/templates/paging-content-item.mustache with 61% similarity]
lib/templates/paged_content.mustache
lib/templates/paged_content_pages.mustache
lib/templates/paged_content_paging_bar.mustache
lib/templates/paged_content_paging_dropdown.mustache
lib/tests/accesslib_test.php
lib/tests/component_test.php
lib/tests/externallib_test.php
lib/tests/grouplib_test.php
lib/tests/moodlelib_test.php
lib/tests/questionlib_test.php
lib/tests/user_menu_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/userkey/classes/privacy/provider.php
lib/xmldb/xmldb_index.php
lib/xmldb/xmldb_table.php
message/amd/build/message_area_profile.min.js
message/amd/build/message_preferences.min.js
message/amd/src/message_area_profile.js
message/amd/src/message_preferences.js
message/classes/api.php
message/classes/helper.php
message/classes/output/messagearea/messages.php
message/classes/privacy/provider.php
message/classes/task/migrate_message_data.php
message/classes/tests/helper.php [new file with mode: 0644]
message/classes/time_last_message_between_users.php
message/defaultoutputs.php
message/externallib.php
message/index.php
message/lib.php
message/output/airnotifier/classes/privacy/provider.php
message/output/airnotifier/tests/privacy_test.php
message/output/email/classes/privacy/provider.php
message/output/jabber/classes/privacy/provider.php
message/output/popup/tests/behat/message_popover_unread.feature
message/pendingcontactrequests.php [new file with mode: 0644]
message/renderer.php
message/templates/message_preferences.mustache
message/tests/api_test.php
message/tests/behat/delete_all_messages.feature
message/tests/behat/delete_messages.feature
message/tests/behat/manage_contacts.feature
message/tests/behat/reply_message.feature
message/tests/behat/search_messages.feature
message/tests/behat/view_messages.feature
message/tests/events_test.php
message/tests/externallib_test.php
message/tests/messagelib_test.php
message/tests/privacy_provider_test.php
message/upgrade.txt
mnet/service/enrol/classes/privacy/provider.php
mnet/service/enrol/tests/privacy_test.php
mod/assign/feedback/comments/backup/moodle2/backup_assignfeedback_comments_subplugin.class.php
mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php
mod/assign/feedback/comments/classes/privacy/provider.php
mod/assign/feedback/comments/lang/en/assignfeedback_comments.php
mod/assign/feedback/comments/lib.php [new file with mode: 0644]
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/tests/privacy_test.php
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/event/observer.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/classes/task/convert_submissions.php
mod/assign/feedback/editpdf/db/install.xml
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/editpdf/tests/editpdf_test.php
mod/assign/feedback/editpdf/version.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedbackplugin.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/classes/privacy/provider.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/tests/behat/edit_student_submission.feature
mod/assign/tests/behat/grant_extension.feature
mod/assign/tests/behat/prevent_submission_changes.feature
mod/assign/tests/behat/reopen_locked_submission.feature
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/assignment/classes/privacy/provider.php
mod/assignment/tests/privacy_test.php
mod/chat/classes/privacy/provider.php
mod/chat/tests/privacy_test.php
mod/choice/classes/privacy/provider.php
mod/choice/tests/privacy_provider_test.php
mod/data/classes/privacy/provider.php
mod/data/tests/privacy_provider_test.php
mod/feedback/classes/privacy/provider.php
mod/feedback/tests/lib_test.php
mod/feedback/tests/privacy_test.php
mod/forum/classes/privacy/provider.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/styles.css
mod/forum/tests/behat/posts_ordering_blog.feature
mod/forum/tests/privacy_provider_test.php
mod/forum/tests/subscriptions_test.php
mod/forum/upgrade.txt
mod/glossary/classes/privacy/provider.php
mod/glossary/tests/privacy_provider_test.php
mod/imscp/lib.php
mod/imscp/tests/lib_test.php
mod/label/lib.php
mod/label/tests/lib_test.php
mod/lesson/classes/privacy/provider.php
mod/lesson/lib.php
mod/lesson/tests/lib_test.php
mod/lesson/tests/privacy_test.php
mod/lti/classes/local/ltiservice/resource_base.php
mod/lti/classes/privacy/provider.php
mod/lti/service/gradebookservices/classes/privacy/provider.php
mod/lti/service/memberships/classes/privacy/provider.php
mod/lti/tests/privacy_provider_test.php
mod/quiz/attemptlib.php
mod/quiz/locallib.php
mod/quiz/report/grading/renderer.php [new file with mode: 0755]
mod/quiz/report/grading/report.php
mod/quiz/report/grading/tests/behat/grading.feature
mod/quiz/report/overview/tests/behat/basic.feature
mod/quiz/report/reportlib.php
mod/quiz/report/responses/tests/behat/basic.feature
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/statistics_table.php
mod/quiz/report/statistics/tests/behat/basic.feature [new file with mode: 0644]
mod/quiz/report/statistics/tests/statistics_table_test.php [new file with mode: 0644]
mod/quiz/tests/behat/attempt_basic.feature
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/quiz/tests/behat/editing_repaginate.feature
mod/quiz/tests/behat/editing_section_headings.feature
mod/quiz/tests/behat/preview.feature
mod/quiz/tests/behat/quiz_reset.feature
mod/quiz/tests/generator/lib.php
mod/quiz/tests/reportlib_test.php
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js
mod/quiz/yui/src/dragdrop/js/resource.js
mod/scorm/classes/privacy/provider.php
mod/scorm/tests/privacy_test.php
mod/survey/classes/privacy/provider.php
mod/survey/tests/privacy_test.php
mod/wiki/classes/privacy/provider.php
mod/wiki/tests/privacy_test.php
mod/workshop/form/numerrors/edit_form.php
mod/workshop/form/numerrors/lang/en/workshopform_numerrors.php
my/indexsys.php
npm-shrinkwrap.json
phpunit.xml.dist
pix/i/moremenu.png [new file with mode: 0644]
pix/i/moremenu.svg [new file with mode: 0644]
pix/i/star.png [new file with mode: 0644]
pix/i/star.svg [new file with mode: 0644]
pix/s/angry.svg [new file with mode: 0644]
pix/s/approve.svg [new file with mode: 0644]
pix/s/biggrin.svg [new file with mode: 0644]
pix/s/blackeye.svg [new file with mode: 0644]
pix/s/blush.svg [new file with mode: 0644]
pix/s/clown.svg [new file with mode: 0644]
pix/s/cool.svg [new file with mode: 0644]
pix/s/dead.svg [new file with mode: 0644]
pix/s/egg.svg [new file with mode: 0644]
pix/s/evil.svg [new file with mode: 0644]
pix/s/heart.svg [new file with mode: 0644]
pix/s/kiss.svg [new file with mode: 0644]
pix/s/martin.svg [new file with mode: 0644]
pix/s/mixed.svg [new file with mode: 0644]
pix/s/no.svg [new file with mode: 0644]
pix/s/sad.svg [new file with mode: 0644]
pix/s/shy.svg [new file with mode: 0644]
pix/s/sleepy.svg [new file with mode: 0644]
pix/s/smiley.svg [new file with mode: 0644]
pix/s/surprise.svg [new file with mode: 0644]
pix/s/thoughtful.svg [new file with mode: 0644]
pix/s/tongueout.svg [new file with mode: 0644]
pix/s/wideeyes.svg [new file with mode: 0644]
pix/s/wink.svg [new file with mode: 0644]
pix/s/yes.svg [new file with mode: 0644]
plagiarism/classes/privacy/plagiarims_user_provider.php [new file with mode: 0644]
plagiarism/classes/privacy/provider.php
portfolio/classes/privacy/provider.php
portfolio/tests/privacy_provider_test.php
privacy/classes/local/request/approved_userlist.php [new file with mode: 0644]
privacy/classes/local/request/core_userlist_provider.php [new file with mode: 0644]
privacy/classes/local/request/helper.php
privacy/classes/local/request/moodle_content_writer.php
privacy/classes/local/request/userlist.php [new file with mode: 0644]
privacy/classes/local/request/userlist_base.php [new file with mode: 0644]
privacy/classes/local/request/userlist_collection.php [new file with mode: 0644]
privacy/classes/manager.php
privacy/classes/output/exported_html_page.php [new file with mode: 0644]
privacy/classes/output/exported_navigation_page.php [new file with mode: 0644]
privacy/classes/output/renderer.php [new file with mode: 0644]
privacy/export_files/general.css [new file with mode: 0644]
privacy/export_files/general.js [new file with mode: 0644]
privacy/templates/htmlpage.mustache [new file with mode: 0644]
privacy/templates/navigation.mustache [moved from blocks/myoverview/templates/paging-content.mustache with 52% similarity]
privacy/tests/approved_userlist_test.php [new file with mode: 0644]
privacy/tests/moodle_content_writer_test.php
privacy/tests/userlist_base_test.php [new file with mode: 0644]
privacy/tests/userlist_collection.php [new file with mode: 0644]
privacy/tests/userlist_test.php [new file with mode: 0644]
question/behaviour/interactive/behaviour.php
question/category.php
question/category_class.php
question/category_form.php
question/classes/statistics/questions/all_calculated_for_qubaid_condition.php
question/classes/statistics/questions/calculated_question_summary.php [new file with mode: 0644]
question/engine/tests/helpers.php
question/format.php
question/format/aiken/format.php
question/format/aiken/lang/en/qformat_aiken.php
question/format/aiken/tests/aikenformat_test.php [new file with mode: 0644]
question/format/aiken/tests/fixtures/aiken_errors.txt [new file with mode: 0644]
question/format/xml/format.php
question/format/xml/tests/fixtures/export_category.xml
question/format/xml/tests/fixtures/nested_categories.xml
question/format/xml/tests/fixtures/nested_categories_with_questions.xml
question/format/xml/tests/qformat_xml_import_export_test.php
question/format/xml/tests/xmlformat_test.php
question/tests/backup_test.php
question/tests/behat/question_categories_idnumber.feature [new file with mode: 0644]
question/tests/calculated_question_summary_test.php [new file with mode: 0644]
question/tests/generator/lib.php
question/tests/generator_test.php
question/type/ddmarker/amd/build/form.min.js [new file with mode: 0644]
question/type/ddmarker/amd/build/question.min.js [new file with mode: 0644]
question/type/ddmarker/amd/build/shapes.min.js [new file with mode: 0644]
question/type/ddmarker/amd/src/form.js [new file with mode: 0644]
question/type/ddmarker/amd/src/question.js [new file with mode: 0644]
question/type/ddmarker/amd/src/shapes.js [new file with mode: 0644]
question/type/ddmarker/edit_ddmarker_form.php
question/type/ddmarker/lang/en/qtype_ddmarker.php
question/type/ddmarker/renderer.php
question/type/ddmarker/shapes.php
question/type/ddmarker/styles.css
question/type/ddmarker/tests/behat/preview.feature
question/type/ddmarker/tests/helper.php
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-debug.js [deleted file]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd-min.js [deleted file]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-dd/moodle-qtype_ddmarker-dd.js [deleted file]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form-debug.js [deleted file]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form-min.js [deleted file]
question/type/ddmarker/yui/build/moodle-qtype_ddmarker-form/moodle-qtype_ddmarker-form.js [deleted file]
question/type/ddmarker/yui/src/ddmarker/build.json [deleted file]
question/type/ddmarker/yui/src/ddmarker/js/ddmarker.js [deleted file]
question/type/ddmarker/yui/src/ddmarker/meta/ddmarker.json [deleted file]
question/type/ddmarker/yui/src/form/build.json [deleted file]
question/type/ddmarker/yui/src/form/js/form.js [deleted file]
question/type/ddmarker/yui/src/form/meta/form.json [deleted file]
question/type/ddwtos/amd/build/ddwtos.min.js [new file with mode: 0644]
question/type/ddwtos/amd/src/ddwtos.js [new file with mode: 0644]
question/type/ddwtos/renderer.php
question/type/ddwtos/styles.css
question/type/ddwtos/tests/behat/preview.feature
question/type/ddwtos/tests/questiontype_test.php
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-debug.js [deleted file]
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd-min.js [deleted file]
question/type/ddwtos/yui/build/moodle-qtype_ddwtos-dd/moodle-qtype_ddwtos-dd.js [deleted file]
question/type/ddwtos/yui/src/ddwtos/build.json [deleted file]
question/type/ddwtos/yui/src/ddwtos/js/ddwtos.js [deleted file]
question/type/ddwtos/yui/src/ddwtos/meta/ddwtos.json [deleted file]
question/type/edit_question_form.php
question/type/essay/question.php
question/type/gapselect/rendererbase.php
question/type/gapselect/tests/questiontype_test.php
question/type/match/tests/questiontype_test.php
question/type/missingtype/question.php
question/type/missingtype/tests/missingtype_test.php
question/type/multichoice/questiontype.php
question/type/multichoice/tests/questiontype_test.php
question/type/numerical/question.php
question/type/questionbase.php
question/type/questiontypebase.php
question/type/shortanswer/question.php
question/type/truefalse/question.php
rating/classes/privacy/provider.php
report/loglive/index.php
report/performance/index.php
report/security/index.php
report/stats/classes/privacy/provider.php
report/stats/tests/privacy_test.php
repository/boxnet/classes/privacy/provider.php
repository/classes/privacy/provider.php
repository/dropbox/classes/privacy/provider.php
repository/dropbox/pix/icon.png
repository/dropbox/pix/icon.svg [new file with mode: 0644]
repository/flickr/classes/privacy/provider.php
repository/flickr_public/classes/privacy/provider.php
repository/googledocs/classes/privacy/provider.php
repository/merlot/classes/privacy/provider.php
repository/onedrive/classes/privacy/provider.php
repository/onedrive/tests/privacy_test.php
repository/picasa/classes/privacy/provider.php
repository/tests/privacy_test.php
repository/wikimedia/classes/privacy/provider.php
repository/youtube/classes/privacy/provider.php
rss/classes/privacy/provider.php
rss/tests/privacy_test.php
search/engine/simpledb/classes/privacy/provider.php
search/engine/simpledb/tests/privacy_test.php
search/engine/solr/classes/privacy/provider.php
tag/classes/privacy/provider.php
tag/tests/external_test.php
tag/tests/privacy_test.php
theme/boost/amd/build/aria.min.js [new file with mode: 0644]
theme/boost/amd/build/loader.min.js
theme/boost/amd/src/aria.js [new file with mode: 0644]
theme/boost/amd/src/loader.js
theme/boost/config.php
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/bs4alphacompat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/icons.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/boost/templates/columns2.mustache
theme/boost/templates/core/action_menu.mustache
theme/boost/templates/core/action_menu_trigger.mustache
theme/boost/templates/core/auth_verify_age_location_page.mustache
theme/boost/templates/core/block.mustache
theme/boost/templates/core/custom_menu_item.mustache
theme/boost/templates/core/initials_bar.mustache
theme/boost/templates/core/loginform.mustache
theme/boost/templates/core/navbar.mustache
theme/boost/templates/core/notification_error.mustache
theme/boost/templates/core/notification_info.mustache
theme/boost/templates/core/notification_success.mustache
theme/boost/templates/core/notification_warning.mustache
theme/boost/templates/core_admin/setting_description.mustache [new file with mode: 0644]
theme/boost/templates/core_admin/settings_search_results.mustache
theme/boost/templates/header.mustache
theme/boost/templates/navbar.mustache
theme/boost/tests/behat/behat_theme_boost_behat_action_menu.php
theme/boost/tests/behat/contextmenu.feature
theme/boost/tests/behat/regionmainsettingsmenu.feature
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/bs4-compat.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/templates/block_myoverview/course-action-menu.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_myoverview/course-event-list-item.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/course-event-list.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/course-paging-content-item.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/course-summary.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/courses-view-course-item.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/courses-view.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/event-list-item.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/main.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/paging-bar.mustache [deleted file]
theme/bootstrapbase/templates/block_myoverview/timeline-view.mustache [deleted file]
theme/bootstrapbase/templates/block_timeline/course-item-loading-placeholder.mustache [moved from blocks/myoverview/templates/courses-view-course-item.mustache with 51% similarity]
theme/bootstrapbase/templates/block_timeline/event-list-item.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/event-list-items.mustache [moved from blocks/myoverview/templates/event-list-items.mustache with 91% similarity]
theme/bootstrapbase/templates/block_timeline/event-list.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/main.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/nav-day-filter.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/nav-view-selector.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/placeholder-event-list-item.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/block_timeline/view.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/paged_content_paging_bar.mustache
theme/bootstrapbase/templates/core/paged_content_paging_dropdown.mustache
user/classes/participants_table.php
user/classes/privacy/provider.php
user/index.php
user/lib.php
user/profile/definelib.php
user/profile/field/checkbox/classes/privacy/provider.php
user/profile/field/checkbox/tests/privacy_test.php
user/profile/field/datetime/classes/privacy/provider.php
user/profile/field/datetime/tests/privacy_test.php
user/profile/field/menu/classes/privacy/provider.php
user/profile/field/menu/tests/privacy_test.php
user/profile/field/text/classes/privacy/provider.php
user/profile/field/text/tests/privacy_test.php
user/profile/field/textarea/classes/privacy/provider.php
user/profile/field/textarea/tests/privacy_test.php
user/profilesys.php
user/renderer.php
user/tests/behat/bulk_editenrolment.feature
user/tests/behat/delete_users.feature
user/tests/behat/filter_participants.feature
user/tests/privacy_test.php
user/tests/userlib_test.php
version.php
webservice/classes/privacy/provider.php
webservice/tests/privacy_test.php

index 0b6690a..aa0db16 100644 (file)
@@ -263,7 +263,9 @@ script:
         grunt ;
         # Add all files to the git index and then run diff --cached to see all changes.
         # This ensures that we get the status of all files, including new files.
+        # We ignore npm-shrinkwrap.json to make the tasks immune to npm changes.
         git add . ;
+        git reset -- npm-shrinkwrap.json ;
         git diff --cached --exit-code ;
       fi
 
index 5915e24..8e439c5 100644 (file)
@@ -51,7 +51,8 @@ list($options, $unrecognized) = cli_get_params(
         'non-interactive'   => false,
         'allow-unstable'    => false,
         'help'              => false,
-        'lang'              => $lang
+        'lang'              => $lang,
+        'verbose-settings'  => false
     ),
     array(
         'h' => 'help'
@@ -84,6 +85,9 @@ Options:
                       site language if not set. Defaults to 'en' if the lang
                       parameter is invalid or if the language pack is not
                       installed.
+--verbose-settings    Show new settings values. By default only the name of
+                      new core or plugin settings are displayed. This option
+                      outputs the new values as well as the setting name.
 -h, --help            Print out this help
 
 Example:
@@ -184,9 +188,24 @@ upgrade_noncore(true);
 // log in as admin - we need doanything permission when applying defaults
 \core\session\manager::set_user(get_admin());
 
-// apply all default settings, just in case do it twice to fill all defaults
-admin_apply_default_settings(NULL, false);
-admin_apply_default_settings(NULL, false);
+// Apply default settings and output those that have changed.
+cli_heading(get_string('cliupgradedefaultheading', 'admin'));
+$settingsoutput = admin_apply_default_settings(null, false);
+
+foreach ($settingsoutput as $setting => $value) {
+
+    if ($options['verbose-settings']) {
+        $stringvlaues = array(
+                'name' => $setting,
+                'defaultsetting' => var_export($value, true) // Expand objects.
+        );
+        echo get_string('cliupgradedefaultverbose', 'admin', $stringvlaues) . PHP_EOL;
+
+    } else {
+        echo get_string('cliupgradedefault', 'admin', $setting) . PHP_EOL;
+
+    }
+}
 
 // This needs to happen at the end to ensure it occurs after all caches
 // have been purged for the last time.
index fbda3f3..40e66be 100644 (file)
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="7.0.0" level="required">
+      <RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="7.0.0" level="required">
+      <RESTRICT function="restrict_php_version_73" message="unsupportedphpversion73" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
index 9ae73f3..958fc6e 100644 (file)
@@ -28,10 +28,6 @@ require_once($CFG->libdir . '/adminlib.php');
 $action = optional_param('action', '', PARAM_ALPHA);
 $filterpath = optional_param('filterpath', '', PARAM_PLUGIN);
 
-require_login();
-$systemcontext = context_system::instance();
-require_capability('moodle/site:config', $systemcontext);
-
 admin_externalpage_setup('managefilters');
 
 // Clean up bogus filter states first.
index 6924e61..c10034a 100644 (file)
@@ -28,9 +28,6 @@ require_once($CFG->libdir.'/adminlib.php');
 // This is an admin page
 admin_externalpage_setup('managemessageoutputs');
 
-// Require site configuration capability
-require_capability('moodle/site:config', context_system::instance());
-
 // Get the submitted params
 $disable    = optional_param('disable', 0, PARAM_INT);
 $enable     = optional_param('enable', 0, PARAM_INT);
index 88abd77..a122301 100644 (file)
@@ -12,8 +12,6 @@ $page         = optional_param('page', 0, PARAM_INT);
 $perpage      = optional_param('perpage', 30, PARAM_INT);
 $action       = trim(strtolower(optional_param('action', '', PARAM_ALPHA)));
 
-require_login();
-
 admin_externalpage_setup('ssoaccesscontrol');
 
 if (!extension_loaded('openssl')) {
index 03eac9b..479d559 100644 (file)
@@ -34,10 +34,8 @@ $step   = optional_param('step', 'verify', PARAM_ALPHA);
 $hostid = required_param('hostid', PARAM_INT);
 
 
-require_login();
 
 $context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
 
 $mnet = get_mnet_environment();
 
index 879caa8..fff9386 100644 (file)
@@ -6,12 +6,10 @@
     require_once($CFG->libdir.'/adminlib.php');
     include_once($CFG->dirroot.'/mnet/lib.php');
 
-    require_login();
     admin_externalpage_setup('net');
 
     $context = context_system::instance();
 
-    require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
 
     $site = get_site();
     $mnet = get_mnet_environment();
index 559d2f4..49fd346 100644 (file)
@@ -32,10 +32,6 @@ require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->dirroot.'/mnet/lib.php');
 require_once($CFG->dirroot.'/'.$CFG->admin.'/mnet/peer_forms.php');
 
-require_login();
-
-$context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, 'nopermissions');
 
 /// Initialize variables.
 $hostid = optional_param('hostid', 0, PARAM_INT);
index a27f162..88e9004 100644 (file)
@@ -29,14 +29,10 @@ require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->dirroot . '/' . $CFG->admin .'/mnet/profilefields_form.php');
 $mnet = get_mnet_environment();
 
-require_login();
 $hostid = required_param('hostid', PARAM_INT);
 $mnet_peer = new mnet_peer();
 $mnet_peer->set_id($hostid);
 
-$context = context_system::instance();
-
-require_capability('moodle/site:config', $context, $USER->id, true, 'nopermissions');
 admin_externalpage_setup('mnetpeers');
 $form = new mnet_profile_form(null, array('hostid' => $hostid));
 
index 79e46e9..d5048eb 100644 (file)
@@ -30,11 +30,8 @@ require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->dirroot . '/' . $CFG->admin . '/mnet/services_form.php');
 $mnet = get_mnet_environment();
 
-require_login();
 admin_externalpage_setup('mnetpeers');
 
-$context = context_system::instance();
-require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
 
 $hostid = required_param('hostid', PARAM_INT);
 
index 2560e84..a12ebb6 100644 (file)
@@ -21,12 +21,8 @@ if ($CFG->mnet_dispatcher_mode === 'off') {
     print_error('mnetdisabled', 'mnet');
 }
 
-require_login();
 admin_externalpage_setup('mnettestclient');
 
-$context = context_system::instance();
-require_capability('moodle/site:config', $context);
-
 error_reporting(DEBUG_ALL);
 
 echo $OUTPUT->header();
index cbbbfa3..5ad348c 100644 (file)
@@ -5,12 +5,8 @@
     require_once($CFG->libdir.'/adminlib.php');
     include_once($CFG->dirroot.'/mnet/lib.php');
 
-    require_login();
     admin_externalpage_setup('trustedhosts');
 
-    $context = context_system::instance();
-
-    require_capability('moodle/site:config', $context, $USER->id, true, "nopermissions");
 
     if (!extension_loaded('openssl')) {
         echo $OUTPUT->header();
index 6a33fc0..66ff3a7 100644 (file)
@@ -35,8 +35,6 @@ if ($action == 'newon') {
 
 admin_externalpage_setup($pagename);
 
-require_capability('moodle/site:config', context_system::instance());
-
 $baseurl    = "$CFG->wwwroot/$CFG->admin/portfolio.php";
 $sesskeyurl = "$CFG->wwwroot/$CFG->admin/portfolio.php?sesskey=" . sesskey();
 $configstr  = get_string('manageportfolios', 'portfolio');
index b13baf0..f9dfc2e 100644 (file)
@@ -31,7 +31,6 @@ require_once($CFG->libdir . '/adminlib.php');
 require_once($CFG->libdir . '/tablelib.php');
 
 // Check permissions.
-require_login();
 $systemcontext = context_system::instance();
 require_capability('moodle/question:config', $systemcontext);
 $canviewreports = has_capability('report/questioninstances:view', $systemcontext);
index 6984da6..0c79987 100644 (file)
@@ -47,7 +47,6 @@ if ($action == 'newon') {
     $visible = false;
 }
 
-require_capability('moodle/site:config', context_system::instance());
 admin_externalpage_setup($pagename);
 
 $sesskeyurl = $CFG->wwwroot.'/'.$CFG->admin.'/repository.php?sesskey=' . sesskey();
index 960d0ff..7020224 100644 (file)
@@ -42,7 +42,6 @@ if ($edit){
 }
 
 admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstance.php'));
-require_capability('moodle/site:config', $context);
 
 $baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey()));
 
index 71e9bcb..88609cf 100644 (file)
@@ -46,7 +46,6 @@ $controller = new $classformode[$mode]();
 
 if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_sesskey()) {
     $controller->process_submission();
-    $syscontext->mark_dirty();
     $event = null;
     // Create event depending on mode.
     switch ($mode) {
index db7e16b..4e597f4 100644 (file)
@@ -123,9 +123,6 @@ abstract class core_role_capability_table_with_risks extends core_role_capabilit
             assign_capability($changedcap, $this->permissions[$changedcap],
                 $this->roleid, $this->context->id, true);
         }
-
-        // Force accessinfo refresh for users visiting this context.
-        $this->context->mark_dirty();
     }
 
     public function display() {
index de3eac1..a298052 100644 (file)
@@ -54,7 +54,6 @@ if ($return === 'manage') {
 
 // Check access permissions.
 $systemcontext = context_system::instance();
-require_login();
 require_capability('moodle/role:manage', $systemcontext);
 admin_externalpage_setup('defineroles', '', array('action' => $action, 'roleid' => $roleid), new moodle_url('/admin/roles/define.php'));
 
index fb390ea..9da57eb 100644 (file)
@@ -48,7 +48,6 @@ $defineurl = $CFG->wwwroot . '/' . $CFG->admin . '/roles/define.php';
 
 // Check access permissions.
 $systemcontext = context_system::instance();
-require_login();
 require_capability('moodle/role:manage', $systemcontext);
 admin_externalpage_setup('defineroles');
 
@@ -85,12 +84,10 @@ switch ($action) {
             die;
         }
         if (!delete_role($roleid)) {
-            // The delete failed, but mark the context dirty in case.
-            $systemcontext->mark_dirty();
+            // The delete failed.
             print_error('cannotdeleterolewithid', 'error', $baseurl, $roleid);
         }
         // Deleted a role sitewide...
-        $systemcontext->mark_dirty();
         redirect($baseurl);
         break;
 
index b42a252..d58124c 100644 (file)
@@ -37,8 +37,8 @@ if ($hassiteconfig) {
             $predictors[$fullclassname] = new lang_string('pluginname', $pluginname);
         }
         $settings->add(new \core_analytics\admin_setting_predictor('analytics/predictionsprocessor',
-            new lang_string('predictionsprocessor', 'analytics'), new lang_string('predictionsprocessor_help', 'analytics'),
-            '\mlbackend_php\processor', $predictors)
+            new lang_string('defaultpredictionsprocessor', 'analytics'), new lang_string('predictionsprocessor_help', 'analytics'),
+            \core_analytics\manager::default_mlbackend(), $predictors)
         );
 
         // Log store.
index db05c1d..57a230e 100644 (file)
@@ -32,9 +32,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
         'customusermenuitems',
         new lang_string('customusermenuitems', 'admin'),
         new lang_string('configcustomusermenuitems', 'admin'),
-        'grades,grades|/grade/report/mygrades.php|grades
-messages,message|/message/index.php|message
-preferences,moodle|/user/preferences.php|preferences',
+        'grades,grades|/grade/report/mygrades.php|t/grades
+messages,message|/message/index.php|t/message
+preferences,moodle|/user/preferences.php|t/preferences',
         PARAM_RAW,
         '50',
         '10'
@@ -218,6 +218,9 @@ preferences,moodle|/user/preferences.php|preferences',
     // coursecontact is the person responsible for course - usually manages enrolments, receives notification, etc.
     $temp = new admin_settingpage('coursecontact', new lang_string('courses'));
     $temp->add(new admin_setting_special_coursecontact());
+    $temp->add(new admin_setting_configcheckbox('coursecontactduplicates',
+            new lang_string('coursecontactduplicates', 'admin'),
+            new lang_string('coursecontactduplicates_desc', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('courselistshortnames',
             new lang_string('courselistshortnames', 'admin'),
             new lang_string('courselistshortnames_desc', 'admin'), 0));
@@ -227,6 +230,10 @@ preferences,moodle|/user/preferences.php|preferences',
             new lang_string('configcourseoverviewfileslimit', 'admin'), 1, PARAM_INT));
     $temp->add(new admin_setting_configtext('courseoverviewfilesext', new lang_string('courseoverviewfilesext'),
             new lang_string('configcourseoverviewfilesext', 'admin'), '.jpg,.gif,.png'));
+    $temp->add(new admin_setting_configtext('coursegraceperiodbefore', new lang_string('coursegraceperiodbefore', 'admin'),
+        new lang_string('configcoursegraceperiodbefore', 'admin'), 0, PARAM_INT));
+    $temp->add(new admin_setting_configtext('coursegraceperiodafter', new lang_string('coursegraceperiodafter', 'admin'),
+        new lang_string('configcoursegraceperiodafter', 'admin'), 0, PARAM_INT));
     $ADMIN->add('appearance', $temp);
 
     $temp = new admin_settingpage('ajax', new lang_string('ajaxuse'));
@@ -252,4 +259,3 @@ preferences,moodle|/user/preferences.php|preferences',
     $ADMIN->add('appearance', $temp);
 
 } // end of speedup
-
index 64ef150..cf9b286 100644 (file)
@@ -43,6 +43,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
             array('moodle/category:manage')
         )
     );
+    $ADMIN->add('courses',
+        new admin_externalpage('addnewcourse', new lang_string('addnewcourse'),
+            new moodle_url('/course/edit.php', array('category' => 0)),
+            array('moodle/category:manage')
+        )
+    );
     $ADMIN->add('courses',
         new admin_externalpage('restorecourse', new lang_string('restorecourse', 'admin'),
             new moodle_url('/backup/restorefile.php', array('contextid' => context_system::instance()->id)),
diff --git a/admin/settings/moodleservices.php b/admin/settings/moodleservices.php
new file mode 100644 (file)
index 0000000..2f34cfd
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file gives information about Moodle Services
+ *
+ * @package    core
+ * @copyright  2018 Amaia Anabitarte <amaia@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+
+    // Create Moodle Services information.
+    $moodleservices->add(new admin_setting_heading('moodleservicesintro', '',
+        new lang_string('moodleservices_help', 'admin')));
+
+    // Moodle Partners information.
+    if (empty($CFG->disableserviceads_partner)) {
+        $moodleservices->add(new admin_setting_heading('moodlepartners',
+            new lang_string('moodlepartners', 'admin'),
+            new lang_string('moodlepartners_help', 'admin')));
+    }
+
+    // Moodle app information.
+    $moodleservices->add(new admin_setting_heading('moodleapp',
+        new lang_string('moodleapp', 'admin'),
+        new lang_string('moodleapp_help', 'admin')));
+
+    // Branded Moodle app information.
+    if (empty($CFG->disableserviceads_branded)) {
+        $moodleservices->add(new admin_setting_heading('moodlebrandedapp',
+            new lang_string('moodlebrandedapp', 'admin'),
+            new lang_string('moodlebrandedapp_help', 'admin')));
+    }
+}
+
+
index c0d57c3..f4c24c0 100644 (file)
@@ -7,7 +7,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
 // "systempaths" settingpage
 $temp = new admin_settingpage('systempaths', new lang_string('systempaths','admin'));
-
+$temp->add(new admin_setting_configexecutable('pathtophp', new lang_string('pathtophp', 'admin'),
+    new lang_string('configpathtophp', 'admin'), ''));
 $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('pathtodu', 'admin'), new lang_string('configpathtodu', 'admin'), ''));
 $temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
 $temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
index 17621cb..102b758 100644 (file)
@@ -15,6 +15,11 @@ $ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_str
  // hidden upgrade script
 $ADMIN->add('root', new admin_externalpage('upgradesettings', new lang_string('upgradesettings', 'admin'), "$CFG->wwwroot/$CFG->admin/upgradesettings.php", 'moodle/site:config', true));
 
+// Adding Moodle Services information page.
+$moodleservices = new admin_settingpage('moodleservices', new lang_string('moodleservices',
+    'admin'));
+$ADMIN->add('root', $moodleservices);
+
 if ($hassiteconfig) {
     $optionalsubsystems = new admin_settingpage('optionalsubsystems', new lang_string('advancedfeatures', 'admin'));
     $ADMIN->add('root', $optionalsubsystems);
diff --git a/admin/templates/setting_description.mustache b/admin/templates/setting_description.mustache
new file mode 100644 (file)
index 0000000..c6d0e57
--- /dev/null
@@ -0,0 +1,45 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core_admin/setting_description
+
+    Admin setting description template.
+
+    Context variables required for this template:
+    * labelfor - id of the form element
+    * title - Setting title
+    * name - Setting name
+
+    Example context (json):
+    {
+        "title": "Setting title",
+        "name": "Name",
+        "description": "Description goes here"
+    }
+}}
+{{!
+    Setting description.
+}}
+<div class="form-item form-horizontal clearfix">
+    <div class="form-label">
+        <label>
+            {{{title}}}
+        </label>
+        <span class="form-shortname ">{{{name}}}</span>
+    </div>
+    <div class="controls felement fstatic">{{{description}}}</div>
+</div>
\ No newline at end of file
index 78b6205..df82b0f 100644 (file)
@@ -22,7 +22,7 @@
     Context variables required for this template:
     * actionurl - Url to post to
     * hasresults - True if there are results
-    * results - List of results containing url, title, settings (array of raw html)
+    * results - List of results containing url, title, path (array of strings), settings (array of raw html)
     * showsave - Show save buttons
 
     Example context (json):
@@ -30,7 +30,7 @@
         "actionurl": "/",
         "hasresults": true,
         "results": [
-            { "url": "/", "title": "Match!", "settings": [ "blah blah blah" ] }
+            { "url": "/", "title": "Match!", "path": ["Administration", "Match!"], "settings": [ "blah blah blah" ] }
         ]
     }
 }}
     </div>
     <fieldset>
         <div class="clearer"></div>
+        <h2 class="main">{{#str}}searchresults, admin{{/str}}</h2>
         {{#hasresults}}
             {{#results}}
-                <h2 class="main">{{#str}}searchresults, admin{{/str}} - <a href="{{url}}">{{{title}}}</a></h2>
+                <h3 class="adminpagetitle"><a href="{{url}}">{{{title}}}</a></h3>
+                <ul class="adminpagepath" aria-label="{{#str}} pagepath, core {{/str}}">
+                    {{#path}}
+                    <li>{{.}}</li>
+                    {{/path}}
+                </ul>
                 <fieldset class="adminsettings">
                     {{#settings}}
                         <div class="clearer"></div>
index 66270b2..91fb876 100644 (file)
@@ -72,6 +72,22 @@ class edit_model extends \moodleform {
         $mform->addElement('select', 'timesplitting', get_string('timesplittingmethod', 'analytics'), $timesplittings);
         $mform->addHelpButton('timesplitting', 'timesplittingmethod', 'analytics');
 
+        $defaultprocessor = \core_analytics\manager::get_predictions_processor_name(
+            \core_analytics\manager::get_predictions_processor()
+        );
+        $predictionprocessors = ['' => get_string('defaultpredictoroption', 'analytics', $defaultprocessor)];
+        foreach ($this->_customdata['predictionprocessors'] as $classname => $predictionsprocessor) {
+            if ($predictionsprocessor->is_ready() !== true) {
+                continue;
+            }
+            $optionname = \tool_analytics\output\helper::class_to_option($classname);
+            $predictionprocessors[$optionname] = \core_analytics\manager::get_predictions_processor_name($predictionsprocessor);
+        }
+
+        $mform->addElement('select', 'predictionsprocessor', get_string('predictionsprocessor', 'analytics'),
+            $predictionprocessors);
+        $mform->addHelpButton('predictionsprocessor', 'predictionsprocessor', 'analytics');
+
         $mform->addElement('hidden', 'id', $this->_customdata['id']);
         $mform->setType('id', PARAM_INT);
 
index 58f1129..b70fc24 100644 (file)
@@ -110,7 +110,8 @@ switch ($action) {
             'id' => $model->get_id(),
             'model' => $model,
             'indicators' => $model->get_potential_indicators(),
-            'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods()
+            'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods(),
+            'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors()
         );
         $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
 
@@ -126,7 +127,8 @@ switch ($action) {
                 $indicators[] = \core_analytics\manager::get_indicator($indicatorclass);
             }
             $timesplitting = \tool_analytics\output\helper::option_to_class($data->timesplitting);
-            $model->update($data->enabled, $indicators, $timesplitting);
+            $predictionsprocessor = \tool_analytics\output\helper::option_to_class($data->predictionsprocessor);
+            $model->update($data->enabled, $indicators, $timesplitting, $predictionsprocessor);
             redirect(new \moodle_url('/admin/tool/analytics/index.php'));
         }
 
@@ -137,6 +139,7 @@ switch ($action) {
         $callable = array('\tool_analytics\output\helper', 'class_to_option');
         $modelobj->indicators = array_map($callable, json_decode($modelobj->indicators));
         $modelobj->timesplitting = \tool_analytics\output\helper::class_to_option($modelobj->timesplitting);
+        $modelobj->predictionsprocessor = \tool_analytics\output\helper::class_to_option($modelobj->predictionsprocessor);
         $mform->set_data($modelobj);
         $mform->display();
         break;
index 7e5f350..e73a34c 100644 (file)
@@ -244,14 +244,6 @@ class api {
             if (self::is_site_dpo($requestinguser)) {
                 // The user making the request is a DPO. Should be fine.
                 $datarequest->set('dpo', $requestinguser);
-            } else {
-                // If not a DPO, only users with the capability to make data requests for the user should be allowed.
-                // (e.g. users with the Parent role, etc).
-                if (!self::can_create_data_request_for_user($foruser)) {
-                    $forusercontext = \context_user::instance($foruser);
-                    throw new required_capability_exception($forusercontext,
-                            'tool/dataprivacy:makedatarequestsforchildren', 'nopermissions', '');
-                }
             }
         }
         // The user making the request.
@@ -667,16 +659,31 @@ class api {
     /**
      * Checks whether a non-DPO user can make a data request for another user.
      *
-     * @param int $user The user ID of the target user.
-     * @param int $requester The user ID of the user making the request.
-     * @return bool
-     * @throws coding_exception
+     * @param   int     $user The user ID of the target user.
+     * @param   int     $requester The user ID of the user making the request.
+     * @return  bool
      */
     public static function can_create_data_request_for_user($user, $requester = null) {
         $usercontext = \context_user::instance($user);
+
         return has_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
     }
 
+    /**
+     * Require that the current user can make a data request for the specified other user.
+     *
+     * @param   int     $user The user ID of the target user.
+     * @param   int     $requester The user ID of the user making the request.
+     * @return  bool
+     */
+    public static function require_can_create_data_request_for_user($user, $requester = null) {
+        $usercontext = \context_user::instance($user);
+
+        require_capability('tool/dataprivacy:makedatarequestsforchildren', $usercontext, $requester);
+
+        return true;
+    }
+
     /**
      * Checks whether a user can download a data request.
      *
@@ -732,8 +739,6 @@ class api {
      * @return \tool_dataprivacy\purpose.
      */
     public static function create_purpose(stdClass $record) {
-        self::check_can_manage_data_registry();
-
         $purpose = new purpose(0, $record);
         $purpose->create();
 
@@ -747,8 +752,6 @@ class api {
      * @return \tool_dataprivacy\purpose.
      */
     public static function update_purpose(stdClass $record) {
-        self::check_can_manage_data_registry();
-
         if (!isset($record->sensitivedatareasons)) {
             $record->sensitivedatareasons = '';
         }
@@ -768,8 +771,6 @@ class api {
      * @return bool
      */
     public static function delete_purpose($id) {
-        self::check_can_manage_data_registry();
-
         $purpose = new purpose($id);
         if ($purpose->is_used()) {
             throw new \moodle_exception('Purpose with id ' . $id . ' can not be deleted because it is used.');
@@ -783,8 +784,6 @@ class api {
      * @return \tool_dataprivacy\purpose[]
      */
     public static function get_purposes() {
-        self::check_can_manage_data_registry();
-
         return purpose::get_records([], 'name', 'ASC');
     }
 
@@ -795,8 +794,6 @@ class api {
      * @return \tool_dataprivacy\category.
      */
     public static function create_category(stdClass $record) {
-        self::check_can_manage_data_registry();
-
         $category = new category(0, $record);
         $category->create();
 
@@ -810,8 +807,6 @@ class api {
      * @return \tool_dataprivacy\category.
      */
     public static function update_category(stdClass $record) {
-        self::check_can_manage_data_registry();
-
         $category = new category($record->id);
         $category->from_record($record);
 
@@ -827,8 +822,6 @@ class api {
      * @return bool
      */
     public static function delete_category($id) {
-        self::check_can_manage_data_registry();
-
         $category = new category($id);
         if ($category->is_used()) {
             throw new \moodle_exception('Category with id ' . $id . ' can not be deleted because it is used.');
@@ -842,8 +835,6 @@ class api {
      * @return \tool_dataprivacy\category[]
      */
     public static function get_categories() {
-        self::check_can_manage_data_registry();
-
         return category::get_records([], 'name', 'ASC');
     }
 
@@ -854,8 +845,6 @@ class api {
      * @return \tool_dataprivacy\context_instance
      */
     public static function set_context_instance($record) {
-        self::check_can_manage_data_registry($record->contextid);
-
         if ($instance = context_instance::get_record_by_contextid($record->contextid, false)) {
             // Update.
             $instance->from_record($record);
@@ -882,7 +871,6 @@ class api {
      * @return null
      */
     public static function unset_context_instance(context_instance $instance) {
-        self::check_can_manage_data_registry($instance->get('contextid'));
         $instance->delete();
     }
 
@@ -896,9 +884,6 @@ class api {
     public static function set_contextlevel($record) {
         global $DB;
 
-        // Only manager at system level can set this.
-        self::check_can_manage_data_registry();
-
         if ($record->contextlevel != CONTEXT_SYSTEM && $record->contextlevel != CONTEXT_USER) {
             throw new \coding_exception('Only context system and context user can set a contextlevel ' .
                 'purpose and retention');
@@ -929,8 +914,7 @@ class api {
      * @param int $forcedvalue Use this categoryid value as if this was this context instance category.
      * @return category|false
      */
-    public static function get_effective_context_category(\context $context, $forcedvalue=false) {
-        self::check_can_manage_data_registry($context->id);
+    public static function get_effective_context_category(\context $context, $forcedvalue = false) {
         if (!data_registry::defaults_set()) {
             return false;
         }
@@ -945,8 +929,7 @@ class api {
      * @param int $forcedvalue Use this purposeid value as if this was this context instance purpose.
      * @return purpose|false
      */
-    public static function get_effective_context_purpose(\context $context, $forcedvalue=false) {
-        self::check_can_manage_data_registry($context->id);
+    public static function get_effective_context_purpose(\context $context, $forcedvalue = false) {
         if (!data_registry::defaults_set()) {
             return false;
         }
@@ -958,16 +941,14 @@ class api {
      * Returns the effective category given a context level.
      *
      * @param int $contextlevel
-     * @param int $forcedvalue Use this categoryid value as if this was this context level category.
      * @return category|false
      */
-    public static function get_effective_contextlevel_category($contextlevel, $forcedvalue=false) {
-        self::check_can_manage_data_registry(\context_system::instance()->id);
+    public static function get_effective_contextlevel_category($contextlevel) {
         if (!data_registry::defaults_set()) {
             return false;
         }
 
-        return data_registry::get_effective_contextlevel_value($contextlevel, 'category', $forcedvalue);
+        return data_registry::get_effective_contextlevel_value($contextlevel, 'category');
     }
 
     /**
@@ -978,7 +959,6 @@ class api {
      * @return purpose|false
      */
     public static function get_effective_contextlevel_purpose($contextlevel, $forcedvalue=false) {
-        self::check_can_manage_data_registry(\context_system::instance()->id);
         if (!data_registry::defaults_set()) {
             return false;
         }
@@ -986,38 +966,6 @@ class api {
         return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
     }
 
-    /**
-     * Creates an expired context record for the provided context id.
-     *
-     * @param int $contextid
-     * @return \tool_dataprivacy\expired_context
-     */
-    public static function create_expired_context($contextid) {
-        self::check_can_manage_data_registry();
-
-        $record = (object)[
-            'contextid' => $contextid,
-            'status' => expired_context::STATUS_EXPIRED,
-        ];
-        $expiredctx = new expired_context(0, $record);
-        $expiredctx->save();
-
-        return $expiredctx;
-    }
-
-    /**
-     * Deletes an expired context record.
-     *
-     * @param int $id The tool_dataprivacy_ctxexpire id.
-     * @return bool True on success.
-     */
-    public static function delete_expired_context($id) {
-        self::check_can_manage_data_registry();
-
-        $expiredcontext = new expired_context($id);
-        return $expiredcontext->delete();
-    }
-
     /**
      * Updates the status of an expired context.
      *
@@ -1026,8 +974,6 @@ class api {
      * @return null
      */
     public static function set_expired_context_status(expired_context $expiredctx, $status) {
-        self::check_can_manage_data_registry();
-
         $expiredctx->set('status', $status);
         $expiredctx->save();
     }
@@ -1041,6 +987,7 @@ class api {
      */
     public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) {
         $request = new data_request($requestid);
+        $user = \core_user::get_user($request->get('userid'));
         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);
@@ -1051,10 +998,14 @@ class api {
             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'))) {
+                    $purpose = static::get_effective_context_purpose($context);
+
+                    // Data can only be deleted from it if the context is either expired, or unprotected.
+                    if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $user)) {
                         continue;
                     }
                 }
+
                 $context = new contextlist_context();
                 $context->set('contextid', $contextid)
                     ->set('contextlistid', $contextlistid)
@@ -1152,6 +1103,15 @@ class api {
                 $contexts = [];
             }
 
+            if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) {
+                $context = \context::instance_by_id($record->contextid);
+                $purpose = static::get_effective_context_purpose($context);
+                // Data can only be deleted from it if the context is either expired, or unprotected.
+                if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $foruser)) {
+                    continue;
+                }
+            }
+
             $contexts[] = $record->contextid;
             $lastcomponent = $record->component;
         }
@@ -1178,8 +1138,6 @@ class api {
     public static function set_context_defaults($contextlevel, $categoryid, $purposeid, $activity = null, $override = false) {
         global $DB;
 
-        self::check_can_manage_data_registry();
-
         // Get the class name associated with this context level.
         $classname = context_helper::get_class_for_level($contextlevel);
         list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname, $activity);
@@ -1251,4 +1209,25 @@ class api {
 
         return true;
     }
+
+    /**
+     * Format the supplied date interval as a retention period.
+     *
+     * @param   \DateInterval   $interval
+     * @return  string
+     */
+    public static function format_retention_period(\DateInterval $interval) : string {
+        // It is one or another.
+        if ($interval->y) {
+            $formattedtime = get_string('numyears', 'moodle', $interval->format('%y'));
+        } else if ($interval->m) {
+            $formattedtime = get_string('nummonths', 'moodle', $interval->format('%m'));
+        } else if ($interval->d) {
+            $formattedtime = get_string('numdays', 'moodle', $interval->format('%d'));
+        } else {
+            $formattedtime = get_string('retentionperiodzero', 'tool_dataprivacy');
+        }
+
+        return $formattedtime;
+    }
 }
index 7b46b4d..50681ba 100644 (file)
@@ -39,18 +39,6 @@ defined('MOODLE_INTERNAL') || die();
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class data_registry {
-
-    /**
-     * @var array Inheritance between context levels.
-     */
-    private static $contextlevelinheritance = [
-        CONTEXT_USER => [CONTEXT_SYSTEM],
-        CONTEXT_COURSECAT => [CONTEXT_SYSTEM],
-        CONTEXT_COURSE => [CONTEXT_COURSECAT, CONTEXT_SYSTEM],
-        CONTEXT_MODULE => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
-        CONTEXT_BLOCK => [CONTEXT_COURSE, CONTEXT_COURSECAT, CONTEXT_SYSTEM],
-    ];
-
     /**
      * Returns purpose and category var names from a context class name
      *
@@ -83,7 +71,6 @@ class data_registry {
      * @return int[]|false[]
      */
     public static function get_defaults($contextlevel, $pluginname = '') {
-
         $classname = \context_helper::get_class_for_level($contextlevel);
         list($purposevar, $categoryvar) = self::var_names_from_context($classname, $pluginname);
 
@@ -104,10 +91,10 @@ class data_registry {
         }
 
         if (empty($purposeid)) {
-            $purposeid = false;
+            $purposeid = context_instance::NOTSET;
         }
         if (empty($categoryid)) {
-            $categoryid = false;
+            $categoryid = context_instance::NOTSET;
         }
 
         return [$purposeid, $categoryid];
@@ -189,61 +176,93 @@ class data_registry {
      * @param int|false $forcedvalue Use this value as if this was this context instance value.
      * @return persistent|false It return a 'purpose' instance or a 'category' instance, depending on $element
      */
-    public static function get_effective_context_value(\context $context, $element, $forcedvalue=false) {
+    public static function get_effective_context_value(\context $context, $element, $forcedvalue = false) {
+        global $DB;
 
         if ($element !== 'purpose' && $element !== 'category') {
             throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
         }
         $fieldname = $element . 'id';
 
-        if ($forcedvalue === false) {
-            $instance = context_instance::get_record_by_contextid($context->id, false);
+        if (!empty($forcedvalue) && ($forcedvalue === context_instance::INHERIT)) {
+            // Do not include the current context when calculating the value.
+            // This has the effect that an inheritted value is calculated.
+            $parentcontextids = $context->get_parent_context_ids(false);
+        } else if (!empty($forcedvalue) && ($forcedvalue !== context_instance::NOTSET)) {
+            return self::get_element_instance($element, $forcedvalue);
+        } else {
+            // Fetch all parent contexts, including self.
+            $parentcontextids = $context->get_parent_context_ids(true);
+        }
+        list($insql, $inparams) = $DB->get_in_or_equal($parentcontextids, SQL_PARAMS_NAMED);
+        $inparams['contextmodule'] = CONTEXT_MODULE;
 
-            if (!$instance) {
-                // If the instance does not have a value defaults to not set, so we grab the context level default as its value.
-                $instancevalue = context_instance::NOTSET;
-            } else {
-                $instancevalue = $instance->get($fieldname);
-            }
+        if ('purpose' === $element) {
+             $elementjoin = 'LEFT JOIN {tool_dataprivacy_purpose} ele ON ctxins.purposeid = ele.id';
+             $elementfields = purpose::get_sql_fields('ele', 'ele');
         } else {
-            $instancevalue = $forcedvalue;
+             $elementjoin = 'LEFT JOIN {tool_dataprivacy_category} ele ON ctxins.categoryid = ele.id';
+             $elementfields = category::get_sql_fields('ele', 'ele');
+        }
+        $contextfields = \context_helper::get_preload_record_columns_sql('ctx');
+        $fields = implode(', ', ['ctx.id', 'm.name AS modname', $contextfields, $elementfields]);
+
+        $sql = "SELECT $fields
+                  FROM {context} ctx
+             LEFT JOIN {tool_dataprivacy_ctxinstance} ctxins ON ctx.id = ctxins.contextid
+             LEFT JOIN {course_modules} cm ON ctx.contextlevel = :contextmodule AND ctx.instanceid = cm.id
+             LEFT JOIN {modules} m ON m.id = cm.module
+             {$elementjoin}
+                 WHERE ctx.id {$insql}
+              ORDER BY ctx.path DESC";
+        $contextinstances = $DB->get_records_sql($sql, $inparams);
+
+        // Check whether this context is a user context, or a child of a user context.
+        // All children of a User context share the same context and cannot be set individually.
+        foreach ($contextinstances as $record) {
+            \context_helper::preload_from_record($record);
+            $parent = \context::instance_by_id($record->id, false);
+
+            if ($parent->contextlevel == CONTEXT_USER) {
+                // Use the context level value for the user.
+                return self::get_effective_contextlevel_value(CONTEXT_USER, $element);
+            }
         }
 
-        // Not set.
-        if ($instancevalue == context_instance::NOTSET) {
+        foreach ($contextinstances as $record) {
+            $parent = \context::instance_by_id($record->id, false);
 
-            // The effective value varies depending on the context level.
-            if ($context->contextlevel == CONTEXT_USER) {
-                // Use the context level value as we don't allow people to set specific instances values.
-                return self::get_effective_contextlevel_value($context->contextlevel, $element);
-            } else {
-                // Check if we need to pass the plugin name of an activity.
-                $forplugin = '';
-                if ($context->contextlevel == CONTEXT_MODULE) {
-                    list($course, $cm) = get_course_and_cm_from_cmid($context->instanceid);
-                    $forplugin = $cm->modname;
-                }
-                // Use the default context level value.
-                list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
-                    $context->contextlevel, false, false, $forplugin
-                );
-                return self::get_element_instance($element, $$fieldname);
+            $checkcontextlevel = false;
+            if (empty($record->eleid)) {
+                $checkcontextlevel = true;
             }
-        }
 
-        // Specific value for this context instance.
-        if ($instancevalue != context_instance::INHERIT) {
-            return self::get_element_instance($element, $instancevalue);
-        }
+            if (!empty($forcedvalue) && context_instance::NOTSET === $forcedvalue) {
+                $checkcontextlevel = true;
+            }
 
-        // This context is using inherited so let's return the parent effective value.
-        $parentcontext = $context->get_parent_context();
-        if (!$parentcontext) {
-            return false;
+            if ($checkcontextlevel) {
+                // Check for a value at the contextlevel
+                $forplugin = empty($record->modname) ? '' : $record->modname;
+                list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category(
+                        $parent->contextlevel, false, false, $forplugin);
+
+                $instancevalue = $$fieldname;
+
+                if (context_instance::NOTSET !== $instancevalue && context_instance::INHERIT !== $instancevalue) {
+                    // There is an actual value. Return it.
+                    return self::get_element_instance($element, $instancevalue);
+                }
+            } else {
+                $elementclass = "\\tool_dataprivacy\\{$element}";
+                $instance = new $elementclass(null, $elementclass::extract_record($record, 'ele'));
+                $instance->validate();
+
+                return $instance;
+            }
         }
 
-        // The forced value should not be transmitted to parent contexts.
-        return self::get_effective_context_value($parentcontext, $element);
+        throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
     }
 
     /**
@@ -255,11 +274,9 @@ class data_registry {
      *
      * @param int $contextlevel
      * @param string $element 'category' or 'purpose'
-     * @param int $forcedvalue Use this value as if this was this context level purpose.
      * @return \tool_dataprivacy\purpose|false
      */
-    public static function get_effective_contextlevel_value($contextlevel, $element, $forcedvalue = false) {
-
+    public static function get_effective_contextlevel_value($contextlevel, $element) {
         if ($element !== 'purpose' && $element !== 'category') {
             throw new coding_exception('Only \'purpose\' and \'category\' are supported.');
         }
@@ -270,39 +287,15 @@ class data_registry {
                 'have a purpose or a category.');
         }
 
-        if ($forcedvalue === false) {
-            $instance = contextlevel::get_record_by_contextlevel($contextlevel, false);
-            if (!$instance) {
-                // If the context level does not have a value defaults to not set, so we grab the context level default as
-                // its value.
-                $instancevalue = context_instance::NOTSET;
-            } else {
-                $instancevalue = $instance->get($fieldname);
-            }
-        } else {
-            $instancevalue = $forcedvalue;
-        }
+        list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
 
-        // Not set -> Use the default context level value.
-        if ($instancevalue == context_instance::NOTSET) {
-            list($purposeid, $categoryid) = self::get_effective_default_contextlevel_purpose_and_category($contextlevel);
+        // Note: The $$fieldname points to either $purposeid, or $categoryid.
+        if (context_instance::NOTSET !== $$fieldname && context_instance::INHERIT !== $$fieldname) {
+            // There is a specific value set.
             return self::get_element_instance($element, $$fieldname);
         }
 
-        // Specific value for this context instance.
-        if ($instancevalue != context_instance::INHERIT) {
-            return self::get_element_instance($element, $instancevalue);
-        }
-
-        if ($contextlevel == CONTEXT_SYSTEM) {
-            throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
-        }
-
-        // If we reach this point is that we are inheriting so get the parent context level and repeat.
-        $parentcontextlevel = reset(self::$contextlevelinheritance[$contextlevel]);
-
-        // Forced value are intentionally not passed as the force value should only affect the immediate context level.
-        return self::get_effective_contextlevel_value($parentcontextlevel, $element);
+        throw new coding_exception('Something went wrong, system defaults should be set and we should already have a value.');
     }
 
     /**
@@ -311,13 +304,13 @@ class data_registry {
      * @param int $contextlevel
      * @param int|bool $forcedpurposevalue Use this value as if this was this context level purpose.
      * @param int|bool $forcedcategoryvalue Use this value as if this was this context level category.
-     * @param string $activity The plugin name of the activity.
+     * @param string $component The name of the component to check.
      * @return int[]
      */
     public static function get_effective_default_contextlevel_purpose_and_category($contextlevel, $forcedpurposevalue = false,
-                                                                                   $forcedcategoryvalue = false, $activity = '') {
-
-        list($purposeid, $categoryid) = self::get_defaults($contextlevel, $activity);
+                                                                                   $forcedcategoryvalue = false, $component = '') {
+        // Get the defaults for this context level.
+        list($purposeid, $categoryid) = self::get_defaults($contextlevel, $component);
 
         // Honour forced values.
         if ($forcedpurposevalue) {
@@ -327,37 +320,19 @@ class data_registry {
             $categoryid = $forcedcategoryvalue;
         }
 
-        // Not set == INHERIT for defaults.
-        if ($purposeid == context_instance::INHERIT || $purposeid == context_instance::NOTSET) {
-            $purposeid = false;
-        }
-        if ($categoryid == context_instance::INHERIT || $categoryid == context_instance::NOTSET) {
-            $categoryid = false;
-        }
-
-        if ($contextlevel != CONTEXT_SYSTEM && ($purposeid === false || $categoryid === false)) {
-            foreach (self::$contextlevelinheritance[$contextlevel] as $parent) {
+        if ($contextlevel == CONTEXT_USER) {
+            // Only user context levels inherit from a parent context level.
+            list($parentpurposeid, $parentcategoryid) = self::get_defaults(CONTEXT_SYSTEM);
 
-                list($parentpurposeid, $parentcategoryid) = self::get_defaults($parent);
-                // Not set == INHERIT for defaults.
-                if ($parentpurposeid == context_instance::INHERIT || $parentpurposeid == context_instance::NOTSET) {
-                    $parentpurposeid = false;
-                }
-                if ($parentcategoryid == context_instance::INHERIT || $parentcategoryid == context_instance::NOTSET) {
-                    $parentcategoryid = false;
-                }
-
-                if ($purposeid === false && $parentpurposeid) {
-                    $purposeid = $parentpurposeid;
-                }
+            if (context_instance::INHERIT == $purposeid || context_instance::NOTSET == $purposeid) {
+                $purposeid = $parentpurposeid;
+            }
 
-                if ($categoryid === false && $parentcategoryid) {
-                    $categoryid = $parentcategoryid;
-                }
+            if (context_instance::INHERIT == $categoryid || context_instance::NOTSET == $categoryid) {
+                $categoryid = $parentcategoryid;
             }
         }
 
-        // They may still be false, but we return anyway.
         return [$purposeid, $categoryid];
     }
 
@@ -370,7 +345,6 @@ class data_registry {
      * @return \core\persistent
      */
     private static function get_element_instance($element, $id) {
-
         if ($element !== 'purpose' && $element !== 'category') {
             throw new coding_exception('No other elements than purpose and category are allowed');
         }
index 3010438..26143b5 100644 (file)
@@ -60,12 +60,27 @@ class expired_context extends \core\persistent {
      * @return array
      */
     protected static function define_properties() {
-        return array(
-            'contextid' => array(
+        return [
+            'contextid' => [
                 'type' => PARAM_INT,
                 'description' => 'The context id.',
-            ),
-            'status' => array(
+            ],
+            'defaultexpired' => [
+                'type' => PARAM_INT,
+                'description' => 'Whether to default retention period for the purpose has been reached',
+                'default' => 1,
+            ],
+            'expiredroles' => [
+                'type' => PARAM_TEXT,
+                'description' => 'This list of roles to include during deletion',
+                'default'  => '',
+            ],
+            'unexpiredroles' => [
+                'type' => PARAM_TEXT,
+                'description' => 'This list of roles to exclude during deletion',
+                'default'  => '',
+            ],
+            'status' => [
                 'choices' => [
                     self::STATUS_EXPIRED,
                     self::STATUS_APPROVED,
@@ -73,8 +88,8 @@ class expired_context extends \core\persistent {
                 ],
                 'type' => PARAM_INT,
                 'description' => 'The deletion status of the context.',
-            ),
-        );
+            ],
+        ];
     }
 
     /**
@@ -159,4 +174,205 @@ class expired_context extends \core\persistent {
 
         return $DB->count_records_sql($sql, $params);
     }
+
+    /**
+     * Set the list of role IDs for either expiredroles, or unexpiredroles.
+     *
+     * @param   string  $field
+     * @param   int[]   $roleids
+     * @return  expired_context
+     */
+    protected function set_roleids_for(string $field, array $roleids) : expired_context {
+        $roledata = json_encode($roleids);
+
+        $this->raw_set($field, $roledata);
+
+        return $this;
+    }
+
+    /**
+     * Get the list of role IDs for either expiredroles, or unexpiredroles.
+     *
+     * @param   string  $field
+     * @return  int[]
+     */
+    protected function get_roleids_for(string $field) {
+        $value = $this->raw_get($field);
+        if (empty($value)) {
+            return [];
+        }
+
+        return json_decode($value);
+    }
+
+    /**
+     * Set the list of unexpired role IDs.
+     *
+     * @param   int[]   $roleids
+     * @return  expired_context
+     */
+    protected function set_unexpiredroles(array $roleids) : expired_context {
+        $this->set_roleids_for('unexpiredroles', $roleids);
+
+        return $this;
+    }
+
+    /**
+     * Add a set of role IDs to the list of expired role IDs.
+     *
+     * @param   int[]   $roleids
+     * @return  expired_context
+     */
+    public function add_expiredroles(array $roleids) : expired_context {
+        $existing = $this->get('expiredroles');
+        $newvalue = array_merge($existing, $roleids);
+
+        $this->set('expiredroles', $newvalue);
+
+        return $this;
+    }
+
+    /**
+     * Add a set of role IDs to the list of unexpired role IDs.
+     *
+     * @param   int[]   $roleids
+     * @return  unexpired_context
+     */
+    public function add_unexpiredroles(array $roleids) : expired_context {
+        $existing = $this->get('unexpiredroles');
+        $newvalue = array_merge($existing, $roleids);
+
+        $this->set('unexpiredroles', $newvalue);
+
+        return $this;
+    }
+
+    /**
+     * Set the list of expired role IDs.
+     *
+     * @param   int[]   $roleids
+     * @return  expired_context
+     */
+    protected function set_expiredroles(array $roleids) : expired_context {
+        $this->set_roleids_for('expiredroles', $roleids);
+
+        return $this;
+    }
+
+    /**
+     * Get the list of expired role IDs.
+     *
+     * @return  int[]
+     */
+    protected function get_expiredroles() {
+        return $this->get_roleids_for('expiredroles');
+    }
+
+    /**
+     * Get the list of unexpired role IDs.
+     *
+     * @return  int[]
+     */
+    protected function get_unexpiredroles() {
+        return $this->get_roleids_for('unexpiredroles');
+    }
+
+    /**
+     * Create a new expired_context based on the context, and expiry_info object.
+     *
+     * @param   \context        $context
+     * @param   expiry_info     $info
+     * @param   boolean         $save
+     * @return  expired_context
+     */
+    public static function create_from_expiry_info(\context $context, expiry_info $info, bool $save = true) : expired_context {
+        $record = (object) [
+            'contextid' => $context->id,
+            'status' => self::STATUS_EXPIRED,
+            'defaultexpired' => (int) $info->is_default_expired(),
+        ];
+
+        $expiredcontext = new static(0, $record);
+        $expiredcontext->set('expiredroles', $info->get_expired_roles());
+        $expiredcontext->set('unexpiredroles', $info->get_unexpired_roles());
+
+        if ($save) {
+            $expiredcontext->save();
+        }
+
+        return $expiredcontext;
+    }
+
+    /**
+     * Update the expired_context from an expiry_info object which relates to this context.
+     *
+     * @param   expiry_info     $info
+     * @return  $this
+     */
+    public function update_from_expiry_info(expiry_info $info) : expired_context {
+        $save = false;
+
+        // Compare the expiredroles.
+        $thisexpired = $this->get('expiredroles');
+        $infoexpired = $info->get_expired_roles();
+
+        sort($thisexpired);
+        sort($infoexpired);
+        if ($infoexpired != $thisexpired) {
+            $this->set('expiredroles', $infoexpired);
+            $save = true;
+        }
+
+        // Compare the unexpiredroles.
+        $thisunexpired = $this->get('unexpiredroles');
+        $infounexpired = $info->get_unexpired_roles();
+
+        sort($thisunexpired);
+        sort($infounexpired);
+        if ($infounexpired != $thisunexpired) {
+            $this->set('unexpiredroles', $infounexpired);
+            $save = true;
+        }
+
+        if (empty($this->get('defaultexpired')) == $info->is_default_expired()) {
+            $this->set('defaultexpired', (int) $info->is_default_expired());
+            $save = true;
+        }
+
+        if ($save) {
+            $this->set('status', self::STATUS_EXPIRED);
+            $this->save();
+        }
+
+        return $this;
+
+    }
+
+    /**
+     * Check whether this expired_context record is in a state ready for deletion to actually take place.
+     *
+     * @return  bool
+     */
+    public function can_process_deletion() : bool {
+        return ($this->get('status') == self::STATUS_APPROVED);
+    }
+
+    /**
+     * Check whether this expired_context record has already been cleaned.
+     *
+     * @return  bool
+     */
+    public function is_complete() : bool {
+        return ($this->get('status') == self::STATUS_CLEANED);
+    }
+
+    /**
+     * Whether this context has 'fully' expired.
+     * That is to say that the default retention period has been reached, and that there are no unexpired roles.
+     *
+     * @return  bool
+     */
+    public function is_fully_expired() : bool {
+        return $this->get('defaultexpired') && empty($this->get('unexpiredroles'));
+    }
 }
index 539fc28..c2dc169 100644 (file)
@@ -34,121 +34,965 @@ defined('MOODLE_INTERNAL') || die();
  * @copyright  2018 David Monllao
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class expired_contexts_manager {
+class expired_contexts_manager {
 
     /**
      * Number of deleted contexts for each scheduled task run.
      */
     const DELETE_LIMIT = 200;
 
+    /** @var progress_trace The log progress tracer */
+    protected $progresstracer = null;
+
+    /** @var manager The privacy manager */
+    protected $manager = null;
+
+    /** @var \progress_trace Trace tool for logging */
+    protected $trace = null;
+
     /**
-     * Returns the list of expired context instances.
+     * Constructor for the expired_contexts_manager.
      *
-     * @return \stdClass[]
+     * @param   \progress_trace $trace
      */
-    abstract protected function get_expired_contexts();
+    public function __construct(\progress_trace $trace = null) {
+        if (null === $trace) {
+            $trace = new \null_progress_trace();
+        }
+
+        $this->trace = $trace;
+    }
 
     /**
-     * Specify with context levels this expired contexts manager is deleting.
+     * Flag expired contexts as expired.
      *
-     * @return int[]
+     * @return  int[]   The number of contexts flagged as expired for courses, and users.
      */
-    abstract protected function get_context_levels();
+    public function flag_expired_contexts() : array {
+        $this->trace->output('Checking requirements');
+        if (!$this->check_requirements()) {
+            $this->trace->output('Requirements not met. Cannot process expired retentions.', 1);
+            return [0, 0];
+        }
+
+        // Clear old and stale records first.
+        $this->trace->output('Clearing obselete records.', 0);
+        static::clear_old_records();
+        $this->trace->output('Done.', 1);
+
+        $this->trace->output('Calculating potential course expiries.', 0);
+        $data = static::get_nested_expiry_info_for_courses();
+
+        $coursecount = 0;
+        $this->trace->output('Updating course expiry data.', 0);
+        foreach ($data as $expiryrecord) {
+            if ($this->update_from_expiry_info($expiryrecord)) {
+                $coursecount++;
+            }
+        }
+        $this->trace->output('Done.', 1);
+
+        $this->trace->output('Calculating potential user expiries.', 0);
+        $data = static::get_nested_expiry_info_for_user();
+
+        $usercount = 0;
+        $this->trace->output('Updating user expiry data.', 0);
+        foreach ($data as $expiryrecord) {
+            if ($this->update_from_expiry_info($expiryrecord)) {
+                $usercount++;
+            }
+        }
+        $this->trace->output('Done.', 1);
+
+        return [$coursecount, $usercount];
+    }
 
     /**
-     * Flag expired contexts as expired.
-     *
-     * @return int The number of contexts flagged as expired.
+     * Clear old and stale records.
      */
-    public function flag_expired() {
+    protected static function clear_old_records() {
+        global $DB;
 
-        if (!$this->check_requirements()) {
-            return 0;
+        $sql = "SELECT dpctx.*
+                  FROM {tool_dataprivacy_ctxexpired} dpctx
+             LEFT JOIN {context} ctx ON ctx.id = dpctx.contextid
+                 WHERE ctx.id IS NULL";
+
+        $orphaned = $DB->get_recordset_sql($sql);
+        foreach ($orphaned as $orphan) {
+            $expiredcontext = new expired_context(0, $orphan);
+            $expiredcontext->delete();
         }
 
-        $contexts = $this->get_expired_contexts();
-        foreach ($contexts as $context) {
-            api::create_expired_context($context->id);
+        // Delete any child of a user context.
+        $parentpath = $DB->sql_concat('ctxuser.path', "'/%'");
+        $params = [
+            'contextuser' => CONTEXT_USER,
+        ];
+
+        $sql = "SELECT dpctx.*
+                  FROM {tool_dataprivacy_ctxexpired} dpctx
+                 WHERE dpctx.contextid IN (
+                    SELECT ctx.id
+                        FROM {context} ctxuser
+                        JOIN {context} ctx ON ctx.path LIKE {$parentpath}
+                       WHERE ctxuser.contextlevel = :contextuser
+                    )";
+        $userchildren = $DB->get_recordset_sql($sql, $params);
+        foreach ($userchildren as $child) {
+            $expiredcontext = new expired_context(0, $child);
+            $expiredcontext->delete();
         }
+    }
 
-        return count($contexts);
+    /**
+     * Get the full nested set of expiry data relating to all contexts.
+     *
+     * @param   string      $contextpath A contexpath to restrict results to
+     * @return  \stdClass[]
+     */
+    protected static function get_nested_expiry_info($contextpath = '') : array {
+        $coursepaths = self::get_nested_expiry_info_for_courses($contextpath);
+        $userpaths = self::get_nested_expiry_info_for_user($contextpath);
+
+        return array_merge($coursepaths, $userpaths);
     }
 
     /**
-     * Deletes the expired contexts.
+     * Get the full nested set of expiry data relating to course-related contexts.
      *
-     * @return int The number of deleted contexts.
+     * @param   string      $contextpath A contexpath to restrict results to
+     * @return  \stdClass[]
      */
-    public function delete() {
+    protected static function get_nested_expiry_info_for_courses($contextpath = '') : array {
+        global $DB;
 
-        $numprocessed = 0;
+        $contextfields = \context_helper::get_preload_record_columns_sql('ctx');
+        $expiredfields = expired_context::get_sql_fields('expiredctx', 'expiredctx');
+        $purposefields = 'dpctx.purposeid';
+        $coursefields = 'ctxcourse.expirydate AS expirydate';
+        $fields = implode(', ', ['ctx.id', $contextfields, $expiredfields, $coursefields, $purposefields]);
 
-        if (!$this->check_requirements()) {
-            return $numprocessed;
+        // We want all contexts at course-dependa