Merge branch 'MDL-70863-master' of git://github.com/jleyva/moodle
authorJun Pataleta <jun@moodle.com>
Thu, 29 Apr 2021 07:04:11 +0000 (15:04 +0800)
committerJun Pataleta <jun@moodle.com>
Thu, 29 Apr 2021 07:04:11 +0000 (15:04 +0800)
891 files changed:
.eslintignore
.github/workflows/push.yml
.grunt/tasks/ignorefiles.js
.stylelintignore
.travis.yml
admin/auth_config.php
admin/cli/fix_orphaned_calendar_events.php [new file with mode: 0644]
admin/cli/upgrade.php
admin/message.php
admin/oauth2callback.php
admin/purgecaches.php
admin/settings/courses.php
admin/settings/users.php
admin/tests/behat/webservice_users.feature [new file with mode: 0644]
admin/tool/behat/cli/run.php
admin/tool/cohortroles/classes/output/cohort_role_assignments_table.php
admin/tool/cohortroles/index.php
admin/tool/dataprivacy/amd/build/contactdpo.min.js [new file with mode: 0644]
admin/tool/dataprivacy/amd/build/contactdpo.min.js.map [new file with mode: 0644]
admin/tool/dataprivacy/amd/build/myrequestactions.min.js
admin/tool/dataprivacy/amd/build/myrequestactions.min.js.map
admin/tool/dataprivacy/amd/src/contactdpo.js [new file with mode: 0644]
admin/tool/dataprivacy/amd/src/myrequestactions.js
admin/tool/dataprivacy/classes/external.php
admin/tool/dataprivacy/classes/form/contactdpo.php [new file with mode: 0644]
admin/tool/dataprivacy/classes/output/renderer.php
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/lib.php
admin/tool/dataprivacy/templates/contact_dpo.mustache [deleted file]
admin/tool/dataprivacy/tests/behat/contact_privacy_officer.feature
admin/tool/dataprivacy/tests/behat/my_data_requests.feature [new file with mode: 0644]
admin/tool/httpsreplace/tests/httpsreplace_test.php
admin/tool/lp/templates/user_competency_summary.mustache
admin/tool/monitor/tests/behat/subscription.feature
admin/tool/moodlenet/classes/profile_manager.php
admin/tool/oauth2/classes/form/issuer.php
admin/tool/oauth2/classes/output/renderer.php
admin/tool/oauth2/issuers.php
admin/tool/oauth2/lang/en/tool_oauth2.php
admin/tool/oauth2/pix/notconfigured.svg [new file with mode: 0644]
admin/tool/oauth2/tests/behat/basic_settings.feature
admin/tool/uploaduser/classes/process.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/tests/behat/upload_users.feature
admin/tool/uploaduser/tests/cli_test.php
admin/tool/uploaduser/user_form.php
admin/tool/usertours/classes/manager.php
admin/user/user_bulk_download.php
analytics/classes/prediction.php
analytics/tests/prediction_actions_test.php
analytics/upgrade.txt
auth/db/tests/db_test.php
auth/email/tests/external_test.php
auth/mnet/classes/privacy/provider.php
auth/mnet/lang/en/auth_mnet.php
auth/oauth2/classes/api.php
auth/oauth2/classes/auth.php
auth/oauth2/classes/output/renderer.php
auth/oauth2/linkedlogins.php
auth/oauth2/login.php
availability/condition/completion/tests/behat/availability_completion.feature
availability/condition/profile/classes/frontend.php
availability/condition/profile/tests/behat/availability_profile.feature
availability/condition/profile/tests/condition_test.php
backup/moodle2/backup_course_task.class.php
backup/moodle2/backup_root_task.class.php
backup/moodle2/backup_settingslib.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_final_task.class.php
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_settingslib.php
backup/moodle2/restore_stepslib.php
backup/tests/automated_backup_test.php
backup/tests/backup_cleanup_task_test.php
backup/tests/backup_restore_base_testcase.php [new file with mode: 0644]
backup/tests/backup_restore_permission_test.php [new file with mode: 0644]
backup/upgrade.txt
backup/util/dbops/backup_controller_dbops.class.php
backup/util/dbops/restore_controller_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_anonymizer_helper.class.php
backup/util/settings/tests/settings_test.php
backup/util/ui/tests/behat/import_contentbank_content.feature
backup/util/ui/tests/behat/import_course.feature
backup/util/ui/tests/behat/restore_moodle2_courses.feature
badges/backpack-connect.php
badges/classes/form/external_backpack.php
badges/classes/oauth2/client.php
badges/criteria/award_criteria_cohort.php
badges/criteria/award_criteria_profile.php
badges/renderer.php
badges/tests/badgeslib_test.php
badges/tests/behat/award_badge.feature
badges/tests/behat/criteria_activity.feature
blocks/myprofile/classes/output/myprofile.php
blocks/myprofile/edit_form.php
blocks/myprofile/lang/en/block_myprofile.php
blocks/myprofile/templates/myprofile.mustache
blocks/myprofile/tests/behat/block_myprofile.feature
blog/edit_form.php
calendar/tests/calendartype_test.php
calendar/tests/helpers.php
completion/classes/activity_custom_completion.php
completion/classes/cm_completion_details.php [new file with mode: 0644]
completion/classes/external.php
completion/classes/external/completion_info_exporter.php [new file with mode: 0644]
completion/criteria/completion_criteria_grade.php
completion/tests/behat/behat_completion.php
completion/tests/behat/completion_course_page_display.feature [moved from completion/tests/behat/completion_course_page_checkboxes.feature with 67% similarity]
completion/tests/behat/custom_completion_display_conditions.feature [new file with mode: 0644]
completion/tests/behat/enable_manual_complete_mark.feature
completion/tests/behat/restrict_section_availability.feature
completion/tests/cm_completion_details_test.php [new file with mode: 0644]
completion/tests/externallib_test.php
completion/upgrade.txt
contentbank/amd/build/upload.min.js [new file with mode: 0644]
contentbank/amd/build/upload.min.js.map [new file with mode: 0644]
contentbank/amd/src/upload.js [new file with mode: 0644]
contentbank/classes/form/upload_files.php [new file with mode: 0644]
contentbank/contenttype/h5p/classes/content.php
contentbank/contenttype/h5p/classes/contenttype.php
contentbank/contenttype/h5p/tests/behat/admin_replace_content.feature
contentbank/contenttype/h5p/tests/behat/admin_upload_content.feature
contentbank/contenttype/h5p/tests/behat/disable_contenttypes.feature [new file with mode: 0644]
contentbank/contenttype/h5p/tests/behat/manage_content.feature
contentbank/contenttype/h5p/tests/behat/teacher_replace_content.feature
contentbank/contenttype/h5p/tests/behat/teacher_upload_content.feature
contentbank/contenttype/h5p/tests/content_h5p_test.php
contentbank/files_form.php [deleted file]
contentbank/index.php
contentbank/templates/bankcontent/toolbar.mustache
contentbank/tests/behat/delete_content.feature
contentbank/tests/behat/download_content.feature
contentbank/tests/behat/edit_content.feature
contentbank/tests/behat/events.feature
contentbank/tests/behat/search_content.feature
contentbank/tests/behat/sort_content.feature
contentbank/tests/behat/view_preferences.feature
contentbank/tests/behat/visibility.feature
contentbank/tests/contentbank_test.php
contentbank/upgrade.txt
contentbank/upload.php [deleted file]
contentbank/view.php
course/amd/build/events.min.js
course/amd/build/events.min.js.map
course/amd/build/manual_completion_toggle.min.js [new file with mode: 0644]
course/amd/build/manual_completion_toggle.min.js.map [new file with mode: 0644]
course/amd/build/repository.min.js
course/amd/build/repository.min.js.map
course/amd/build/view.min.js [new file with mode: 0644]
course/amd/build/view.min.js.map [new file with mode: 0644]
course/amd/src/events.js
course/amd/src/manual_completion_toggle.js [new file with mode: 0644]
course/amd/src/repository.js
course/amd/src/view.js [new file with mode: 0644]
course/classes/external/course_summary_exporter.php
course/classes/local/service/content_item_service.php
course/classes/output/activity_information.php [new file with mode: 0644]
course/classes/output/cm_format.php
course/classes/output/section_format/cmitem.php
course/completion.js [deleted file]
course/edit_form.php
course/externallib.php
course/lib.php
course/modlib.php
course/moodleform_mod.php
course/renderer.php
course/templates/activity_date.mustache [new file with mode: 0644]
course/templates/activity_info.mustache [new file with mode: 0644]
course/templates/completion_automatic.mustache [new file with mode: 0644]
course/templates/completion_manual.mustache [new file with mode: 0644]
course/templates/local/cm_format.mustache
course/templates/local/section_format/cmitem.mustache
course/tests/behat/behat_course.php
course/tests/behat/course_activity_dates.feature [new file with mode: 0644]
course/tests/externallib_test.php
course/togglecompletion.php
course/upgrade.txt
course/view.php
customfield/field/text/classes/field_controller.php
enrol/externallib.php
enrol/locallib.php
enrol/manual/manage.php
enrol/self/tests/behat/self_enrolment.feature
enrol/tests/enrollib_test.php
files/tests/converter_test.php
filter/displayh5p/tests/behat/h5p_filter.feature
filter/tex/mimetex.darwin
filter/tex/mimetex.exe
filter/tex/mimetex.freebsd
filter/tex/mimetex.linux
filter/tex/mimetex.linux.aarch64
filter/tex/readme_moodle.txt
filter/tex/thirdpartylibs.xml
grade/classes/external/create_gradecategories.php [new file with mode: 0644]
grade/grading/form/guide/db/services.php
grade/grading/form/rubric/db/services.php
grade/grading/manage.php
grade/grading/tests/behat/behat_grading.php
grade/import/csv/classes/load_data.php
grade/lib.php
grade/report/grader/classes/privacy/provider.php
grade/report/grader/lib.php
grade/report/grader/tests/privacy_test.php
grade/report/overview/lib.php
grade/report/user/externallib.php
grade/report/user/lib.php
grade/tests/external/create_gradecategories_test.php [new file with mode: 0644]
grade/tests/reportlib_test.php
group/index.php
group/lib.php
group/overview.php
group/tests/behat/custom_fields.feature [new file with mode: 0644]
group/tests/lib_test.php
h5p/classes/api.php
h5p/classes/core.php
h5p/classes/editor_ajax.php
h5p/classes/framework.php
h5p/classes/helper.php
h5p/classes/local/library/handler.php
h5p/classes/output/libraries.php
h5p/classes/player.php
h5p/h5plib/v124/joubel/editor/h5peditor.class.php
h5p/h5plib/v124/joubel/editor/readme_moodle.txt
h5p/libraries.php
h5p/templates/h5plibraries.mustache
h5p/tests/api_test.php
h5p/tests/behat/h5p_libraries.feature
h5p/tests/editor_framework_test.php
h5p/tests/generator/lib.php
h5p/tests/generator_test.php
h5p/upgrade.txt
install/lang/da_wp/langconfig.php [new file with mode: 0644]
install/lang/el/install.php
install/lang/hat/admin.php
install/lang/he/langconfig.php
install/lang/id/error.php [new file with mode: 0644]
install/lang/mk/admin.php
install/lang/mk/install.php
install/lang/mk/langconfig.php
install/lang/pt/install.php
install/lang/sv/error.php
install/lang/vi/moodle.php
lang/en/admin.php
lang/en/backup.php
lang/en/completion.php
lang/en/contentbank.php
lang/en/course.php
lang/en/deprecated.txt
lang/en/h5p.php
lang/en/moodle.php
lib/adodb/LICENSE.md
lib/adodb/README.md
lib/adodb/adodb-active-record.inc.php
lib/adodb/adodb-active-recordx.inc.php
lib/adodb/adodb-csvlib.inc.php
lib/adodb/adodb-datadict.inc.php
lib/adodb/adodb-error.inc.php
lib/adodb/adodb-errorhandler.inc.php
lib/adodb/adodb-errorpear.inc.php
lib/adodb/adodb-exceptions.inc.php
lib/adodb/adodb-iterator.inc.php
lib/adodb/adodb-lib.inc.php
lib/adodb/adodb-loadbalancer.inc.php [new file with mode: 0644]
lib/adodb/adodb-memcache.lib.inc.php
lib/adodb/adodb-pager.inc.php
lib/adodb/adodb-pear.inc.php
lib/adodb/adodb-perf.inc.php
lib/adodb/adodb-php4.inc.php
lib/adodb/adodb-time.inc.php
lib/adodb/adodb-xmlschema.inc.php
lib/adodb/adodb-xmlschema03.inc.php
lib/adodb/adodb.inc.php
lib/adodb/datadict/datadict-access.inc.php
lib/adodb/datadict/datadict-db2.inc.php
lib/adodb/datadict/datadict-firebird.inc.php
lib/adodb/datadict/datadict-generic.inc.php
lib/adodb/datadict/datadict-ibase.inc.php
lib/adodb/datadict/datadict-informix.inc.php
lib/adodb/datadict/datadict-mssql.inc.php
lib/adodb/datadict/datadict-mssqlnative.inc.php
lib/adodb/datadict/datadict-mysql.inc.php
lib/adodb/datadict/datadict-oci8.inc.php
lib/adodb/datadict/datadict-postgres.inc.php
lib/adodb/datadict/datadict-sapdb.inc.php
lib/adodb/datadict/datadict-sqlite.inc.php
lib/adodb/datadict/datadict-sybase.inc.php
lib/adodb/drivers/adodb-access.inc.php
lib/adodb/drivers/adodb-ado.inc.php
lib/adodb/drivers/adodb-ado5.inc.php
lib/adodb/drivers/adodb-ado_access.inc.php
lib/adodb/drivers/adodb-ado_mssql.inc.php
lib/adodb/drivers/adodb-ads.inc.php
lib/adodb/drivers/adodb-borland_ibase.inc.php
lib/adodb/drivers/adodb-csv.inc.php
lib/adodb/drivers/adodb-db2.inc.php
lib/adodb/drivers/adodb-db2oci.inc.php
lib/adodb/drivers/adodb-db2ora.inc.php
lib/adodb/drivers/adodb-fbsql.inc.php
lib/adodb/drivers/adodb-firebird.inc.php
lib/adodb/drivers/adodb-ibase.inc.php
lib/adodb/drivers/adodb-informix.inc.php
lib/adodb/drivers/adodb-informix72.inc.php
lib/adodb/drivers/adodb-ldap.inc.php
lib/adodb/drivers/adodb-mssql.inc.php
lib/adodb/drivers/adodb-mssql_n.inc.php
lib/adodb/drivers/adodb-mssqlnative.inc.php
lib/adodb/drivers/adodb-mssqlpo.inc.php
lib/adodb/drivers/adodb-mysql.inc.php
lib/adodb/drivers/adodb-mysqli.inc.php
lib/adodb/drivers/adodb-mysqlpo.inc.php
lib/adodb/drivers/adodb-mysqlt.inc.php
lib/adodb/drivers/adodb-netezza.inc.php
lib/adodb/drivers/adodb-oci8.inc.php
lib/adodb/drivers/adodb-oci805.inc.php
lib/adodb/drivers/adodb-oci8po.inc.php
lib/adodb/drivers/adodb-oci8quercus.inc.php
lib/adodb/drivers/adodb-odbc.inc.php
lib/adodb/drivers/adodb-odbc_db2.inc.php
lib/adodb/drivers/adodb-odbc_mssql.inc.php
lib/adodb/drivers/adodb-odbc_mssql2012.inc.php [new file with mode: 0644]
lib/adodb/drivers/adodb-odbc_oracle.inc.php
lib/adodb/drivers/adodb-odbtp.inc.php
lib/adodb/drivers/adodb-odbtp_unicode.inc.php
lib/adodb/drivers/adodb-oracle.inc.php
lib/adodb/drivers/adodb-pdo.inc.php
lib/adodb/drivers/adodb-pdo_dblib.inc.php [new file with mode: 0644]
lib/adodb/drivers/adodb-pdo_firebird.inc.php [new file with mode: 0644]
lib/adodb/drivers/adodb-pdo_mssql.inc.php
lib/adodb/drivers/adodb-pdo_mysql.inc.php
lib/adodb/drivers/adodb-pdo_oci.inc.php
lib/adodb/drivers/adodb-pdo_pgsql.inc.php
lib/adodb/drivers/adodb-pdo_sqlite.inc.php
lib/adodb/drivers/adodb-pdo_sqlsrv.inc.php
lib/adodb/drivers/adodb-postgres.inc.php
lib/adodb/drivers/adodb-postgres64.inc.php
lib/adodb/drivers/adodb-postgres7.inc.php
lib/adodb/drivers/adodb-postgres8.inc.php
lib/adodb/drivers/adodb-postgres9.inc.php
lib/adodb/drivers/adodb-proxy.inc.php
lib/adodb/drivers/adodb-sapdb.inc.php
lib/adodb/drivers/adodb-sqlanywhere.inc.php
lib/adodb/drivers/adodb-sqlite.inc.php
lib/adodb/drivers/adodb-sqlite3.inc.php
lib/adodb/drivers/adodb-sqlitepo.inc.php
lib/adodb/drivers/adodb-sybase.inc.php
lib/adodb/drivers/adodb-sybase_ase.inc.php
lib/adodb/drivers/adodb-text.inc.php [new file with mode: 0644]
lib/adodb/drivers/adodb-vfp.inc.php
lib/adodb/perf/perf-db2.inc.php
lib/adodb/perf/perf-informix.inc.php
lib/adodb/perf/perf-mssql.inc.php
lib/adodb/perf/perf-mssqlnative.inc.php
lib/adodb/perf/perf-mysql.inc.php
lib/adodb/perf/perf-oci8.inc.php
lib/adodb/perf/perf-postgres.inc.php
lib/adodb/pivottable.inc.php
lib/adodb/readme_moodle.txt
lib/adodb/rsfilter.inc.php
lib/adodb/toexport.inc.php
lib/adodb/tohtml.inc.php
lib/adodb/xmlschema03.dtd
lib/amd/build/templates.min.js
lib/amd/build/templates.min.js.map
lib/amd/build/toast.min.js.map
lib/amd/src/templates.js
lib/amd/src/toast.js
lib/antivirus/clamav/settings.php
lib/authlib.php
lib/behat/classes/behat_config_util.php
lib/behat/classes/behat_generator_base.php
lib/behat/classes/behat_session_trait.php
lib/behat/form_field/behat_form_date.php
lib/classes/event/base.php
lib/classes/external/exporter.php
lib/classes/grades_external.php
lib/classes/navigation/views/primary.php [new file with mode: 0644]
lib/classes/oauth2/api.php
lib/classes/oauth2/discovery/openidconnect.php
lib/classes/oauth2/issuer.php
lib/classes/oauth2/refresh_system_tokens_task.php
lib/classes/oauth2/service/facebook.php [new file with mode: 0644]
lib/classes/oauth2/service/google.php
lib/classes/oauth2/service/imsobv2p1.php
lib/classes/oauth2/service/microsoft.php [new file with mode: 0644]
lib/classes/oauth2/service/nextcloud.php [new file with mode: 0644]
lib/classes/plugin_manager.php
lib/classes/task/calendar_fix_orphaned_events.php [new file with mode: 0644]
lib/classes/task/course_backup_task.php
lib/classes/user.php
lib/completionlib.php
lib/datalib.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/tests/dml_mysqli_read_slave_test.php
lib/dml/tests/dml_pgsql_read_slave_test.php
lib/dml/tests/fixtures/test_moodle_read_slave_trait.php
lib/editor/classes/privacy/provider.php
lib/editor/tests/privacy_provider_test.php
lib/enrollib.php
lib/filelib.php
lib/form/classes/privacy/provider.php
lib/form/filemanager.js
lib/form/tests/privacy_provider_test.php
lib/grade/grade_item.php
lib/modinfolib.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/pagelib.php
lib/phpunit/tests/basic_test.php
lib/plagiarismlib.php
lib/setup.php
lib/setuplib.php
lib/tcpdf/composer.json
lib/tcpdf/fonts/freefont-20120503/CREDITS
lib/tcpdf/include/barcodes/pdf417.php
lib/tcpdf/include/barcodes/qrcode.php
lib/tcpdf/include/tcpdf_colors.php
lib/tcpdf/include/tcpdf_fonts.php
lib/tcpdf/include/tcpdf_images.php
lib/tcpdf/include/tcpdf_static.php
lib/tcpdf/readme_moodle.txt
lib/tcpdf/tcpdf.php
lib/tcpdf/tcpdf_barcodes_1d.php
lib/templates/local/toast/message.mustache
lib/testing/generator/data_generator.php
lib/tests/behat/behat_permissions.php
lib/tests/behat/datetime_any.feature [new file with mode: 0644]
lib/tests/behat/showuseridentity.feature
lib/tests/completion_daily_task_test.php
lib/tests/datalib_test.php
lib/tests/encryption_test.php
lib/tests/event/contentbank_content_uploaded_test.php
lib/tests/event_profile_field_test.php
lib/tests/event_test.php
lib/tests/moodlelib_test.php
lib/tests/myprofilelib_test.php
lib/tests/navigation/views/primary_test.php [new file with mode: 0644]
lib/tests/oauth2_test.php
lib/tests/portfoliolib_test.php
lib/tests/scheduled_task_test.php
lib/tests/task_database_logger_test.php
lib/tests/upgradelib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/upgradelib.php
media/player/videojs/amd/build/loader.min.js
media/player/videojs/amd/build/loader.min.js.map
media/player/videojs/amd/src/loader.js
media/player/videojs/db/services.php
message/amd/build/message_drawer_view_conversation_patcher.min.js
message/amd/build/message_drawer_view_conversation_patcher.min.js.map
message/amd/src/message_drawer_view_conversation_patcher.js
message/output/airnotifier/checkconfiguration.php [new file with mode: 0644]
message/output/airnotifier/classes/manager.php
message/output/airnotifier/lang/en/message_airnotifier.php
message/output/airnotifier/settings.php
message/output/airnotifier/tests/manager_test.php [new file with mode: 0644]
message/templates/message_drawer_view_conversation_body.mustache
message/templates/message_drawer_view_conversation_footer.mustache
message/templates/message_drawer_view_conversation_header.mustache
message/templates/message_preferences.mustache
message/templates/message_preferences_notification_processor.mustache
message/templates/notification_preferences.mustache
message/templates/notification_preferences_component_notification.mustache
message/tests/api_test.php
message/tests/externallib_test.php
message/tests/privacy_provider_test.php
mod/assign/classes/cache/overrides.php [new file with mode: 0644]
mod/assign/classes/completion/custom_completion.php [new file with mode: 0644]
mod/assign/classes/dates.php [new file with mode: 0644]
mod/assign/db/caches.php [new file with mode: 0644]
mod/assign/deprecatedlib.php [new file with mode: 0644]
mod/assign/externallib.php
mod/assign/feedback/offline/importgradeslib.php
mod/assign/feedback/offline/lang/en/assignfeedback_offline.php
mod/assign/feedback/offline/locallib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/overrideedit.php
mod/assign/renderer.php
mod/assign/tests/behat/assign_activity_completion.feature [new file with mode: 0644]
mod/assign/tests/custom_completion_test.php [new file with mode: 0644]
mod/assign/tests/dates_test.php [new file with mode: 0644]
mod/assign/tests/generator/lib.php
mod/assign/tests/lib_test.php
mod/assign/tests/locallib_participants_test.php
mod/assign/tests/locallib_test.php
mod/assign/version.php
mod/book/view.php
mod/chat/classes/dates.php [new file with mode: 0644]
mod/chat/lang/en/chat.php
mod/chat/lang/en/deprecated.txt [new file with mode: 0644]
mod/chat/lib.php
mod/chat/tests/behat/behat_mod_chat.php [new file with mode: 0644]
mod/chat/tests/dates_test.php [new file with mode: 0644]
mod/chat/view.php
mod/choice/deprecatedlib.php [new file with mode: 0644]
mod/choice/lib.php
mod/choice/tests/behat/activity_info_completion_automatic.feature [new file with mode: 0644]
mod/choice/tests/behat/activity_info_completion_manual.feature [new file with mode: 0644]
mod/choice/view.php
mod/data/classes/completion/custom_completion.php [new file with mode: 0644]
mod/data/classes/dates.php [new file with mode: 0644]
mod/data/deprecatedlib.php [new file with mode: 0644]
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/tests/custom_completion_test.php [new file with mode: 0644]
mod/data/tests/dates_test.php [new file with mode: 0644]
mod/data/view.php
mod/feedback/classes/completion/custom_completion.php [new file with mode: 0644]
mod/feedback/classes/dates.php [new file with mode: 0644]
mod/feedback/deprecatedlib.php [new file with mode: 0644]
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/feedback/tests/custom_completion_test.php [new file with mode: 0644]
mod/feedback/tests/dates_test.php [new file with mode: 0644]
mod/feedback/view.php
mod/folder/backup/moodle2/backup_folder_stepslib.php
mod/folder/lib.php
mod/folder/view.php
mod/forum/classes/completion/custom_completion.php [new file with mode: 0644]
mod/forum/classes/dates.php [new file with mode: 0644]
mod/forum/classes/privacy/provider.php
mod/forum/classes/task/send_user_notifications.php
mod/forum/deprecatedlib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/tests/behat/completion_condition_number_discussions.feature
mod/forum/tests/custom_completion_test.php [new file with mode: 0644]
mod/forum/tests/dates_test.php [new file with mode: 0644]
mod/forum/tests/mail_test.php
mod/forum/tests/privacy_provider_test.php
mod/forum/view.php
mod/glossary/classes/completion/custom_completion.php [new file with mode: 0644]
mod/glossary/db/services.php
mod/glossary/deprecatedlib.php [new file with mode: 0644]
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/tests/custom_completion_test.php [new file with mode: 0644]
mod/glossary/view.php
mod/h5pactivity/classes/external/get_user_attempts.php
mod/h5pactivity/classes/local/manager.php
mod/h5pactivity/classes/local/report/participants.php
mod/h5pactivity/db/services.php
mod/h5pactivity/tests/behat/locking.feature [new file with mode: 0644]
mod/h5pactivity/tests/behat/result_longfillin.feature
mod/h5pactivity/tests/external/get_attempts_test.php
mod/h5pactivity/tests/external/get_user_attempts_test.php
mod/h5pactivity/tests/local/manager_test.php
mod/h5pactivity/upgrade.txt [new file with mode: 0644]
mod/h5pactivity/view.php
mod/imscp/view.php
mod/label/classes/completion/custom_completion.php [new file with mode: 0644]
mod/label/tests/behat/label_activity_completion.feature [new file with mode: 0644]
mod/label/view.php
mod/lesson/classes/cache/overrides.php [new file with mode: 0644]
mod/lesson/classes/completion/custom_completion.php [new file with mode: 0644]
mod/lesson/classes/dates.php [new file with mode: 0644]
mod/lesson/db/caches.php [new file with mode: 0644]
mod/lesson/deprecatedlib.php [new file with mode: 0644]
mod/lesson/index.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/overrideedit.php
mod/lesson/renderer.php
mod/lesson/settings.php
mod/lesson/tests/behat/completion_condition_end_reached.feature
mod/lesson/tests/behat/completion_condition_time_spent.feature
mod/lesson/tests/custom_completion_test.php [new file with mode: 0644]
mod/lesson/tests/dates_test.php [new file with mode: 0644]
mod/lesson/tests/generator/lib.php
mod/lesson/version.php
mod/lti/OAuth.php
mod/lti/amd/build/tool_configure_controller.min.js
mod/lti/amd/build/tool_configure_controller.min.js.map
mod/lti/amd/src/tool_configure_controller.js
mod/lti/classes/local/ltiopenid/registration_helper.php
mod/lti/classes/output/registration_upgrade_choice_page.php [new file with mode: 0644]
mod/lti/classes/output/renderer.php
mod/lti/lang/en/lti.php
mod/lti/locallib.php
mod/lti/openid-configuration.php
mod/lti/openid-registration.php
mod/lti/startltiadvregistration.php
mod/lti/templates/registration_upgrade_choice_page.mustache [new file with mode: 0644]
mod/lti/tests/behat/lti_activity_completion.feature [new file with mode: 0644]
mod/lti/tests/openidregistration_test.php
mod/lti/view.php
mod/page/tests/behat/page_activity_completion.feature [new file with mode: 0644]
mod/page/view.php
mod/quiz/accessrule/seb/classes/property_list.php
mod/quiz/attemptlib.php
mod/quiz/classes/cache/overrides.php [new file with mode: 0644]
mod/quiz/classes/completion/custom_completion.php [new file with mode: 0644]
mod/quiz/classes/dates.php [new file with mode: 0644]
mod/quiz/classes/question/bank/custom_view.php
mod/quiz/db/caches.php [new file with mode: 0644]
mod/quiz/deprecatedlib.php [new file with mode: 0644]
mod/quiz/index.php
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/overrideedit.php
mod/quiz/renderer.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/default.php
mod/quiz/report/overview/classes/privacy/provider.php
mod/quiz/report/overview/report.php
mod/quiz/report/overview/tests/privacy_provider_test.php
mod/quiz/review.php
mod/quiz/reviewquestion.php
mod/quiz/tests/behat/attempt_basic.feature
mod/quiz/tests/behat/attempt_redo_questions.feature
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_minimum_attempts.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
mod/quiz/tests/behat/editing_add_from_question_bank.feature
mod/quiz/tests/behat/editing_add_random.feature
mod/quiz/tests/behat/info_page.feature [new file with mode: 0644]
mod/quiz/tests/behat/preview.feature
mod/quiz/tests/custom_completion_test.php [new file with mode: 0644]
mod/quiz/tests/dates_test.php [new file with mode: 0644]
mod/quiz/tests/lib_test.php
mod/quiz/version.php
mod/quiz/view.php
mod/resource/classes/completion/custom_completion.php [new file with mode: 0644]
mod/resource/lib.php
mod/resource/locallib.php
mod/resource/tests/behat/resource_activity_completion.feature [new file with mode: 0644]
mod/scorm/classes/completion/custom_completion.php [new file with mode: 0644]
mod/scorm/classes/dates.php [new file with mode: 0644]
mod/scorm/deprecatedlib.php [new file with mode: 0644]
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/mod_form.php
mod/scorm/tests/custom_completion_test.php [new file with mode: 0644]
mod/scorm/tests/dates_test.php [new file with mode: 0644]
mod/scorm/view.php
mod/survey/classes/completion/custom_completion.php [new file with mode: 0644]
mod/survey/deprecatedlib.php [new file with mode: 0644]
mod/survey/lang/en/survey.php
mod/survey/lib.php
mod/survey/tests/behat/survey_completion.feature
mod/survey/tests/custom_completion_test.php [new file with mode: 0644]
mod/survey/view.php
mod/url/classes/completion/custom_completion.php [new file with mode: 0644]
mod/url/lib.php
mod/url/locallib.php
mod/url/tests/behat/url_activity_completion.feature [new file with mode: 0644]
mod/wiki/renderer.php
mod/wiki/tests/behat/wiki_activity_completion.feature [new file with mode: 0644]
mod/workshop/classes/dates.php [new file with mode: 0644]
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/tests/behat/workshop_activity_completion.feature [new file with mode: 0644]
mod/workshop/tests/behat/workshop_assessment.feature
mod/workshop/tests/dates_test.php [new file with mode: 0644]
mod/workshop/view.php
payment/gateway/paypal/db/services.php
plagiarism/upgrade.txt
question/engine/lib.php
question/engine/questionattemptstep.php
question/engine/questionusage.php
question/engine/renderer.php
question/engine/tests/questionattemptstep_test.php
question/engine/tests/questionusagebyactivity_test.php
question/engine/tests/walkthrough_test.php
question/question.php
question/type/calculatedmulti/edit_calculatedmulti_form.php
question/type/ddimageortext/edit_ddimageortext_form.php
question/type/ddimageortext/questiontype.php
question/type/ddimageortext/tests/behat/add.feature
question/type/ddmarker/edit_ddmarker_form.php
question/type/ddmarker/questiontype.php
question/type/ddmarker/tests/behat/add.feature
question/type/ddwtos/tests/behat/add.feature
question/type/edit_question_form.php
question/type/essay/edit_essay_form.php
question/type/essay/question.php
question/type/essay/questiontype.php
question/type/essay/renderer.php
question/type/essay/tests/behat/add.feature
question/type/essay/tests/question_test.php
question/type/gapselect/edit_form_base.php
question/type/gapselect/questiontypebase.php
question/type/gapselect/tests/behat/add.feature [new file with mode: 0644]
question/type/gapselect/tests/edit_form_test.php
question/type/match/edit_match_form.php
question/type/match/questiontype.php
question/type/match/tests/behat/add.feature
question/type/multianswer/edit_multianswer_form.php
question/type/multichoice/edit_multichoice_form.php
question/type/multichoice/questiontype.php
question/type/multichoice/tests/behat/add.feature
question/type/numerical/edit_numerical_form.php
question/type/numerical/lang/en/qtype_numerical.php
question/type/numerical/questiontype.php
question/type/numerical/tests/behat/add.feature
question/type/questiontypebase.php
question/type/shortanswer/edit_shortanswer_form.php
question/type/shortanswer/questiontype.php
question/type/shortanswer/tests/behat/add.feature
question/type/upgrade.txt
report/insights/classes/output/insight.php
report/log/user.php
report/participation/amd/build/participants.min.js
report/participation/amd/build/participants.min.js.map
report/participation/amd/src/participants.js
report/participation/tests/behat/message_participants.feature
report/progress/classes/local/helper.php [new file with mode: 0644]
report/progress/classes/output/renderer.php [new file with mode: 0644]
report/progress/index.php
report/progress/lang/en/report_progress.php
report/progress/tests/behat/activity_completion_report.feature
report/progress/tests/behat/activity_completion_report_filter.feature [new file with mode: 0644]
report/progress/tests/report_progress_helper_testcase.php [new file with mode: 0644]
repository/contentbank/tests/browser_test.php
repository/contentbank/tests/search_test.php
repository/googledocs/classes/googledocs_content.php [new file with mode: 0644]
repository/googledocs/classes/googledocs_content_search.php [new file with mode: 0644]
repository/googledocs/classes/helper.php [new file with mode: 0644]
repository/googledocs/classes/local/browser/googledocs_drive_content.php [new file with mode: 0644]
repository/googledocs/classes/local/browser/googledocs_root_content.php [new file with mode: 0644]
repository/googledocs/classes/local/browser/googledocs_shared_drives_content.php [new file with mode: 0644]
repository/googledocs/classes/local/node/file_node.php [new file with mode: 0644]
repository/googledocs/classes/local/node/folder_node.php [new file with mode: 0644]
repository/googledocs/classes/local/node/node.php [new file with mode: 0644]
repository/googledocs/classes/rest.php
repository/googledocs/lang/en/repository_googledocs.php
repository/googledocs/lib.php
repository/googledocs/tests/generator/lib.php
repository/googledocs/tests/googledocs_content_testcase.php [new file with mode: 0644]
repository/googledocs/tests/googledocs_search_content_test.php [new file with mode: 0644]
repository/googledocs/tests/helper_test.php [new file with mode: 0644]
repository/googledocs/tests/local/browser/googledocs_drive_content_test.php [new file with mode: 0644]
repository/googledocs/tests/local/browser/googledocs_root_content_test.php [new file with mode: 0644]
repository/googledocs/tests/local/browser/googledocs_shared_drives_content_test.php [new file with mode: 0644]
repository/googledocs/tests/local/node/file_node_test.php [new file with mode: 0644]
repository/googledocs/tests/local/node/folder_node_test.php [new file with mode: 0644]
repository/googledocs/tests/repository_googledocs_testcase.php [new file with mode: 0644]
repository/googledocs/version.php
repository/upgrade.txt
search/classes/external.php
search/classes/manager.php
search/engine/solr/classes/engine.php
search/tests/external_test.php
security.txt
theme/boost/amd/build/bootstrap/alert.min.js
theme/boost/amd/build/bootstrap/alert.min.js.map
theme/boost/amd/build/bootstrap/button.min.js
theme/boost/amd/build/bootstrap/button.min.js.map
theme/boost/amd/build/bootstrap/carousel.min.js
theme/boost/amd/build/bootstrap/carousel.min.js.map
theme/boost/amd/build/bootstrap/collapse.min.js
theme/boost/amd/build/bootstrap/collapse.min.js.map
theme/boost/amd/build/bootstrap/dropdown.min.js
theme/boost/amd/build/bootstrap/dropdown.min.js.map
theme/boost/amd/build/bootstrap/index.min.js [deleted file]
theme/boost/amd/build/bootstrap/index.min.js.map [deleted file]
theme/boost/amd/build/bootstrap/modal.min.js
theme/boost/amd/build/bootstrap/modal.min.js.map
theme/boost/amd/build/bootstrap/popover.min.js
theme/boost/amd/build/bootstrap/popover.min.js.map
theme/boost/amd/build/bootstrap/scrollspy.min.js
theme/boost/amd/build/bootstrap/scrollspy.min.js.map
theme/boost/amd/build/bootstrap/tab.min.js
theme/boost/amd/build/bootstrap/tab.min.js.map
theme/boost/amd/build/bootstrap/toast.min.js
theme/boost/amd/build/bootstrap/toast.min.js.map
theme/boost/amd/build/bootstrap/tools/sanitizer.min.js.map
theme/boost/amd/build/bootstrap/tooltip.min.js
theme/boost/amd/build/bootstrap/tooltip.min.js.map
theme/boost/amd/build/bootstrap/util.min.js.map
theme/boost/amd/build/index.min.js [new file with mode: 0644]
theme/boost/amd/build/index.min.js.map [new file with mode: 0644]
theme/boost/amd/build/loader.min.js
theme/boost/amd/build/loader.min.js.map
theme/boost/amd/src/bootstrap/alert.js
theme/boost/amd/src/bootstrap/button.js
theme/boost/amd/src/bootstrap/carousel.js
theme/boost/amd/src/bootstrap/collapse.js
theme/boost/amd/src/bootstrap/dropdown.js
theme/boost/amd/src/bootstrap/index.js [deleted file]
theme/boost/amd/src/bootstrap/modal.js
theme/boost/amd/src/bootstrap/popover.js
theme/boost/amd/src/bootstrap/scrollspy.js
theme/boost/amd/src/bootstrap/tab.js
theme/boost/amd/src/bootstrap/toast.js
theme/boost/amd/src/bootstrap/tools/sanitizer.js
theme/boost/amd/src/bootstrap/tooltip.js
theme/boost/amd/src/bootstrap/util.js
theme/boost/amd/src/index.js [new file with mode: 0644]
theme/boost/amd/src/loader.js
theme/boost/readme_moodle.txt
theme/boost/scss/bootstrap/_alert.scss
theme/boost/scss/bootstrap/_breadcrumb.scss
theme/boost/scss/bootstrap/_card.scss
theme/boost/scss/bootstrap/_carousel.scss
theme/boost/scss/bootstrap/_custom-forms.scss
theme/boost/scss/bootstrap/_dropdown.scss
theme/boost/scss/bootstrap/_functions.scss
theme/boost/scss/bootstrap/_grid.scss
theme/boost/scss/bootstrap/_input-group.scss
theme/boost/scss/bootstrap/_list-group.scss
theme/boost/scss/bootstrap/_modal.scss
theme/boost/scss/bootstrap/_nav.scss
theme/boost/scss/bootstrap/_navbar.scss
theme/boost/scss/bootstrap/_pagination.scss
theme/boost/scss/bootstrap/_progress.scss
theme/boost/scss/bootstrap/_reboot.scss
theme/boost/scss/bootstrap/_root.scss
theme/boost/scss/bootstrap/_spinners.scss
theme/boost/scss/bootstrap/_toasts.scss
theme/boost/scss/bootstrap/_type.scss
theme/boost/scss/bootstrap/_variables.scss
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/_border-radius.scss
theme/boost/scss/bootstrap/mixins/_forms.scss
theme/boost/scss/bootstrap/mixins/_grid-framework.scss
theme/boost/scss/bootstrap/mixins/_grid.scss
theme/boost/scss/bootstrap/mixins/_image.scss
theme/boost/scss/bootstrap/mixins/_screen-reader.scss
theme/boost/scss/bootstrap/mixins/_transition.scss
theme/boost/scss/bootstrap/utilities/_borders.scss
theme/boost/scss/bootstrap/utilities/_text.scss
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/message.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/toasts.scss [new file with mode: 0644]
theme/boost/scss/moodle/user.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/boost/thirdpartylibs.xml
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css
user/amd/build/edit_profile_fields.min.js [new file with mode: 0644]
user/amd/build/edit_profile_fields.min.js.map [new file with mode: 0644]
user/amd/src/edit_profile_fields.js [new file with mode: 0644]
user/classes/analytics/indicator/user_profile_set.php
user/classes/fields.php
user/classes/form/profile_category_form.php [new file with mode: 0644]
user/classes/form/profile_field_form.php [new file with mode: 0644]
user/classes/privacy/provider.php
user/editlib.php
user/externallib.php
user/filters/profilefield.php
user/lib.php
user/profile/definelib.php
user/profile/field/checkbox/tests/privacy_test.php
user/profile/field/datetime/tests/privacy_test.php
user/profile/field/menu/tests/privacy_test.php
user/profile/field/social/classes/helper.php [new file with mode: 0644]
user/profile/field/social/classes/privacy/provider.php [new file with mode: 0644]
user/profile/field/social/define.class.php [new file with mode: 0644]
user/profile/field/social/field.class.php [new file with mode: 0644]
user/profile/field/social/lang/en/profilefield_social.php [new file with mode: 0644]
user/profile/field/social/tests/behat/social_profile_field.feature [new file with mode: 0644]
user/profile/field/social/tests/privacy_test.php [new file with mode: 0644]
user/profile/field/social/upgradelib.php [new file with mode: 0644]
user/profile/field/social/version.php [new file with mode: 0644]
user/profile/field/text/tests/privacy_test.php
user/profile/field/textarea/tests/privacy_test.php
user/profile/index.php
user/profile/index_category_form.php [deleted file]
user/profile/index_field_form.php [deleted file]
user/profile/lib.php
user/selector/lib.php
user/templates/edit_profile_fields.mustache [new file with mode: 0644]
user/templates/local/participantsfilter/filterrow.mustache
user/tests/behat/custom_profile_fields.feature
user/tests/externallib_test.php
user/tests/privacy_test.php
user/tests/profilelib_test.php
user/tests/userlib_test.php
version.php
webservice/lib.php
webservice/renderer.php

