Merge branch 'MDL-61928' of git://github.com/timhunt/moodle
authorJun Pataleta <jun@moodle.com>
Wed, 18 Apr 2018 08:00:49 +0000 (16:00 +0800)
committerJun Pataleta <jun@moodle.com>
Wed, 18 Apr 2018 08:00:49 +0000 (16:00 +0800)
908 files changed:
.eslintignore
.stylelintignore
admin/cli/install.php
admin/cli/upgrade.php
admin/settings/appearance.php
admin/settings/security.php
admin/settings/users.php
admin/tool/log/store/database/classes/helper.php
admin/tool/lp/templates/manage_competencies_page.mustache
admin/tool/messageinbound/classes/manager.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/db/services.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/tests/fixtures/output/mobile.php [new file with mode: 0644]
admin/tool/mobile/upgrade.txt
admin/tool/mobile/version.php
admin/tool/monitor/classes/privacy/provider.php [new file with mode: 0644]
admin/tool/monitor/classes/rule.php
admin/tool/monitor/lang/en/tool_monitor.php
admin/tool/monitor/tests/privacy_test.php [new file with mode: 0644]
admin/tool/policy/accept.php [new file with mode: 0644]
admin/tool/policy/acceptances.php [new file with mode: 0644]
admin/tool/policy/amd/build/acceptances_filter.min.js [new file with mode: 0644]
admin/tool/policy/amd/build/acceptances_filter_datasource.min.js [new file with mode: 0644]
admin/tool/policy/amd/build/acceptmodal.min.js [new file with mode: 0644]
admin/tool/policy/amd/build/jquery-eu-cookie-law-popup.min.js [new file with mode: 0644]
admin/tool/policy/amd/build/managedocsactions.min.js [new file with mode: 0644]
admin/tool/policy/amd/build/policyactions.min.js [new file with mode: 0644]
admin/tool/policy/amd/src/acceptances_filter.js [new file with mode: 0644]
admin/tool/policy/amd/src/acceptances_filter_datasource.js [new file with mode: 0644]
admin/tool/policy/amd/src/acceptmodal.js [new file with mode: 0644]
admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js [new file with mode: 0644]
admin/tool/policy/amd/src/managedocsactions.js [new file with mode: 0644]
admin/tool/policy/amd/src/policyactions.js [new file with mode: 0644]
admin/tool/policy/classes/acceptances_table.php [new file with mode: 0644]
admin/tool/policy/classes/api.php [new file with mode: 0644]
admin/tool/policy/classes/event/acceptance_base.php [new file with mode: 0644]
admin/tool/policy/classes/event/acceptance_created.php [new file with mode: 0644]
admin/tool/policy/classes/event/acceptance_updated.php [new file with mode: 0644]
admin/tool/policy/classes/external.php [new file with mode: 0644]
admin/tool/policy/classes/form/accept_policy.php [new file with mode: 0644]
admin/tool/policy/classes/form/policydoc.php [new file with mode: 0644]
admin/tool/policy/classes/output/acceptances.php [new file with mode: 0644]
admin/tool/policy/classes/output/acceptances_filter.php [new file with mode: 0644]
admin/tool/policy/classes/output/guestconsent.php [new file with mode: 0644]
admin/tool/policy/classes/output/page_agreedocs.php [new file with mode: 0644]
admin/tool/policy/classes/output/page_managedocs_list.php [new file with mode: 0644]
admin/tool/policy/classes/output/page_nopermission.php [new file with mode: 0644]
admin/tool/policy/classes/output/page_viewalldoc.php [new file with mode: 0644]
admin/tool/policy/classes/output/page_viewdoc.php [new file with mode: 0644]
admin/tool/policy/classes/output/renderer.php [new file with mode: 0644]
admin/tool/policy/classes/output/user_agreement.php [new file with mode: 0644]
admin/tool/policy/classes/policy_exporter.php [new file with mode: 0644]
admin/tool/policy/classes/policy_version.php [new file with mode: 0644]
admin/tool/policy/classes/policy_version_exporter.php [new file with mode: 0644]
admin/tool/policy/classes/privacy/local/sitepolicy/handler.php [new file with mode: 0644]
admin/tool/policy/classes/privacy/provider.php [new file with mode: 0644]
admin/tool/policy/db/access.php [new file with mode: 0644]
admin/tool/policy/db/events.php [new file with mode: 0644]
admin/tool/policy/db/install.xml [new file with mode: 0644]
admin/tool/policy/db/services.php [new file with mode: 0644]
admin/tool/policy/editpolicydoc.php [new file with mode: 0644]
admin/tool/policy/index.php [new file with mode: 0644]
admin/tool/policy/lang/en/tool_policy.php [new file with mode: 0644]
admin/tool/policy/lib.php [new file with mode: 0644]
admin/tool/policy/managedocs.php [new file with mode: 0644]
admin/tool/policy/pix/agreedno.png [new file with mode: 0644]
admin/tool/policy/pix/agreedno.svg [new file with mode: 0644]
admin/tool/policy/pix/agreedyes.png [new file with mode: 0644]
admin/tool/policy/pix/agreedyes.svg [new file with mode: 0644]
admin/tool/policy/pix/agreedyesonbehalf.png [new file with mode: 0644]
admin/tool/policy/pix/agreedyesonbehalf.svg [new file with mode: 0644]
admin/tool/policy/pix/level.png [new file with mode: 0644]
admin/tool/policy/pix/level.svg [new file with mode: 0644]
admin/tool/policy/readme_moodle.txt [new file with mode: 0644]
admin/tool/policy/settings.php [new file with mode: 0644]
admin/tool/policy/styles.css [new file with mode: 0644]
admin/tool/policy/templates/acceptances.mustache [new file with mode: 0644]
admin/tool/policy/templates/acceptances_filter.mustache [new file with mode: 0644]
admin/tool/policy/templates/guestconsent.mustache [new file with mode: 0644]
admin/tool/policy/templates/page_agreedocs.mustache [new file with mode: 0644]
admin/tool/policy/templates/page_managedocs_list.mustache [new file with mode: 0644]
admin/tool/policy/templates/page_nopermission.mustache [new file with mode: 0644]
admin/tool/policy/templates/page_viewalldoc.mustache [new file with mode: 0644]
admin/tool/policy/templates/page_viewdoc.mustache [new file with mode: 0644]
admin/tool/policy/templates/user_agreement.mustache [new file with mode: 0644]
admin/tool/policy/tests/api_test.php [new file with mode: 0644]
admin/tool/policy/tests/behat/acceptances.feature [new file with mode: 0644]
admin/tool/policy/tests/behat/behat_tool_policy.php [new file with mode: 0644]
admin/tool/policy/tests/behat/consent.feature [new file with mode: 0644]
admin/tool/policy/tests/behat/managepolicies.feature [new file with mode: 0644]
admin/tool/policy/tests/externallib_test.php [new file with mode: 0644]
admin/tool/policy/tests/privacy_provider_test.php [new file with mode: 0644]
admin/tool/policy/thirdpartylibs.xml [new file with mode: 0644]
admin/tool/policy/user.php [new file with mode: 0644]
admin/tool/policy/version.php [new file with mode: 0644]
admin/tool/policy/view.php [new file with mode: 0644]
admin/tool/policy/viewall.php [new file with mode: 0644]
admin/tool/profiling/classes/privacy/provider.php [new file with mode: 0644]
admin/tool/profiling/lang/en/tool_profiling.php
admin/tool/recyclebin/classes/category_bin.php
admin/tool/recyclebin/classes/course_bin.php
admin/tool/uploadcourse/classes/course.php
admin/tool/uploadcourse/classes/helper.php
admin/tool/uploadcourse/classes/processor.php
admin/tool/uploaduser/user_form.php
admin/tool/usertours/amd/build/popper.min.js [deleted file]
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/readme_moodle.txt
admin/tool/usertours/amd/src/popper.js [deleted file]
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/tests/privacy_provider_test.php [moved from admin/tool/usertours/tests/privcacy_provider_test.php with 100% similarity]
admin/tool/usertours/thirdpartylibs.xml
admin/tool/usertours/upgrade.txt [new file with mode: 0644]
auth/cas/classes/privacy/provider.php [new file with mode: 0644]
auth/cas/lang/en/auth_cas.php
auth/db/classes/privacy/provider.php [new file with mode: 0644]
auth/db/lang/en/auth_db.php
auth/db/tests/db_test.php
auth/email/classes/privacy/provider.php [new file with mode: 0644]
auth/email/lang/en/auth_email.php
auth/ldap/classes/privacy/provider.php [new file with mode: 0644]
auth/ldap/lang/en/auth_ldap.php
auth/lti/classes/privacy/provider.php [new file with mode: 0644]
auth/lti/lang/en/auth_lti.php
auth/mnet/classes/privacy/provider.php [new file with mode: 0644]
auth/mnet/lang/en/auth_mnet.php
auth/nologin/classes/privacy/provider.php [new file with mode: 0644]
auth/nologin/lang/en/auth_nologin.php
auth/none/classes/privacy/provider.php [new file with mode: 0644]
auth/none/lang/en/auth_none.php
auth/shibboleth/classes/privacy/provider.php [new file with mode: 0644]
auth/shibboleth/lang/en/auth_shibboleth.php
auth/test_settings.php
auth/webservice/classes/privacy/provider.php [new file with mode: 0644]
auth/webservice/lang/en/auth_webservice.php
availability/condition/completion/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/completion/lang/en/availability_completion.php
availability/condition/date/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/date/lang/en/availability_date.php
availability/condition/grade/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/grade/lang/en/availability_grade.php
availability/condition/group/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/group/lang/en/availability_group.php
availability/condition/grouping/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/grouping/lang/en/availability_grouping.php
availability/condition/profile/classes/privacy/provider.php [new file with mode: 0644]
availability/condition/profile/lang/en/availability_profile.php
backup/controller/restore_controller.class.php
backup/controller/tests/controller_test.php
backup/converter/convertlib.php
backup/converter/imscc11/backuplib.php
backup/converter/moodle1/lib.php
backup/converter/moodle1/tests/moodle1_converter_test.php
backup/import.php
backup/moodle2/restore_stepslib.php
backup/moodle2/restore_subplugin.class.php
backup/moodle2/tests/behat/import_multiple_times.feature [new file with mode: 0644]
backup/moodle2/tests/moodle2_test.php
backup/restorefile.php
backup/util/factories/backup_factory.class.php
backup/util/helper/backup_file_manager.class.php
backup/util/helper/backup_general_helper.class.php
backup/util/helper/backup_helper.class.php
backup/util/helper/convert_helper.class.php
backup/util/plan/backup_plan.class.php
backup/util/plan/restore_plan.class.php
backup/util/ui/base_moodleform.class.php
backup/util/ui/renderer.php
backup/util/ui/restore_ui_stage.class.php
badges/classes/observer.php
badges/criteria/award_criteria.php
badges/criteria/award_criteria_cohort.php [new file with mode: 0644]
badges/tests/badgeslib_test.php
badges/tests/behat/criteria_cohort.feature [new file with mode: 0644]
blocks/community/classes/privacy/provider.php [new file with mode: 0644]
blocks/community/communitycourse.php
blocks/community/lang/en/block_community.php
blocks/community/tests/privacy_test.php [new file with mode: 0644]
blocks/html/classes/privacy/provider.php
blocks/myoverview/templates/course-event-list-item.mustache
blocks/myoverview/templates/course-summary.mustache
blocks/myoverview/templates/courses-view-course-item.mustache
blocks/myoverview/templates/courses-view.mustache
blocks/myoverview/templates/event-list-item.mustache
blocks/myoverview/templates/timeline-view.mustache
blocks/rss_client/classes/privacy/provider.php [new file with mode: 0644]
blocks/rss_client/lang/en/block_rss_client.php
blocks/rss_client/tests/privacy_test.php [new file with mode: 0644]
blocks/tests/behat/behat_blocks.php
calendar/templates/month_detailed.mustache
cohort/classes/external/cohort_summary_exporter.php
cohort/edit_form.php
cohort/externallib.php
cohort/lib.php
cohort/tests/behat/upload_cohorts.feature
cohort/tests/cohortlib_test.php
cohort/tests/externallib_test.php
cohort/tests/fixtures/uploadcohorts4.csv [new file with mode: 0644]
cohort/upload_form.php
completion/tests/externallib_test.php
config-dist.php
course/classes/management_renderer.php
course/classes/output/modchooser_item.php
course/externallib.php
course/format/lib.php
course/format/singleactivity/classes/privacy/provider.php [new file with mode: 0644]
course/format/singleactivity/lang/en/format_singleactivity.php
course/format/singleactivity/lib.php
course/format/social/classes/privacy/provider.php [new file with mode: 0644]
course/format/social/lang/en/format_social.php
course/format/social/lib.php
course/format/topics/classes/privacy/provider.php [new file with mode: 0644]
course/format/topics/lang/en/format_topics.php
course/format/topics/lib.php
course/format/upgrade.txt
course/format/weeks/classes/privacy/provider.php [new file with mode: 0644]
course/format/weeks/lang/en/format_weeks.php
course/format/weeks/lib.php
course/tests/behat/category_change_visibility.feature
course/tests/behat/category_management.feature
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_change_visibility.feature
course/tests/behat/course_search.feature
course/tests/behat/navigate_course_list.feature
course/tests/externallib_test.php
course/tests/restore_test.php
dataformat/csv/classes/privacy/provider.php [new file with mode: 0644]
dataformat/csv/lang/en/dataformat_csv.php
dataformat/excel/classes/privacy/provider.php [new file with mode: 0644]
dataformat/excel/lang/en/dataformat_excel.php
dataformat/html/classes/privacy/provider.php [new file with mode: 0644]
dataformat/html/lang/en/dataformat_html.php
dataformat/json/classes/privacy/provider.php [new file with mode: 0644]
dataformat/json/lang/en/dataformat_json.php
dataformat/ods/classes/privacy/provider.php [new file with mode: 0644]
dataformat/ods/lang/en/dataformat_ods.php
enrol/category/classes/task/enrol_category_sync.php [new file with mode: 0644]
enrol/category/db/tasks.php [new file with mode: 0644]
enrol/category/lang/en/enrol_category.php
enrol/category/lib.php
enrol/category/version.php
enrol/cohort/classes/task/enrol_cohort_sync.php [new file with mode: 0644]
enrol/cohort/db/tasks.php [new file with mode: 0644]
enrol/cohort/lang/en/enrol_cohort.php
enrol/cohort/lib.php
enrol/cohort/version.php
enrol/database/tests/sync_test.php
enrol/locallib.php
enrol/lti/lib.php
enrol/lti/settings.php
enrol/lti/tests/behat/basic_settings.feature
enrol/manual/classes/task/send_expiry_notifications.php [new file with mode: 0644]
enrol/manual/classes/task/sync_enrolments.php [new file with mode: 0644]
enrol/manual/db/tasks.php [new file with mode: 0644]
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/version.php
enrol/meta/classes/task/enrol_meta_sync.php [new file with mode: 0644]
enrol/meta/db/tasks.php [new file with mode: 0644]
enrol/meta/lang/en/enrol_meta.php
enrol/meta/lib.php
enrol/meta/version.php
enrol/paypal/classes/task/process_expirations.php [new file with mode: 0644]
enrol/paypal/db/tasks.php [new file with mode: 0644]
enrol/paypal/ipn.php
enrol/paypal/lang/en/enrol_paypal.php
enrol/paypal/lib.php
enrol/paypal/version.php
enrol/self/classes/task/send_expiry_notifications.php [new file with mode: 0644]
enrol/self/classes/task/sync_enrolments.php [new file with mode: 0644]
enrol/self/db/tasks.php [new file with mode: 0644]
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/version.php
enrol/test_settings.php
files/converter/unoconv/classes/privacy/provider.php [new file with mode: 0644]
files/converter/unoconv/lang/en/fileconverter_unoconv.php
filter/activitynames/classes/privacy/provider.php [new file with mode: 0644]
filter/activitynames/lang/en/filter_activitynames.php
filter/algebra/classes/privacy/provider.php [new file with mode: 0644]
filter/algebra/lang/en/filter_algebra.php
filter/censor/classes/privacy/provider.php [new file with mode: 0644]
filter/censor/lang/en/filter_censor.php
filter/data/classes/privacy/provider.php [new file with mode: 0644]
filter/data/lang/en/filter_data.php
filter/emailprotect/classes/privacy/provider.php [new file with mode: 0644]
filter/emailprotect/lang/en/filter_emailprotect.php
filter/emoticon/classes/privacy/provider.php [new file with mode: 0644]
filter/emoticon/lang/en/filter_emoticon.php
filter/glossary/classes/privacy/provider.php [new file with mode: 0644]
filter/glossary/lang/en/filter_glossary.php
filter/mathjaxloader/classes/privacy/provider.php [new file with mode: 0644]
filter/mathjaxloader/lang/en/filter_mathjaxloader.php
filter/mediaplugin/classes/privacy/provider.php [new file with mode: 0644]
filter/mediaplugin/lang/en/filter_mediaplugin.php
filter/multilang/classes/privacy/provider.php [new file with mode: 0644]
filter/multilang/lang/en/filter_multilang.php
filter/tex/classes/privacy/provider.php [new file with mode: 0644]
filter/tex/lang/en/filter_tex.php
filter/tidy/classes/privacy/provider.php [new file with mode: 0644]
filter/tidy/lang/en/filter_tidy.php
filter/urltolink/classes/privacy/provider.php [new file with mode: 0644]
filter/urltolink/lang/en/filter_urltolink.php
grade/export/ods/classes/privacy/provider.php [new file with mode: 0644]
grade/export/ods/lang/en/gradeexport_ods.php
grade/export/txt/classes/privacy/provider.php [new file with mode: 0644]
grade/export/txt/lang/en/gradeexport_txt.php
grade/export/xls/classes/privacy/provider.php [new file with mode: 0644]
grade/export/xls/lang/en/gradeexport_xls.php
grade/export/xml/classes/privacy/provider.php [new file with mode: 0644]
grade/export/xml/lang/en/gradeexport_xml.php
grade/import/csv/classes/privacy/provider.php [new file with mode: 0644]
grade/import/csv/lang/en/gradeimport_csv.php
grade/import/direct/classes/privacy/provider.php [new file with mode: 0644]
grade/import/direct/lang/en/gradeimport_direct.php
grade/import/xml/classes/privacy/provider.php [new file with mode: 0644]
grade/import/xml/lang/en/gradeimport_xml.php
grade/report/grader/classes/privacy/provider.php [new file with mode: 0644]
grade/report/grader/lang/en/gradereport_grader.php
grade/report/grader/tests/privacy_test.php [new file with mode: 0644]
grade/report/history/classes/privacy/provider.php [new file with mode: 0644]
grade/report/history/lang/en/gradereport_history.php
grade/report/outcomes/classes/privacy/provider.php [new file with mode: 0644]
grade/report/outcomes/lang/en/gradereport_outcomes.php
grade/report/overview/classes/privacy/provider.php [new file with mode: 0644]
grade/report/overview/lang/en/gradereport_overview.php
grade/report/singleview/classes/privacy/provider.php [new file with mode: 0644]
grade/report/singleview/lang/en/gradereport_singleview.php
grade/report/user/classes/privacy/provider.php [new file with mode: 0644]
grade/report/user/lang/en/gradereport_user.php
grade/report/user/tests/privacy_test.php [new file with mode: 0644]
install.php
install/lang/de_ch/langconfig.php
install/lang/id/moodle.php
lang/en/admin.php
lang/en/antivirus.php
lang/en/badges.php
lang/en/cohort.php
lang/en/deprecated.txt
lang/en/editor.php
lang/en/install.php
lang/en/message.php
lang/en/moodle.php
lang/en/portfolio.php
lang/en/repository.php
lang/en/tag.php
lib/amd/build/adapter.min.js [new file with mode: 0644]
lib/amd/build/popper.min.js [new file with mode: 0644]
lib/amd/src/adapter.js [new file with mode: 0644]
lib/amd/src/popper.js [new file with mode: 0644]
lib/antivirus/clamav/classes/scanner.php
lib/antivirus/clamav/tests/scanner_test.php
lib/badgeslib.php
lib/behat/classes/partial_named_selector.php
lib/classes/antivirus/manager.php
lib/classes/antivirus/scanner.php
lib/classes/component.php
lib/classes/hub/publication.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/plugin_manager.php
lib/classes/task/file_temp_cleanup_task.php
lib/db/events.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/mssql_native_moodle_database.php [deleted file]
lib/dml/mssql_native_moodle_recordset.php [deleted file]
lib/dml/mssql_native_moodle_temptables.php [deleted file]
lib/dml/sqlsrv_native_moodle_temptables.php
lib/dmllib.php
lib/editor/atto/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/atto/db/upgrade.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/plugins/recordrtc/classes/privacy/provider.php [moved from theme/boost/classes/output/core/admin_renderer.php with 60% similarity]
lib/editor/atto/plugins/recordrtc/lang/en/atto_recordrtc.php [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/lib.php [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/pix/i/audiortc.png [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/pix/i/videortc.png [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/pix/icon.png [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/settings.php [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/styles.css [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/version.php [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-min.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/button/build.json [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/button/js/button.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/button/meta/button.json [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/build.json [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/abstractmodule.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/audiomodule.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/commonmodule.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/compatcheckmodule.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/videomodule.js [new file with mode: 0644]
lib/editor/atto/plugins/recordrtc/yui/src/recording/meta/recording.json [new file with mode: 0644]
lib/editor/atto/settings.php
lib/editor/atto/tests/privacy_provider.php [new file with mode: 0644]
lib/editor/atto/version.php
lib/editor/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tests/privacy_provider_test.php [new file with mode: 0644]
lib/editor/textarea/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/textarea/lang/en/editor_textarea.php
lib/editor/tinymce/classes/privacy/provider.php [new file with mode: 0644]
lib/editor/tinymce/lang/en/editor_tinymce.php
lib/externallib.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/setup.php
lib/setuplib.php
lib/tablelib.php
lib/testing/classes/util.php
lib/tests/antivirus_test.php
lib/tests/behat/alpha_chooser.feature
lib/tests/component_test.php
lib/tests/cronlib_test.php
lib/tests/fixtures/testable_antivirus.php
lib/tests/moodle_page_test.php
lib/tests/scheduled_task_test.php
lib/tests/weblib_format_text_test.php
lib/thirdpartylibs.xml
lib/upgradelib.php
lib/weblib.php
media/player/html5audio/classes/privacy/provider.php [new file with mode: 0644]
media/player/html5audio/lang/en/media_html5audio.php
media/player/html5video/classes/privacy/provider.php [new file with mode: 0644]
media/player/html5video/lang/en/media_html5video.php
media/player/swf/classes/privacy/provider.php [new file with mode: 0644]
media/player/swf/lang/en/media_swf.php
media/player/videojs/classes/plugin.php
media/player/videojs/classes/privacy/provider.php [new file with mode: 0644]
media/player/videojs/lang/en/media_videojs.php
media/player/videojs/settings.php
media/player/videojs/tests/player_test.php
media/player/vimeo/classes/privacy/provider.php [new file with mode: 0644]
media/player/vimeo/lang/en/media_vimeo.php
media/player/youtube/classes/privacy/provider.php [new file with mode: 0644]
media/player/youtube/lang/en/media_youtube.php
message/classes/helper.php
message/classes/task/migrate_message_data.php [new file with mode: 0644]
message/index.php
message/output/airnotifier/classes/privacy/provider.php
message/output/email/classes/privacy/provider.php [new file with mode: 0644]
message/output/email/lang/en/message_email.php
message/output/email/tests/privacy_test.php [new file with mode: 0644]
message/output/popup/classes/api.php
message/output/popup/db/install.xml
message/output/popup/db/upgrade.php
message/output/popup/message_output_popup.php
message/output/popup/notifications.php
message/output/popup/tests/base.php
message/output/popup/version.php
message/tests/migrate_message_data_task_test.php [new file with mode: 0644]
message/upgrade.txt
mod/assign/feedback/editpdf/styles.css
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/comment.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/gradingtable.php
mod/assignment/type/offline/classes/privacy/provider.php [new file with mode: 0644]
mod/assignment/type/offline/lang/en/assignment_offline.php
mod/assignment/type/online/classes/privacy/provider.php [new file with mode: 0644]
mod/assignment/type/online/lang/en/assignment_online.php
mod/assignment/type/upload/classes/privacy/provider.php [new file with mode: 0644]
mod/assignment/type/upload/lang/en/assignment_upload.php
mod/assignment/type/uploadsingle/classes/privacy/provider.php [new file with mode: 0644]
mod/assignment/type/uploadsingle/lang/en/assignment_uploadsingle.php
mod/book/tool/exportimscp/classes/privacy/provider.php [new file with mode: 0644]
mod/book/tool/exportimscp/lang/en/booktool_exportimscp.php
mod/book/tool/importhtml/classes/privacy/provider.php [new file with mode: 0644]
mod/book/tool/importhtml/lang/en/booktool_importhtml.php
mod/book/tool/print/classes/privacy/provider.php [new file with mode: 0644]
mod/book/tool/print/lang/en/booktool_print.php
mod/choice/classes/privacy/provider.php
mod/feedback/classes/privacy/provider.php [new file with mode: 0644]
mod/feedback/lang/en/feedback.php
mod/feedback/tests/privacy_test.php [new file with mode: 0644]
mod/glossary/import.php
mod/glossary/lib.php
mod/glossary/tests/behat/import_entries.feature
mod/glossary/tests/fixtures/musicians.xml [new file with mode: 0644]
mod/lesson/renderer.php
mod/lti/classes/local/ltiservice/resource_base.php
mod/lti/classes/local/ltiservice/response.php
mod/lti/classes/local/ltiservice/service_base.php
mod/lti/edit_form.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/service/gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php [new file with mode: 0644]
mod/lti/service/gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/local/resources/lineitem.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/local/resources/lineitems.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/local/resources/results.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/local/resources/scores.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/local/service/gradebookservices.php [new file with mode: 0644]
mod/lti/service/gradebookservices/classes/task/cleanup_task.php [new file with mode: 0644]
mod/lti/service/gradebookservices/db/install.xml [new file with mode: 0644]
mod/lti/service/gradebookservices/db/tasks.php [new file with mode: 0644]
mod/lti/service/gradebookservices/lang/en/ltiservice_gradebookservices.php [new file with mode: 0644]
mod/lti/service/gradebookservices/tests/task_cleanup_test.php [new file with mode: 0644]
mod/lti/service/gradebookservices/version.php [new file with mode: 0644]
mod/lti/service/memberships/classes/local/resources/contextmemberships.php
mod/lti/service/memberships/classes/local/resources/linkmemberships.php
mod/lti/service/memberships/classes/local/service/memberships.php
mod/lti/service/memberships/lang/en/ltiservice_memberships.php
mod/lti/service/profile/classes/local/resources/profile.php
mod/lti/service/toolsettings/classes/local/resources/contextsettings.php
mod/lti/service/toolsettings/classes/local/resources/linksettings.php
mod/lti/service/toolsettings/classes/local/resources/systemsettings.php
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/autosave.ajax.php
mod/quiz/comment.php
mod/quiz/lang/en/quiz.php
mod/quiz/locallib.php
mod/quiz/processattempt.php
mod/quiz/renderer.php
mod/quiz/report/responses/last_responses_table.php
mod/quiz/report/statistics/report.php
mod/quiz/review.php
mod/quiz/reviewquestion.php
mod/quiz/summary.php
mod/quiz/tests/attempt_test.php
mod/quiz/tests/attempts_test.php
mod/quiz/tests/tags_test.php
mod/scorm/classes/external.php
mod/scorm/datamodels/scorm_12.php
mod/scorm/datamodels/scorm_12lib.php
mod/scorm/datamodels/scorm_13lib.php
mod/scorm/db/install.xml
mod/scorm/db/upgrade.php
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/settings.php
mod/scorm/tests/behat/multisco_review_mode.feature
mod/scorm/tests/packages/RuntimeMinimumCalls_SCORM12-mini.zip [new file with mode: 0644]
mod/scorm/tests/packages/readme_moodle.txt
mod/scorm/upgrade.txt
mod/scorm/version.php
mod/survey/lib.php
mod/upgrade.txt
phpunit.xml.dist
pix/i/addblock.png [new file with mode: 0644]
pix/i/addblock.svg [new file with mode: 0644]
pix/i/emtpy.png [new file with mode: 0644]
pix/i/emtpy.svg [new file with mode: 0644]
pix/i/home.svg [new file with mode: 0644]
pix/i/privatefiles.svg [new file with mode: 0644]
pix/i/section.svg [new file with mode: 0644]
portfolio/boxnet/classes/privacy/provider.php [new file with mode: 0644]
portfolio/boxnet/lang/en/portfolio_boxnet.php
portfolio/boxnet/tests/privacy_provider_test.php [new file with mode: 0644]
portfolio/classes/privacy/legacy_polyfill.php [new file with mode: 0644]
portfolio/classes/privacy/portfolio_provider.php [new file with mode: 0644]
portfolio/classes/privacy/provider.php [new file with mode: 0644]
portfolio/download/classes/privacy/provider.php [new file with mode: 0644]
portfolio/download/lang/en/portfolio_download.php
portfolio/flickr/classes/privacy/provider.php [new file with mode: 0644]
portfolio/flickr/lang/en/portfolio_flickr.php
portfolio/flickr/tests/privacy_provider_test.php [new file with mode: 0644]
portfolio/googledocs/classes/privacy/provider.php [new file with mode: 0644]
portfolio/googledocs/lang/en/portfolio_googledocs.php
portfolio/googledocs/tests/privacy_provider_test.php [new file with mode: 0644]
portfolio/mahara/classes/privacy/provider.php [new file with mode: 0644]
portfolio/mahara/lang/en/portfolio_mahara.php
portfolio/mahara/tests/privacy_provider_test.php [new file with mode: 0644]
portfolio/picasa/classes/privacy/provider.php [new file with mode: 0644]
portfolio/picasa/lang/en/portfolio_picasa.php
portfolio/picasa/tests/privacy_provider_test.php [new file with mode: 0644]
portfolio/tests/privacy_legacy_polyfill_test.php [new file with mode: 0644]
portfolio/tests/privacy_provider_test.php [new file with mode: 0644]
privacy/classes/local/request/helper.php
privacy/classes/manager.php
privacy/classes/tests/request/content_writer.php
privacy/tests/provider_test.php [new file with mode: 0644]
privacy/tests/tests_content_writer_test.php [new file with mode: 0644]
question/classes/external.php
question/classes/statistics/questions/calculator.php
question/lib.php
question/type/edit_question_form.php
question/type/tags_form.php
report/backups/classes/privacy/provider.php [new file with mode: 0644]
report/backups/lang/en/report_backups.php
report/competency/classes/privacy/provider.php [new file with mode: 0644]
report/competency/lang/en/report_competency.php
report/completion/classes/privacy/provider.php [new file with mode: 0644]
report/completion/lang/en/report_completion.php
report/configlog/classes/privacy/provider.php [new file with mode: 0644]
report/configlog/lang/en/report_configlog.php
report/courseoverview/classes/privacy/provider.php [new file with mode: 0644]
report/courseoverview/lang/en/report_courseoverview.php
report/eventlist/classes/privacy/provider.php [new file with mode: 0644]
report/eventlist/lang/en/report_eventlist.php
report/insights/classes/privacy/provider.php [new file with mode: 0644]
report/insights/lang/en/report_insights.php
report/log/classes/privacy/provider.php [new file with mode: 0644]
report/log/lang/en/report_log.php
report/loglive/classes/privacy/provider.php [new file with mode: 0644]
report/loglive/lang/en/report_loglive.php
report/outline/classes/privacy/provider.php [new file with mode: 0644]
report/outline/lang/en/report_outline.php
report/participation/classes/privacy/provider.php [new file with mode: 0644]
report/participation/lang/en/report_participation.php
report/performance/classes/privacy/provider.php [new file with mode: 0644]
report/performance/lang/en/report_performance.php
report/progress/classes/privacy/provider.php [new file with mode: 0644]
report/progress/lang/en/report_progress.php
report/questioninstances/classes/privacy/provider.php [new file with mode: 0644]
report/questioninstances/lang/en/report_questioninstances.php
report/security/classes/privacy/provider.php [new file with mode: 0644]
report/security/lang/en/report_security.php
report/stats/classes/privacy/provider.php [new file with mode: 0644]
report/stats/lang/en/report_stats.php
report/usersessions/classes/privacy/provider.php [new file with mode: 0644]
report/usersessions/lang/en/report_usersessions.php
repository/areafiles/classes/privacy/provider.php [new file with mode: 0644]
repository/areafiles/lang/en/repository_areafiles.php
repository/boxnet/classes/privacy/provider.php [new file with mode: 0644]
repository/boxnet/lang/en/repository_boxnet.php
repository/classes/privacy/provider.php [new file with mode: 0644]
repository/coursefiles/classes/privacy/provider.php [new file with mode: 0644]
repository/coursefiles/lang/en/repository_coursefiles.php
repository/dropbox/classes/privacy/provider.php [new file with mode: 0644]
repository/dropbox/lang/en/repository_dropbox.php
repository/equella/classes/privacy/provider.php [new file with mode: 0644]
repository/equella/lang/en/repository_equella.php
repository/filesystem/classes/privacy/provider.php [new file with mode: 0644]
repository/filesystem/lang/en/repository_filesystem.php
repository/flickr/classes/privacy/provider.php [new file with mode: 0644]
repository/flickr/lang/en/repository_flickr.php
repository/flickr_public/classes/privacy/provider.php [new file with mode: 0644]
repository/flickr_public/lang/en/repository_flickr_public.php
repository/googledocs/classes/privacy/provider.php [new file with mode: 0644]
repository/googledocs/lang/en/repository_googledocs.php
repository/local/classes/privacy/provider.php [new file with mode: 0644]
repository/local/lang/en/repository_local.php
repository/merlot/classes/privacy/provider.php [new file with mode: 0644]
repository/merlot/lang/en/repository_merlot.php
repository/onedrive/classes/privacy/provider.php [new file with mode: 0644]
repository/onedrive/lang/en/repository_onedrive.php
repository/onedrive/tests/privacy_test.php [new file with mode: 0644]
repository/picasa/classes/privacy/provider.php [new file with mode: 0644]
repository/picasa/lang/en/repository_picasa.php
repository/recent/classes/privacy/provider.php [new file with mode: 0644]
repository/recent/lang/en/repository_recent.php
repository/s3/classes/privacy/provider.php [new file with mode: 0644]
repository/s3/lang/en/repository_s3.php
repository/skydrive/classes/privacy/provider.php [new file with mode: 0644]
repository/skydrive/lang/en/repository_skydrive.php
repository/tests/privacy_test.php [new file with mode: 0644]
repository/upload/classes/privacy/provider.php [new file with mode: 0644]
repository/upload/lang/en/repository_upload.php
repository/url/classes/privacy/provider.php [new file with mode: 0644]
repository/url/lang/en/repository_url.php
repository/user/classes/privacy/provider.php [new file with mode: 0644]
repository/user/lang/en/repository_user.php
repository/webdav/classes/privacy/provider.php [new file with mode: 0644]
repository/webdav/lang/en/repository_webdav.php
repository/wikimedia/classes/privacy/provider.php [new file with mode: 0644]
repository/wikimedia/lang/en/repository_wikimedia.php
repository/youtube/classes/privacy/provider.php [new file with mode: 0644]
repository/youtube/lang/en/repository_youtube.php
search/classes/manager.php
search/classes/output/form/search.php
search/index.php
search/tests/behat/search_query.feature
search/tests/manager_test.php
tag/classes/tag.php
tag/tests/behat/edit_tag.feature
theme/boost/amd/build/alert.min.js
theme/boost/amd/build/button.min.js
theme/boost/amd/build/carousel.min.js
theme/boost/amd/build/collapse.min.js
theme/boost/amd/build/dropdown.min.js
theme/boost/amd/build/form-display-errors.min.js
theme/boost/amd/build/modal.min.js
theme/boost/amd/build/popover.min.js
theme/boost/amd/build/scrollspy.min.js
theme/boost/amd/build/tab.min.js
theme/boost/amd/build/tooltip.min.js
theme/boost/amd/build/util.min.js
theme/boost/amd/src/alert.js
theme/boost/amd/src/button.js
theme/boost/amd/src/carousel.js
theme/boost/amd/src/collapse.js
theme/boost/amd/src/dropdown.js
theme/boost/amd/src/form-display-errors.js
theme/boost/amd/src/modal.js
theme/boost/amd/src/popover.js
theme/boost/amd/src/scrollspy.js
theme/boost/amd/src/tab.js
theme/boost/amd/src/tooltip.js
theme/boost/amd/src/util.js
theme/boost/classes/output/core_course/management/renderer.php [new file with mode: 0644]
theme/boost/classes/output/core_renderer.php
theme/boost/cli/import-bootswatch.php
theme/boost/lang/en/theme_boost.php
theme/boost/lib.php
theme/boost/readme_moodle.txt
theme/boost/scss/bootstrap.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/LICENSE
theme/boost/scss/bootstrap/_alert.scss
theme/boost/scss/bootstrap/_animation.scss [deleted file]
theme/boost/scss/bootstrap/_badge.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_breadcrumb.scss
theme/boost/scss/bootstrap/_button-group.scss
theme/boost/scss/bootstrap/_buttons.scss
theme/boost/scss/bootstrap/_card.scss
theme/boost/scss/bootstrap/_carousel.scss
theme/boost/scss/bootstrap/_close.scss
theme/boost/scss/bootstrap/_code.scss
theme/boost/scss/bootstrap/_custom-forms.scss
theme/boost/scss/bootstrap/_custom.scss [deleted file]
theme/boost/scss/bootstrap/_dropdown.scss
theme/boost/scss/bootstrap/_forms.scss
theme/boost/scss/bootstrap/_functions.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_grid.scss
theme/boost/scss/bootstrap/_images.scss
theme/boost/scss/bootstrap/_input-group.scss
theme/boost/scss/bootstrap/_jumbotron.scss
theme/boost/scss/bootstrap/_list-group.scss
theme/boost/scss/bootstrap/_media.scss
theme/boost/scss/bootstrap/_mixins.scss
theme/boost/scss/bootstrap/_modal.scss
theme/boost/scss/bootstrap/_nav.scss
theme/boost/scss/bootstrap/_navbar.scss
theme/boost/scss/bootstrap/_normalize.scss [deleted file]
theme/boost/scss/bootstrap/_pagination.scss
theme/boost/scss/bootstrap/_popover.scss
theme/boost/scss/bootstrap/_print.scss
theme/boost/scss/bootstrap/_progress.scss
theme/boost/scss/bootstrap/_reboot.scss
theme/boost/scss/bootstrap/_root.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_tables.scss
theme/boost/scss/bootstrap/_tags.scss [deleted file]
theme/boost/scss/bootstrap/_tooltip.scss
theme/boost/scss/bootstrap/_transitions.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_type.scss
theme/boost/scss/bootstrap/_utilities.scss
theme/boost/scss/bootstrap/_variables.scss
theme/boost/scss/bootstrap/bootstrap-flex.scss [deleted file]
theme/boost/scss/bootstrap/bootstrap-grid.scss
theme/boost/scss/bootstrap/bootstrap-reboot.scss
theme/boost/scss/bootstrap/bootstrap.scss
theme/boost/scss/bootstrap/mixins/_alert.scss
theme/boost/scss/bootstrap/mixins/_background-variant.scss
theme/boost/scss/bootstrap/mixins/_badge.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_border-radius.scss
theme/boost/scss/bootstrap/mixins/_box-shadow.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_breakpoints.scss
theme/boost/scss/bootstrap/mixins/_buttons.scss
theme/boost/scss/bootstrap/mixins/_cards.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_caret.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_clearfix.scss
theme/boost/scss/bootstrap/mixins/_float.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_forms.scss
theme/boost/scss/bootstrap/mixins/_gradients.scss
theme/boost/scss/bootstrap/mixins/_grid-framework.scss
theme/boost/scss/bootstrap/mixins/_grid.scss
theme/boost/scss/bootstrap/mixins/_hover.scss
theme/boost/scss/bootstrap/mixins/_image.scss
theme/boost/scss/bootstrap/mixins/_list-group.scss
theme/boost/scss/bootstrap/mixins/_nav-divider.scss
theme/boost/scss/bootstrap/mixins/_navbar-align.scss
theme/boost/scss/bootstrap/mixins/_pagination.scss
theme/boost/scss/bootstrap/mixins/_progress.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_pulls.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_reset-filter.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_reset-text.scss
theme/boost/scss/bootstrap/mixins/_resize.scss
theme/boost/scss/bootstrap/mixins/_screen-reader.scss
theme/boost/scss/bootstrap/mixins/_tab-focus.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_tag.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_text-emphasis.scss
theme/boost/scss/bootstrap/mixins/_text-hide.scss
theme/boost/scss/bootstrap/mixins/_text-truncate.scss
theme/boost/scss/bootstrap/mixins/_transition.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_visibility.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_align.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_background.scss
theme/boost/scss/bootstrap/utilities/_borders.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_display.scss
theme/boost/scss/bootstrap/utilities/_embed.scss [moved from theme/boost/scss/bootstrap/_responsive-embed.scss with 60% similarity]
theme/boost/scss/bootstrap/utilities/_flex.scss
theme/boost/scss/bootstrap/utilities/_float.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_position.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_pulls.scss [deleted file]
theme/boost/scss/bootstrap/utilities/_sizing.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_spacing.scss
theme/boost/scss/bootstrap/utilities/_text.scss
theme/boost/scss/bootstrap/utilities/_visibility.scss
theme/boost/scss/fontawesome.scss [new file with mode: 0644]
theme/boost/scss/fontawesome/moodle-path.scss [deleted file]
theme/boost/scss/fontawesome/readme_moodle.txt
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/backup-restore.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/bootswatch.scss
theme/boost/scss/moodle/bs2-compat.scss
theme/boost/scss/moodle/bs4alphacompat.scss [new file with mode: 0644]
theme/boost/scss/moodle/calendar.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/debug.scss
theme/boost/scss/moodle/drawer.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/icons.scss
theme/boost/scss/moodle/message.scss
theme/boost/scss/moodle/modal.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/responsive-tabs.scss [deleted file]
theme/boost/scss/moodle/search.scss
theme/boost/scss/moodle/undo.scss
theme/boost/scss/moodle/user.scss
theme/boost/scss/preset/default.scss
theme/boost/scss/preset/plain.scss
theme/boost/templates/admin_setting_tabs.mustache
theme/boost/templates/block_search_forums/search_form.mustache
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/core/action_menu.mustache
theme/boost/templates/core/action_menu_trigger.mustache
theme/boost/templates/core/block.mustache
theme/boost/templates/core/custom_menu_item.mustache
theme/boost/templates/core/dataformat_selector.mustache
theme/boost/templates/core/filemanager_fileselect.mustache
theme/boost/templates/core/filemanager_modal_generallayout.mustache
theme/boost/templates/core/filemanager_page_generallayout.mustache
theme/boost/templates/core/form_autocomplete_selection.mustache
theme/boost/templates/core/help_icon.mustache
theme/boost/templates/core/initials_bar.mustache [new file with mode: 0644]
theme/boost/templates/core/loginform.mustache
theme/boost/templates/core/modal.mustache
theme/boost/templates/core/preferences_groups.mustache
theme/boost/templates/core/settings_link_page.mustache
theme/boost/templates/core/settings_link_page_single.mustache
theme/boost/templates/core/signup_form_layout.mustache
theme/boost/templates/core/tabtree.mustache
theme/boost/templates/core_admin/setting.mustache
theme/boost/templates/core_form/element-advcheckbox-inline.mustache
theme/boost/templates/core_form/element-advcheckbox.mustache
theme/boost/templates/core_form/element-autocomplete-inline.mustache
theme/boost/templates/core_form/element-autocomplete.mustache
theme/boost/templates/core_form/element-button.mustache
theme/boost/templates/core_form/element-checkbox-inline.mustache
theme/boost/templates/core_form/element-checkbox.mustache
theme/boost/templates/core_form/element-date_time_selector-inline.mustache
theme/boost/templates/core_form/element-date_time_selector.mustache
theme/boost/templates/core_form/element-password.mustache
theme/boost/templates/core_form/element-passwordunmask.mustache
theme/boost/templates/core_form/element-radio-inline.mustache
theme/boost/templates/core_form/element-radio.mustache
theme/boost/templates/core_form/element-select-inline.mustache
theme/boost/templates/core_form/element-select.mustache
theme/boost/templates/core_form/element-selectgroups-inline.mustache
theme/boost/templates/core_form/element-selectgroups.mustache
theme/boost/templates/core_form/element-selectwithlink.mustache
theme/boost/templates/core_form/element-tags-inline.mustache
theme/boost/templates/core_form/element-tags.mustache
theme/boost/templates/core_form/element-template-inline.mustache
theme/boost/templates/core_form/element-template.mustache
theme/boost/templates/core_form/element-text-inline.mustache
theme/boost/templates/core_form/element-text.mustache
theme/boost/templates/core_form/element-textarea.mustache
theme/boost/templates/core_form/element-url.mustache
theme/boost/templates/core_grades/edit_tree.mustache
theme/boost/templates/custom_menu_footer.mustache
theme/boost/templates/flat_navigation.mustache
theme/boost/templates/footer.mustache [new file with mode: 0644]
theme/boost/templates/head.mustache [new file with mode: 0644]
theme/boost/templates/header.mustache
theme/boost/templates/login.mustache
theme/boost/templates/maintenance.mustache
theme/boost/templates/mod_forum/quick_search_form.mustache
theme/boost/templates/nav-drawer.mustache
theme/boost/templates/navbar-secure.mustache [moved from theme/boost/templates/header-secure.mustache with 66% similarity]
theme/boost/templates/navbar.mustache [new file with mode: 0644]
theme/boost/templates/secure.mustache
theme/boost/templates/tool_usertours/tourstep.mustache
theme/boost/tests/behat/behat_theme_boost_behat_action_menu.php
theme/boost/tests/behat/behat_theme_boost_behat_navigation.php
theme/boost/thirdpartylibs.xml
theme/boost/upgrade.txt
user/classes/participants_table.php
user/editlib.php
user/lib.php
user/tests/behat/set_default_homepage.feature
user/tests/behat/set_email_display.feature [new file with mode: 0644]
user/tests/behat/view_participants.feature
version.php
webservice/rest/classes/privacy/provider.php [new file with mode: 0644]
webservice/rest/lang/en/webservice_rest.php
webservice/soap/classes/privacy/provider.php [new file with mode: 0644]
webservice/soap/lang/en/webservice_soap.php
webservice/xmlrpc/classes/privacy/provider.php [new file with mode: 0644]
webservice/xmlrpc/lang/en/webservice_xmlrpc.php

index 8e67b3e..296343b 100644 (file)
@@ -3,8 +3,8 @@
 */**/build/
 node_modules/
 vendor/
+admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
-admin/tool/usertours/amd/src/popper.js
 auth/cas/CAS/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
@@ -57,7 +57,9 @@ lib/maxmind/MaxMind/
 lib/ltiprovider/
 lib/amd/src/truncate.js
 lib/fonts/
+lib/amd/src/adapter.js
 lib/validateurlsyntax.php
+lib/amd/src/popper.js
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
index 38e84cd..bdd33f8 100644 (file)
@@ -4,8 +4,8 @@ theme/clean/style/custom.css
 theme/more/style/custom.css
 node_modules/
 vendor/
+admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
-admin/tool/usertours/amd/src/popper.js
 auth/cas/CAS/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
@@ -58,7 +58,9 @@ lib/maxmind/MaxMind/
 lib/ltiprovider/
 lib/amd/src/truncate.js
 lib/fonts/
+lib/amd/src/adapter.js
 lib/validateurlsyntax.php
+lib/amd/src/popper.js
 media/player/videojs/amd/src/video-lazy.js
 media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
index 240a819..1e5837f 100644 (file)
@@ -219,7 +219,6 @@ $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'n
                    'pgsql'  => moodle_database::get_driver_instance('pgsql',  'native'),
                    'oci'    => moodle_database::get_driver_instance('oci',    'native'),
                    'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver
-                   'mssql'  => moodle_database::get_driver_instance('mssql',  'native'), // FreeTDS driver
                   );
 foreach ($databases as $type=>$database) {
     if ($database->driver_installed() !== true) {
@@ -443,6 +442,7 @@ if ($interactive) {
     }
 }
 $CFG->tempdir       = $CFG->dataroot.'/temp';
+$CFG->backuptempdir = $CFG->tempdir.'/backup';
 $CFG->cachedir      = $CFG->dataroot.'/cache';
 $CFG->localcachedir = $CFG->dataroot.'/localcache';
 
index 6c10b9a..5915e24 100644 (file)
@@ -131,10 +131,11 @@ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
     cli_error(get_string('pluginschecktodo', 'admin'));
 }
 
+$a = new stdClass();
+$a->oldversion = $oldversion;
+$a->newversion = $newversion;
+
 if ($interactive) {
-    $a = new stdClass();
-    $a->oldversion = $oldversion;
-    $a->newversion = $newversion;
     echo cli_heading(get_string('databasechecking', '', $a)) . PHP_EOL;
 }
 
@@ -193,5 +194,5 @@ admin_apply_default_settings(NULL, false);
 // to immediately start browsing the site.
 upgrade_themes();
 
-echo get_string('cliupgradefinished', 'admin')."\n";
+echo get_string('cliupgradefinished', 'admin', $a)."\n";
 exit(0); // 0 means success
index 7486db8..db05c1d 100644 (file)
@@ -22,6 +22,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
     $temp->add(new admin_setting_configcheckbox('allowuserthemes', new lang_string('allowuserthemes', 'admin'), new lang_string('configallowuserthemes', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowcoursethemes', new lang_string('allowcoursethemes', 'admin'), new lang_string('configallowcoursethemes', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowcategorythemes',  new lang_string('allowcategorythemes', 'admin'), new lang_string('configallowcategorythemes', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('allowcohortthemes',  new lang_string('allowcohortthemes', 'admin'), new lang_string('configallowcohortthemes', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowthemechangeonurl',  new lang_string('allowthemechangeonurl', 'admin'), new lang_string('configallowthemechangeonurl', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'), new lang_string('configallowuserblockhiding', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('allowblockstodock', new lang_string('allowblockstodock', 'admin'), new lang_string('configallowblockstodock', 'admin'), 1));
index a6ac61f..b6a9f89 100644 (file)
@@ -43,6 +43,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
                 new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
 
+    $temp->add(new admin_setting_configcheckbox('forceclean', new lang_string('forceclean', 'core_admin'),
+        new lang_string('forceclean_desc', 'core_admin'), 0));
+
     $temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
     $temp->add(new admin_setting_configselect('maxeditingtime', new lang_string('maxeditingtime','admin'), new lang_string('configmaxeditingtime','admin'), 1800,
index 7fc752f..d444961 100644 (file)
@@ -28,7 +28,7 @@ if ($hassiteconfig
         $choices['1'] = new lang_string('emaildisplayyes');
         $choices['2'] = new lang_string('emaildisplaycourse');
         $temp->add(new admin_setting_configselect('defaultpreference_maildisplay', new lang_string('emaildisplay'),
-            '', 2, $choices));
+            new lang_string('emaildisplay_help'), 2, $choices));
 
         $choices = array();
         $choices['0'] = new lang_string('textformat');
@@ -150,6 +150,7 @@ if ($hassiteconfig
         $temp->add(new admin_setting_configmultiselect('hiddenuserfields', new lang_string('hiddenuserfields', 'admin'),
                    new lang_string('confighiddenuserfields', 'admin'), array(),
                        array('description' => new lang_string('description'),
+                             'email' => new lang_string('email'),
                              'city' => new lang_string('city'),
                              'country' => new lang_string('country'),
                              'timezone' => new lang_string('timezone'),
index 1acb99c..bad2c1b 100644 (file)
@@ -44,8 +44,7 @@ class helper {
             'native/mariadb' => \moodle_database::get_driver_instance('mariadb', 'native')->get_name(),
             'native/pgsql'   => \moodle_database::get_driver_instance('pgsql', 'native')->get_name(),
             'native/oci'     => \moodle_database::get_driver_instance('oci', 'native')->get_name(),
-            'native/sqlsrv'  => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name(),
-            'native/mssql'   => \moodle_database::get_driver_instance('mssql', 'native')->get_name()
+            'native/sqlsrv'  => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name()
         );
     }
 
index e99f7c5..9f5c317 100644 (file)
@@ -39,7 +39,7 @@
 </h2>
 <div>{{{framework.description}}}</div>
     <h3>{{#str}}competencies, core_competency{{/str}}</h3>
-    <div class="row-fluid">
+    <div class="row-fluid row">
         <div class="span6 col-lg-6">
             <p>
                 <form data-region="filtercompetencies" data-frameworkid="{{framework.id}}" class="form-inline">
@@ -56,7 +56,7 @@
         </div>
 
         <div class="span6 card col-lg-6">
-            <div class="card-block">
+            <div class="card-block card-body">
                 <div class="card-title">
                     <h4 data-region="selected-competency">{{#str}}selectedcompetency, tool_lp{{/str}}</h4>
                         <span data-region="competencyactionsmenu" class="pull-xs-right">
index c9925e4..c8ef59f 100644 (file)
@@ -666,25 +666,9 @@ class manager {
 
         if (!empty($CFG->antiviruses)) {
             mtrace("--> Attempting virus scan of '{$attachment->filename}'");
-
-            // Store the file on disk - it will need to be virus scanned first.
-            $itemid = rand(1, 999999999);;
-            $directory = make_temp_directory("/messageinbound/{$itemid}", false);
-            $filepath = $directory . "/" . $attachment->filename;
-            if (!$fp = fopen($filepath, "w")) {
-                // Unable to open the temporary file to write this to disk.
-                mtrace("--> Unable to save the file to disk for virus scanning. Check file permissions.");
-
-                throw new \core\message\inbound\processing_failed_exception('attachmentfilepermissionsfailed',
-                        'tool_messageinbound');
-            }
-
-            fwrite($fp, $attachment->content);
-            fclose($fp);
-
             // Perform a virus scan now.
             try {
-                \core\antivirus\manager::scan_file($filepath, $attachment->filename, true);
+                \core\antivirus\manager::scan_data($attachment->content);
             } catch (\core\antivirus\scanner_exception $e) {
                 mtrace("--> A virus was found in the attachment '{$attachment->filename}'.");
                 $this->inform_attachment_virus();
index 160c4f0..9ef98e1 100644 (file)
@@ -90,6 +90,25 @@ class api {
 
                 require("$path/db/mobile.php");
                 foreach ($addons as $addonname => $addoninfo) {
+
+                    // Add handlers (for site add-ons).
+                    $handlers = !empty($addoninfo['handlers']) ? $addoninfo['handlers'] : array();
+                    $handlers = json_encode($handlers); // JSON formatted, since it is a complex structure that may vary over time.
+
+                    // Now language strings used by the app.
+                    $lang = array();
+                    if (!empty($addoninfo['lang'])) {
+                        $stringmanager = get_string_manager();
+                        $langs = $stringmanager->get_list_of_translations();
+                        foreach ($langs as $langid => $langname) {
+                            foreach ($addoninfo['lang'] as $stringinfo) {
+                                $lang[$langid][$stringinfo[0]] =
+                                    $stringmanager->get_string($stringinfo[0], $stringinfo[1], null, $langid);
+                            }
+                        }
+                    }
+                    $lang = json_encode($lang);
+
                     $plugininfo = array(
                         'component' => $component,
                         'version' => $version,
@@ -97,7 +116,9 @@ class api {
                         'dependencies' => !empty($addoninfo['dependencies']) ? $addoninfo['dependencies'] : array(),
                         'fileurl' => '',
                         'filehash' => '',
-                        'filesize' => 0
+                        'filesize' => 0,
+                        'handlers' => $handlers,
+                        'lang' => $lang,
                     );
 
                     // All the mobile packages must be under the plugin mobile directory.
@@ -337,6 +358,7 @@ class api {
         }
 
         $features = array(
+            'NoDelegate_CoreOffline' => new lang_string('offlineuse', 'tool_mobile'),
             '$mmLoginEmailSignup' => new lang_string('startsignup'),
             "$mainmenu" => array(
                 '$mmSideMenuDelegate_mmCourses' => new lang_string('mycourses'),
@@ -357,6 +379,8 @@ class api {
                 '$mmCoursesDelegate_mmaGrades' => new lang_string('grades', 'grades'),
                 '$mmCoursesDelegate_mmaCourseCompletion' => new lang_string('coursecompletion', 'completion'),
                 '$mmCoursesDelegate_mmaNotes' => new lang_string('notes', 'notes'),
+                'NoDelegate_CoreCourseDownload' => new lang_string('downloadcourse', 'tool_mobile'),
+                'NoDelegate_CoreCoursesDownload' => new lang_string('downloadcourses', 'tool_mobile'),
             ),
             "$user" => array(
                 '$mmUserDelegate_mmaBadges' => new lang_string('badges', 'badges'),
index 7df769b..6a46264 100644 (file)
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
 require_once("$CFG->libdir/externallib.php");
 
 use external_api;
+use external_files;
 use external_function_parameters;
 use external_value;
 use external_single_structure;
@@ -37,6 +38,7 @@ use context_system;
 use moodle_exception;
 use moodle_url;
 use core_text;
+use coding_exception;
 
 /**
  * This is the external API for this tool.
@@ -91,7 +93,9 @@ class external extends external_api {
                             'fileurl' => new external_value(PARAM_URL, 'The addon package url for download
                                                             or empty if it doesn\'t exist.'),
                             'filehash' => new external_value(PARAM_RAW, 'The addon package hash or empty if it doesn\'t exist.'),
-                            'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.')
+                            'filesize' => new external_value(PARAM_INT, 'The addon package size or empty if it doesn\'t exist.'),
+                            'handlers' => new external_value(PARAM_RAW, 'Handlers definition (JSON)', VALUE_OPTIONAL),
+                            'lang' => new external_value(PARAM_RAW, 'Language strings used by the handlers (JSON)', VALUE_OPTIONAL),
                         )
                     )
                 ),
@@ -330,4 +334,130 @@ class external extends external_api {
             )
         );
     }
+
+    /**
+     * Returns description of get_content() parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.5
+     */
+    public static function get_content_parameters() {
+        return new external_function_parameters(
+            array(
+                'component' => new external_value(PARAM_COMPONENT, 'Component where the class is e.g. mod_assign.'),
+                'method' => new external_value(PARAM_ALPHANUMEXT, 'Method to execute in class \$component\output\mobile.'),
+                'args' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_ALPHANUMEXT, 'Param name.'),
+                            'value' => new external_value(PARAM_RAW, 'Param value.')
+                        )
+                    ), 'Args for the method are optional.', VALUE_OPTIONAL
+                )
+            )
+        );
+    }
+
+    /**
+     * Returns a piece of content to be displayed in the Mobile app, it usually returns a template, javascript and
+     * other structured data that will be used to render a view in the Mobile app..
+     *
+     * Callbacks (placed in \$component\output\mobile) that are called by this web service are responsible for doing the
+     * appropriate security checks to access the information to be returned.
+     *
+     * @param string $component fame of the component.
+     * @param string $method function method name in class \$component\output\mobile.
+     * @param array $args optional arguments for the method.
+     * @return array HTML, JavaScript and other required data and information to create a view in the app.
+     * @since Moodle 3.5
+     * @throws coding_exception
+     */
+    public static function get_content($component, $method, $args = array()) {
+        global $OUTPUT, $PAGE, $USER;
+
+        $params = self::validate_parameters(self::get_content_parameters(),
+            array(
+                'component' => $component,
+                'method' => $method,
+                'args' => $args
+            )
+        );
+
+        // Reformat arguments into something less unwieldy.
+        $arguments = array();
+        foreach ($params['args'] as $paramargument) {
+            $arguments[$paramargument['name']] = $paramargument['value'];
+        }
+
+        // The component was validated via the PARAM_COMPONENT parameter type.
+        $classname = '\\' . $params['component'] .'\output\mobile';
+        if (!method_exists($classname, $params['method'])) {
+            throw new coding_exception("Missing method in $classname");
+        }
+        $result = call_user_func_array(array($classname, $params['method']), array($arguments));
+
+        // Populate otherdata.
+        $otherdata = array();
+        if (!empty($result['otherdata'])) {
+            $result['otherdata'] = (array) $result['otherdata'];
+            foreach ($result['otherdata'] as $name => $value) {
+                $otherdata[] = array(
+                    'name' => $name,
+                    'value' => $value
+                );
+            }
+        }
+
+        return array(
+            'templates'  => !empty($result['templates']) ? $result['templates'] : array(),
+            'javascript' => !empty($result['javascript']) ? $result['javascript'] : '',
+            'otherdata'  => $otherdata,
+            'files'      => !empty($result['files']) ? $result['files'] : array(),
+            'restrict'   => !empty($result['restrict']) ? $result['restrict'] : array(),
+        );
+    }
+
+    /**
+     * Returns description of get_content() result value
+     *
+     * @return array
+     * @since Moodle 3.5
+     */
+    public static function get_content_returns() {
+        return new external_single_structure(
+            array(
+                'templates' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_TEXT, 'ID of the template.'),
+                            'html' => new external_value(PARAM_RAW, 'HTML code.'),
+                        )
+                    ),
+                    'Templates required by the generated content.'
+                ),
+                'javascript' => new external_value(PARAM_RAW, 'JavaScript code.'),
+                'otherdata' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_RAW, 'Field name.'),
+                            'value' => new external_value(PARAM_RAW, 'Field value.')
+                        )
+                    ),
+                    'Other data that can be used or manipulated by the template via 2-way data-binding.'
+                ),
+                'files' => new external_files('Files in the content.'),
+                'restrict' => new external_single_structure(
+                    array(
+                        'users' => new external_multiple_structure(
+                            new external_value(PARAM_INT, 'user id'), 'List of allowed users.', VALUE_OPTIONAL
+                        ),
+                        'courses' => new external_multiple_structure(
+                            new external_value(PARAM_INT, 'course id'), 'List of allowed courses.', VALUE_OPTIONAL
+                        ),
+                    ),
+                    'Restrict this content to certain users or courses.'
+                )
+            )
+        );
+    }
 }
index aa64cc7..5e329c6 100644 (file)
@@ -60,6 +60,13 @@ $functions = array(
                             Is created only in https sites and is restricted by time and ip address.',
         'type'        => 'write',
         'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
-    )
+    ),
+    'tool_mobile_get_content' => array(
+        'classname'   => 'tool_mobile\external',
+        'methodname'  => 'get_content',
+        'description' => 'Returns a piece of content to be displayed in the Mobile app.',
+        'type'        => 'read',
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+    ),
 );
 
index d3ddbec..1693ba5 100644 (file)
@@ -51,6 +51,8 @@ Mis calificaciones|https://someurl.xyz/local/mygrades/index.php|embedded|es
 $string['disabledfeatures'] = 'Disabled features';
 $string['disabledfeatures_desc'] = 'Select here the features you want to disable in the Mobile app for your site. Please note that some features listed here could be already disabled via other site settings. You will have to log out and log in again in the app to see the changes.';
 $string['displayerrorswarning'] = 'Display debug messages (debugdisplay) is enabled. It should be disabled.';
+$string['downloadcourse'] = 'Download course';
+$string['downloadcourses'] = 'Download courses';
 $string['enablesmartappbanners'] = 'Enable App Banners';
 $string['enablesmartappbanners_desc'] = 'If enabled, a banner promoting the mobile app will be displayed when accessing the site using a mobile browser.';
 $string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here; otherwise leave the field empty.';
@@ -79,13 +81,14 @@ $string['mobilecssurl'] = 'CSS';
 $string['mobilefeatures'] = 'Mobile features';
 $string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Manage message outputs.';
 $string['mobilesettings'] = 'Mobile settings';
+$string['offlineuse'] = 'Offline use';
 $string['pluginname'] = 'Moodle Mobile tools';
+$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
+$string['remoteaddons'] = 'Remote add-ons';
 $string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites.';
 $string['setuplink'] = 'App download page';
 $string['setuplink_desc'] = 'URL of page with links to download the mobile app from the App Store and Google Play.';
 $string['smartappbanners'] = 'App Banners';
-$string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
-$string['remoteaddons'] = 'Remote add-ons';
 $string['typeoflogin'] = 'Type of login';
 $string['typeoflogin_desc'] = 'If the site uses a SSO authentication method, then select via a browser window or via an embedded browser. An embedded browser provides a better user experience, though it doesn\'t work with all SSO plugins.';
 $string['getmoodleonyourmobile'] = 'Get the mobile app';
index e61657c..0b8873b 100644 (file)
@@ -29,6 +29,7 @@ defined('MOODLE_INTERNAL') || die();
 global $CFG;
 
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/admin/tool/mobile/tests/fixtures/output/mobile.php');
 
 use tool_mobile\external;
 use tool_mobile\api;
@@ -305,4 +306,53 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $this->expectExceptionMessage(get_string('autologinkeygenerationlockout', 'tool_mobile'));
         $result = external::get_autologin_key($token->privatetoken);
     }
+
+    /**
+     * Test get_content.
+     */
+    public function test_get_content() {
+
+        $paramval = 16;
+        $result = external::get_content('tool_mobile', 'test_view', array(array('name' => 'param1', 'value' => $paramval)));
+        $result = external_api::clean_returnvalue(external::get_content_returns(), $result);
+        $this->assertCount(1, $result['templates']);
+        $this->assertCount(1, $result['otherdata']);
+        $this->assertCount(2, $result['restrict']['users']);
+        $this->assertCount(2, $result['restrict']['courses']);
+        $this->assertEquals('alert();', $result['javascript']);
+        $this->assertEquals('main', $result['templates'][0]['id']);
+        $this->assertEquals('The HTML code', $result['templates'][0]['html']);
+        $this->assertEquals('otherdata1', $result['otherdata'][0]['name']);
+        $this->assertEquals($paramval, $result['otherdata'][0]['value']);
+        $this->assertEquals(array(1, 2), $result['restrict']['users']);
+        $this->assertEquals(array(3, 4), $result['restrict']['courses']);
+        $this->assertEmpty($result['files']);
+    }
+
+    /**
+     * Test get_content non existent function in valid component.
+     */
+    public function test_get_content_non_existent_function() {
+
+        $this->expectException('coding_exception');
+        $result = external::get_content('tool_mobile', 'test_blahblah');
+    }
+
+    /**
+     * Test get_content incorrect component.
+     */
+    public function test_get_content_invalid_component() {
+
+        $this->expectException('moodle_exception');
+        $result = external::get_content('tool_mobile\hack', 'test_view');
+    }
+
+    /**
+     * Test get_content non existent component.
+     */
+    public function test_get_content_non_existent_component() {
+
+        $this->expectException('moodle_exception');
+        $result = external::get_content('tool_blahblahblah', 'test_view');
+    }
 }
diff --git a/admin/tool/mobile/tests/fixtures/output/mobile.php b/admin/tool/mobile/tests/fixtures/output/mobile.php
new file mode 100644 (file)
index 0000000..d803743
--- /dev/null
@@ -0,0 +1,60 @@
+<?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/>.
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_mobile\output;
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Mock class for get_content.
+ *
+ * @package tool_mobile
+ * @copyright 2018 Juan Leyva
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mobile {
+
+    /**
+     * Returns a test view.
+     * @param  array $args Arguments from tool_mobile_get_content WS
+     *
+     * @return array       HTML, javascript and otherdata
+     */
+    public static function test_view($args) {
+        $args = (object) $args;
+
+        return array(
+            'templates' => array(
+                array(
+                    'id' => 'main',
+                    'html' => 'The HTML code',
+                ),
+            ),
+            'javascript' => 'alert();',
+            'otherdata' => array('otherdata1' => $args->param1),
+            'restrict' => array('users' => array(1, 2), 'courses' => array(3, 4)),
+            'files' => array()
+        );
+    }
+}
index 6c2c704..2149382 100644 (file)
@@ -1,6 +1,11 @@
 This files describes changes in tool_mobile code.
 Information provided here is intended especially for developers.
 
+=== 3.5 ===
+
+ * External function tool_mobile::tool_mobile_get_plugins_supporting_mobile now returns additional plugins information required by
+   Moodle Mobile 3.5.0.
+
 === 3.4 ===
 
  * External function tool_mobile::tool_mobile_get_plugins_supporting_mobile is now available via AJAX for not logged users.
index c09e6fd..438a6d5 100644 (file)
@@ -23,7 +23,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2017111300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2017111301; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2017110800; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(
diff --git a/admin/tool/monitor/classes/privacy/provider.php b/admin/tool/monitor/classes/privacy/provider.php
new file mode 100644 (file)
index 0000000..9b481d3
--- /dev/null
@@ -0,0 +1,211 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+/**
+ * Privacy class for requesting user data.
+ *
+ * @package    tool_monitor
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_monitor\privacy;
+
+defined('MOODLE_INTERNAL') || die();
+
+use \core_privacy\local\metadata\collection;
+use \core_privacy\local\request\contextlist;
+use \core_privacy\local\request\approved_contextlist;
+use \core_privacy\local\request\transform;
+use \core_privacy\local\request\writer;
+use \tool_monitor\subscription_manager;
+use \tool_monitor\rule_manager;
+
+/**
+ * Privacy provider for tool_monitor
+ *
+ * @package    tool_monitor
+ * @copyright  2018 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\plugin\provider {
+
+    /**
+     * Get information about the user data stored by this plugin.
+     *
+     * @param  collection $collection An object for storing metadata.
+     * @return collection The metadata.
+     */
+    public static function get_metadata(collection $collection) : collection {
+        $toolmonitorrules = [
+            'description' => 'privacy:metadata:description',
+            'name' => 'privacy:metadata:name',
+            'userid' => 'privacy:metadata:userid',
+            'plugin' => 'privacy:metadata:plugin',
+            'eventname' => 'privacy:metadata:eventname',
+            'template' => 'privacy:metadata:template',
+            'frequency' => 'privacy:metadata:frequency',
+            'timewindow' => 'privacy:metadata:timewindow',
+            'timemodified' => 'privacy:metadata:timemodifiedrule',
+            'timecreated' => 'privacy:metadata:timecreatedrule'
+        ];
+        $toolmonitorsubscriptions = [
+            'userid' => 'privacy:metadata:useridsub',
+            'timecreated' => 'privacy:metadata:timecreatedsub',
+            'lastnotificationsent' => 'privacy:metadata:lastnotificationsent',
+            'inactivedate' => 'privacy:metadata:inactivedate'
+        ];
+        // Tool monitor history doesn't look like it is used at all.
+        $toolmonitorhistory = [
+            'userid' => 'privacy:metadata:useridhistory',
+            'timesent' => 'privacy:metadata:timesent'
+        ];
+        $collection->add_database_table('tool_monitor_rules', $toolmonitorrules, 'privacy:metadata:rulessummary');
+        $collection->add_database_table('tool_monitor_subscriptions', $toolmonitorsubscriptions,
+                'privacy:metadata:subscriptionssummary');
+        $collection->add_database_table('tool_monitor_history', $toolmonitorhistory, 'privacy:metadata:historysummary');
+        $collection->link_subsystem('core_message', 'privacy:metadata:messagesummary');
+        return $collection;
+    }
+
+    /**
+     * Return all contexts for this userid. In this situation the user context.
+     *
+     * @param  int $userid The user ID.
+     * @return contextlist The list of context IDs.
+     */
+    public static function get_contexts_for_userid(int $userid) : contextlist {
+        $params = ['useridrules' => $userid, 'useridsubscriptions' => $userid, 'contextuserrule' => CONTEXT_USER,
+                'contextusersub' => CONTEXT_USER];
+        $sql = "SELECT DISTINCT ctx.id
+                  FROM {context} ctx
+             LEFT JOIN {tool_monitor_rules} mr ON ctx.instanceid = mr.userid AND ctx.contextlevel = :contextuserrule
+             LEFT JOIN {tool_monitor_subscriptions} ms ON ctx.instanceid = ms.userid AND ctx.contextlevel = :contextusersub
+                 WHERE (ms.userid = :useridrules OR mr.userid = :useridsubscriptions)";
+
+        $contextlist = new contextlist();
+        $contextlist->add_from_sql($sql, $params);
+        return $contextlist;
+    }
+
+    /**
+     * Export all event monitor information for the list of contexts and this user.
+     *
+     * @param  approved_contextlist $contextlist The list of approved contexts for a user.
+     */
+    public static function export_user_data(approved_contextlist $contextlist) {
+        global $DB;
+        // Export rules.
+        $context = \context_user::instance($contextlist->get_user()->id);
+        $rules = $DB->get_records('tool_monitor_rules', ['userid' => $contextlist->get_user()->id]);
+        if ($rules) {
+            static::export_monitor_rules($rules, $context);
+        }
+        // Export subscriptions.
+        $subscriptions = subscription_manager::get_user_subscriptions(0, 0, $contextlist->get_user()->id);
+        if ($subscriptions) {
+            static::export_monitor_subscriptions($subscriptions, $context);
+        }
+    }
+
+    /**
+     * Delete all user data for this context.
+     *
+     * @param  \context $context The context to delete data for.
+     */
+    public static function delete_data_for_all_users_in_context(\context $context) {
+        // Only delete data for user contexts.
+        if ($context->contextlevel == CONTEXT_USER) {
+            static::delete_user_data($context->instanceid);
+        }
+    }
+
+    /**
+     * Delete all user data for this user only.
+     *
+     * @param  approved_contextlist $contextlist The list of approved contexts for a user.
+     */
+    public static function delete_data_for_user(approved_contextlist $contextlist) {
+        static::delete_user_data($contextlist->get_user()->id);
+    }
+
+    /**
+     * This does the deletion of user data for the event monitor.
+     *
+     * @param  int $userid The user ID
+     */
+    protected static function delete_user_data(int $userid) {
+        global $DB;
+        // Delete this user's subscriptions first.
+        subscription_manager::delete_user_subscriptions($userid);
+        // Because we only use user contexts the instance ID is the user ID.
+        // Get the rules and check if this user has the capability to delete them.
+        $rules = $DB->get_records('tool_monitor_rules', ['userid' => $userid]);
+        foreach ($rules as $ruledata) {
+            $rule = rule_manager::get_rule($ruledata);
+            // If no-one is suscribed to the rule then it is safe to delete.
+            if ($rule->can_manage_rule($userid) && subscription_manager::count_rule_subscriptions($rule->id) == 0) {
+                $rule->delete_rule();
+            }
+        }
+    }
+
+    /**
+     * This formats and then exports the monitor rules.
+     *
+     * @param  array $rules The monitor rules.
+     * @param  context_user $context The user context
+     */
+    protected static function export_monitor_rules(array $rules, \context_user $context) {
+        foreach ($rules as $rule) {
+            $rule = rule_manager::get_rule($rule);
+            $ruledata = new \stdClass();
+            $ruledata->name = $rule->name;
+            $ruledata->eventname = $rule->get_event_name();
+            $ruledata->description = $rule->get_description($context);
+            $ruledata->plugin = $rule->get_plugin_name();
+            $ruledata->template = $rule->template;
+            $ruledata->frequency = $rule->get_filters_description();
+            $ruledata->course = $rule->get_course_name($context);
+            $ruledata->timecreated = transform::datetime($rule->timecreated);
+            $ruledata->timemodified = transform::datetime($rule->timemodified);
+            writer::with_context($context)->export_data([get_string('privacy:createdrules', 'tool_monitor'),
+                    $rule->name . '_' . $rule->id], $ruledata);
+        }
+    }
+
+    /**
+     * This formats and then exports the event monitor subscriptions.
+     *
+     * @param  array $subscriptions Subscriptions
+     * @param  \context_user $context The user context
+     */
+    protected static function export_monitor_subscriptions(array $subscriptions, \context_user $context) {
+        foreach ($subscriptions as $subscription) {
+            $subscriptiondata = new \stdClass();
+            $subscriptiondata->instancename = $subscription->get_instance_name();
+            $subscriptiondata->eventname = $subscription->get_event_name();
+            $subscriptiondata->frequency = $subscription->get_filters_description();
+            $subscriptiondata->name = $subscription->get_name($context);
+            $subscriptiondata->description = $subscription->get_description($context);
+            $subscriptiondata->pluginname = $subscription->get_plugin_name();
+            $subscriptiondata->course = $subscription->get_course_name($context);
+            $subscriptiondata->timecreated = transform::datetime($subscription->timecreated);
+            $subscriptiondata->lastnotificationsent = transform::datetime($subscription->lastnotificationsent);
+            writer::with_context($context)->export_data([get_string('privacy:subscriptions', 'tool_monitor'),
+                    $subscriptiondata->name . '_' . $subscription->id, $subscriptiondata->course, $subscriptiondata->instancename],
+                    $subscriptiondata);
+        }
+    }
+}
index 3c5e6d2..4cc3920 100644 (file)
@@ -51,14 +51,15 @@ class rule {
     }
 
     /**
-     * Can the current user manage this rule?
+     * Can the user manage this rule? Defaults to $USER.
      *
+     * @param int $userid Check against this userid.
      * @return bool true if the current user can manage this rule, else false.
      */
-    public function can_manage_rule() {
+    public function can_manage_rule($userid = null) {
         $courseid = $this->courseid;
         $context = empty($courseid) ? \context_system::instance() : \context_course::instance($this->courseid);
-        return has_capability('tool/monitor:managerules', $context);
+        return has_capability('tool/monitor:managerules', $context, $userid);
     }
 
     /**
index ed41095..81d20bb 100644 (file)
@@ -79,6 +79,28 @@ $string['monitor:managetool'] = 'Enable/disable event monitoring';
 $string['monitor:subscribe'] = 'Subscribe to event monitor rules';
 $string['norules'] = 'There are no event monitoring rules.';
 $string['pluginname'] = 'Event monitor';
+$string['privacy:createdrules'] = 'Event monitor rules I created';
+$string['privacy:metadata:description'] = 'Description of the rule';
+$string['privacy:metadata:eventname'] = 'Fully qualified name of the event';
+$string['privacy:metadata:frequency'] = 'Frequency of notifications';
+$string['privacy:metadata:historysummary'] = 'Stores the history of the message notifications sent';
+$string['privacy:metadata:inactivedate'] = 'Period of time, in days, after which an inactive subscription will be removed completely';
+$string['privacy:metadata:lastnotificationsent'] = 'When a notification was last sent for this subscription.';
+$string['privacy:metadata:messagesummary'] = 'Notifications are sent to the message system.';
+$string['privacy:metadata:name'] = 'Name of the rule';
+$string['privacy:metadata:plugin'] = 'Frankenstlye name of the plugin';
+$string['privacy:metadata:rulessummary'] = 'This stores monitor rules.';
+$string['privacy:metadata:subscriptionssummary'] = 'Stores user subscriptions to various rules';
+$string['privacy:metadata:template'] = 'Message template';
+$string['privacy:metadata:timecreatedrule'] = 'When this rule was created';
+$string['privacy:metadata:timecreatedsub'] = 'When this subscription was created';
+$string['privacy:metadata:timemodifiedrule'] = 'When this rule was last modified';
+$string['privacy:metadata:timesent'] = 'When the message was sent';
+$string['privacy:metadata:timewindow'] = 'Time window in seconds';
+$string['privacy:metadata:userid'] = 'Id of user who created the rule';
+$string['privacy:metadata:useridhistory'] = 'User to whom this notification was sent';
+$string['privacy:metadata:useridsub'] = 'User id of the subscriber';
+$string['privacy:subscriptions'] = 'My event monitor subscriptions';
 $string['processevents'] = 'Process events';
 $string['rulename'] = 'Rule name';
 $string['ruleareyousure'] = 'Are you sure you want to delete the rule "{$a}"?';
diff --git a/admin/tool/monitor/tests/privacy_test.php b/admin/tool/monitor/tests/privacy_test.php
new file mode 100644 (file)
index 0000000..5c5f11c
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2018 Adrian Greeve <adriangreeve.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use \tool_monitor\privacy\provider;
+use \core_privacy\local\request\approved_contextlist;
+
+/**
+ * Privacy test for the event monitor
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2018 Adrian Greeve <adriangreeve.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_monitor_privacy_testcase extends advanced_testcase {
+
+    /**
+     * Set up method.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+        // Enable monitor.
+        set_config('enablemonitor', 1, 'tool_monitor');
+    }
+
+    /**
+     * Assign a capability to $USER
+     * The function creates a student $USER if $USER->id is empty
+     *
+     * @param string $capability capability name
+     * @param int $contextid
+     * @param int $roleid
+     * @return int the role id - mainly returned for creation, so calling function can reuse it
+     */
+    public static function assign_user_capability($capability, $contextid, $roleid = null) {
+        global $USER;
+
+        // Create a new student $USER if $USER doesn't exist.
+        if (empty($USER->id)) {
+            $user  = self::getDataGenerator()->create_user();
+            self::setUser($user);
+        }
+
+        if (empty($roleid)) {
+            $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
+        }
+
+        assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
+
+        role_assign($roleid, $USER->id, $contextid);
+
+        accesslib_clear_all_caches_for_unit_testing();
+
+        return $roleid;
+    }
+
+    /**
+     * Test that a collection with data is returned when calling this function.
+     */
+    public function test_get_metadata() {
+        $collection = new \core_privacy\local\metadata\collection('tool_monitor');
+        $collection = provider::get_metadata($collection);
+        $this->assertNotEmpty($collection);
+    }
+
+    /**
+     * Check that a user context is returned if there is any user data for this user.
+     */
+    public function test_get_contexts_for_userid() {
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $usercontext2 = \context_user::instance($user2->id);
+        $this->assertEmpty(provider::get_contexts_for_userid($user->id));
+        $this->assertEmpty(provider::get_contexts_for_userid($user2->id));
+
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule with this user.
+        $this->setUser($user);
+        $rule = $monitorgenerator->create_rule();
+        $contextlist = provider::get_contexts_for_userid($user->id);
+
+        // Check that we only get back one context.
+        $this->assertCount(1, $contextlist);
+
+        // Check that a context is returned for just creating a rule.
+        $this->assertEquals($usercontext->id, $contextlist->get_contextids()[0]);
+
+        $this->setUser($user2);
+
+        $record = new stdClass();
+        $record->courseid = 0;
+        $record->userid = $user2->id;
+        $record->ruleid = $rule->id;
+
+        $subscription = $monitorgenerator->create_subscription($record);
+        $contextlist = provider::get_contexts_for_userid($user2->id);
+
+        // Check that we only get back one context.
+        $this->assertCount(1, $contextlist);
+
+        // Check that a context is returned for just subscribing to a rule.
+        $this->assertEquals($usercontext2->id, $contextlist->get_contextids()[0]);
+    }
+
+    /**
+     * Test that user data is exported correctly.
+     */
+    public function test_export_user_data() {
+        $user = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $this->setUser($user);
+        $rulerecord = (object)['name' => 'privacy rule'];
+        $rule = $monitorgenerator->create_rule($rulerecord);
+
+        $secondrulerecord = (object)['name' => 'privacy rule2'];
+        $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+        $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+        $subscription = $monitorgenerator->create_subscription($subscription);
+
+        $writer = \core_privacy\local\request\writer::with_context($usercontext);
+        $this->assertFalse($writer->has_any_data());
+
+        $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+        provider::export_user_data($approvedlist);
+
+        // Check that the rules created by this user are exported.
+        $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+                $rulerecord->name . '_' . $rule->id])->name);
+        $this->assertEquals($secondrulerecord->name, $writer->get_data([get_string('privacy:createdrules', 'tool_monitor'),
+                $secondrulerecord->name . '_' . $rule2->id])->name);
+
+        // Check that the subscriptions for this user are also exported.
+        $this->assertEquals($rulerecord->name, $writer->get_data([get_string('privacy:subscriptions', 'tool_monitor'),
+                $rulerecord->name . '_' . $subscription->id, 'Site' , 'All events'])->name);
+    }
+
+    /**
+     * Test deleting all user data for a specific context.
+     */
+    public function test_delete_data_for_all_users_in_context() {
+        global $DB;
+
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $usercontext2 = \context_user::instance($user2->id);
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $this->setUser($user);
+        // Need to give user one the ability to manage rules.
+        $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+        $rulerecord = (object)['name' => 'privacy rule'];
+        $rule = $monitorgenerator->create_rule($rulerecord);
+
+        $secondrulerecord = (object)['name' => 'privacy rule2'];
+        $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+        $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+        $subscription = $monitorgenerator->create_subscription($subscription);
+
+        // Have user 2 subscribe to the second rule created by user 1.
+        $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+        $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+        $this->setUser($user2);
+        $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+        $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+        $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+        $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+        // Try a different context first.
+        provider::delete_data_for_all_users_in_context(context_system::instance());
+
+        // Get all of the monitor rules.
+        $dbrules = $DB->get_records('tool_monitor_rules');
+
+        // All of the rules should still be present.
+        $this->assertCount(3, $dbrules);
+        $this->assertEquals($user->id, $dbrules[$rule->id]->userid);
+        $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+        $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+        // Delete everything for the first user context.
+        provider::delete_data_for_all_users_in_context($usercontext);
+
+        // Get all of the monitor rules.
+        $dbrules = $DB->get_records('tool_monitor_rules');
+
+        // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+        $this->assertCount(2, $dbrules);
+        $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+        $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+        // Get all of the monitor subscriptions.
+        $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+        // There should be two subscriptions left, both for user 2.
+        $this->assertCount(2, $dbsubs);
+        $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+        $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+    }
+
+    /**
+     * This should work identical to the above test.
+     */
+    public function test_delete_data_for_user() {
+        global $DB;
+
+        $user = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $usercontext = \context_user::instance($user->id);
+        $usercontext2 = \context_user::instance($user2->id);
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        $this->setUser($user);
+        // Need to give user one the ability to manage rules.
+        $this->assign_user_capability('tool/monitor:managerules', \context_system::instance());
+
+        $rulerecord = (object)['name' => 'privacy rule'];
+        $rule = $monitorgenerator->create_rule($rulerecord);
+
+        $secondrulerecord = (object)['name' => 'privacy rule2'];
+        $rule2 = $monitorgenerator->create_rule($secondrulerecord);
+
+        $subscription = (object)['ruleid' => $rule->id, 'userid' => $user->id];
+        $subscription = $monitorgenerator->create_subscription($subscription);
+
+        // Have user 2 subscribe to the second rule created by user 1.
+        $subscription2 = (object)['ruleid' => $rule2->id, 'userid' => $user2->id];
+        $subscription2 = $monitorgenerator->create_subscription($subscription2);
+
+        $this->setUser($user2);
+        $thirdrulerecord = (object)['name' => 'privacy rule for second user'];
+        $rule3 = $monitorgenerator->create_rule($thirdrulerecord);
+
+        $subscription3 = (object)['ruleid' => $rule3->id, 'userid' => $user2->id];
+        $subscription3 = $monitorgenerator->create_subscription($subscription3);
+
+        $approvedlist = new approved_contextlist($user, 'tool_monitor', [$usercontext->id]);
+
+        // Delete everything for the first user.
+        provider::delete_data_for_user($approvedlist);
+
+        // Get all of the monitor rules.
+        $dbrules = $DB->get_records('tool_monitor_rules');
+
+        // Only the rules for user 1 that does not have any more subscriptions should be deleted (the first rule).
+        $this->assertCount(2, $dbrules);
+        $this->assertEquals($user->id, $dbrules[$rule2->id]->userid);
+        $this->assertEquals($user2->id, $dbrules[$rule3->id]->userid);
+
+        // Get all of the monitor subscriptions.
+        $dbsubs = $DB->get_records('tool_monitor_subscriptions');
+        // There should be two subscriptions left, both for user 2.
+        $this->assertCount(2, $dbsubs);
+        $this->assertEquals($user2->id, $dbsubs[$subscription2->id]->userid);
+        $this->assertEquals($user2->id, $dbsubs[$subscription3->id]->userid);
+    }
+}
diff --git a/admin/tool/policy/accept.php b/admin/tool/policy/accept.php
new file mode 100644 (file)
index 0000000..8a79198
--- /dev/null
@@ -0,0 +1,65 @@
+<?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/>.
+
+/**
+ * Accept policies on behalf of users (non-JS version)
+ *
+ * @package     tool_policy
+ * @copyright   2018 Marina Glancy
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(__DIR__.'/../../../config.php');
+require_once($CFG->dirroot.'/user/editlib.php');
+
+$userids = optional_param_array('userids', null, PARAM_INT);
+$versionids = optional_param_array('versionids', null, PARAM_INT);
+$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
+
+require_login();
+if (isguestuser()) {
+    print_error('noguest');
+}
+$context = context_system::instance();
+
+$PAGE->set_context($context);
+$PAGE->set_url(new moodle_url('/admin/tool/policy/accept.php'));
+
+if ($returnurl) {
+    $returnurl = new moodle_url($returnurl);
+} else if (count($userids) == 1) {
+    $userid = reset($userids);
+    $returnurl = new moodle_url('/admin/tool/policy/user.php', ['userid' => $userid]);
+} else {
+    $returnurl = new moodle_url('/admin/tool/policy/acceptances.php');
+}
+// Initialise the form, this will also validate users, versions and check permission to accept policies.
+$form = new \tool_policy\form\accept_policy(null,
+    ['versionids' => $versionids, 'userids' => $userids, 'showbuttons' => true]);
+$form->set_data(['returnurl' => $returnurl]);
+
+if ($form->is_cancelled()) {
+    redirect($returnurl);
+} else if ($form->get_data()) {
+    $form->process();
+    redirect($returnurl);
+}
+
+$output = $PAGE->get_renderer('tool_policy');
+echo $output->header();
+echo $output->heading(get_string('consentdetails', 'tool_policy'));
+$form->display();
+echo $output->footer();
diff --git a/admin/tool/policy/acceptances.php b/admin/tool/policy/acceptances.php
new file mode 100644 (file)
index 0000000..dbf0dc9
--- /dev/null
@@ -0,0 +1,65 @@
+<?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/>.
+
+/**
+ * View user acceptances to the policies
+ *
+ * @package     tool_policy
+ * @copyright   2018 Marina Glancy
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(__DIR__.'/../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+use core\output\notification;
+
+$policyid = optional_param('policyid', null, PARAM_INT);
+$versionid = optional_param('versionid', null, PARAM_INT);
+$versionid = optional_param('versionid', null, PARAM_INT);
+$filtersapplied = optional_param_array('unified-filters', [], PARAM_NOTAGS);
+
+$acceptancesfilter = new \tool_policy\output\acceptances_filter($policyid, $versionid, $filtersapplied);
+$policyid = $acceptancesfilter->get_policy_id_filter();
+$versionid = $acceptancesfilter->get_version_id_filter();
+
+// Set up the page as an admin page 'tool_policy_managedocs'.
+$urlparams = ($policyid ? ['policyid' => $policyid] : []) + ($versionid ? ['versionid' => $versionid] : []);
+admin_externalpage_setup('tool_policy_acceptances', '', $urlparams,
+    new moodle_url('/admin/tool/policy/acceptances.php'));
+
+$acceptancesfilter->validate_ids();
+$output = $PAGE->get_renderer('tool_policy');
+if ($acceptancesfilter->get_versions()) {
+    $acceptances = new \tool_policy\acceptances_table('tool_policy_user_acceptances', $acceptancesfilter, $output);
+    if ($acceptances->is_downloading()) {
+        $acceptances->download();
+    }
+}
+
+echo $output->header();
+echo $output->heading(get_string('useracceptances', 'tool_policy'));
+echo $output->render($acceptancesfilter);
+if (!empty($acceptances)) {
+    $acceptances->display();
+} else if ($acceptancesfilter->get_avaliable_policies()) {
+    // There are no non-guest policies.
+    echo $output->notification(get_string('selectpolicyandversion', 'tool_policy'), notification::NOTIFY_INFO);
+} else {
+    // There are no non-guest policies.
+    echo $output->notification(get_string('nopolicies', 'tool_policy'), notification::NOTIFY_INFO);
+}
+echo $output->footer();
diff --git a/admin/tool/policy/amd/build/acceptances_filter.min.js b/admin/tool/policy/amd/build/acceptances_filter.min.js
new file mode 100644 (file)
index 0000000..793e5af
Binary files /dev/null and b/admin/tool/policy/amd/build/acceptances_filter.min.js differ
diff --git a/admin/tool/policy/amd/build/acceptances_filter_datasource.min.js b/admin/tool/policy/amd/build/acceptances_filter_datasource.min.js
new file mode 100644 (file)
index 0000000..567ed4a
Binary files /dev/null and b/admin/tool/policy/amd/build/acceptances_filter_datasource.min.js differ
diff --git a/admin/tool/policy/amd/build/acceptmodal.min.js b/admin/tool/policy/amd/build/acceptmodal.min.js
new file mode 100644 (file)
index 0000000..8c9aab2
Binary files /dev/null and b/admin/tool/policy/amd/build/acceptmodal.min.js differ
diff --git a/admin/tool/policy/amd/build/jquery-eu-cookie-law-popup.min.js b/admin/tool/policy/amd/build/jquery-eu-cookie-law-popup.min.js
new file mode 100644 (file)
index 0000000..8ea0109
Binary files /dev/null and b/admin/tool/policy/amd/build/jquery-eu-cookie-law-popup.min.js differ
diff --git a/admin/tool/policy/amd/build/managedocsactions.min.js b/admin/tool/policy/amd/build/managedocsactions.min.js
new file mode 100644 (file)
index 0000000..5c351dc
Binary files /dev/null and b/admin/tool/policy/amd/build/managedocsactions.min.js differ
diff --git a/admin/tool/policy/amd/build/policyactions.min.js b/admin/tool/policy/amd/build/policyactions.min.js
new file mode 100644 (file)
index 0000000..9a87001
Binary files /dev/null and b/admin/tool/policy/amd/build/policyactions.min.js differ
diff --git a/admin/tool/policy/amd/src/acceptances_filter.js b/admin/tool/policy/amd/src/acceptances_filter.js
new file mode 100644 (file)
index 0000000..b02c64b
--- /dev/null
@@ -0,0 +1,144 @@
+// 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/>.
+
+/**
+ * Unified filter page JS module for the course participants page.
+ *
+ * @module     tool_policy/acceptances_filter
+ * @package    tool_policy
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'],
+    function($, Autocomplete, Str, Notification) {
+
+        /**
+         * Selectors.
+         *
+         * @access private
+         * @type {{UNIFIED_FILTERS: string}}
+         */
+        var SELECTORS = {
+            UNIFIED_FILTERS: '#unified-filters'
+        };
+
+        /**
+         * Init function.
+         *
+         * @method init
+         * @private
+         */
+        var init = function() {
+            var stringkeys = [{
+                key: 'filterplaceholder',
+                component: 'tool_policy'
+            }, {
+                key: 'nofiltersapplied',
+                component: 'tool_policy'
+            }];
+
+            M.util.js_pending('acceptances_filter_datasource');
+            Str.get_strings(stringkeys).done(function(langstrings) {
+                var placeholder = langstrings[0];
+                var noSelectionString = langstrings[1];
+                Autocomplete.enhance(SELECTORS.UNIFIED_FILTERS, true, 'tool_policy/acceptances_filter_datasource', placeholder,
+                    false, true, noSelectionString, true)
+                    .then(function() {
+                        M.util.js_complete('acceptances_filter_datasource');
+
+                        return;
+                    })
+                    .fail(Notification.exception);
+            }).fail(Notification.exception);
+
+            var last = $(SELECTORS.UNIFIED_FILTERS).val();
+            $(SELECTORS.UNIFIED_FILTERS).on('change', function() {
+                var current = $(this).val();
+                var listoffilters = [];
+                var textfilters = [];
+                var updatedselectedfilters = false;
+
+                $.each(current, function(index, catoption) {
+                    var catandoption = catoption.split(':', 2);
+                    if (catandoption.length !== 2) {
+                        textfilters.push(catoption);
+                        return true; // Text search filter.
+                    }
+
+                    var category = catandoption[0];
+                    var option = catandoption[1];
+
+                    // The last option (eg. 'Teacher') out of a category (eg. 'Role') in this loop is the one that was last
+                    // selected, so we want to use that if there are multiple options from the same category. Eg. The user
+                    // may have chosen to filter by the 'Student' role, then wanted to filter by the 'Teacher' role - the
+                    // last option in the category to be selected (in this case 'Teacher') will come last, so will overwrite
+                    // 'Student' (after this if). We want to let the JS know that the filters have been updated.
+                    if (typeof listoffilters[category] !== 'undefined') {
+                        updatedselectedfilters = true;
+                    }
+
+                    listoffilters[category] = option;
+                    return true;
+                });
+
+                // Check if we have something to remove from the list of filters.
+                if (updatedselectedfilters) {
+                    // Go through and put the list into something we can use to update the list of filters.
+                    var updatefilters = [];
+                    for (var category in listoffilters) {
+                        updatefilters.push(category + ":" + listoffilters[category]);
+                    }
+                    updatefilters = updatefilters.concat(textfilters);
+                    $(this).val(updatefilters);
+                }
+
+                // Prevent form from submitting unnecessarily, eg. on blur when no filter is selected.
+                if (last.join(',') != current.join(',')) {
+                    this.form.submit();
+                }
+            });
+        };
+
+        /**
+         * Return the unified user filter form.
+         *
+         * @method getForm
+         * @return {DOMElement}
+         */
+        var getForm = function() {
+            return $(SELECTORS.UNIFIED_FILTERS).closest('form');
+        };
+
+        return /** @alias module:core/form-autocomplete */ {
+            /**
+             * Initialise the unified user filter.
+             *
+             * @method init
+             */
+            init: function() {
+                init();
+            },
+
+            /**
+             * Return the unified user filter form.
+             *
+             * @method getForm
+             * @return {DOMElement}
+             */
+            getForm: function() {
+                return getForm();
+            }
+        };
+    });
diff --git a/admin/tool/policy/amd/src/acceptances_filter_datasource.js b/admin/tool/policy/amd/src/acceptances_filter_datasource.js
new file mode 100644 (file)
index 0000000..7b1f98e
--- /dev/null
@@ -0,0 +1,93 @@
+// 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/>.
+
+/**
+ * Datasource for the tool_policy/acceptances_filter.
+ *
+ * This module is compatible with core/form-autocomplete.
+ *
+ * @package    tool_policy
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
+
+    return /** @alias module:tool_policy/acceptances_filter_datasource */ {
+        /**
+         * List filter options.
+         *
+         * @param {String} selector The select element selector.
+         * @param {String} query The query string.
+         * @return {Promise}
+         */
+        list: function(selector, query) {
+            var filteredOptions = [];
+
+            var el = $(selector);
+            var originalOptions = $(selector).data('originaloptionsjson');
+            var selectedFilters = el.val();
+            $.each(originalOptions, function(index, option) {
+                // Skip option if it does not contain the query string.
+                if ($.trim(query) !== '' && option.label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) === -1) {
+                    return true;
+                }
+                // Skip filters that have already been selected.
+                if ($.inArray(option.value, selectedFilters) > -1) {
+                    return true;
+                }
+
+                filteredOptions.push(option);
+                return true;
+            });
+
+            var deferred = new $.Deferred();
+            deferred.resolve(filteredOptions);
+
+            return deferred.promise();
+        },
+
+        /**
+         * Process the results for auto complete elements.
+         *
+         * @param {String} selector The selector of the auto complete element.
+         * @param {Array} results An array or results.
+         * @return {Array} New array of results.
+         */
+        processResults: function(selector, results) {
+            var options = [];
+            $.each(results, function(index, data) {
+                options.push({
+                    value: data.value,
+                    label: data.label
+                });
+            });
+            return options;
+        },
+
+        /**
+         * Source of data for Ajax element.
+         *
+         * @param {String} selector The selector of the auto complete element.
+         * @param {String} query The query string.
+         * @param {Function} callback A callback function receiving an array of results.
+         */
+        /* eslint-disable promise/no-callback-in-promise */
+        transport: function(selector, query, callback) {
+            this.list(selector, query).then(callback).catch(Notification.exception);
+        }
+    };
+
+});
diff --git a/admin/tool/policy/amd/src/acceptmodal.js b/admin/tool/policy/amd/src/acceptmodal.js
new file mode 100644 (file)
index 0000000..07b9ed7
--- /dev/null
@@ -0,0 +1,243 @@
+// 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/>.
+
+/**
+ * Add policy consent modal to the page
+ *
+ * @module     tool_policy/acceptmodal
+ * @class      AcceptOnBehalf
+ * @package    tool_policy
+ * @copyright  2018 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/notification', 'core/fragment',
+        'core/ajax', 'core/yui'],
+    function($, Str, ModalFactory, ModalEvents, Notification, Fragment, Ajax, Y) {
+
+        "use strict";
+
+        /**
+         * Constructor
+         *
+         * @param {int} contextid
+         *
+         * Each call to init gets it's own instance of this class.
+         */
+        var AcceptOnBehalf = function(contextid) {
+            this.contextid = contextid;
+            this.init();
+        };
+
+        /**
+         * @var {Modal} modal
+         * @private
+         */
+        AcceptOnBehalf.prototype.modal = null;
+
+        /**
+         * @var {int} contextid
+         * @private
+         */
+        AcceptOnBehalf.prototype.contextid = -1;
+
+        /**
+         * @var {Array} strings
+         * @private
+         */
+        AcceptOnBehalf.prototype.stringKeys = [
+            {
+                key: 'consentdetails',
+                component: 'tool_policy'
+            },
+            {
+                key: 'iagreetothepolicy',
+                component: 'tool_policy'
+            },
+            {
+                key: 'selectusersforconsent',
+                component: 'tool_policy'
+            },
+            {
+                key: 'ok'
+            }
+        ];
+
+        /**
+         * Initialise the class.
+         *
+         * @private
+         */
+        AcceptOnBehalf.prototype.init = function() {
+            // Initialise for links accepting policies for individual users.
+            var triggers = $('a[data-action=acceptmodal]');
+            triggers.on('click', function(e) {
+                e.preventDefault();
+                var href = $(e.currentTarget).attr('href'),
+                    formData = href.slice(href.indexOf('?') + 1);
+                this.showFormModal(formData);
+            }.bind(this));
+
+            // Initialise for multiple users acceptance form.
+            triggers = $('form[data-action=acceptmodal]');
+            triggers.on('submit', function(e) {
+                e.preventDefault();
+                if ($(e.currentTarget).find('input[type=checkbox][name="userids[]"]:checked').length) {
+                    var formData = $(e.currentTarget).serialize();
+                    this.showFormModal(formData, triggers);
+                } else {
+                    Str.get_strings(this.stringKeys).done(function(strings) {
+                        Notification.alert('', strings[2], strings[3]);
+                    });
+                }
+            }.bind(this));
+        };
+
+        /**
+         * Show modal with a form
+         *
+         * @param {String} formData
+         * @param {object} triggerElement The trigger HTML jQuery object
+         */
+        AcceptOnBehalf.prototype.showFormModal = function(formData, triggerElement) {
+            // Fetch the title string.
+            Str.get_strings(this.stringKeys).done(function(strings) {
+                // Create the modal.
+                ModalFactory.create({
+                    type: ModalFactory.types.SAVE_CANCEL,
+                    title: strings[0],
+                    body: ''
+                }, triggerElement).done(function(modal) {
+                    this.modal = modal;
+                    this.setupFormModal(formData, strings[1]);
+                }.bind(this));
+            }.bind(this))
+                .fail(Notification.exception);
+        };
+
+        /**
+         * Setup form inside a modal
+         *
+         * @param {String} formData
+         * @param {String} saveText
+         */
+        AcceptOnBehalf.prototype.setupFormModal = function(formData, saveText) {
+            var modal = this.modal;
+
+            modal.setLarge();
+
+            modal.setSaveButtonText(saveText);
+
+            // We want to reset the form every time it is opened.
+            modal.getRoot().on(ModalEvents.hidden, this.destroy.bind(this));
+
+            modal.setBody(this.getBody(formData));
+
+            // We catch the modal save event, and use it to submit the form inside the modal.
+            // Triggering a form submission will give JS validation scripts a chance to check for errors.
+            modal.getRoot().on(ModalEvents.save, this.submitForm.bind(this));
+            // We also catch the form submit event and use it to submit the form with ajax.
+            modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
+
+            modal.show();
+        };
+
+        /**
+         * Load the body of the modal (contains the form)
+         *
+         * @method getBody
+         * @private
+         * @param {String} formData
+         * @return {Promise}
+         */
+        AcceptOnBehalf.prototype.getBody = function(formData) {
+            if (typeof formData === "undefined") {
+                formData = {};
+            }
+            // Get the content of the modal.
+            var params = {jsonformdata: JSON.stringify(formData)};
+            return Fragment.loadFragment('tool_policy', 'accept_on_behalf', this.contextid, params);
+        };
+
+        /**
+         * Submit the form inside the modal via AJAX request
+         *
+         * @method submitFormAjax
+         * @private
+         * @param {Event} e Form submission event.
+         */
+        AcceptOnBehalf.prototype.submitFormAjax = function(e) {
+            // We don't want to do a real form submission.
+            e.preventDefault();
+
+            // Convert all the form elements values to a serialised string.
+            var formData = this.modal.getRoot().find('form').serialize();
+
+            var requests = Ajax.call([{
+                methodname: 'tool_policy_submit_accept_on_behalf',
+                args: {jsonformdata: JSON.stringify(formData)}
+            }]);
+            requests[0].done(function(data) {
+                if (data.validationerrors) {
+                    this.modal.setBody(this.getBody(formData));
+                } else {
+                    this.close();
+                }
+            }.bind(this)).fail(Notification.exception);
+        };
+
+        /**
+         * This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
+         *
+         * @method submitForm
+         * @param {Event} e Form submission event.
+         * @private
+         */
+        AcceptOnBehalf.prototype.submitForm = function(e) {
+            e.preventDefault();
+            this.modal.getRoot().find('form').submit();
+        };
+
+        /**
+         * Close the modal
+         */
+        AcceptOnBehalf.prototype.close = function() {
+            this.destroy();
+            document.location.reload();
+        };
+
+        /**
+         * Destroy the modal
+         */
+        AcceptOnBehalf.prototype.destroy = function() {
+            Y.use('moodle-core-formchangechecker', function() {
+                M.core_formchangechecker.reset_form_dirty_state();
+            });
+            this.modal.destroy();
+        };
+
+        return /** @alias module:tool_policy/acceptmodal */ {
+            // Public variables and functions.
+            /**
+             * Attach event listeners to initialise this module.
+             *
+             * @method init
+             * @param {int} contextid The contextid for the course.
+             * @return {AcceptOnBehalf}
+             */
+            getInstance: function(contextid) {
+                return new AcceptOnBehalf(contextid);
+            }
+        };
+    });
diff --git a/admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js b/admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
new file mode 100644 (file)
index 0000000..22688c5
--- /dev/null
@@ -0,0 +1,254 @@
+/**\r
+ *     \r
+ * JQUERY EU COOKIE LAW POPUPS\r
+ * version 1.0.1\r
+ * \r
+ * Code on Github:\r
+ * https://github.com/wimagguc/jquery-eu-cookie-law-popup\r
+ * \r
+ * To see a live demo, go to:\r
+ * http://www.wimagguc.com/2015/03/jquery-eu-cookie-law-popup/\r
+ * \r
+ * by Richard Dancsi\r
+ * http://www.wimagguc.com/\r
+ * \r
+ */\r
+\r
+define(\r
+['jquery'],\r
+function($) {\r
+\r
+// for ie9 doesn't support debug console >>>\r
+if (!window.console) window.console = {};\r
+if (!window.console.log) window.console.log = function () { };\r
+// ^^^\r
+\r
+$.fn.euCookieLawPopup = (function() {\r
+\r
+       var _self = this;\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // PARAMETERS (MODIFY THIS PART) //////////////////////////////////////////////////////////////\r
+       _self.params = {\r
+               cookiePolicyUrl : 'http://www.wimagguc.com/?cookie-policy',\r
+               popupPosition : 'top',\r
+               colorStyle : 'default',\r
+               compactStyle : false,\r
+               popupTitle : 'This website is using cookies',\r
+               popupText : 'We use cookies to ensure that we give you the best experience on our website. If you continue without changing your settings, we\'ll assume that you are happy to receive all cookies on this website.',\r
+               buttonContinueTitle : 'Continue',\r
+               buttonLearnmoreTitle : 'Learn&nbsp;more',\r
+               buttonLearnmoreOpenInNewWindow : true,\r
+               agreementExpiresInDays : 30,\r
+               autoAcceptCookiePolicy : false,\r
+               htmlMarkup : null\r
+       };\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // VARIABLES USED BY THE FUNCTION (DON'T MODIFY THIS PART) ////////////////////////////////////\r
+       _self.vars = {\r
+               INITIALISED : false,\r
+               HTML_MARKUP : null,\r
+               COOKIE_NAME : 'EU_COOKIE_LAW_CONSENT'\r
+       };\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // PRIVATE FUNCTIONS FOR MANIPULATING DATA ////////////////////////////////////////////////////\r
+\r
+       // Overwrite default parameters if any of those is present\r
+       var parseParameters = function(object, markup, settings) {\r
+\r
+               if (object) {\r
+                       var className = $(object).attr('class') ? $(object).attr('class') : '';\r
+                       if (className.indexOf('eupopup-top') > -1) {\r
+                               _self.params.popupPosition = 'top';\r
+                       }\r
+                       else if (className.indexOf('eupopup-fixedtop') > -1) {\r
+                               _self.params.popupPosition = 'fixedtop';\r
+                       }\r
+                       else if (className.indexOf('eupopup-bottomright') > -1) {\r
+                               _self.params.popupPosition = 'bottomright';\r
+                       }\r
+                       else if (className.indexOf('eupopup-bottomleft') > -1) {\r
+                               _self.params.popupPosition = 'bottomleft';\r
+                       }\r
+                       else if (className.indexOf('eupopup-bottom') > -1) {\r
+                               _self.params.popupPosition = 'bottom';\r
+                       }\r
+                       else if (className.indexOf('eupopup-block') > -1) {\r
+                               _self.params.popupPosition = 'block';\r
+                       }\r
+                       if (className.indexOf('eupopup-color-default') > -1) {\r
+                               _self.params.colorStyle = 'default';\r
+                       }\r
+                       else if (className.indexOf('eupopup-color-inverse') > -1) {\r
+                               _self.params.colorStyle = 'inverse';\r
+                       }\r
+                       if (className.indexOf('eupopup-style-compact') > -1) {\r
+                               _self.params.compactStyle = true;\r
+                       }\r
+               }\r
+\r
+               if (markup) {\r
+                       _self.params.htmlMarkup = markup;\r
+               }\r
+\r
+               if (settings) {\r
+                       if (typeof settings.cookiePolicyUrl !== 'undefined') {\r
+                               _self.params.cookiePolicyUrl = settings.cookiePolicyUrl;\r
+                       }\r
+                       if (typeof settings.popupPosition !== 'undefined') {\r
+                               _self.params.popupPosition = settings.popupPosition;\r
+                       }\r
+                       if (typeof settings.colorStyle !== 'undefined') {\r
+                               _self.params.colorStyle = settings.colorStyle;\r
+                       }\r
+                       if (typeof settings.popupTitle !== 'undefined') {\r
+                               _self.params.popupTitle = settings.popupTitle;\r
+                       }\r
+                       if (typeof settings.popupText !== 'undefined') {\r
+                               _self.params.popupText = settings.popupText;\r
+                       }\r
+                       if (typeof settings.buttonContinueTitle !== 'undefined') {\r
+                               _self.params.buttonContinueTitle = settings.buttonContinueTitle;\r
+                       }\r
+                       if (typeof settings.buttonLearnmoreTitle !== 'undefined') {\r
+                               _self.params.buttonLearnmoreTitle = settings.buttonLearnmoreTitle;\r
+                       }\r
+                       if (typeof settings.buttonLearnmoreOpenInNewWindow !== 'undefined') {\r
+                               _self.params.buttonLearnmoreOpenInNewWindow = settings.buttonLearnmoreOpenInNewWindow;\r
+                       }\r
+                       if (typeof settings.agreementExpiresInDays !== 'undefined') {\r
+                               _self.params.agreementExpiresInDays = settings.agreementExpiresInDays;\r
+                       }\r
+                       if (typeof settings.autoAcceptCookiePolicy !== 'undefined') {\r
+                               _self.params.autoAcceptCookiePolicy = settings.autoAcceptCookiePolicy;\r
+                       }\r
+                       if (typeof settings.htmlMarkup !== 'undefined') {\r
+                               _self.params.htmlMarkup = settings.htmlMarkup;\r
+                       }\r
+               }\r
+\r
+       };\r
+\r
+       var createHtmlMarkup = function() {\r
+\r
+               if (_self.params.htmlMarkup) {\r