index a6d5413..2262b1f 100644 (file)
@@ -84,7 +84,6 @@ theme/boost/amd/src/bootstrap/button.js
 theme/boost/amd/src/bootstrap/carousel.js
 theme/boost/amd/src/bootstrap/collapse.js
 theme/boost/amd/src/bootstrap/dropdown.js
-theme/boost/amd/src/bootstrap/index.js
 theme/boost/amd/src/bootstrap/modal.js
 theme/boost/amd/src/bootstrap/popover.js
 theme/boost/amd/src/bootstrap/tools/sanitizer.js
@@ -93,4 +92,5 @@ theme/boost/amd/src/bootstrap/tab.js
 theme/boost/amd/src/bootstrap/toast.js
 theme/boost/amd/src/bootstrap/tooltip.js
 theme/boost/amd/src/bootstrap/util.js
-theme/boost/scss/fontawesome/
\ No newline at end of file
+theme/boost/amd/src/index.js
+theme/boost/scss/fontawesome/
index 35fd4a7..0c4f1ba 100644 (file)
@@ -1,6 +1,12 @@
 name: Core
 
-on: [push]
+on:
+  push:
+    branches-ignore:
+      - master
+      - MOODLE_[0-9]+_STABLE
+    tags-ignore:
+      - v[0-9]+.[0-9]+.[0-9]+*
 
 env:
   php: 7.4
index d8b9ec1..9cc2f6c 100644 (file)
@@ -41,7 +41,7 @@ module.exports = grunt => {
             '*/**/yui/src/*/meta/',
             '*/**/build/',
         ].concat(thirdPartyPaths);
-        grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
+        grunt.file.write('.eslintignore', eslintIgnores.join('\n') + '\n');
 
         // Generate .stylelintignore.
         const stylelintIgnores = [
@@ -50,7 +50,7 @@ module.exports = grunt => {
             'theme/boost/style/moodle.css',
             'theme/classic/style/moodle.css',
         ].concat(thirdPartyPaths);
-        grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
+        grunt.file.write('.stylelintignore', stylelintIgnores.join('\n') + '\n');
     };
 
     grunt.registerTask('ignorefiles', 'Generate ignore files for linters', handler);
index 0207e41..2d28f80 100644 (file)
@@ -84,7 +84,6 @@ theme/boost/amd/src/bootstrap/button.js
 theme/boost/amd/src/bootstrap/carousel.js
 theme/boost/amd/src/bootstrap/collapse.js
 theme/boost/amd/src/bootstrap/dropdown.js
-theme/boost/amd/src/bootstrap/index.js
 theme/boost/amd/src/bootstrap/modal.js
 theme/boost/amd/src/bootstrap/popover.js
 theme/boost/amd/src/bootstrap/tools/sanitizer.js
@@ -93,4 +92,5 @@ theme/boost/amd/src/bootstrap/tab.js
 theme/boost/amd/src/bootstrap/toast.js
 theme/boost/amd/src/bootstrap/tooltip.js
 theme/boost/amd/src/bootstrap/util.js
-theme/boost/scss/fontawesome/
\ No newline at end of file
+theme/boost/amd/src/index.js
+theme/boost/scss/fontawesome/
index 0c565f3..6d52469 100644 (file)
@@ -19,6 +19,12 @@ services:
 addons:
   postgresql: "9.6"
 
+branches:
+  except:
+    - master
+    - /MOODLE_[0-9]+_STABLE/
+    - /^v[0-9]+\.[0-9]+\.[0-9]+.*/
+
 jobs:
     # Enable fast finish.
     # This will fail the build if a single job fails (except those in allow_failures).
index 3ee8ebc..503dd91 100644 (file)
@@ -171,8 +171,6 @@ function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts,
                 // limit for the setting name is 100.
                 continue;
             }
-        } elseif ($fieldname == 'url') {
-            $fieldname = get_string('webpage');
         } else {
             $fieldname = get_string($fieldname);
         }
diff --git a/admin/cli/fix_orphaned_calendar_events.php b/admin/cli/fix_orphaned_calendar_events.php
new file mode 100644 (file)
index 0000000..304b276
--- /dev/null
@@ -0,0 +1,138 @@
+<?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/>.
+
+/**
+ * Fix orphaned calendar events that were broken by MDL-67494.
+ *
+ * This script will look for all the calendar events which userids
+ * where broken by a wrong upgrade step, affecting to Moodle 3.9.5
+ * and up.
+ *
+ * It performs checks to both:
+ *    a) Detect if the site was affected (ran the wrong upgrade step).
+ *    b) Look for orphaned calendar events, categorising them as:
+ *       - standard: site / category / course / group / user events
+ *       - subscription: events created via subscriptions.
+ *       - action: normal action events, created to show common important dates.
+ *       - override: user and group override events, particular, that some activities support.
+ *       - custom: other events, not being any of the above, common or particular.
+ * By specifying it (--fix) try to recover as many broken events (missing userid) as
+ * possible. Standard, subscription, action, override events in core are fully supported but
+ * override or custom events should be fixed by each plugin as far as there isn't any standard
+ * API (plugin-wise) to launch a rebuild of the calendar events.
+ *
+ * @package core
+ * @copyright 2021 onwards Simey Lameze <simey@moodle.com>
+ * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require_once(__DIR__ . '/../../config.php');
+require_once($CFG->libdir . "/clilib.php");
+require_once($CFG->libdir . '/db/upgradelib.php');
+
+// Supported options.
+$long = ['fix'  => false, 'help' => false];
+$short = ['f' => 'fix', 'h' => 'help'];
+
+// CLI options.
+[$options, $unrecognized] = cli_get_params($long, $short);
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help = <<<EOT
+Fix orphaned calendar events.
+
+  This script detects calendar events that have had their
+  userid lost. By default it will perform various checks
+  and report them, showing the site status in an easy way.
+
+  Also, optionally (--fix), it wil try to recover as many
+  lost userids as possible from different sources. Note that
+  this script aims to process well-know events in core,
+  leaving custom events in 3rd part plugins mostly unmodified
+  because there isn't any consistent way to regenerate them.
+
+  For more details:  https://tracker.moodle.org/browse/MDL-71156
+
+Options:
+  -h, --help    Print out this help.
+  -f, --fix     Fix the orphaned calendar events in the DB.
+                If not specified only check and report problems to STDERR.
+
+Usage:
+  - Only report:    \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php
+  - Report and fix: \$ sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_calendar_events.php -f
+EOT;
+
+    cli_writeln($help);
+    die;
+}
+
+// Check various usual pre-requisites.
+if (empty($CFG->version)) {
+    cli_error('Database is not yet installed.');
+}
+
+$admin = get_admin();
+if (!$admin) {
+    cli_error('Error: No admin account was found.');
+}
+
+if (moodle_needs_upgrading()) {
+    cli_error('Moodle upgrade pending, script execution suspended.');
+}
+
+// Do everything as admin by default.
+\core\session\manager::set_user($admin);
+
+// Report current site status.
+cli_heading('Checking the site status');
+$needsfix = upgrade_calendar_site_status();
+
+// Report current calendar events status.
+cli_heading('Checking the calendar events status');
+$info = upgrade_calendar_events_status();
+$hasbadevents = $info['total']->bad > 0 || $info['total']->bad != $info['other']->bad;
+$needsfix = $needsfix || $hasbadevents;
+
+// If, selected, fix as many calendar events as possible.
+if ($options['fix']) {
+
+    // If the report has told us that the fix was not needed... ask for confirmation!
+    if (!$needsfix) {
+        cli_writeln("This site DOES NOT NEED to run the calendar events fix.");
+        $input = cli_input('Are you completely sure that you want to run the fix? (y/N)', 'N', ['y', 'Y', 'n', 'N']);
+        if (strtolower($input) != 'y') {
+            exit(0);
+        }
+        cli_writeln("");
+    }
+    cli_heading('Fixing as many as possible calendar events');
+    upgrade_calendar_events_fix_remaining($info);
+    // Report current (after fix) calendar events status.
+    cli_heading('Checking the calendar events status (after fix)');
+    upgrade_calendar_events_status();
+} else if ($needsfix) {
+    // Fix option was not provided but problem events have been found. Notify the user and provide info how to fix these events.
+    cli_writeln("This site NEEDS to run the calendar events fix!");
+    cli_writeln("To fix the calendar events, re-run this script with the --fix option.");
+}
index 12904e5..cbfa2be 100644 (file)
@@ -135,6 +135,15 @@ if (!$envstatus) {
     exit(1);
 }
 
+// Make sure there are no files left over from previous versions.
+if (upgrade_stale_php_files_present()) {
+    cli_problem(get_string('upgradestalefiles', 'admin'));
+
+    // Stale file info contains HTML elements which aren't suitable for CLI.
+    $upgradestalefilesinfo = get_string('upgradestalefilesinfo', 'admin', get_docs_url('Upgrading'));
+    cli_error(strip_tags($upgradestalefilesinfo));
+}
+
 // Test plugin dependencies.
 $failed = array();
 if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed, $CFG->branch)) {
index b5c31d7..aa632b1 100644 (file)
@@ -119,10 +119,19 @@ if (($form = data_submitted()) && confirm_sesskey()) {
     // Save processors enabled/disabled status.
     foreach ($allprocessors as $processor) {
         $enabled = isset($form->{$processor->name});
+        if ($enabled != $processor->enabled) {
+            add_to_config_log($processor->name, $processor->enabled, $enabled, 'core');
+        }
         \core_message\api::update_processor_status($processor, $enabled);
     }
 
     foreach ($newpreferences as $name => $value) {
+        $old = isset($preferences->$name) ? $preferences->$name : '';
+
+        if ($old != $value) {
+            add_to_config_log($name, $old, $value, 'core');
+        }
+
         set_config($name, $value, 'message');
     }
     $transaction->allow_commit();
index 709e28c..c0b5572 100644 (file)
@@ -34,11 +34,12 @@ $error = optional_param('error', '', PARAM_RAW);
 if ($error) {
     $message = optional_param('error_description', '', PARAM_RAW);
     if ($message) {
-        print_error($message);
+        $SESSION->loginerrormsg = $message;
+        redirect(new moodle_url(get_login_url()));
     } else {
-        print_error($error);
+        $SESSION->loginerrormsg = $error;
+        redirect(new moodle_url(get_login_url()));
     }
-    die();
 }
 
 // The authorization code generated by the authorization server.
@@ -53,5 +54,6 @@ if (isset($params['sesskey']) and confirm_sesskey($params['sesskey'])) {
     $redirecturl->param('oauth2code', $code);
     redirect($redirecturl);
 } else {
-    print_error('invalidsesskey');
+    $SESSION->loginerrormsg = get_string('invalidsesskey', 'error');
+    redirect(new moodle_url(get_login_url()));
 }
index b4401c4..b43aa77 100644 (file)
@@ -52,8 +52,14 @@ if ($data = $form->get_data()) {
     $message = get_string('purgecachesfinished', 'admin');
 }
 
+// Redirect and/or show notification message confirming cache(s) were purged.
 if (isset($message)) {
-    redirect($returnurl, $message);
+    if (!$PAGE->url->compare($returnurl, URL_MATCH_BASE)) {
+        redirect($returnurl, $message);
+    }
+
+    // We are already on the purge caches page, add the notification.
+    \core\notification::add($message, \core\output\notification::NOTIFY_INFO);
 }
 
 // Otherwise, show a form to actually purge the caches.
index c0da1aa..2037a63 100644 (file)
@@ -139,6 +139,13 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
         new lang_string('coursehelpshowgrades'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
     $temp->add(new admin_setting_configselect('moodlecourse/showreports', new lang_string('showreports'), '', 0,
         array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
+    $temp->add(new admin_setting_configselect('moodlecourse/showactivitydates',
+        new lang_string('showactivitydates'),
+        new lang_string('showactivitydates_help'), 1, [
+            0 => new lang_string('no'),
+            1 => new lang_string('yes')
+        ]
+    ));
 
     // Files and uploads.
     $temp->add(new admin_setting_heading('filesanduploadshdr', new lang_string('filesanduploads'), ''));
@@ -163,6 +170,15 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configselect('moodlecourse/enablecompletion', new lang_string('completion', 'completion'),
         new lang_string('enablecompletion_help', 'completion'), 1, array(0 => new lang_string('no'), 1 => new lang_string('yes'))));
 
+    // Display completion conditions.
+    $temp->add(new admin_setting_configselect('moodlecourse/showcompletionconditions',
+        new lang_string('showcompletionconditions', 'completion'),
+        new lang_string('showcompletionconditions_help', 'completion'), 1, [
+            0 => new lang_string('no'),
+            1 => new lang_string('yes')
+        ]
+    ));
+
     // Groups.
     $temp->add(new admin_setting_heading('groups', new lang_string('groups', 'group'), ''));
     $choices = array();
@@ -327,6 +343,11 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
 
     // Import defaults section.
     $temp->add(new admin_setting_heading('importsettings', new lang_string('importsettings', 'backup'), ''));
+    $temp->add(new admin_setting_configcheckbox_with_lock(
+            'backup/backup_import_permissions',
+            new lang_string('generalpermissions', 'backup'),
+            new lang_string('configgeneralpermissions', 'backup'),
+            array('value' => 0, 'locked' => 0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_blocks', new lang_string('generalblocks','backup'), new lang_string('configgeneralblocks','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_filters', new lang_string('generalfilters','backup'), new lang_string('configgeneralfilters','backup'), array('value'=>1, 'locked'=>0)));
@@ -501,6 +522,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_role_assignments',
         new lang_string('generalroleassignments', 'backup'),
         new lang_string('configrestoreroleassignments', 'backup'), array('value' => 1, 'locked' => 0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_permissions',
+        new lang_string('generalpermissions', 'backup'),
+        new lang_string('configrestorepermissions', 'backup'), array('value' => 1, 'locked' => 0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_activities',
         new lang_string('generalactivities', 'backup'),
         new lang_string('configrestoreactivities', 'backup'), array('value' => 1, 'locked' => 0)));
index 6e58d4c..aa60229 100644 (file)
@@ -196,12 +196,6 @@ if ($hassiteconfig
                              'country' => new lang_string('country'),
                              'moodlenetprofile' => new lang_string('moodlenetprofile', 'user'),
                              'timezone' => new lang_string('timezone'),
-                             'webpage' => new lang_string('webpage'),
-                             'icqnumber' => new lang_string('icqnumber'),
-                             'skypeid' => new lang_string('skypeid'),
-                             'yahooid' => new lang_string('yahooid'),
-                             'aimid' => new lang_string('aimid'),
-                             'msnid' => new lang_string('msnid'),
                              'firstaccess' => new lang_string('firstaccess'),
                              'lastaccess' => new lang_string('lastaccess'),
                              'lastip' => new lang_string('lastip'),
@@ -219,7 +213,8 @@ if ($hassiteconfig
                 new lang_string('showuseridentity', 'admin'),
                 new lang_string('showuseridentity_desc', 'admin'), ['email' => 1],
                 function() {
-                    global $DB;
+                    global $CFG;
+                    require_once($CFG->dirroot.'/user/profile/lib.php');
 
                     // Basic fields available in user table.
                     $fields = [
@@ -235,10 +230,10 @@ if ($hassiteconfig
                     ];
 
                     // Custom profile fields.
-                    $profilefields = $DB->get_records('user_info_field', ['datatype' => 'text'], 'sortorder ASC');
-                    foreach ($profilefields as $key => $field) {
-                        // Only reasonable-length fields can be used as identity fields.
-                        if ($field->param2 > 255) {
+                    $profilefields = profile_get_custom_fields();
+                    foreach ($profilefields as $field) {
+                        // Only reasonable-length text fields can be used as identity fields.
+                        if ($field->param2 > 255 || $field->datatype != 'text') {
                             continue;
                         }
                         $fields['profile_field_' . $field->shortname] = $field->name . ' *';
diff --git a/admin/tests/behat/webservice_users.feature b/admin/tests/behat/webservice_users.feature
new file mode 100644 (file)
index 0000000..e334b0f
--- /dev/null
@@ -0,0 +1,31 @@
+@core @core_admin
+Feature: Web service user settings
+  In order to configure authorised users for a web service
+  As an admin
+  I need to use the page that lets you do that
+
+  Background:
+    # Include a custom profile field so we can check it gets displayed
+    Given the following "custom profile fields" exist:
+      | datatype | shortname | name           | param2 |
+      | text     | frog      | Favourite frog | 100    |
+    And the following config values are set as admin:
+      | showuseridentity | email,profile_field_frog |
+    And the following "users" exist:
+      | username | firstname | lastname | email         | profile_field_frog |
+      | user1    | User      | One      | 1@example.org | Kermit             |
+    And the following "core_webservice > Service" exists:
+      | name            | Silly service |
+      | shortname       | silly         |
+      | restrictedusers | 1             |
+      | enabled         | 1             |
+
+  Scenario: Add a user to a web service
+    When I log in as "admin"
+    And I navigate to "Server > Web services > External services" in site administration
+    And I click on "Authorised users" "link" in the "Silly service" "table_row"
+    And I set the field "Not authorised users" to "User One"
+    And I press "Add"
+    Then I should see "User One" in the ".alloweduserlist" "css_element"
+    And I should see "1@example.org" in the ".alloweduserlist" "css_element"
+    And I should see "Kermit" in the ".alloweduserlist" "css_element"
index 98375c4..a5dfa1d 100644 (file)
@@ -45,7 +45,7 @@ list($options, $unrecognised) = cli_get_params(
     array(
         'stop-on-failure' => 0,
         'verbose'  => false,
-        'replace'  => false,
+        'replace'  => '',
         'help'     => false,
         'tags'     => '',
         'profile'  => '',
@@ -70,7 +70,7 @@ $help = "
 Behat utilities to run behat tests in parallel
 
 Usage:
-  php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace] [--fromrun=value --torun=value] [--help]
+  php run.php [--BEHAT_OPTION=\"value\"] [--feature=\"value\"] [--replace=\"{run}\"] [--fromrun=value --torun=value] [--help]
 
 Options:
 --BEHAT_OPTION     Any combination of behat option specified in http://behat.readthedocs.org/en/v2.5/guides/6.cli.html
@@ -144,9 +144,10 @@ $extraopts = $unrecognised;
 if ($options['profile']) {
     $profile = $options['profile'];
 
-    // If profile passed is not set, then exit.
+    // If profile passed is not set, then exit (note we skip if the 'replace' option is found within the 'profile' value).
     if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile]) &&
-        !(isset($options['replace']) && (strpos($options['profile'], $options['replace']) >= 0 ))) {
+            !($options['replace'] && (strpos($profile, (string) $options['replace']) !== false))) {
+
         echo "Invalid profile passed: " . $profile . PHP_EOL;
         exit(1);
     }
index 364544a..6172ef5 100644 (file)
@@ -188,7 +188,12 @@ class cohort_role_assignments_table extends table_sql {
                    FROM {tool_cohortroles} uca
                    JOIN {user} u ON u.id = uca.userid
                    JOIN {cohort} c ON c.id = uca.cohortid";
-        $params = array();
+
+        // Check if any additional filtering is required.
+        [$sqlwhere, $params] = $this->get_sql_where();
+        if ($sqlwhere) {
+            $sql .= " WHERE {$sqlwhere}";
+        }
 
         // Add order by if needed.
         if (!$count && $sqlsort = $this->get_sql_sort()) {
index cc9947c..3396ddb 100644 (file)
@@ -86,8 +86,8 @@ if ($removeid) {
 
     $title = get_string('existingcohortroles', 'tool_cohortroles');
     echo $output->heading($title);
-    $url = new moodle_url('/admin/tool/cohortroles/index.php');
-    $table = new tool_cohortroles\output\cohort_role_assignments_table(uniqid(), $url);
+
+    $table = new tool_cohortroles\output\cohort_role_assignments_table('cohort-role-assignments', $pageurl);
     echo $table->out(50, true);
 
     echo $output->spacer();
diff --git a/admin/tool/dataprivacy/amd/build/contactdpo.min.js b/admin/tool/dataprivacy/amd/build/contactdpo.min.js
new file mode 100644 (file)
index 0000000..7351478
Binary files /dev/null and b/admin/tool/dataprivacy/amd/build/contactdpo.min.js differ
diff --git a/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map b/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map
new file mode 100644 (file)
index 0000000..6445af7
Binary files /dev/null and b/admin/tool/dataprivacy/amd/build/contactdpo.min.js.map differ
index a6ba759..b1b7317 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/myrequestactions.min.js and b/admin/tool/dataprivacy/amd/build/myrequestactions.min.js differ
index c9f78ed..4dfdc98 100644 (file)
Binary files a/admin/tool/dataprivacy/amd/build/myrequestactions.min.js.map and b/admin/tool/dataprivacy/amd/build/myrequestactions.min.js.map differ
diff --git a/admin/tool/dataprivacy/amd/src/contactdpo.js b/admin/tool/dataprivacy/amd/src/contactdpo.js
new file mode 100644 (file)
index 0000000..50417a8
--- /dev/null
@@ -0,0 +1,67 @@
+// 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/>.
+
+/**
+ * Javascript module for contacting the site DPO
+ *
+ * @module      tool_dataprivacy/contactdpo
+ * @package     tool_dataprivacy
+ * @copyright   2021 Paul Holden <paulh@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import ModalForm from 'core_form/modalform';
+import Notification from 'core/notification';
+import {get_string as getString} from 'core/str';
+import {add as addToast} from 'core/toast';
+
+const SELECTORS = {
+    CONTACT_DPO: '[data-action="contactdpo"]',
+};
+
+/**
+ * Initialize module
+ */
+export const init = () => {
+    const triggerElement = document.querySelector(SELECTORS.CONTACT_DPO);
+
+    triggerElement.addEventListener('click', event => {
+        event.preventDefault();
+
+        const modalForm = new ModalForm({
+            modalConfig: {
+                title: getString('contactdataprotectionofficer', 'tool_dataprivacy'),
+            },
+            formClass: 'tool_dataprivacy\\form\\contactdpo',
+            saveButtonText: getString('send', 'tool_dataprivacy'),
+            returnFocus: triggerElement,
+        });
+
+        // Show a toast notification when the form is submitted.
+        modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, event => {
+            if (event.detail.result) {
+                getString('requestsubmitted', 'tool_dataprivacy').then(addToast).catch();
+            } else {
+                const warningMessages = event.detail.warnings.map(warning => warning.message);
+                Notification.addNotification({
+                    type: 'error',
+                    message: warningMessages.join('<br>')
+                });
+            }
+        });
+
+        modalForm.show();
+    });
+};
index 54f94fb..b75b386 100644 (file)
  * @copyright  2018 Jun Pataleta
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define([
-    'jquery',
-    'core/ajax',
-    'core/notification',
-    'core/str',
-    'core/modal_factory',
-    'core/modal_events',
-    'core/templates',
-    'core/pending'],
-function($, Ajax, Notification, Str, ModalFactory, ModalEvents, Templates, Pending) {
 
-    /**
-     * List of action selectors.
-     *
-     * @type {{CANCEL_REQUEST: string}}
-     * @type {{CONTACT_DPO: string}}
-     */
-    var ACTIONS = {
-        CANCEL_REQUEST: '[data-action="cancel"]',
-        CONTACT_DPO: '[data-action="contactdpo"]',
-    };
+import Ajax from 'core/ajax';
+import Notification from 'core/notification';
+import Pending from 'core/pending';
+import {get_strings as getStrings} from 'core/str';
 
-    /**
-     * MyRequestActions class.
-     */
-    var MyRequestActions = function() {
-        this.registerEvents();
-    };
+const SELECTORS = {
+    CANCEL_REQUEST: '[data-action="cancel"][data-requestid]',
+};
 
-    /**
-     * Register event listeners.
-     */
-    MyRequestActions.prototype.registerEvents = function() {
-        $(ACTIONS.CANCEL_REQUEST).click(function(e) {
-            e.preventDefault();
-
-            var requestId = $(this).data('requestid');
-            var stringkeys = [
-                {
-                    key: 'cancelrequest',
-                    component: 'tool_dataprivacy'
-                },
-                {
-                    key: 'cancelrequestconfirmation',
-                    component: 'tool_dataprivacy'
-                }
-            ];
-
-            Str.get_strings(stringkeys).then(function(langStrings) {
-                var title = langStrings[0];
-                var confirmMessage = langStrings[1];
-                return ModalFactory.create({
-                    title: title,
-                    body: confirmMessage,
-                    type: ModalFactory.types.SAVE_CANCEL
-                }).then(function(modal) {
-                    modal.setSaveButtonText(title);
-
-                    // Handle save event.
-                    modal.getRoot().on(ModalEvents.save, function() {
-                        // Cancel the request.
-                        var params = {
-                            'requestid': requestId
-                        };
-
-                        var request = {
-                            methodname: 'tool_dataprivacy_cancel_data_request',
-                            args: params
-                        };
-
-                        Ajax.call([request])[0].done(function(data) {
-                            if (data.result) {
-                                window.location.reload();
-                            } else {
-                                Notification.addNotification({
-                                    message: data.warnings[0].message,
-                                    type: 'error'
-                                });
-                            }
-                        }).fail(Notification.exception);
-                    });
-
-                    // Handle hidden event.
-                    modal.getRoot().on(ModalEvents.hidden, function() {
-                        // Destroy when hidden.
-                        modal.destroy();
-                    });
-
-                    return modal;
-                });
-            }).done(function(modal) {
-                // Show the modal!
-                modal.show();
-
-            }).fail(Notification.exception);
-        });
-
-        $(ACTIONS.CONTACT_DPO).click(function(e) {
-            var pendingPromise = new Pending('dataprivacy/crud:initModal:contactdpo');
-            e.preventDefault();
+/**
+ * Initialize module
+ */
+export const init = () => {
+    document.addEventListener('click', event => {
+        const triggerElement = event.target.closest(SELECTORS.CANCEL_REQUEST);
+        if (triggerElement === null) {
+            return;
+        }
 
-            var replyToEmail = $(this).data('replytoemail');
+        event.preventDefault();
 
-            var keys = [
-                {
-                    key: 'contactdataprotectionofficer',
-                    component: 'tool_dataprivacy'
-                },
-                {
-                    key: 'send',
-                    component: 'tool_dataprivacy'
-                },
-            ];
+        const requiredStrings = [
+            {key: 'cancelrequest', component: 'tool_dataprivacy'},
+            {key: 'cancelrequestconfirmation', component: 'tool_dataprivacy'},
+        ];
 
-            var sendButtonText = '';
-            Str.get_strings(keys).then(function(langStrings) {
-                var modalTitle = langStrings[0];
-                sendButtonText = langStrings[1];
-                var context = {
-                    'replytoemail': replyToEmail
+        getStrings(requiredStrings).then(([cancelRequest, cancelConfirm]) => {
+            return Notification.confirm(cancelRequest, cancelConfirm, cancelRequest, null, () => {
+                const pendingPromise = new Pending('tool/dataprivacy:cancelRequest');
+                const request = {
+                    methodname: 'tool_dataprivacy_cancel_data_request',
+                    args: {requestid: triggerElement.dataset.requestid}
                 };
-                return ModalFactory.create({
-                    title: modalTitle,
-                    body: Templates.render('tool_dataprivacy/contact_dpo', context),
-                    type: ModalFactory.types.SAVE_CANCEL,
-                    large: true
-                });
-            }).then(function(modal) {
-                modal.setSaveButtonText(sendButtonText);
-
-                // Show the modal!
-                modal.show();
 
-                // Handle send event.
-                modal.getRoot().on(ModalEvents.save, function(e) {
-                    var message = $('#message').val().trim();
-                    if (message.length === 0) {
-                        e.preventDefault();
-                        // Show validation error when the message is empty.
-                        $('[data-region="messageinput"]').addClass('has-danger notifyproblem');
-                        $('#id_error_message').removeAttr('hidden');
+                Ajax.call([request])[0].then(response => {
+                    if (response.result) {
+                        window.location.reload();
                     } else {
-                        // Send the message.
-                        sendMessageToDPO(message);
+                        Notification.addNotification({
+                            type: 'error',
+                            message: response.warnings[0].message
+                        });
                     }
-                });
-
-                // Handle hidden event.
-                modal.getRoot().on(ModalEvents.hidden, function() {
-                    // Destroy when hidden.
-                    modal.destroy();
-                });
-
-                return;
-            }).then(pendingPromise.resolve)
-            .catch(Notification.exception);
-        });
-    };
-
-    /**
-     * Send message to the Data Protection Officer.
-     *
-     * @param {String} message The message to send.
-     */
-    function sendMessageToDPO(message) {
-        var request = {
-            methodname: 'tool_dataprivacy_contact_dpo',
-            args: {
-                message: message
-            }
-        };
-
-        var requestType = 'success';
-        Ajax.call([request])[0].then(function(data) {
-            if (data.result) {
-                return Str.get_string('requestsubmitted', 'tool_dataprivacy');
-            }
-            requestType = 'error';
-            return data.warnings.join('<br>');
-
-        }).done(function(message) {
-            Notification.addNotification({
-                message: message,
-                type: requestType
+                    return pendingPromise.resolve();
+                }).catch(Notification.exception);
             });
-
-        }).fail(Notification.exception);
-    }
-
-    return /** @alias module:tool_dataprivacy/myrequestactions */ {
-        // Public variables and functions.
-
-        /**
-         * Initialise the unified user filter.
-         *
-         * @method init
-         * @return {MyRequestActions}
-         */
-        'init': function() {
-            return new MyRequestActions();
-        }
-    };
-});
+        }).catch();
+    });
+};
index 92dd398..30d2e6d 100644 (file)
@@ -212,7 +212,8 @@ class external extends external_api {
                 $warnings[] = [
                     'item' => $dpo->id,
                     'warningcode' => 'errorsendingtodpo',
-                    'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy')
+                    'message' => get_string('errorsendingmessagetodpo', 'tool_dataprivacy',
+                        fullname($dpo))
                 ];
             }
         }
diff --git a/admin/tool/dataprivacy/classes/form/contactdpo.php b/admin/tool/dataprivacy/classes/form/contactdpo.php
new file mode 100644 (file)
index 0000000..dc17291
--- /dev/null
@@ -0,0 +1,100 @@
+<?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/>.
+
+
+namespace tool_dataprivacy\form;
+
+use context;
+use context_user;
+use moodle_exception;
+use moodle_url;
+use core_form\dynamic_form;
+use tool_dataprivacy\api;
+use tool_dataprivacy\external;
+
+/**
+ * Contact DPO modal form
+ *
+ * @package    tool_dataprivacy
+ * @copyright  2021 Paul Holden <paulh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class contactdpo extends dynamic_form {
+
+    /**
+     * Form definition
+     */
+    protected function definition() {
+        global $USER;
+
+        $mform = $this->_form;
+
+        $mform->addElement('static', 'replyto', get_string('replyto', 'tool_dataprivacy'), s($USER->email));
+
+        $mform->addElement('textarea', 'message', get_string('message', 'tool_dataprivacy'), 'cols="60" rows="8"');
+        $mform->setType('message', PARAM_TEXT);
+        $mform->addRule('message', get_string('required'), 'required', null, 'client');
+    }
+
+    /**
+     * Return form context
+     *
+     * @return context
+     */
+    protected function get_context_for_dynamic_submission(): context {
+        global $USER;
+
+        return context_user::instance($USER->id);
+    }
+
+    /**
+     * Check if current user has access to this form, otherwise throw exception
+     *
+     * @throws moodle_exception
+     */
+    protected function check_access_for_dynamic_submission(): void {
+        if (!api::can_contact_dpo()) {
+            throw new moodle_exception('errorcontactdpodisabled', 'tool_dataprivacy');
+        }
+    }
+
+    /**
+     * Process the form submission, used if form was submitted via AJAX
+     *
+     * @return array
+     */
+    public function process_dynamic_submission() {
+        return external::contact_dpo($this->get_data()->message);
+    }
+
+    /**
+     * Load in existing data as form defaults (not applicable)
+     */
+    public function set_data_for_dynamic_submission(): void {
+        return;
+    }
+
+    /**
+     * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
+     *
+     * @return moodle_url
+     */
+    protected function get_page_url_for_dynamic_submission(): moodle_url {
+        global $USER;
+
+        return new moodle_url('/user/profile.php', ['id' => $USER->id]);
+    }
+}
index 8032906..5d3f4cb 100644 (file)
@@ -55,14 +55,11 @@ class renderer extends plugin_renderer_base {
     /**
      * Render the contact DPO link.
      *
-     * @param string $replytoemail The Reply-to email address
      * @return string The HTML for the link.
-     * @throws coding_exception
      */
-    public function render_contact_dpo_link($replytoemail) {
+    public function render_contact_dpo_link() {
         $params = [
             'data-action' => 'contactdpo',
-            'data-replytoemail' => $replytoemail,
         ];
         return html_writer::link('#', get_string('contactdataprotectionofficer', 'tool_dataprivacy'), $params);
     }
index 46d7af2..53ce860 100644 (file)
@@ -135,6 +135,7 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac
 $string['emailsalutation'] = 'Dear {$a},';
 $string['errorcannotrequestdeleteforself'] = 'You don\'t have permission to create deletion request for yourself.';
 $string['errorcannotrequestdeleteforother'] = 'You don\'t have permission to create deletion request for this user.';
+$string['errorcontactdpodisabled'] = 'Contacting the privacy officer is disabled';
 $string['errorinvalidrequestcomments'] = 'The comments field may contain plain text only.';
 $string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!';
 $string['errorinvalidrequeststatus'] = 'Invalid request status!';
index 5b5f28a..25091fd 100644 (file)
@@ -54,10 +54,12 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser
     // Contact data protection officer link.
     if (\tool_dataprivacy\api::can_contact_dpo() && $iscurrentuser) {
         $renderer = $PAGE->get_renderer('tool_dataprivacy');
-        $content = $renderer->render_contact_dpo_link($USER->email);
+        $content = $renderer->render_contact_dpo_link();
         $node = new core_user\output\myprofile\node('privacyandpolicies', 'contactdpo', null, null, null, $content);
         $category->add_node($node);
-        $PAGE->requires->js_call_amd('tool_dataprivacy/myrequestactions', 'init');
+
+        // Require our Javascript module to handle contact DPO interaction.
+        $PAGE->requires->js_call_amd('tool_dataprivacy/contactdpo', 'init');
 
         $url = new moodle_url('/admin/tool/dataprivacy/mydatarequests.php');
         $node = new core_user\output\myprofile\node('privacyandpolicies', 'datarequests',
diff --git a/admin/tool/dataprivacy/templates/contact_dpo.mustache b/admin/tool/dataprivacy/templates/contact_dpo.mustache
deleted file mode 100644 (file)
index d3a0e86..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-{{!
-    This file is part of Moodle - http://moodle.org/
-
-    Moodle is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Moodle is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-}}
-{{!
-    @template tool_dataprivacy/contact_dpo
-
-    The purpose of this template is to enable the user to contact the site's DPO via email.
-
-    Classes required for JS:
-    * none
-
-    Data attributes required for JS:
-    * none
-
-    Context variables required for this template:
-    * userid int The user's ID.
-    * email string The user's email address.
-
-    Example context (json):
-    {
-        "userid": 1,
-        "replytoemail": "martha@example.com"
-    }
-}}
-<div class="container">
-    <div class="row mb-2">
-        <label class="col-md-3 col-form-label">{{#str}}replyto, tool_dataprivacy{{/str}}</label>
-        <div class="col-md-9 col-form-label">{{replytoemail}}</div>
-    </div>
-    <div class="row" data-region="messageinput">
-        <label for="message" class="col-md-3 col-form-label">
-            {{#str}}message, tool_dataprivacy{{/str}}
-            <span class="float-sm-right text-nowrap">
-            <abbr class="initialism text-danger" title="{{#str}}required{{/str}}">{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}</abbr>
-        </span>
-        </label>
-        <div class="col-md-9">
-            <textarea class="form-control" id="message" cols="60" rows="8"></textarea>
-            <div class="form-control-feedback" id="id_error_message" hidden="hidden">
-                {{#str}}required, moodle{{/str}}
-            </div>
-        </div>
-    </div>
-</div>
-
index 528d1f6..28f5de8 100644 (file)
@@ -8,19 +8,21 @@ Feature: Contact the privacy officer
     Given the following "users" exist:
       | username | firstname | lastname | email          |
       | student1 | Student   | 1        | s1@example.com |
-    And I log in as "admin"
-    And I set the following administration settings values:
-      | contactdataprotectionofficer | 1 |
-    And I log out
 
   @javascript
   Scenario: Contacting the privacy officer
-    Given I log in as "student1"
+    Given the following config values are set as admin:
+      | contactdataprotectionofficer | 1 | tool_dataprivacy |
+    When I log in as "student1"
     And I follow "Profile" in the user menu
-    And I should see "Contact the privacy officer"
     And I click on "Contact the privacy officer" "link"
     And I set the field "Message" to "Hello DPO!"
     And I click on "Send" "button" in the "Contact the privacy officer" "dialogue"
-    And I should see "Your request has been submitted to the privacy officer"
+    Then I should see "Your request has been submitted to the privacy officer"
     And I click on "Data requests" "link"
     And I should see "Hello DPO!" in the "General inquiry" "table_row"
+
+  Scenario: Contacting the privacy officer when not enabled
+    When I log in as "student1"
+    And I follow "Profile" in the user menu
+    Then "Contact the privacy officer" "link" should not exist
diff --git a/admin/tool/dataprivacy/tests/behat/my_data_requests.feature b/admin/tool/dataprivacy/tests/behat/my_data_requests.feature
new file mode 100644 (file)
index 0000000..15de9cd
--- /dev/null
@@ -0,0 +1,26 @@
+@tool @tool_dataprivacy
+Feature: Manage my own data requests
+  In order to manage my own data requests
+  As a user
+  I need to be able to view and cancel all my data requests
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email          |
+      | student1 | Student   | 1        | s1@example.com |
+    And the following config values are set as admin:
+      | contactdataprotectionofficer | 1 | tool_dataprivacy |
+
+  @javascript
+  Scenario: Cancel my own data request
+    Given I log in as "student1"
+    And I follow "Profile" in the user menu
+    And I click on "Contact the privacy officer" "link"
+    And I set the field "Message" to "Hello DPO!"
+    And I click on "Send" "button" in the "Contact the privacy officer" "dialogue"
+    And I should see "Your request has been submitted to the privacy officer"
+    When I click on "Data requests" "link"
+    And I open the action menu in "Hello DPO!" "table_row"
+    And I choose "Cancel" in the open action menu
+    And I click on "Cancel request" "button" in the "Cancel request" "dialogue"
+    Then I should see "Cancelled" in the "Hello DPO!" "table_row"
index fb60090..afc5cac 100644 (file)
@@ -145,7 +145,7 @@ class httpsreplace_test extends \advanced_testcase {
         $this->resetAfterTest();
         $this->expectOutputRegex($ouputregex);
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course = $generator->create_course((object) [
@@ -216,7 +216,7 @@ class httpsreplace_test extends \advanced_testcase {
     public function test_http_link_stats($content, $domain, $expectedcount) {
         $this->resetAfterTest();
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course = $generator->create_course((object) [
@@ -237,7 +237,7 @@ class httpsreplace_test extends \advanced_testcase {
         $this->resetAfterTest();
         $this->expectOutputRegex('/^$/');
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course = $generator->create_course((object) [
@@ -269,7 +269,7 @@ class httpsreplace_test extends \advanced_testcase {
         $CFG->wwwroot = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
         $this->expectOutputRegex('/^$/');
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course = $generator->create_course((object) [
@@ -292,7 +292,7 @@ class httpsreplace_test extends \advanced_testcase {
 
         set_config('test_upgrade_http_links', '<img src="http://somesite/someimage.png" />');
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
         ob_start();
         $results = $finder->upgrade_http_links();
         $output = ob_get_contents();
@@ -318,7 +318,7 @@ class httpsreplace_test extends \advanced_testcase {
 
         set_config('renames', json_encode($renames), 'tool_httpsreplace');
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course = $generator->create_course((object) [
@@ -353,7 +353,7 @@ class httpsreplace_test extends \advanced_testcase {
             $original2 .= '<img src="http://example.com/image' . ($i + 15 ) . '.png">';
             $expected2 .= '<img src="https://example.com/image' . ($i + 15) . '.png">';
         }
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
 
         $generator = $this->getDataGenerator();
         $course1 = $generator->create_course((object) ['summary' => $original1]);
@@ -397,7 +397,7 @@ class httpsreplace_test extends \advanced_testcase {
         $columnamequoted = $dbman->generator->getEncQuoted('where');
         $DB->execute("INSERT INTO {reserved_words_temp} ($columnamequoted) VALUES (?)", [$content]);
 
-        $finder = new tool_httpreplace_url_finder_test();
+        $finder = new tool_httpreplace_url_finder_mock();
         $finder->upgrade_http_links();
 
         $record = $DB->get_record('reserved_words_temp', []);
@@ -408,13 +408,13 @@ class httpsreplace_test extends \advanced_testcase {
 }
 
 /**
- * Class tool_httpreplace_url_finder_test for testing replace tool without calling curl
+ * Class tool_httpreplace_url_finder_mock for testing replace tool without calling curl
  *
  * @package   tool_httpsreplace
  * @copyright 2017 Marina Glancy
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class tool_httpreplace_url_finder_test extends \tool_httpsreplace\url_finder {
+class tool_httpreplace_url_finder_mock extends \tool_httpsreplace\url_finder {
     /**
      * Check if url is available (check hardcoded for unittests)
      *
index 863aaed..cac07d9 100644 (file)
     This template does not have an example context because it includes ajax functionality.
 }}
 <div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}" data-region-id="{{uniqid}}">
-<div data-region="competency-summary">
-    {{#competency}}
-        {{> tool_lp/competency_summary }}
-    {{/competency}}
-    <dl>
-        {{#usercompetency}}
-        <dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
-        <dd data-region="user-competency-status">{{statusname}}
+    <div data-region="competency-summary">
+        {{#competency}}
+            {{> tool_lp/competency_summary }}
+        {{/competency}}
+        <dl>
+            {{#usercompetency}}
+            <dt>{{#str}}reviewstatus, tool_lp{{/str}}</dt>
+            <dd data-region="user-competency-status">{{statusname}}
 
-            {{#isstatusinreview}}
-                - {{reviewer.fullname}}
-            {{/isstatusinreview}}
+                {{#isstatusinreview}}
+                    - {{reviewer.fullname}}
+                {{/isstatusinreview}}
 
-            {{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
-            {{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
-            {{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
-            {{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
-        </dd>
-        <dt>{{#str}}proficient, tool_lp{{/str}}</dt>
-        <dd>
-            <span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
-                {{proficiencyname}}
-            </span>
-        </dd>
-        <dt>{{#str}}rating, tool_lp{{/str}}</dt>
-        <dd>{{gradename}}
-            {{#cangrade}}
-                <button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
-            {{/cangrade}}
-        </dd>
-        {{#js}}
-        require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
+                {{#isrequestreviewallowed}}<button class="btn btn-secondary" data-action="request-review">{{#str}}requestreview, tool_lp{{/str}}</button>{{/isrequestreviewallowed}}
+                {{#iscancelreviewrequestallowed}}<button class="btn btn-secondary" data-action="cancel-review-request">{{#str}}cancelreviewrequest, tool_lp{{/str}}</button>{{/iscancelreviewrequestallowed}}
+                {{#isstartreviewallowed}}<button class="btn btn-secondary" data-action="start-review">{{#str}}startreview, tool_lp{{/str}}</button>{{/isstartreviewallowed}}
+                {{#isstopreviewallowed}}<button class="btn btn-secondary" data-action="stop-review">{{#str}}stopreview, tool_lp{{/str}}</button>{{/isstopreviewallowed}}
+            </dd>
+            <dt>{{#str}}proficient, tool_lp{{/str}}</dt>
+            <dd>
+                <span class="badge {{#proficiency}}badge-success{{/proficiency}}{{^proficiency}}badge-danger{{/proficiency}} float-left">
+                    {{proficiencyname}}
+                </span>
+            </dd>
+            <dt>{{#str}}rating, tool_lp{{/str}}</dt>
+            <dd>{{gradename}}
+                {{#cangrade}}
+                    <button class="btn btn-secondary" id="rate_{{uniqid}}">{{#str}}rate, tool_lp{{/str}}</button>
+                {{/cangrade}}
+            </dd>
+            {{#js}}
+            require(['jquery', 'tool_lp/grade_user_competency_inline', 'tool_lp/user_competency_info', 'tool_lp/user_competency_workflow'], function($, mod, info, UserCompWorkflow) {
 
-            var competencyElement = $('[data-region-id="{{uniqid}}"]');
-            var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
+                var competencyElement = $('[data-region-id="{{uniqid}}"]');
+                var infoReloader = new info(competencyElement, '{{competency.competency.id}}', '{{user.id}}');
 
-            var ucw = new UserCompWorkflow();
-            ucw.registerEvents('[data-region="user-competency-status"]');
-            ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
-            ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
+                var ucw = new UserCompWorkflow();
+                ucw.registerEvents('[data-region="user-competency-status"]');
+                ucw.on('status-changed', infoReloader.reload.bind(infoReloader));
+                ucw.on('error-occured', infoReloader.reload.bind(infoReloader));
 
-            var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
-            inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
-        });
-        {{/js}}
-        {{/usercompetency}}
-    </dl>
-    {{#commentarea}}
-        {{#canpostorhascomments}}
-            {{>tool_lp/comment_area}}
-        {{/canpostorhascomments}}
-    {{/commentarea}}
-    <dl data-region="evidence-listing">
-        <dt>{{#str}}evidence, tool_lp{{/str}}</dt>
-        <dd>
-            {{#evidence}}
-                {{> tool_lp/evidence_summary }}
-            {{/evidence}}
-            {{^evidence}}
-                <p>{{#str}}noevidence, tool_lp{{/str}}</p>
-            {{/evidence}}
-        </dd>
-    </dl>
+                var inlineGrader = new mod('#rate_{{uniqid}}', '{{competency.scaleid}}', '{{competency.competency.id}}', '{{user.id}}', '{{plan.id}}', '', '{{#str}}chooserating, tool_lp{{/str}}');
+                inlineGrader.on('competencyupdated', infoReloader.reload.bind(infoReloader));
+            });
+            {{/js}}
+            {{/usercompetency}}
+        </dl>
+        {{#commentarea}}
+            {{#canpostorhascomments}}
+                {{>tool_lp/comment_area}}
+            {{/canpostorhascomments}}
+        {{/commentarea}}
+        <dl data-region="evidence-listing">
+            <dt>{{#str}}evidence, tool_lp{{/str}}</dt>
+            <dd>
+                {{#evidence}}
+                    {{> tool_lp/evidence_summary }}
+                {{/evidence}}
+                {{^evidence}}
+                    <p>{{#str}}noevidence, tool_lp{{/str}}</p>
+                {{/evidence}}
+            </dd>
+        </dl>
+    </div>
 </div>
index 9b74a68..c91d45a 100644 (file)
@@ -102,7 +102,7 @@ Feature: tool_monitor_subscriptions
     Given I log in as "admin"
     And I follow "Preferences" in the user menu
     And I click on "Notification preferences" "link" in the "#page-content" "css_element"
-    And I click on "//td[@data-processor-name='popup']//label[@class='preference-state']" "xpath_element" in the "Notifications of rule subscriptions" "table_row"
+    And I click on "//td[@data-processor-name='popup']//label[@data-state='loggedin']" "xpath_element" in the "Notifications of rule subscriptions" "table_row"
     And I wait until the page is ready
     And I follow "Preferences" in the user menu
     And I follow "Event monitoring"
@@ -123,7 +123,7 @@ Feature: tool_monitor_subscriptions
     Given I log in as "teacher1"
     And I follow "Preferences" in the user menu
     And I click on "Notification preferences" "link" in the "#page-content" "css_element"
-    And I click on "//td[@data-processor-name='popup']//label[@class='preference-state']" "xpath_element" in the "Notifications of rule subscriptions" "table_row"
+    And I click on "//td[@data-processor-name='popup']//label[@data-state='loggedin']" "xpath_element" in the "Notifications of rule subscriptions" "table_row"
     And I wait until the page is ready
     And I follow "Preferences" in the user menu
     And I follow "Event monitoring"
index 49027fd..d4096b7 100644 (file)
@@ -310,11 +310,10 @@ class profile_manager {
             'CURLOPT_HEADER' => 0,
         ];
         $content = $curl->get($url, null, $options);
-        $errno   = $curl->get_errno();
         $info = $curl->get_info();
 
         // The base cURL seems fine, let's press on.
-        if (!$errno) {
+        if (!$curl->get_errno() && !$curl->error) {
             // WebFinger gave us a 404 back so the user has no droids here.
             if ($info['http_code'] >= 400) {
                 if ($info['http_code'] === 404) {
index a5d6c97..84b0669 100644 (file)
@@ -115,54 +115,69 @@ class issuer extends persistent {
         $mform->addElement('checkbox', 'basicauth', get_string('usebasicauth', 'tool_oauth2'));
         $mform->addHelpButton('basicauth', 'usebasicauth', 'tool_oauth2');
 
+        // Base Url.
+        $mform->addElement('text', 'baseurl', get_string('issuerbaseurl', 'tool_oauth2'));
+        $mform->addRule('baseurl', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
+        $mform->addHelpButton('baseurl', 'issuerbaseurl', 'tool_oauth2');
+        if ($this->type && $this->type == 'nextcloud') {
+            $mform->addRule('baseurl', null, 'required', null, 'client');
+        }
+
+        // Image.
+        $mform->addElement('text', 'image', get_string('issuerimage', 'tool_oauth2'), 'maxlength="1024"');
+        $mform->addRule('image', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
+        $mform->addHelpButton('image', 'issuername', 'tool_oauth2');
+
+        // Show on login page.
+        $options = [
+            \core\oauth2\issuer::EVERYWHERE => get_string('issueruseineverywhere', 'tool_oauth2'),
+            \core\oauth2\issuer::LOGINONLY => get_string('issueruseinloginonly', 'tool_oauth2'),
+            \core\oauth2\issuer::SERVICEONLY => get_string('issueruseininternalonly', 'tool_oauth2'),
+        ];
+        $mform->addElement('select', 'showonloginpage', get_string('issuerusein', 'tool_oauth2'), $options);
+        $mform->addHelpButton('showonloginpage', 'issuerusein', 'tool_oauth2');
+
+        // Name on login page.
+        $mform->addElement('text', 'loginpagename', get_string('issuerloginpagename', 'tool_oauth2'));
+        $mform->addRule('loginpagename', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
+        $mform->addHelpButton('loginpagename', 'issuerloginpagename', 'tool_oauth2');
+        $mform->hideIf('loginpagename', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
+
         // Login scopes.
         $mform->addElement('text', 'loginscopes', get_string('issuerloginscopes', 'tool_oauth2'));
-        $mform->addRule('loginscopes', null, 'required', null, 'client');
         $mform->addRule('loginscopes', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
         $mform->addHelpButton('loginscopes', 'issuerloginscopes', 'tool_oauth2');
+        $mform->hideIf('loginscopes', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
 
         // Login scopes offline.
         $mform->addElement('text', 'loginscopesoffline', get_string('issuerloginscopesoffline', 'tool_oauth2'));
-        $mform->addRule('loginscopesoffline', null, 'required', null, 'client');
         $mform->addRule('loginscopesoffline', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
         $mform->addHelpButton('loginscopesoffline', 'issuerloginscopesoffline', 'tool_oauth2');
+        $mform->hideIf('loginscopesoffline', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
 
         // Login params.
         $mform->addElement('text', 'loginparams', get_string('issuerloginparams', 'tool_oauth2'));
         $mform->addRule('loginparams', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
         $mform->addHelpButton('loginparams', 'issuerloginparams', 'tool_oauth2');
+        $mform->hideIf('loginparams', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
 
         // Login params offline.
         $mform->addElement('text', 'loginparamsoffline', get_string('issuerloginparamsoffline', 'tool_oauth2'));
         $mform->addRule('loginparamsoffline', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
         $mform->addHelpButton('loginparamsoffline', 'issuerloginparamsoffline', 'tool_oauth2');
-
-        // Base Url.
-        $mform->addElement('text', 'baseurl', get_string('issuerbaseurl', 'tool_oauth2'));
-        $mform->addRule('baseurl', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
-        $mform->addHelpButton('baseurl', 'issuerbaseurl', 'tool_oauth2');
-        if ($this->type && $this->type == 'nextcloud') {
-            $mform->addRule('baseurl', null, 'required', null, 'client');
-        }
+        $mform->hideIf('loginparamsoffline', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
 
         // Allowed Domains.
         $mform->addElement('text', 'alloweddomains', get_string('issueralloweddomains', 'tool_oauth2'));
         $mform->addRule('alloweddomains', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
         $mform->addHelpButton('alloweddomains', 'issueralloweddomains', 'tool_oauth2');
-
-        // Image.
-        $mform->addElement('text', 'image', get_string('issuerimage', 'tool_oauth2'), 'maxlength="1024"');
-        $mform->addRule('image', get_string('maximumchars', '', 1024), 'maxlength', 1024, 'client');
-        $mform->addHelpButton('image', 'issuername', 'tool_oauth2');
-
-        // Show on login page.
-        $mform->addElement('checkbox', 'showonloginpage', get_string('issuershowonloginpage', 'tool_oauth2'));
-        $mform->addHelpButton('showonloginpage', 'issuershowonloginpage', 'tool_oauth2');
+        $mform->hideIf('alloweddomains', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
 
         if ($this->showrequireconfirm) {
             // Require confirmation email for new accounts.
             $mform->addElement('advcheckbox', 'requireconfirmation', get_string('issuerrequireconfirmation', 'tool_oauth2'));
             $mform->addHelpButton('requireconfirmation', 'issuerrequireconfirmation', 'tool_oauth2');
+            $mform->hideIf('requireconfirmation', 'showonloginpage', 'eq', \core\oauth2\issuer::SERVICEONLY);
         }
 
         if ($this->type == 'imsobv2p1' || $issuer->get('servicetype') == 'imsobv2p1') {
@@ -209,4 +224,36 @@ class issuer extends persistent {
             $mform->getElement('servicetype')->setValue($this->type);
         }
     }
+
+    /**
+     * Define extra validation mechanims.
+     *
+     * The data here:
+     * - does not include {@see self::$fieldstoremove}.
+     * - does include {@see self::$foreignfields}.
+     * - was converted to map persistent-like data, e.g. array $description to string $description + int $descriptionformat.
+     *
+     * You can modify the $errors parameter in order to remove some validation errors should you
+     * need to. However, the best practice is to return new or overriden errors. Only modify the
+     * errors passed by reference when you have no other option.
+     *
+     * Do not add any logic here, it is only intended to be used by child classes.
+     *
+     * @param  stdClass $data Data to validate.
+     * @param  array $files Array of files.
+     * @param  array $errors Currently reported errors.
+     * @return array of additional errors, or overridden errors.
+     */
+    protected function extra_validation($data, $files, array &$errors) {
+        $errors = [];
+        if ($data->showonloginpage != \core\oauth2\issuer::SERVICEONLY) {
+            if (!strlen(trim($data->loginscopes))) {
+                $errors['loginscopes'] = get_string('required');
+            }
+            if (!strlen(trim($data->loginscopesoffline))) {
+                $errors['loginscopesoffline'] = get_string('required');
+            }
+        }
+        return $errors;
+    }
 }
index b838d0a..cb79978 100644 (file)
@@ -53,8 +53,9 @@ class renderer extends plugin_renderer_base {
         $table = new html_table();
         $table->head  = [
             get_string('name'),
-            get_string('configuredstatus', 'tool_oauth2'),
-            get_string('loginissuer', 'tool_oauth2'),
+            get_string('issuerusedforlogin', 'tool_oauth2'),
+            get_string('logindisplay', 'tool_oauth2'),
+            get_string('issuerusedforinternal', 'tool_oauth2'),
             get_string('discoverystatus', 'tool_oauth2') . ' ' . $this->help_icon('discovered', 'tool_oauth2'),
             get_string('systemauthstatus', 'tool_oauth2') . ' ' . $this->help_icon('systemaccountconnected', 'tool_oauth2'),
             get_string('edit'),
@@ -84,21 +85,29 @@ class renderer extends plugin_renderer_base {
             $namecell = new html_table_cell($name);
             $namecell->header = true;
 
-            // Configured.
-            if ($issuer->is_configured()) {
-                $configured = $this->pix_icon('yes', get_string('configured', 'tool_oauth2'), 'tool_oauth2');
+            // Login issuer.
+            if ((int)$issuer->get('showonloginpage') == issuer::SERVICEONLY) {
+                $loginissuer = $this->pix_icon('no', get_string('notloginissuer', 'tool_oauth2'), 'tool_oauth2');
+                $logindisplayas = '';
             } else {
-                $configured = $this->pix_icon('no', get_string('notconfigured', 'tool_oauth2'), 'tool_oauth2');
+                $logindisplayas = s($issuer->get_display_name());
+                if ($issuer->get('id') && $issuer->is_configured() && !empty($issuer->get_endpoint_url('userinfo'))) {
+                    $loginissuer = $this->pix_icon('yes', get_string('loginissuer', 'tool_oauth2'), 'tool_oauth2');
+                } else {
+                    $loginissuer = $this->pix_icon('notconfigured', get_string('notconfigured', 'tool_oauth2'), 'tool_oauth2');
+                }
             }
-            $configuredstatuscell = new html_table_cell($configured);
+            $loginissuerstatuscell = new html_table_cell($loginissuer);
 
-            // Login issuer.
-            if (!empty($issuer->get('showonloginpage'))) {
-                $loginissuer = $this->pix_icon('yes', get_string('loginissuer', 'tool_oauth2'), 'tool_oauth2');
+            // Internal services issuer.
+            if ((int)$issuer->get('showonloginpage') == issuer::LOGINONLY) {
+                $serviceissuer = $this->pix_icon('no', get_string('issuersservicesnotallow', 'tool_oauth2'), 'tool_oauth2');
+            } else if ($issuer->get('id') && $issuer->is_configured()) {
+                $serviceissuer = $this->pix_icon('yes', get_string('issuersservicesallow', 'tool_oauth2'), 'tool_oauth2');
             } else {
-                $loginissuer = $this->pix_icon('no', get_string('notloginissuer', 'tool_oauth2'), 'tool_oauth2');
+                $serviceissuer = $this->pix_icon('notconfigured', get_string('notconfigured', 'tool_oauth2'), 'tool_oauth2');
             }
-            $loginissuerstatuscell = new html_table_cell($loginissuer);
+            $internalissuerstatuscell = new html_table_cell($serviceissuer);
 
             // Discovered.
             if (!empty($issuer->get('scopessupported'))) {
@@ -186,13 +195,18 @@ class renderer extends plugin_renderer_base {
 
             $row = new html_table_row([
                 $namecell,
-                $configuredstatuscell,
                 $loginissuerstatuscell,
+                $logindisplayas,
+                $internalissuerstatuscell,
                 $discoverystatuscell,
                 $systemauthstatuscell,
                 $editcell,
             ]);
 
+            if (!$issuer->get('enabled')) {
+                $row->attributes['class'] = 'dimmed_text';
+            }
+
             $data[] = $row;
             $index++;
         }
index cc5da40..ad720c4 100644 (file)
@@ -53,7 +53,7 @@ if ($action == 'edit') {
     if ($issuer) {
         $PAGE->navbar->add(get_string('editissuer', 'tool_oauth2', s($issuer->get('name'))));
     } else {
-        $PAGE->navbar->add(get_string('createnewservice', 'tool_oauth2') . get_string('custom_service', 'tool_oauth2'));
+        $PAGE->navbar->add(get_string('createnewservice', 'tool_oauth2') . ' ' . get_string('custom_service', 'tool_oauth2'));
     }
 
     $showrequireconfirm = false;
@@ -96,7 +96,7 @@ if ($mform && $mform->is_cancelled()) {
         if ($issuer) {
             echo $OUTPUT->heading(get_string('editissuer', 'tool_oauth2', s($issuer->get('name'))));
         } else {
-            echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . get_string('custom_service', 'tool_oauth2'));
+            echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . ' ' . get_string('custom_service', 'tool_oauth2'));
         }
         $mform->display();
         echo $OUTPUT->footer();
@@ -115,7 +115,7 @@ if ($mform && $mform->is_cancelled()) {
         redirect($PAGE->url, get_string('changessaved'), null, \core\output\notification::NOTIFY_SUCCESS);
     } else {
         echo $OUTPUT->header();
-        echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . get_string($type . '_service', 'tool_oauth2'));
+        echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . ' ' . get_string($type . '_service', 'tool_oauth2'));
         $mform->display();
         echo $OUTPUT->footer();
     }
@@ -130,9 +130,9 @@ if ($mform && $mform->is_cancelled()) {
     $mform = new \tool_oauth2\form\issuer(null, ['persistent' => $issuer, 'type' => $type,
         'showrequireconfirm' => $showrequireconfirm]);
 
-    $PAGE->navbar->add(get_string('createnewservice', 'tool_oauth2') . get_string($type . '_service', 'tool_oauth2'));
+    $PAGE->navbar->add(get_string('createnewservice', 'tool_oauth2') . ' ' . get_string($type . '_service', 'tool_oauth2'));
     echo $OUTPUT->header();
-    echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . get_string($type . '_service', 'tool_oauth2'));
+    echo $OUTPUT->heading(get_string('createnewservice', 'tool_oauth2') . ' ' . get_string($type . '_service', 'tool_oauth2'));
     $mform->display();
     echo $OUTPUT->footer();
 
@@ -195,11 +195,11 @@ if ($mform && $mform->is_cancelled()) {
     echo $OUTPUT->header();
     echo $OUTPUT->heading(get_string('pluginname', 'tool_oauth2'));
     echo $OUTPUT->doc_link('OAuth2_Services', get_string('serviceshelp', 'tool_oauth2'));
-    $issuers = core\oauth2\api::get_all_issuers();
+    $issuers = core\oauth2\api::get_all_issuers(true);
     echo $renderer->issuers_table($issuers);
 
     echo $renderer->container_start();
-    echo get_string('createnewservice', 'tool_oauth2');
+    echo get_string('createnewservice', 'tool_oauth2') . ' ';
 
     // Google template.
     $docs = 'admin/tool/oauth2/issuers/google';
index 938c576..b929911 100644 (file)
@@ -31,7 +31,7 @@ $string['connectsystemaccount'] = 'Connect to a system account';
 $string['createfromtemplate'] = 'Create an OAuth 2 service from a template';
 $string['createfromtemplatedesc'] = 'Choose one of the OAuth 2 service templates below to create an OAuth service with a valid configuration for one of the known service types. This will create the OAuth 2 service, with all the correct end points and parameters required for authentication, though you will still need to enter the client ID and secret for the new service before it can be used.';
 $string['createnewendpoint'] = 'Create new endpoint for issuer "{$a}"';
-$string['createnewservice'] = 'Create new service: ';
+$string['createnewservice'] = 'Create new service:';
 $string['createnewuserfieldmapping'] = 'Create new user field mapping for issuer "{$a}"';
 $string['custom_service'] = 'Custom';
 $string['deleteconfirm'] = 'Are you sure you want to delete the identity issuer "{$a}"? Any plugins relying on this issuer will stop working.';
@@ -53,7 +53,7 @@ $string['endpointurl_help'] = 'URL for this endpoint. Must use https:// protocol
 $string['endpointurl'] = 'URL';
 $string['facebook_service'] = 'Facebook';
 $string['google_service'] = 'Google';
-$string['imsobv2p1_service'] = 'IMS OBv2.1';
+$string['imsobv2p1_service'] = 'OpenBadges';
 $string['issuersetup'] = 'Detailed instructions on configuring the common OAuth 2 services';
 $string['issuersetuptype'] = 'Detailed instructions on setting up the {$a} OAuth 2 provider';
 $string['issueralloweddomains_help'] = 'If set, this setting is a comma separated list of domains that logins will be restricted to when using this provider.';
@@ -70,6 +70,8 @@ $string['issuerdisabled'] = 'Identity issuer disabled';
 $string['issuerenabled'] = 'Identity issuer enabled';
 $string['issuerimage_help'] = 'An image URL used to show a logo for this issuer. May be displayed on login page.';
 $string['issuerimage'] = 'Logo URL';
+$string['issuerloginpagename'] = 'Name displayed on the login page';
+$string['issuerloginpagename_help'] = 'If specified, this name will be used on the login page instead of the service name above';
 $string['issuerloginparams'] = 'Additional parameters included in a login request.';
 $string['issuerloginparams_help'] = 'Some systems require additional parameters for a login request in order to read the user\'s basic profile.';
 $string['issuerloginparamsoffline'] = 'Additional parameters included in a login request for offline access.';
@@ -85,6 +87,16 @@ $string['issuershowonloginpage'] = 'Show on login page';
 $string['issuerrequireconfirmation_help'] = 'Require that all users verify their email address before they can log in with OAuth. This applies to newly created accounts as part of the login process, or when an existing Moodle account is connected to an OAuth login via matching email addresses.';
 $string['issuerrequireconfirmation'] = 'Require email verification';
 $string['issuers'] = 'Issuers';
+$string['issuersservicesallow'] = 'Allow services';
+$string['issuersservicesnotallow'] = 'Do not allow services';
+$string['issuerusein'] = 'This service will be used';
+$string['issuerusein_help'] = 'OAuth 2 services can be used in some internal services, on the login page, or both, if needed';
+$string['issueruseineverywhere'] = 'Login page and internal services';
+$string['issueruseininternalonly'] = 'Internal services only';
+$string['issueruseinloginonly'] = 'Login page only';
+$string['issuerusedforlogin'] = 'Login';
+$string['issuerusedforinternal'] = 'Internal services';
+$string['logindisplay'] = 'Display on login page as';
 $string['loginissuer'] = 'Allow login';
 $string['microsoft_service'] = 'Microsoft';
 $string['nextcloud_service'] = 'Nextcloud';
diff --git a/admin/tool/oauth2/pix/notconfigured.svg b/admin/tool/oauth2/pix/notconfigured.svg
new file mode 100644 (file)
index 0000000..de01599
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+        viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve" preserveAspectRatio="xMinYMid meet">
+<style type="text/css">
+       .st0{fill:#FFA500;}
+</style>
+<path class="st0" d="M9.1,12.9v-1.7c0-0.1,0-0.2-0.1-0.2s-0.1-0.1-0.2-0.1H7.2c-0.1,0-0.1,0-0.2,0.1s-0.1,0.1-0.1,0.2v1.7
+       c0,0.1,0,0.2,0.1,0.2s0.1,0.1,0.2,0.1h1.7c0.1,0,0.1,0,0.2-0.1S9.1,13,9.1,12.9z M9.1,9.5l0.2-4.1c0-0.1,0-0.1-0.1-0.2
+       C9.1,5.2,9,5.2,9,5.2H7c-0.1,0-0.1,0-0.2,0.1c-0.1,0-0.1,0.1-0.1,0.2l0.2,4.1c0,0.1,0,0.1,0.1,0.1c0.1,0,0.1,0.1,0.2,0.1h1.6
+       c0.1,0,0.2,0,0.2-0.1C9.1,9.7,9.1,9.6,9.1,9.5z M9,1.3l6.8,12.5c0.2,0.4,0.2,0.7,0,1.1c-0.1,0.2-0.2,0.3-0.4,0.4s-0.4,0.2-0.6,0.2
+       H1.2c-0.2,0-0.4-0.1-0.6-0.2S0.3,15,0.2,14.9c-0.2-0.4-0.2-0.7,0-1.1L7,1.3c0.1-0.2,0.2-0.3,0.4-0.4S7.8,0.7,8,0.7s0.4,0.1,0.6,0.2
+       C8.8,0.9,8.9,1.1,9,1.3z"/>
+</svg>
index 2eb3345..1a258b3 100644 (file)
@@ -18,8 +18,8 @@ Feature: Basic OAuth2 functionality
     When I press "Save changes"
     Then I should see "Changes saved"
     And I should see "Testing service"
-    And "Configured" "icon" should exist in the "Testing service" "table_row"
     And "Allow login" "icon" should exist in the "Testing service" "table_row"
+    And "Allow services" "icon" should exist in the "Testing service" "table_row"
     And "Service discovery successful" "icon" should exist in the "Testing service" "table_row"
     And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
     And I should see "https://accounts.google.com/.well-known/openid-configuration" in the "discovery_endpoint" "table_row"
@@ -51,8 +51,8 @@ Feature: Basic OAuth2 functionality
     When I press "Save changes"
     Then I should see "Changes saved"
     And I should see "Testing service"
-    And "Configured" "icon" should exist in the "Testing service" "table_row"
     And "Allow login" "icon" should exist in the "Testing service" "table_row"
+    And "Allow services" "icon" should exist in the "Testing service" "table_row"
     And I should see "-" in the "Testing service" "table_row"
     And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
     And I should see "authorization_endpoint"
@@ -83,8 +83,8 @@ Feature: Basic OAuth2 functionality
     When I press "Save changes"
     Then I should see "Changes saved"
     And I should see "Testing service"
-    And "Configured" "icon" should exist in the "Testing service" "table_row"
     And "Allow login" "icon" should exist in the "Testing service" "table_row"
+    And "Allow services" "icon" should exist in the "Testing service" "table_row"
     And I should see "-" in the "Testing service" "table_row"
     And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
     And I should see "authorization_endpoint"
@@ -120,8 +120,8 @@ Feature: Basic OAuth2 functionality
     When I press "Save changes"
     Then I should see "Changes saved"