Merge branch 'MDL-67301-dynreg-squashed-310' of https://github.com/cengage/moodle...
authorJun Pataleta <jun@moodle.com>
Mon, 26 Oct 2020 04:40:23 +0000 (12:40 +0800)
committerJun Pataleta <jun@moodle.com>
Mon, 26 Oct 2020 04:40:23 +0000 (12:40 +0800)
899 files changed:
.gitignore
.travis.yml
admin/classes/form/testoutgoingmailconf_form.php
admin/classes/local/externalpage/accesscallback.php [new file with mode: 0644]
admin/classes/local/settings/filesize.php
admin/modules.php
admin/plugins.php
admin/roles/tests/privacy_test.php
admin/settings/analytics.php
admin/settings/server.php
admin/templates/setting_configcheckbox.mustache
admin/templates/setting_configcolourpicker.mustache
admin/templates/setting_configdirectory.mustache
admin/templates/setting_configduration.mustache
admin/templates/setting_configexecutable.mustache
admin/templates/setting_configfile.mustache
admin/templates/setting_configfilesize.mustache
admin/templates/setting_configmultiselect.mustache
admin/templates/setting_configmultiselect_optgroup.mustache
admin/templates/setting_configpasswordunmask.mustache
admin/templates/setting_configselect.mustache
admin/templates/setting_configselect_optgroup.mustache
admin/templates/setting_configtext.mustache
admin/templates/setting_configtextarea.mustache
admin/templates/setting_configtime.mustache
admin/tests/behat/language_settings.feature [new file with mode: 0644]
admin/tool/analytics/tests/external_test.php
admin/tool/behat/cli/util_single_run.php
admin/tool/behat/tests/manager_util_test.php
admin/tool/capability/tests/events_test.php
admin/tool/cohortroles/tests/api_test.php
admin/tool/cohortroles/tests/privacy_test.php
admin/tool/customlang/classes/form/export.php [new file with mode: 0644]
admin/tool/customlang/classes/form/import.php [new file with mode: 0644]
admin/tool/customlang/classes/local/importer.php [new file with mode: 0644]
admin/tool/customlang/classes/local/mlang/langstring.php [new file with mode: 0644]
admin/tool/customlang/classes/local/mlang/logstatus.php [new file with mode: 0644]
admin/tool/customlang/classes/local/mlang/phpparser.php [new file with mode: 0644]
admin/tool/customlang/classes/output/renderer.php
admin/tool/customlang/cli/export.php [new file with mode: 0644]
admin/tool/customlang/cli/import.php [new file with mode: 0644]
admin/tool/customlang/db/access.php
admin/tool/customlang/export.php [new file with mode: 0644]
admin/tool/customlang/filter_form.php
admin/tool/customlang/import.php [new file with mode: 0644]
admin/tool/customlang/index.php
admin/tool/customlang/lang/en/tool_customlang.php
admin/tool/customlang/locallib.php
admin/tool/customlang/templates/translator.mustache
admin/tool/customlang/tests/behat/customisation_create.feature [new file with mode: 0644]
admin/tool/customlang/tests/behat/export.feature [new file with mode: 0644]
admin/tool/customlang/tests/behat/import_files.feature [new file with mode: 0644]
admin/tool/customlang/tests/behat/import_mode.feature [new file with mode: 0644]
admin/tool/customlang/tests/fixtures/customlang.zip [new file with mode: 0644]
admin/tool/customlang/tests/fixtures/mod_fakecomponent.php [new file with mode: 0644]
admin/tool/customlang/tests/fixtures/moodle.php [new file with mode: 0644]
admin/tool/customlang/tests/fixtures/tool_customlang.php [new file with mode: 0644]
admin/tool/customlang/tests/local/mlang/langstring_test.php [new file with mode: 0644]
admin/tool/customlang/tests/local/mlang/phpparser_test.php [new file with mode: 0644]
admin/tool/customlang/version.php
admin/tool/dataprivacy/tests/api_test.php
admin/tool/dataprivacy/tests/expired_data_requests_test.php
admin/tool/dataprivacy/tests/manager_observer_test.php
admin/tool/dataprivacy/tests/privacy_provider_test.php
admin/tool/dataprivacy/tests/task_test.php
admin/tool/httpsreplace/tests/httpsreplace_test.php
admin/tool/installaddon/tests/installer_test.php
admin/tool/langimport/tests/events_test.php
admin/tool/log/store/database/tests/privacy_test.php
admin/tool/log/store/legacy/tests/privacy_test.php
admin/tool/log/store/standard/tests/privacy_test.php
admin/tool/log/store/standard/tests/store_test.php
admin/tool/log/tests/manager_test.php
admin/tool/log/tests/privacy_test.php
admin/tool/lp/tests/externallib_test.php
admin/tool/lpmigrate/tests/processor_test.php
admin/tool/messageinbound/tests/manager_test.php
admin/tool/messageinbound/tests/privacy_test.php
admin/tool/mobile/classes/api.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/lib.php
admin/tool/mobile/logout.php [new file with mode: 0644]
admin/tool/mobile/tests/api_test.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/tests/privacy_provider_test.php
admin/tool/monitor/tests/eventobservers_test.php
admin/tool/monitor/tests/events_test.php
admin/tool/monitor/tests/generator_test.php
admin/tool/monitor/tests/privacy_test.php
admin/tool/monitor/tests/rule_manager_test.php
admin/tool/monitor/tests/subscription_test.php
admin/tool/monitor/tests/task_check_subscriptions_test.php
admin/tool/monitor/tests/task_clean_events_test.php
admin/tool/policy/tests/externallib_test.php
admin/tool/policy/tests/privacy_provider_test.php
admin/tool/recyclebin/tests/category_bin_test.php
admin/tool/recyclebin/tests/course_bin_test.php
admin/tool/recyclebin/tests/events_test.php
admin/tool/replace/classes/form.php
admin/tool/replace/cli/replace.php
admin/tool/replace/index.php
admin/tool/replace/lang/en/tool_replace.php
admin/tool/templatelibrary/tests/externallib_test.php
admin/tool/uploadcourse/tests/course_test.php
admin/tool/uploadcourse/tests/processor_test.php
admin/tool/uploaduser/classes/cli_helper.php [new file with mode: 0644]
admin/tool/uploaduser/classes/local/cli_progress_tracker.php [new file with mode: 0644]
admin/tool/uploaduser/classes/local/text_progress_tracker.php [new file with mode: 0644]
admin/tool/uploaduser/classes/preview.php [new file with mode: 0644]
admin/tool/uploaduser/classes/process.php [new file with mode: 0644]
admin/tool/uploaduser/cli/uploaduser.php [new file with mode: 0644]
admin/tool/uploaduser/index.php
admin/tool/uploaduser/lang/en/tool_uploaduser.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/tests/cli_test.php [new file with mode: 0644]
admin/tool/uploaduser/user_form.php
admin/tool/usertours/amd/build/filter_cssselector.min.js [new file with mode: 0644]
admin/tool/usertours/amd/build/filter_cssselector.min.js.map [new file with mode: 0644]
admin/tool/usertours/amd/build/usertours.min.js
admin/tool/usertours/amd/build/usertours.min.js.map
admin/tool/usertours/amd/src/filter_cssselector.js [new file with mode: 0644]
admin/tool/usertours/amd/src/usertours.js
admin/tool/usertours/classes/external/tour.php
admin/tool/usertours/classes/helper.php
admin/tool/usertours/classes/local/clientside_filter/clientside_filter.php [new file with mode: 0644]
admin/tool/usertours/classes/local/clientside_filter/cssselector.php [new file with mode: 0644]
admin/tool/usertours/classes/manager.php
admin/tool/usertours/classes/tour.php
admin/tool/usertours/lang/en/tool_usertours.php
admin/tool/usertours/tests/accessdate_filter_test.php
admin/tool/usertours/tests/behat/tour_filter.feature
admin/tool/usertours/tests/cache_test.php
admin/tool/usertours/tests/manager_test.php
admin/tool/usertours/tests/privacy_provider_test.php
admin/tool/usertours/tests/role_filter_test.php
admin/tool/usertours/tests/step_test.php
admin/tool/usertours/tests/tour_test.php
admin/tool/usertours/version.php
analytics/classes/model.php
analytics/tests/course_test.php
analytics/tests/dataset_manager_test.php
analytics/tests/indicator_test.php
analytics/tests/model_test.php
analytics/tests/prediction_actions_test.php
analytics/tests/prediction_test.php
analytics/tests/privacy_test.php
analytics/tests/stats_test.php
auth/email/tests/external_test.php
auth/manual/tests/manual_test.php
auth/manual/tests/privacy_provider_test.php
auth/mnet/tests/privacy_provider_test.php
auth/oauth2/tests/auth_test.php
auth/oauth2/tests/privacy_provider_test.php
auth/tests/external_test.php
auth/tests/privacy_test.php
availability/condition/completion/tests/condition_test.php
availability/condition/date/tests/condition_test.php
availability/condition/grade/tests/behat/availability_grade.feature
availability/condition/grade/tests/condition_test.php
availability/condition/group/tests/condition_test.php
availability/condition/grouping/tests/condition_test.php
availability/condition/profile/tests/condition_test.php
availability/tests/info_test.php
availability/tests/tree_test.php
backup/cc/cc2moodle.php
backup/cc/entities.class.php
backup/controller/tests/controller_test.php
backup/converter/moodle1/tests/moodle1_converter_test.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/backup_encrypted_content_test.php
backup/moodle2/tests/backup_xml_transformer_test.php
backup/tests/automated_backup_test.php
backup/tests/course_copy_test.php
backup/tests/externallib_test.php
backup/tests/privacy_provider_test.php
backup/util/checks/tests/checks_test.php
backup/util/dbops/tests/backup_dbops_test.php
backup/util/factories/tests/factories_test.php
backup/util/helper/tests/restore_structure_parser_processor_test.php
backup/util/plan/tests/plan_test.php
backup/util/plan/tests/step_test.php
backup/util/plan/tests/task_test.php
backup/util/structure/tests/structure_test.php
backup/util/ui/renderer.php
badges/tests/badgeslib_test.php
badges/tests/external_test.php
badges/tests/privacy_test.php
blocks/comments/tests/events_test.php
blocks/comments/tests/privacy_provider_test.php
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/online_users/tests/online_users_test.php
blocks/recentlyaccesseditems/tests/observer_test.php
blocks/rss_client/tests/cron_test.php
blocks/rss_client/tests/privacy_test.php
blocks/site_main_menu/block_site_main_menu.php
blocks/social_activities/block_social_activities.php
blocks/tests/privacy_test.php
blog/tests/events_test.php
blog/tests/external_test.php
blog/tests/lib_test.php
blog/tests/privacy_test.php
cache/classes/administration_helper.php
cache/stores/apcu/tests/apcu_test.php
cache/stores/redis/tests/compressor_test.php
cache/stores/redis/tests/redis_test.php
cache/tests/administration_helper_test.php
cache/tests/cache_test.php
cache/tests/config_writer_test.php
cache/tests/fixtures/stores.php
calendar/classes/local/event/strategies/raw_event_retrieval_strategy.php
calendar/export_execute.php
calendar/templates/month_detailed.mustache
calendar/tests/calendartype_test.php
calendar/tests/container_test.php
calendar/tests/events_related_objects_cache_test.php
calendar/tests/events_test.php
calendar/tests/externallib_test.php
calendar/tests/lib_test.php
calendar/tests/local_api_test.php
calendar/tests/privacy_test.php
calendar/tests/raw_event_retrieval_strategy_test.php
calendar/tests/rrule_manager_test.php
calendar/tests/std_proxy_test.php
cohort/tests/cohortlib_test.php
cohort/tests/externallib_test.php
cohort/tests/privacy_test.php
comment/tests/externallib_test.php
comment/tests/privacy_test.php
competency/tests/api_test.php
competency/tests/event_test.php
competency/tests/external_test.php
competency/tests/lib_test.php
competency/tests/plan_test.php
competency/tests/privacy_test.php
completion/tests/api_test.php
completion/tests/behat/restrict_activity_by_grade.feature
completion/tests/behat/restrict_section_availability.feature
completion/tests/progress_test.php
composer.json
composer.lock
config-dist.php
contentbank/contenttype/h5p/tests/contenttype_h5p_test.php
contentbank/tests/contentbank_test.php
contentbank/tests/contenttype_test.php
course/classes/local/service/content_item_service.php
course/classes/search/customfield.php
course/externallib.php
course/format/weeks/tests/observer_test.php
course/renderer.php
course/tests/category_hooks_test.php
course/tests/category_test.php
course/tests/courselib_test.php
course/tests/customfield_test.php
course/tests/events_test.php
course/tests/externallib_test.php
course/tests/management_helper_test.php
course/tests/search_test.php
customfield/field/checkbox/tests/plugin_test.php
customfield/field/date/tests/plugin_test.php
customfield/field/select/tests/plugin_test.php
customfield/field/text/tests/plugin_test.php
customfield/field/textarea/tests/plugin_test.php
customfield/tests/privacy_test.php
enrol/flatfile/tests/privacy_provider_test.php
enrol/imsenterprise/tests/imsenterprise_test.php
enrol/lti/tests/data_connector_test.php
enrol/lti/tests/helper_test.php
enrol/lti/tests/lib_test.php
enrol/lti/tests/privacy_provider_test.php
enrol/lti/tests/sync_members_test.php
enrol/lti/tests/tool_provider_test.php
enrol/manual/tests/lib_test.php
enrol/meta/tests/privacy_test.php
enrol/paypal/tests/privacy_provider_test.php
enrol/self/tests/self_test.php
enrol/tests/course_enrolment_manager_test.php
enrol/tests/enrollib_test.php
enrol/tests/externallib_test.php
enrol/tests/role_external_test.php
favourites/tests/component_favourite_service_test.php
favourites/tests/privacy_test.php
favourites/tests/repository_test.php
favourites/tests/user_favourite_service_test.php
files/classes/external/delete/draft.php [new file with mode: 0644]
files/tests/conversion_test.php
files/tests/externallib_test.php
filter/algebra/tests/filter_test.php
filter/displayh5p/tests/filter_test.php
filter/mediaplugin/tests/filter_test.php
filter/multilang/tests/filter_test.php
filter/tex/tests/filter_test.php
grade/edit/tree/item.php
grade/edit/tree/item_form.php
grade/edit/tree/lib.php
grade/export/ods/tests/logging_test.php
grade/export/txt/tests/logging_test.php
grade/export/xls/tests/logging_test.php
grade/export/xml/tests/logging_test.php
grade/grading/form/guide/tests/behat/edit_guide.feature
grade/grading/tests/behat/behat_grading.php
grade/grading/tests/grading_manager_test.php
grade/import/csv/tests/load_data_test.php
grade/report/grader/module.js
grade/report/grader/tests/behat/ajax_grader.feature
grade/report/grader/tests/privacy_test.php
grade/report/overview/tests/externallib_test.php
grade/report/singleview/tests/behat/bulk_insert_grades.feature
grade/report/user/tests/lib_test.php
grade/report/user/tests/privacy_test.php
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/edittreelib_test.php
grade/tests/events_test.php
grade/tests/grades_grader_gradingpanel_point_external_store_test.php
grade/tests/importlib_test.php
grade/tests/privacy_test.php
grade/tests/report_graderlib_test.php
group/tests/privacy_provider_test.php
h5p/ajax.php
h5p/classes/core.php
h5p/classes/editor_ajax.php
h5p/classes/editor_framework.php
h5p/classes/external.php
h5p/classes/framework.php
h5p/classes/player.php
h5p/tests/editor_ajax_test.php
h5p/tests/editor_framework_test.php
h5p/tests/event_h5p_deleted_test.php
h5p/tests/event_h5p_viewed_test.php
h5p/tests/external_test.php
h5p/tests/framework_test.php
h5p/tests/generator/lib.php
h5p/tests/generator_test.php
h5p/tests/h5p_core_test.php
h5p/tests/h5p_file_storage_test.php
h5p/tests/helper_test.php
iplookup/tests/geoip_test.php
iplookup/tests/geoplugin_test.php
lang/en/admin.php
lang/en/analytics.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/moodle.php
lib/adminlib.php
lib/amd/build/notification.min.js
lib/amd/build/notification.min.js.map
lib/amd/src/notification.js
lib/antivirus/clamav/tests/scanner_test.php
lib/behat/classes/util.php
lib/blocklib.php
lib/classes/component.php
lib/classes/content.php [new file with mode: 0644]
lib/classes/content/export/exportable_item.php [new file with mode: 0644]
lib/classes/content/export/exportable_items/exportable_filearea.php [new file with mode: 0644]
lib/classes/content/export/exportable_items/exportable_stored_file.php [new file with mode: 0644]
lib/classes/content/export/exportable_items/exportable_textarea.php [new file with mode: 0644]
lib/classes/content/export/exported_item.php [new file with mode: 0644]
lib/classes/content/export/exporters/abstract_mod_exporter.php [new file with mode: 0644]
lib/classes/content/export/exporters/component_exporter.php [new file with mode: 0644]
lib/classes/content/export/exporters/course_exporter.php [new file with mode: 0644]
lib/classes/content/export/zipwriter.php [new file with mode: 0644]
lib/classes/notification.php
lib/classes/oauth2/api.php
lib/classes/oauth2/client.php
lib/classes/output/mustache_template_source_loader.php
lib/classes/plugin_manager.php
lib/classes/plugininfo/base.php
lib/classes/privacy/provider.php
lib/classes/session/manager.php
lib/classes/session/redis.php
lib/classes/string_manager_standard.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/ddl/tests/ddl_test.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/dml/tests/pgsql_native_recordset_test.php
lib/dml/tests/recordset_walk_test.php
lib/dml/tests/sqlsrv_native_moodle_database_test.php
lib/filebrowser/tests/file_browser_test.php
lib/filestorage/tests/file_storage_test.php
lib/filestorage/tests/file_system_filedir_test.php
lib/filestorage/tests/file_system_test.php
lib/filestorage/tests/zip_packer_test.php
lib/form/filemanager.js
lib/form/templates/element-defaultcustom.mustache
lib/form/tests/behat/modgrade_validation.feature
lib/form/tests/course_test.php
lib/form/tests/dateselector_test.php
lib/form/tests/datetimeselector_test.php
lib/form/tests/duration_test.php
lib/form/tests/external_test.php
lib/form/tests/filetypes_util_test.php
lib/form/tests/privacy_provider_test.php
lib/grade/tests/fixtures/lib.php
lib/grade/tests/grade_item_test.php
lib/jabber/XMPP/BOSH.php
lib/jabber/XMPP/Exception.php
lib/jabber/XMPP/Log.php
lib/jabber/XMPP/README.txt [deleted file]
lib/jabber/XMPP/Roster.php
lib/jabber/XMPP/XMLObj.php
lib/jabber/XMPP/XMLStream.php
lib/jabber/XMPP/XMPP.php
lib/jabber/XMPP/XMPP_Old.php [deleted file]
lib/jabber/readme_moodle.txt
lib/moodlelib.php
lib/outputlib.php
lib/outputrenderers.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/arraydataset.php [deleted file]
lib/phpunit/classes/constraint_object_is_equal_with_exceptions.php
lib/phpunit/classes/database_driver_testcase.php
lib/phpunit/classes/hint_resultprinter.php [deleted file]
lib/phpunit/classes/phpunit_dataset.php [new file with mode: 0644]
lib/phpunit/classes/restore_date_testcase.php
lib/phpunit/lib.php
lib/phpunit/tests/advanced_test.php
lib/phpunit/tests/basic_test.php
lib/phpunit/tests/fixtures/sample_dataset.csv
lib/phpunit/tests/fixtures/sample_dataset.txt [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset.xml
lib/phpunit/tests/fixtures/sample_dataset2.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_col_before_row.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_insert.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_many.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_many_with_empty.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_number_of_columns.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_only_colrow.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_repeated.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_row_after_col.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_wrong_attribute.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_wrong_dataset.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_wrong_table.xml [new file with mode: 0644]
lib/phpunit/tests/fixtures/sample_dataset_wrong_value.xml [new file with mode: 0644]
lib/phpunit/tests/phpunit_dataset.test.php [new file with mode: 0644]
lib/setup.php
lib/setuplib.php
lib/templates/content/export/course_index.mustache [new file with mode: 0644]
lib/templates/content/export/course_summary.mustache [new file with mode: 0644]
lib/templates/content/export/external_page.mustache [new file with mode: 0644]
lib/templates/content/export/module_index.mustache [new file with mode: 0644]
lib/templates/loginform.mustache
lib/testing/tests/generator_test.php
lib/tests/accesslib_test.php
lib/tests/adminlib_test.php
lib/tests/admintree_test.php
lib/tests/ajaxlib_test.php
lib/tests/antivirus_test.php
lib/tests/authlib_test.php
lib/tests/blocklib_test.php
lib/tests/calendar_cron_task_test.php
lib/tests/collator_test.php
lib/tests/completionlib_test.php
lib/tests/component_test.php
lib/tests/content/export/exportable_items/exportable_filearea_test.php [new file with mode: 0644]
lib/tests/content/export/exportable_items/exportable_stored_file_test.php [new file with mode: 0644]
lib/tests/content/export/exportable_items/exportable_textarea_test.php [new file with mode: 0644]
lib/tests/content/export/exporters/course_exporter_test.php [new file with mode: 0644]
lib/tests/content/export/zipwriter_test.php [new file with mode: 0644]
lib/tests/core_media_player_native.php
lib/tests/core_renderer_template_exploit_test.php
lib/tests/csvclass_test.php
lib/tests/customcontext_test.php
lib/tests/event/contentbank_content_created_test.php
lib/tests/event/contentbank_content_deleted_test.php
lib/tests/event/contentbank_content_updated_test.php
lib/tests/event/contentbank_content_uploaded_test.php
lib/tests/event/contentbank_content_viewed_test.php
lib/tests/event_course_module_viewed.php
lib/tests/event_profile_field_test.php
lib/tests/event_test.php
lib/tests/event_user_graded_test.php
lib/tests/events_test.php
lib/tests/exporter_test.php
lib/tests/externallib_test.php
lib/tests/filelib_test.php
lib/tests/filetypes_test.php
lib/tests/filterlib_test.php
lib/tests/gdlib_test.php
lib/tests/grouplib_test.php
lib/tests/h5p_get_content_types_task_test.php
lib/tests/jquery_test.php
lib/tests/lock_test.php
lib/tests/mathslib_test.php
lib/tests/medialib_test.php
lib/tests/messagelib_test.php
lib/tests/minify_test.php
lib/tests/modinfolib_test.php
lib/tests/moodle_page_test.php
lib/tests/moodle_url_test.php
lib/tests/moodlelib_test.php
lib/tests/mustache_template_finder_test.php
lib/tests/mustache_template_source_loader_test.php
lib/tests/myprofilelib_test.php
lib/tests/notification_test.php
lib/tests/outputcomponents_test.php
lib/tests/outputrequirementslib_test.php
lib/tests/persistent_test.php
lib/tests/plugin_manager_test.php
lib/tests/plugininfo/base_test.php
lib/tests/progress_display_test.php
lib/tests/qrcode_test.php
lib/tests/questionlib_test.php
lib/tests/requirejs_test.php
lib/tests/rsslib_test.php
lib/tests/scheduled_task_test.php
lib/tests/session_manager_test.php
lib/tests/session_redis_test.php
lib/tests/setuplib_test.php
lib/tests/statslib_test.php
lib/tests/string_manager_standard_test.php
lib/tests/tablelib_test.php
lib/tests/task_database_logger_test.php
lib/tests/task_logging_test.php
lib/tests/time_splittings_test.php
lib/tests/update_checker_test.php
lib/tests/update_code_manager_test.php
lib/tests/update_validator_test.php
lib/tests/user_test.php
lib/tests/useragent_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/weblib.php
lib/xapi/tests/external/post_statement_test.php
login/token.php
media/player/html5audio/tests/player_test.php
media/player/html5video/tests/player_test.php
media/player/swf/tests/player_test.php
media/player/videojs/tests/player_test.php
media/player/vimeo/tests/player_test.php
media/player/youtube/tests/player_test.php
message/classes/api.php
message/externallib.php
message/output/airnotifier/tests/externallib_test.php
message/output/airnotifier/tests/privacy_test.php
message/output/email/tests/privacy_test.php
message/output/email/tests/send_email_task_test.php
message/output/jabber/message_output_jabber.php
message/output/jabber/tests/privacy_test.php
message/output/popup/db/upgrade.php
message/output/popup/tests/api_test.php
message/output/popup/tests/externallib_test.php
message/tests/api_test.php
message/tests/events_test.php
message/tests/externallib_test.php
message/tests/helper_test.php
message/tests/inbound_test.php
message/tests/messagelib_test.php
message/tests/migrate_message_data_task_test.php
message/tests/privacy_provider_test.php
message/tests/search_received_test.php
message/tests/search_sent_test.php
mnet/service/enrol/tests/privacy_test.php
mnet/tests/events_test.php
mod/assign/amd/build/grading_panel.min.js
mod/assign/amd/build/grading_panel.min.js.map
mod/assign/amd/src/grading_panel.js
mod/assign/feedback/comments/tests/privacy_test.php
mod/assign/feedback/editpdf/lib.php
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/group_annotations.feature
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/editpdf/tests/editpdf_test.php
mod/assign/feedback/editpdf/tests/privacy_test.php
mod/assign/feedback/file/tests/behat/feedback_file.feature
mod/assign/styles.css
mod/assign/submission/comments/tests/privacy_test.php
mod/assign/tests/base_test.php
mod/assign/tests/behat/allow_another_attempt.feature
mod/assign/tests/behat/assign_hidden.feature
mod/assign/tests/behat/comment_inline.feature
mod/assign/tests/behat/display_grade.feature
mod/assign/tests/behat/edit_previous_feedback.feature
mod/assign/tests/behat/filter_by_marker.feature
mod/assign/tests/behat/grading_app_filters.feature
mod/assign/tests/behat/grading_status.feature
mod/assign/tests/behat/group_submission.feature
mod/assign/tests/behat/hide_grader.feature
mod/assign/tests/behat/outcome_grading.feature
mod/assign/tests/behat/prevent_submission_changes.feature
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/behat/relative_dates.feature
mod/assign/tests/behat/rescale_grades.feature
mod/assign/tests/behat/steps_blind_marking.feature
mod/assign/tests/behat/submission_comments.feature
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/tests/privacy_test.php
mod/book/tests/events_test.php
mod/book/tests/lib_test.php
mod/book/tests/search_test.php
mod/book/tool/exportimscp/tests/events_test.php
mod/book/tool/importhtml/tests/locallib_test.php
mod/book/tool/print/tests/events_test.php
mod/chat/tests/lib_test.php
mod/chat/tests/privacy_test.php
mod/choice/tests/events_test.php
mod/choice/tests/lib_test.php
mod/choice/tests/privacy_provider_test.php
mod/data/mod_form.php
mod/data/tests/behat/create_activity.feature [new file with mode: 0644]
mod/data/tests/events_test.php
mod/data/tests/externallib_test.php
mod/data/tests/import_test.php
mod/data/tests/lib_test.php
mod/data/tests/privacy_provider_test.php
mod/data/tests/search_test.php
mod/feedback/backup/moodle2/restore_feedback_stepslib.php
mod/feedback/tests/events_test.php
mod/feedback/tests/external_test.php
mod/feedback/tests/privacy_test.php
mod/feedback/tests/restore_date_test.php
mod/folder/classes/content/exporter.php [new file with mode: 0644]
mod/folder/tests/events_test.php
mod/folder/tests/lib_test.php
mod/folder/tests/search_test.php
mod/forum/amd/build/discussion_nested_v2.min.js
mod/forum/amd/build/discussion_nested_v2.min.js.map
mod/forum/amd/build/inpage_reply.min.js
mod/forum/amd/build/inpage_reply.min.js.map
mod/forum/amd/build/posts_list.min.js
mod/forum/amd/build/posts_list.min.js.map
mod/forum/amd/src/discussion_nested_v2.js
mod/forum/amd/src/inpage_reply.js
mod/forum/amd/src/posts_list.js
mod/forum/externallib.php
mod/forum/templates/inpage_reply.mustache
mod/forum/templates/inpage_reply_v2.mustache
mod/forum/tests/behat/behat_mod_forum.php
mod/forum/tests/behat/discussion_lock.feature
mod/forum/tests/builders_exported_posts_test.php
mod/forum/tests/events_test.php
mod/forum/tests/exporters_discussion_test.php
mod/forum/tests/externallib_test.php
mod/forum/tests/generator_test.php
mod/forum/tests/grades_gradeitems_test.php
mod/forum/tests/lib_test.php
mod/forum/tests/mail_group_test.php
mod/forum/tests/mail_test.php
mod/forum/tests/maildigest_test.php
mod/forum/tests/managers_capability_test.php
mod/forum/tests/privacy_provider_test.php
mod/forum/tests/private_replies_test.php
mod/forum/tests/qanda_test.php
mod/forum/tests/search_test.php
mod/forum/tests/subscriptions_test.php
mod/forum/tests/vaults_discussion_list_test.php
mod/forum/tests/vaults_discussion_test.php
mod/forum/tests/vaults_post_test.php
mod/forum/upgrade.txt
mod/glossary/classes/external.php
mod/glossary/classes/external/delete_entry.php [new file with mode: 0644]
mod/glossary/classes/external/prepare_entry.php [new file with mode: 0644]
mod/glossary/classes/external/update_entry.php [new file with mode: 0644]
mod/glossary/db/services.php
mod/glossary/deleteentry.php
mod/glossary/edit.php
mod/glossary/lib.php
mod/glossary/tests/events_test.php
mod/glossary/tests/external/delete_entry.php [new file with mode: 0644]
mod/glossary/tests/external/prepare_entry.php [new file with mode: 0644]
mod/glossary/tests/external/update_entry.php [new file with mode: 0644]
mod/glossary/tests/external_test.php
mod/glossary/tests/lib_test.php
mod/glossary/tests/privacy_provider_test.php
mod/glossary/tests/search_test.php
mod/glossary/upgrade.txt
mod/glossary/version.php
mod/label/tests/lib_test.php
mod/lesson/tests/events_test.php
mod/lesson/tests/external_test.php
mod/lesson/tests/privacy_test.php
mod/lti/classes/external.php
mod/lti/db/install.xml
mod/lti/db/upgrade.php
mod/lti/locallib.php
mod/lti/service/gradebookservices/tests/privacy_provider_test.php
mod/lti/service/gradebookservices/tests/task_cleanup_test.php
mod/lti/service/memberships/classes/local/service/memberships.php
mod/lti/service/memberships/tests/privacy_provider_test.php
mod/lti/tests/externallib_test.php
mod/lti/tests/lib_test.php
mod/lti/tests/locallib_test.php
mod/lti/tests/task_clean_access_tokens_test.php
mod/lti/upgrade.txt
mod/lti/version.php
mod/page/classes/content/exporter.php [new file with mode: 0644]
mod/page/tests/lib_test.php
mod/quiz/accessrule/seb/tests/access_manager_test.php
mod/quiz/accessrule/seb/tests/backup_restore_test.php
mod/quiz/accessrule/seb/tests/event_test.php
mod/quiz/accessrule/seb/tests/link_generator_test.php
mod/quiz/accessrule/seb/tests/quiz_settings_test.php
mod/quiz/accessrule/seb/tests/rule_test.php
mod/quiz/accessrule/seb/tests/template_test.php
mod/quiz/report/overview/tests/report_test.php
mod/quiz/report/responses/tests/responses_from_steps_walkthrough_test.php
mod/quiz/report/statistics/tests/statistics_test.php
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
mod/quiz/tests/attempt_walkthrough_from_csv_test.php
mod/quiz/tests/external_test.php
mod/quiz/tests/local_structure_slot_random_test.php
mod/quiz/tests/locallib_test.php
mod/quiz/tests/privacy_provider_test.php
mod/quiz/tests/quiz_question_bank_view_test.php
mod/quiz/tests/repaginate_test.php
mod/quiz/tests/structure_test.php
mod/quiz/tests/tags_test.php
mod/resource/classes/content/exporter.php [new file with mode: 0644]
mod/resource/tests/events_test.php
mod/resource/tests/lib_test.php
mod/resource/tests/search_test.php
mod/scorm/report/basic/tests/privacy_test.php
mod/scorm/report/interactions/tests/privacy_test.php
mod/scorm/report/objectives/tests/privacy_test.php
mod/scorm/tests/events_test.php
mod/scorm/tests/externallib_test.php
mod/scorm/tests/lib_test.php
mod/scorm/tests/locallib_test.php
mod/survey/tests/events_test.php
mod/survey/tests/externallib_test.php
mod/survey/tests/lib_test.php
mod/survey/tests/privacy_test.php
mod/url/tests/lib_test.php
mod/wiki/tests/events_test.php
mod/wiki/tests/externallib_test.php
mod/wiki/tests/lib_test.php
mod/wiki/tests/privacy_test.php
mod/wiki/tests/search_test.php
mod/wiki/tests/wikiparser_test.php
mod/workshop/allocation/manual/tests/privacy_provider_test.php
mod/workshop/allocation/random/tests/allocator_test.php
mod/workshop/eval/best/tests/lib_test.php
mod/workshop/form/accumulative/tests/lib_test.php
mod/workshop/form/numerrors/tests/lib_test.php
mod/workshop/form/rubric/tests/lib_test.php
mod/workshop/tests/cron_task_test.php
mod/workshop/tests/events_test.php
mod/workshop/tests/external_test.php
mod/workshop/tests/locallib_test.php
mod/workshop/tests/portfolio_caller_test.php
mod/workshop/tests/privacy_provider_test.php
my/tests/events_test.php
notes/tests/events_test.php
notes/tests/generator_test.php
notes/tests/lib_test.php
phpunit.xml.dist
privacy/classes/tests/provider_testcase.php
privacy/tests/manager_test.php
privacy/tests/provider_test.php
privacy/tests/request_transform_test.php
privacy/tests/writer_test.php
question/behaviour/adaptive/tests/behaviourtype_test.php
question/behaviour/adaptive/tests/mark_display_test.php
question/behaviour/adaptive/tests/walkthrough_test.php
question/behaviour/adaptivenopenalty/tests/walkthrough_test.php
question/behaviour/deferredcbm/tests/behaviourtype_test.php
question/behaviour/deferredcbm/tests/question_cbm_test.php
question/behaviour/deferredcbm/tests/walkthrough_test.php
question/behaviour/deferredfeedback/tests/behaviourtype_test.php
question/behaviour/deferredfeedback/tests/walkthrough_test.php
question/behaviour/immediatecbm/tests/behaviourtype_test.php
question/behaviour/immediatecbm/tests/walkthrough_test.php
question/behaviour/immediatefeedback/tests/behaviourtype_test.php
question/behaviour/immediatefeedback/tests/walkthrough_test.php
question/behaviour/informationitem/tests/behaviourtype_test.php
question/behaviour/interactive/renderer.php
question/behaviour/interactive/tests/behaviourtype_test.php
question/behaviour/interactive/tests/walkthrough_test.php
question/behaviour/interactivecountback/tests/behaviourtype_test.php
question/behaviour/manualgraded/tests/behaviourtype_test.php
question/behaviour/missing/tests/behaviourtype_test.php
question/behaviour/missing/tests/missingbehaviour_test.php
question/engine/tests/helpers.php
question/engine/tests/questionattempt_test.php
question/engine/tests/questionattempt_with_steps_test.php
question/engine/tests/questionattemptiterator_test.php
question/engine/tests/questionattemptstep_test.php
question/engine/tests/questionattemptstepiterator_test.php
question/engine/tests/questionengine_test.php
question/engine/tests/questionusage_autosave_test.php
question/engine/tests/questionutils_test.php
question/engine/tests/unitofwork_test.php
question/engine/upgrade/tests/helper.php
question/format/aiken/tests/aikenformat_test.php
question/format/multianswer/tests/multianswerformat_test.php
question/format/xml/format.php
question/format/xml/tests/fixtures/html_chars_in_idnumbers.xml [new file with mode: 0644]
question/format/xml/tests/qformat_xml_import_export_test.php
question/tests/backup_test.php
question/tests/bank_view_test.php
question/tests/category_class_test.php
question/tests/events_test.php
question/tests/externallib_test.php
question/tests/generator/lib.php
question/tests/privacy_provider_test.php
question/tests/question_bank_column_test.php
question/type/calculated/tests/formula_validation_test.php
question/type/calculated/tests/questiontype_test.php
question/type/calculated/tests/variablesubstituter_test.php
question/type/calculatedsimple/tests/questiontype_test.php
question/type/ddimageortext/tests/questiontype_test.php
question/type/ddmarker/tests/questiontype_test.php
question/type/ddwtos/amd/build/ddwtos.min.js
question/type/ddwtos/amd/build/ddwtos.min.js.map
question/type/ddwtos/amd/src/ddwtos.js
question/type/ddwtos/tests/behat/behat_qtype_ddwtos.php
question/type/ddwtos/tests/behat/preview.feature
question/type/ddwtos/tests/helper.php
question/type/ddwtos/tests/questiontype_test.php
question/type/description/tests/questiontype_test.php
question/type/essay/tests/questiontype_test.php
question/type/essay/tests/walkthrough_test.php
question/type/gapselect/tests/questiontype_test.php
question/type/match/tests/questiontype_test.php
question/type/missingtype/tests/missingtype_test.php
question/type/multianswer/tests/questiontype_test.php
question/type/multichoice/tests/question_multi_test.php
question/type/multichoice/tests/question_single_test.php
question/type/multichoice/tests/questiontype_test.php
question/type/numerical/tests/answerprocessor_test.php
question/type/numerical/tests/questiontype_test.php
question/type/random/tests/questiontype_test.php
question/type/shortanswer/tests/questiontype_test.php
question/type/tests/question_first_matching_answer_grading_strategy_test.php
question/type/truefalse/tests/questiontype_test.php
rating/tests/externallib_test.php
rating/tests/rating_test.php
report/completion/tests/events_test.php
report/log/tests/events_test.php
report/log/tests/lib_test.php
report/loglive/tests/events_test.php
report/outline/tests/lib_test.php
report/questioninstances/tests/events_test.php
report/stats/tests/events_test.php
report/stats/tests/lib_test.php
report/usersessions/tests/lib_test.php
repository/contentbank/tests/browser_test.php
repository/contentbank/tests/search_test.php
repository/flickr/tests/privacy_test.php
repository/googledocs/lib.php
repository/nextcloud/lib.php
repository/nextcloud/tests/access_controlled_link_manager_test.php
repository/nextcloud/tests/lib_test.php
repository/nextcloud/tests/ocs_test.php
repository/onedrive/lib.php
repository/onedrive/tests/privacy_test.php
repository/recent/tests/lib_test.php
repository/tests/behat/behat_filepicker.php
repository/tests/privacy_test.php
repository/tests/repositorylib_test.php
rss/tests/privacy_test.php
search/engine/simpledb/tests/engine_test.php
search/engine/simpledb/tests/privacy_test.php
search/engine/solr/tests/engine_test.php
search/tests/base_activity_test.php
search/tests/base_test.php
search/tests/behat/setup_search_engine.feature [new file with mode: 0644]
search/tests/document_test.php
search/tests/engine_test.php
search/tests/external_test.php
search/tests/generator/lib.php
search/tests/manager_test.php
tag/tests/events_test.php
tag/tests/taglib_test.php
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/calendar.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/icons.scss
theme/boost/scss/moodle/question.scss
theme/boost/scss/preset/default.scss
theme/boost/style/moodle.css
theme/classic/scss/preset/default.scss
theme/classic/style/moodle.css
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/text/tests/privacy_test.php
user/profile/field/textarea/tests/privacy_test.php
user/tests/externallib_test.php
user/tests/myprofile_test.php
user/tests/search_test.php
user/tests/userlib_test.php
user/tests/userroleseditable_test.php
version.php
webservice/tests/events_test.php
webservice/tests/externallib_test.php
webservice/tests/lib_test.php
webservice/tests/privacy_test.php
webservice/xmlrpc/tests/lib_test.php
webservice/xmlrpc/tests/locallib_test.php
webservice/xmlrpc/tests/xmlrpc_server_test.php

index 9d02c9b..41e19ac 100644 (file)
@@ -33,6 +33,7 @@ CVS
 /.project
 /.buildpath
 /.cache
+.phpunit.result.cache
 phpunit.xml
 # Composer support. Do not ignore composer.json, or composer.lock. These should be shipped by us.
 composer.phar
index da6a827..d103df0 100644 (file)
@@ -2,10 +2,9 @@
 # process (which uses our internal CI system) this file is here for the benefit
 # of community developers git clones - see MDL-51458.
 
-# We currently disable Travis notifications entirely until https://github.com/travis-ci/travis-ci/issues/4976
-# is fixed.
 notifications:
-  email: false
+  email:
+    if: env(MOODLE_EMAIL) != no
 
 language: php
 
index fbf8ac4..f344cfe 100644 (file)
@@ -43,7 +43,7 @@ class testoutgoingmailconf_form extends \moodleform {
         $mform = $this->_form;
 
         // Recipient.
-        $options = ['maxlength' => '100', 'size' => '25'];
+        $options = ['maxlength' => '100', 'size' => '25', 'autocomplete' => 'email'];
         $mform->addElement('text', 'recipient', get_string('testoutgoingmailconf_toemail', 'admin'), $options);
         $mform->setType('recipient', PARAM_EMAIL);
         $mform->addRule('recipient', get_string('required'), 'required');
diff --git a/admin/classes/local/externalpage/accesscallback.php b/admin/classes/local/externalpage/accesscallback.php
new file mode 100644 (file)
index 0000000..05fb07f
--- /dev/null
@@ -0,0 +1,71 @@
+<?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/>.
+
+/**
+ * External admin page class that allows a callback to be provided to determine whether page can be accessed
+ *
+ * @package     core_admin
+ * @copyright   2019 Marina Glancy
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_admin\local\externalpage;
+
+use admin_externalpage;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once("{$CFG->libdir}/adminlib.php");
+
+/**
+ * Admin externalpage class
+ *
+ * @package     core_admin
+ * @copyright   2019 Marina Glancy
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class accesscallback extends admin_externalpage {
+
+    /** @var callable $accesscheckcallback */
+    protected $accesscheckcallback;
+
+    /**
+     * Class constructor
+     *
+     * @param string $name
+     * @param string $visiblename
+     * @param string $url
+     * @param callable $accesscheckcallback The callback method that will be executed to check whether user has access to
+     *     this page. The setting instance ($this) is passed as an argument to the callback. Should return boolean value
+     * @param bool $hidden
+     */
+    public function __construct(string $name, string $visiblename, string $url, callable $accesscheckcallback,
+            bool $hidden = false) {
+
+        $this->accesscheckcallback = $accesscheckcallback;
+
+        parent::__construct($name, $visiblename, $url, [], $hidden);
+    }
+
+    /**
+     * Determines if the current user has access to this external page based on access callback
+     *
+     * @return bool
+     */
+    public function check_access() {
+        return ($this->accesscheckcallback)($this);
+    }
+}
index e5a6edb..3352f1e 100644 (file)
@@ -178,6 +178,7 @@ class filesize extends \admin_setting {
             'id' => $this->get_id(),
             'name' => $this->get_full_name(),
             'value' => $data['v'],
+            'readonly' => $this->is_readonly(),
             'options' => array_map(function($unit, $title) use ($data, $defaultunit) {
                 return [
                     'value' => $unit,
index 01f67a1..e76a8e1 100644 (file)
@@ -49,6 +49,7 @@
                 array($module->id));
         core_plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
+        redirect(new moodle_url('/admin/modules.php'));
     }
 
     if (!empty($show) and confirm_sesskey()) {
@@ -66,6 +67,7 @@
                 array($module->id));
         core_plugin_manager::reset_caches();
         admin_get_root(true, false);  // settings not required - only pages
+        redirect(new moodle_url('/admin/modules.php'));
     }
 
     echo $OUTPUT->header();
             $count = -1;
         }
         if ($count>0) {
-            $countlink = "<a href=\"{$CFG->wwwroot}/course/search.php?modulelist=$module->name" .
-                "&amp;sesskey=".sesskey()."\" title=\"$strshowmodulecourse\">$count</a>";
+            $countlink = $OUTPUT->action_link(new moodle_url('/course/search.php', ['modulelist' => $module->name]),
+                $count, null, ['title' => $strshowmodulecourse]);
         } else if ($count < 0) {
             $countlink = get_string('error');
         } else {
index c99ae52..e4867c1 100644 (file)
@@ -53,7 +53,6 @@ $pageurl = new moodle_url('/admin/plugins.php', $pageparams);
 $pluginman = core_plugin_manager::instance();
 
 if ($uninstall) {
-    require_sesskey();
 
     if (!$confirmed) {
         admin_externalpage_setup('pluginsoverview', '', $pageparams);
@@ -92,6 +91,7 @@ if ($uninstall) {
         exit();
 
     } else {
+        require_sesskey();
         $SESSION->pluginuninstallreturn = $pluginfo->get_return_url_after_uninstall($return);
         $progress = new progress_trace_buffer(new text_progress_trace(), false);
         $pluginman->uninstall_plugin($pluginfo->component, $progress);
index 258b5a7..82203c1 100644 (file)
@@ -536,7 +536,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user1->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist2->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist2->get_userids());
 
         // The user list for coursecontext1 should user1, user2 and admin (role creator).
         $userlist3 = new \core_privacy\local\request\userlist($coursecontext1, $component);
@@ -547,7 +547,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user2->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist3->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist3->get_userids());
 
         // The user list for coursecatcontext should user2 and admin (role creator).
         $userlist4 = new \core_privacy\local\request\userlist($coursecatcontext, $component);
@@ -557,7 +557,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user2->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist4->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist4->get_userids());
 
         // The user list for systemcontext should user1 and admin (role creator).
         $userlist6 = new \core_privacy\local\request\userlist($systemcontext, $component);
@@ -567,7 +567,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user1->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist6->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist6->get_userids());
 
         // The user list for cmcontext should user1, user2 and admin (role creator).
         $userlist7 = new \core_privacy\local\request\userlist($cmcontext, $component);
@@ -578,7 +578,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user2->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist7->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist7->get_userids());
 
         // The user list for blockcontext should user1 and admin (role creator).
         $userlist8 = new \core_privacy\local\request\userlist($blockcontext, $component);
@@ -588,7 +588,7 @@ class core_role_privacy_testcase extends provider_testcase {
             $user1->id,
             $admin->id
         ];
-        $this->assertEquals($expected, $userlist8->get_userids(), '', 0.0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $userlist8->get_userids());
     }
 
     /**
@@ -744,4 +744,4 @@ class core_role_privacy_testcase extends provider_testcase {
         }
         return $rolesnames;
     }
-}
\ No newline at end of file
+}
index a722244..30b6a22 100644 (file)
@@ -131,15 +131,10 @@ if ($hassiteconfig && \core_analytics\manager::is_analytics_enabled()) {
             $timesplittingdefaults, $timesplittingoptions)
         );
 
-        // Predictions processor output dir.
-        $defaultmodeloutputdir = rtrim($CFG->dataroot, '/') . DIRECTORY_SEPARATOR . 'models';
-        if (empty(get_config('analytics', 'modeloutputdir')) && !file_exists($defaultmodeloutputdir) &&
-                is_writable($defaultmodeloutputdir)) {
-            // Automatically create the dir for them so users don't see the invalid value red cross.
-            mkdir($defaultmodeloutputdir, $CFG->directorypermissions, true);
-        }
+        // Predictions processor output dir - specify default in setting description (used if left blank).
+        $defaultmodeloutputdir = \core_analytics\model::default_output_dir();
         $settings->add(new admin_setting_configdirectory('analytics/modeloutputdir', new lang_string('modeloutputdir', 'analytics'),
-            new lang_string('modeloutputdirinfo', 'analytics'), $defaultmodeloutputdir));
+            new lang_string('modeloutputdirwithdefaultinfo', 'analytics', $defaultmodeloutputdir), ''));
 
         // Disable web interface evaluation and get predictions.
         $settings->add(new admin_setting_configcheckbox('analytics/onlycli', new lang_string('onlycli', 'analytics'),
index cb586b4..11541bb 100644 (file)
@@ -455,6 +455,15 @@ if ($hassiteconfig) {
         new lang_string('divertallemailsexcept_desc', 'admin'),
         '', PARAM_RAW, '50', '4'));
 
+    $noreplyaddress = isset($CFG->noreplyaddress) ? $CFG->noreplyaddress : 'noreply@example.com';
+    $dkimdomain = substr(strrchr($noreplyaddress, "@"), 1);
+    $dkimselector = empty($CFG->emaildkimselector) ? '[selector]' : $CFG->emaildkimselector;
+    $pempath = "\$CFG->dataroot/dkim/{$dkimdomain}/{$dkimselector}.private";
+    $temp->add(new admin_setting_heading('emaildkim', new lang_string('emaildkim', 'admin'),
+        new lang_string('emaildkiminfo', 'admin', ['path' => $pempath, 'docs' => \get_docs_url('Mail_configuration#DKIM')])));
+    $temp->add(new admin_setting_configtext('emaildkimselector', new lang_string('emaildkimselector', 'admin'),
+        new lang_string('configemaildkimselector', 'admin'), '', PARAM_FILE));
+
     $url = new moodle_url('/admin/testoutgoingmailconf.php');
     $link = html_writer::link($url, get_string('testoutgoingmailconf', 'admin'));
     $temp->add(new admin_setting_heading('testoutgoinmailc', new lang_string('testoutgoingmailconf', 'admin'),
index f41e5a2..48e0471 100644 (file)
@@ -25,6 +25,7 @@
     * value - yes value
     * id - element id
     * checked - boole
+    * readonly - bool
 
     Example context (json):
     {
         "no": "False",
         "value": "True",
         "id": "test0",
-        "checked": "checked"
+        "checked": "checked",
+        "readonly": false
     }
 }}
 <div class="form-checkbox defaultsnext">
     <input type="hidden" name="{{name}}" value="{{no}}">
-    <input type="checkbox" name="{{name}}" value="{{value}}" id="{{id}}" {{#checked}}checked{{/checked}}>
+    <input {{#readonly}}disabled{{/readonly}} type="checkbox" name="{{name}}" value="{{value}}" id="{{id}}" {{#checked}}checked{{/checked}}>
 </div>
index 3de5aac..50b7da1 100644 (file)
@@ -25,6 +25,7 @@
     * id - element id
     * value - element value
     * haspreviewconfig - show preview of selected color
+    * readonly - bool
 
     Example context (json):
     {
@@ -32,6 +33,7 @@
         "name": "name0",
         "id": "id0",
         "value": "#555655",
+        "readonly": false,
         "haspreviewconfig": false
     }
 }}
@@ -44,7 +46,7 @@
             {{>core/pix_icon}}
         {{/icon}}
     </div>
-    <input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr">
+    <input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
     {{#haspreviewconfig}}
         <input type="button" id="{{id}}_preview" value={{#quote}}{{#str}}preview{{/str}}{{/quote}} class="admin_colourpicker_preview">
     {{/haspreviewconfig}}
index 1804787..cfe8a7d 100644 (file)
@@ -33,7 +33,7 @@
         "name": "test",
         "value": "/my-super-secret-path/",
         "id": "test0",
-        "readonly": true,
+        "readonly": false,
         "showvalidity": true,
         "valid": false
     }
index d20dabc..1a880c1 100644 (file)
@@ -23,6 +23,7 @@
     * name - form element name
     * options - list of options for units containing name, value, selected
     * value - yes
+    * readonly - bool
     * id - element id
 
     Example context (json):
@@ -30,6 +31,7 @@
         "name": "test",
         "value": "5",
         "id": "test0",
+        "readonly": false,
         "options": [ { "name": "Minutes", "value": "mins", "selected": true } ]
     }
 }}
@@ -38,9 +40,9 @@
 }}
 <div class="form-duration defaultsnext">
     <div class="form-inline">
-        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
         <label class="sr-only" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
-        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select" {{#readonly}}disabled{{/readonly}}>
             {{#options}}
                 <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
             {{/options}}
index 72187f2..773b07d 100644 (file)
@@ -33,7 +33,7 @@
         "name": "test",
         "value": "/usr/bin/cowsay",
         "id": "test0",
-        "readonly": true,
+        "readonly": false,
         "showvalidity": true,
         "valid": false
     }
index 1f0b5d5..15a45ce 100644 (file)
@@ -26,6 +26,7 @@
     * readonly - Make the field readonly
     * value - value
     * showvalidity - Show a green check if the path is readable
+    * readonly - bool
     * valid - True if the path is readable
 
     Example context (json):
@@ -33,8 +34,9 @@
         "name": "test",
         "value": "/my-super-secret-path/file",
         "id": "test0",
-        "readonly": true,
+        "readonly": false,
         "showvalidity": true,
+        "readonly": false,
         "valid": false
     }
 }}
index 4716c6e..355cc6d 100644 (file)
     * options - list of options for units containing name, value, selected
     * value - yes
     * id - element id
+    * readonly - bool
 
     Example context (json):
     {
         "name": "test",
         "value": "5",
         "id": "test0",
+        "readonly": false,
         "options": [ { "name": "KB", "value": "1024", "selected": true } ]
     }
 }}
@@ -38,9 +40,9 @@
 }}
 <div class="form-filesize defaultsnext">
     <div class="form-inline">
-        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr" {{#readonly}}disabled{{/readonly}}>
         <label class="sr-only" for="{{id}}u">{{#str}}filesizeunits, admin{{/str}}</label>
-        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select" {{#readonly}}disabled{{/readonly}}>
             {{#options}}
                 <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
             {{/options}}
index 49eb55c..c19b1fd 100644 (file)
     * id - element id
     * size - element size
     * options - list of options containing name, value, selected
+    * readonly - bool
 
     Example context (json):
     {
         "name": "test",
         "id": "test0",
+        "readonly": false,
         "size": "3",
         "options": [ { "name": "Option 1", "value": "V", "selected": true },
                      { "name": "Option 2", "value": "V", "selected": true } ]
@@ -39,7 +41,7 @@
 }}
 <div class="form-select">
     <input type="hidden" name="{{name}}[xxxxx]" value="1">
-    <select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
+    <select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index 20d66e1..a1cbc9d 100644 (file)
     * size - element size
     * options - list of options not grouped
     * optgroups - list of options grouped containing the group label and for each option: name, value, selected
+    * readonly - bool
 
     Example context (json):
     {
         "name": "test",
         "id": "test0",
+        "readonly": false,
         "size": "3",
         "options": [
             { "name": "Option 1", "value": "V", "selected": false },
@@ -58,7 +60,7 @@
 }}
 <div class="form-select">
     <input type="hidden" name="{{name}}[xxxxx]" value="1">
-    <select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
+    <select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
     {{#options}}
         <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
     {{/options}}
index 7092e82..84a27a6 100644 (file)
@@ -24,7 +24,7 @@
     * size - form element size
     * value - form element value
     * id - element id
-    * forced - has value been defined in config.php
+    * readonly - has value been defined in config.php
 
     Example context (json):
     {
         "id": "test0",
         "size": "8",
         "value": "secret",
-        "forced": false
+        "readonly": false
     }
 }}
-{{#forced}}
+{{#readonly}}
     <div class="form-password">
         <input type="text"
                name = "{{ name }}"
@@ -46,8 +46,8 @@
                disabled
         >
     </div>
-{{/forced}}
-{{^forced}}
+{{/readonly}}
+{{^readonly}}
 <div class="form-password">
     <span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
         <span data-passwordunmask="editor">
@@ -76,4 +76,4 @@ require(['core_form/passwordunmask'], function(PasswordUnmask) {
     new PasswordUnmask("{{ id }}");
 });
 {{/js}}
-{{/forced}}
+{{/readonly}}
index 9b6c6e1..eb18b4b 100644 (file)
     * name - form element name
     * id - element id
     * options - list of options containing name, value, selected
+    * readonly - bool
 
     Example context (json):
     {
         "name": "test",
         "id": "test0",
+        "readonly": false,
         "options": [
             { "name": "Option 1", "value": "V", "selected": true },
             { "name": "Option 2", "value": "V", "selected": true }
@@ -38,7 +40,7 @@
     Setting configselect.
 }}
 <div class="form-select defaultsnext">
-    <select id="{{id}}" name="{{name}}" class="custom-select">
+    <select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}" class="custom-select">
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index 95f818b..862ac61 100644 (file)
@@ -22,6 +22,7 @@
     Context variables required for this template:
     * name - form element name
     * id - element id
+    * readonly - bool
     * options - list of options (not grouped)
     * optgroups - list of options grouped containing the group label and for each option: name, value, selected
 
@@ -29,6 +30,7 @@
     {
         "name": "test",
         "id": "test0",
+        "readonly": false,
         "options": [
             { "name": "Option 1", "value": "V", "selected": false },
             { "name": "Option 2", "value": "V", "selected": false }
@@ -55,7 +57,7 @@
     Setting configselect with optgroup support.
 }}
 <div class="form-select defaultsnext">
-    <select id="{{id}}" name="{{name}}" class="custom-select">
+    <select {{#readonly}}disabled{{/readonly}} id="{{id}}" name="{{name}}" class="custom-select">
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index a31f73a..1238c8e 100644 (file)
@@ -26,6 +26,7 @@
     * size - element size
     * forceltr - always display as ltr
     * attributes - list of additional attributes containing name, value
+    * readonly - bool
 
     Example context (json):
     {
@@ -34,6 +35,7 @@
         "value": "A tall, dark stranger will have more fun than you.",
         "size": "21",
         "forceltr": false,
+        "readonly": false,
         "attributes": [ { "name": "readonly", "value": "readonly" } ]
     }
 }}
@@ -41,5 +43,5 @@
     Setting configtext.
 }}
 <div class="form-text defaultsnext">
-    <input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">
+    <input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}" {{#readonly}}disabled{{/readonly}}>
 </div>
index 002b0cd..c877d94 100644 (file)
@@ -33,6 +33,7 @@
         "cols": "30",
         "rows": "3",
         "value": "Excellent day for putting Slinkies on an escalator.",
+        "readonly": false,
         "id": "test0"
     }
 }}
@@ -40,5 +41,5 @@
     Setting configtextarea.
 }}
 <div class="form-textarea">
-    <textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
+    <textarea {{#readonly}}disabled{{/readonly}} rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
 </div>
index 90d37b3..af4435a 100644 (file)
     * id - element id
     * hours - list of valid hour options containing name, value, selected
     * minutes - list of valid minute options containing name, value, selected
+    * readonly - bool
 
     Example context (json):
     {
         "name": "test",
         "id": "test0",
+        "readonly": false,
         "minutes": [
             { "name": "00", "value": "0", "selected": true },
             { "name": "01", "value": "1", "selected": false }
 <div class="form-time defaultsnext">
     <div class="form-inline text-ltr">
         <label class="sr-only" for="{{id}}h">{{#str}}hours{{/str}}</label>
-        <select id="{{id}}h" name="{{name}}[h]" class="custom-select">
+        <select id="{{id}}h" name="{{name}}[h]" class="custom-select" {{#readonly}}disabled{{/readonly}}>
             {{#hours}}
                 <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
             {{/hours}}
         </select>:
         <label class="sr-only" for="{{id}}m">{{#str}}minutes{{/str}}</label>
-        <select id="{{id}}m" name="{{name}}[m]" class="custom-select">
+        <select id="{{id}}m" name="{{name}}[m]" class="custom-select" {{#readonly}}disabled{{/readonly}}>
             {{#minutes}}
                 <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
             {{/minutes}}
diff --git a/admin/tests/behat/language_settings.feature b/admin/tests/behat/language_settings.feature
new file mode 100644 (file)
index 0000000..6d00ebe
--- /dev/null
@@ -0,0 +1,34 @@
+@core @core_admin
+Feature: Configure language settings for the site
+  In order to configure language settings for the site
+  As an admin
+  I want to set language settings relevant to my site users
+
+  Scenario: Set languages on language menu
+    Given I log in as "admin"
+    And I navigate to "Language > Language settings" in site administration
+    When I set the field "Languages on language menu" to "en"
+    And I press "Save changes"
+    Then I should not see "Invalid language code"
+
+  Scenario: Reset languages on language menu
+    Given I log in as "admin"
+    And I navigate to "Language > Language settings" in site administration
+    When I set the field "Languages on language menu" to ""
+    And I press "Save changes"
+    Then I should not see "Invalid language code"
+
+  Scenario Outline: Set languages on language menu with invalid language
+    Given I log in as "admin"
+    And I navigate to "Language > Language settings" in site administration
+    When I set the field "Languages on language menu" to "<fieldvalue>"
+    And I press "Save changes"
+    Then I should see "Invalid language code: <invalidlang>"
+    Examples:
+      | fieldvalue | invalidlang |
+      | xx         | xx          |
+      | xx\|Bad    | xx          |
+      | en,qq      | qq          |
+      | en,qq\|Bad | qq          |
+      | en$$       | en$$        |
+      | en$$\|Bad  | en$$        |
index ff26c64..597dda7 100644 (file)
@@ -66,8 +66,6 @@ class tool_analytics_external_testcase extends externallib_advanced_testcase {
 
     /**
      * test_potential_contexts description
-     *
-     * @expectedException required_capability_exception
      */
     public function test_potential_contexts_no_manager() {
         $this->resetAfterTest();
@@ -75,6 +73,7 @@ class tool_analytics_external_testcase extends externallib_advanced_testcase {
         $user = $this->getDataGenerator()->create_user();
         $this->setUser($user);
 
+        $this->expectException(required_capability_exception::class);
         $this->assertCount(2, \tool_analytics\external::potential_contexts());
     }
 }
index 42b335c..868dd84 100644 (file)
@@ -187,10 +187,13 @@ if ($options['install']) {
     behat_config_manager::set_behat_run_config_value('axe', $options['axe']);
 
     // Enable test mode.
+    $timestart = microtime(true);
+    mtrace('Creating Behat configuration ...', '');
     behat_util::start_test_mode($options['add-core-features-to-theme'], $options['optimize-runs'], $parallel, $run);
+    mtrace(' done in ' . round(microtime(true) - $timestart, 2) . ' seconds.');
 
     // Themes are only built in the 'enable' command.
-    behat_util::build_themes();
+    behat_util::build_themes(true);
     mtrace("Testing environment themes built");
 
     // This is only displayed once for parallel install.
index 98ccd03..71b4b94 100644 (file)
@@ -87,7 +87,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     /**
      * Setup test.
      */
-    public function setup() {
+    public function setUp(): void {
         global $CFG;
 
         $this->resetAfterTest();
@@ -165,7 +165,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -219,7 +219,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -264,7 +264,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -295,7 +295,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -324,7 +324,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -368,7 +368,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -399,7 +399,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -428,7 +428,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -532,7 +532,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -611,7 +611,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
         // Check contexts.
@@ -646,7 +646,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -662,7 +662,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -678,7 +678,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
 
@@ -693,7 +693,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
 
             foreach ($paths as $key => $feature) {
-                $this->assertContains($feature, $suites[$themename]['paths'][$key]);
+                $this->assertStringContainsString($feature, $suites[$themename]['paths'][$key]);
             }
         }
     }
index 30942ca..1e3e8d5 100644 (file)
@@ -36,7 +36,7 @@ class tool_capability_events_testcase extends advanced_testcase {
     /**
      * Setup testcase.
      */
-    public function setUp() {
+    public function setUp(): void {
         $this->setAdminUser();
         $this->resetAfterTest();
     }
index 2d99e5c..0464370 100644 (file)
@@ -49,7 +49,7 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
     /**
      * Setup function- we will create a course and add an assign instance to it.
      */
-    protected function setUp() {
+    protected function setUp(): void {
         $this->resetAfterTest(true);
 
         // Create some users.
@@ -60,9 +60,6 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
         cohort_add_member($this->cohort->id, $this->userassignover->id);
     }
 
-    /**
-     * @expectedException required_capability_exception
-     */
     public function test_create_cohort_role_assignment_without_permission() {
         $this->setUser($this->userassignto);
         $params = (object) array(
@@ -70,12 +67,10 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
             'roleid' => $this->roleid,
             'cohortid' => $this->cohort->id
         );
+        $this->expectException(required_capability_exception::class);
         api::create_cohort_role_assignment($params);
     }
 
-    /**
-     * @expectedException core_competency\invalid_persistent_exception
-     */
     public function test_create_cohort_role_assignment_with_invalid_data() {
         $this->setAdminUser();
         $params = (object) array(
@@ -83,6 +78,7 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
             'roleid' => -8,
             'cohortid' => $this->cohort->id
         );
+        $this->expectException(\core_competency\invalid_persistent_exception::class);
         api::create_cohort_role_assignment($params);
     }
 
@@ -100,9 +96,6 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
         $this->assertEquals($result->get('cohortid'), $this->cohort->id);
     }
 
-    /**
-     * @expectedException required_capability_exception
-     */
     public function test_delete_cohort_role_assignment_without_permission() {
         $this->setAdminUser();
         $params = (object) array(
@@ -112,12 +105,10 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
         );
         $result = api::create_cohort_role_assignment($params);
         $this->setUser($this->userassignto);
+        $this->expectException(required_capability_exception::class);
         api::delete_cohort_role_assignment($result->get('id'));
     }
 
-    /**
-     * @expectedException dml_missing_record_exception
-     */
     public function test_delete_cohort_role_assignment_with_invalid_data() {
         $this->setAdminUser();
         $params = (object) array(
@@ -126,6 +117,7 @@ class tool_cohortroles_api_testcase extends advanced_testcase {
             'cohortid' => $this->cohort->id
         );
         $result = api::create_cohort_role_assignment($params);
+        $this->expectException(dml_missing_record_exception::class);
         api::delete_cohort_role_assignment($result->get('id') + 1);
     }
 
index 6399314..e5c4b9c 100644 (file)
@@ -43,7 +43,7 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
     /**
      * Overriding setUp() function to always reset after tests.
      */
-    public function setUp() {
+    public function setUp(): void {
         $this->resetAfterTest(true);
     }
 
@@ -87,7 +87,7 @@ class tool_cohortroles_privacy_testcase extends \core_privacy\tests\provider_tes
             CONTEXT_COURSECAT
         ];
         // Test the User's contexts equal the system and course category context.
-        $this->assertEquals($expected, $contextlevels, '', 0, 10, true);
+        $this->assertEqualsCanonicalizing($expected, $contextlevels);
     }
 
     /**
diff --git a/admin/tool/customlang/classes/form/export.php b/admin/tool/customlang/classes/form/export.php
new file mode 100644 (file)
index 0000000..843b09a
--- /dev/null
@@ -0,0 +1,68 @@
+<?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/>.
+
+/**
+ * Creates Formular for customlang file export
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Thomas Wedekind <Thomas.Wedekind@univie.ac.at>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_customlang\form;
+
+use tool_customlang_utils;
+
+/**
+ * Formular for customlang file export
+ *
+ * @copyright  2020 Thomas Wedekind <Thomas.Wedekind@univie.ac.at>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class export extends \moodleform {
+
+    /**
+     * Add elements to form
+     */
+    public function definition() {
+        $lng = $this->_customdata['lng'];
+        $mform = $this->_form;
+
+        $langdir = tool_customlang_utils::get_localpack_location($lng);
+
+        // The export button only appears if a local lang is present.
+        if (!check_dir_exists($langdir) || !count(glob("$langdir/*"))) {
+            print_error('nolocallang', 'tool_customlang');
+        }
+
+        $langfiles = scandir($langdir);
+        $fileoptions = [];
+        foreach ($langfiles as $file) {
+            if (substr($file, 0, 1) != '.') {
+                $fileoptions[$file] = $file;
+            }
+        }
+
+        $mform->addElement('hidden', 'lng', $lng);
+        $mform->setType('lng', PARAM_LANG);
+
+        $select = $mform->addElement('select', 'files', get_string('exportfilter', 'tool_customlang'), $fileoptions);
+        $select->setMultiple(true);
+        $mform->addRule('files', get_string('required'), 'required', null, 'client');
+        $mform->setDefault('files', $fileoptions);
+
+        $this->add_action_buttons(true, get_string('export', 'tool_customlang'));
+    }
+}
diff --git a/admin/tool/customlang/classes/form/import.php b/admin/tool/customlang/classes/form/import.php
new file mode 100644 (file)
index 0000000..3d4d068
--- /dev/null
@@ -0,0 +1,68 @@
+<?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/>.
+
+/**
+ * Upload a zip of custom lang php files.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\form;
+
+use tool_customlang\local\importer;
+
+/**
+ * Upload a zip/php of custom lang php files.
+ *
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class import extends \moodleform {
+
+    /**
+     * Form definition.
+     */
+    public function definition() {
+        $mform = $this->_form;
+        $mform->addElement('header', 'settingsheader', get_string('import', 'tool_customlang'));
+
+        $mform->addElement('hidden', 'lng');
+        $mform->setType('lng', PARAM_LANG);
+        $mform->setDefault('lng', $this->_customdata['lng']);
+
+        $filemanageroptions = array(
+            'accepted_types' => array('.php', '.zip'),
+            'maxbytes' => 0,
+            'maxfiles' => 1,
+            'subdirs' => 0
+        );
+
+        $mform->addElement('filepicker', 'pack', get_string('langpack', 'tool_customlang'),
+            null, $filemanageroptions);
+        $mform->addRule('pack', null, 'required');
+
+        $modes = [
+            importer::IMPORTALL => get_string('import_all', 'tool_customlang'),
+            importer::IMPORTUPDATE => get_string('import_update', 'tool_customlang'),
+            importer::IMPORTNEW => get_string('import_new', 'tool_customlang'),
+        ];
+        $mform->addElement('select', 'importmode', get_string('import_mode', 'tool_customlang'), $modes);
+
+        $mform->addElement('submit', 'importcustomstrings', get_string('importfile', 'tool_customlang'));
+    }
+}
diff --git a/admin/tool/customlang/classes/local/importer.php b/admin/tool/customlang/classes/local/importer.php
new file mode 100644 (file)
index 0000000..955ec3f
--- /dev/null
@@ -0,0 +1,268 @@
+<?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/>.
+
+/**
+ * Custom lang importer.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\local;
+
+use tool_customlang\local\mlang\phpparser;
+use tool_customlang\local\mlang\logstatus;
+use tool_customlang\local\mlang\langstring;
+use core\output\notification;
+use stored_file;
+use coding_exception;
+use moodle_exception;
+use core_component;
+use stdClass;
+
+/**
+ * Class containing tha custom lang importer
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class importer {
+
+    /** @var int imports will only create new customizations */
+    public const IMPORTNEW = 1;
+    /** @var int imports will only update the current customizations */
+    public const IMPORTUPDATE = 2;
+    /** @var int imports all strings */
+    public const IMPORTALL = 3;
+
+    /**
+     * @var string the language name
+     */
+    protected $lng;
+
+    /**
+     * @var int the importation mode (new, update, all)
+     */
+    protected $importmode;
+
+    /**
+     * @var string request folder path
+     */
+    private $folder;
+
+    /**
+     * @var array import log messages
+     */
+    private $log;
+
+    /**
+     * Constructor for the importer class.
+     *
+     * @param string $lng the current language to import.
+     * @param int $importmode the import method (IMPORTALL, IMPORTNEW, IMPORTUPDATE).
+     */
+    public function __construct(string $lng, int $importmode = self::IMPORTALL) {
+        $this->lng = $lng;
+        $this->importmode = $importmode;
+        $this->log = [];
+    }
+
+    /**
+     * Returns the last parse log.
+     *
+     * @return logstatus[] mlang logstatus with the messages
+     */
+    public function get_log(): array {
+        return $this->log;
+    }
+
+    /**
+     * Import customlang files.
+     *
+     * @param stored_file[] $files array of files to import
+     */
+    public function import(array $files): void {
+        // Create a temporal folder to store the files.
+        $this->folder = make_request_directory(false);
+
+        $langfiles = $this->deploy_files($files);
+
+        $this->process_files($langfiles);
+    }
+
+    /**
+     * Deploy all files into a request folder.
+     *
+     * @param stored_file[] $files array of files to deploy
+     * @return string[] of file paths
+     */
+    private function deploy_files(array $files): array {
+        $result = [];
+        // Desploy all files.
+        foreach ($files as $file) {
+            if ($file->get_mimetype() == 'application/zip') {
+                $result = array_merge($result, $this->unzip_file($file));
+            } else {
+                $path = $this->folder.'/'.$file->get_filename();
+                $file->copy_content_to($path);
+                $result = array_merge($result, [$path]);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Unzip a file into the request folder.
+     *
+     * @param stored_file $file the zip file to unzip
+     * @return string[] of zip content paths
+     */
+    private function unzip_file(stored_file $file): array {
+        $fp = get_file_packer('application/zip');
+        $zipcontents = $fp->extract_to_pathname($file, $this->folder);
+        if (!$zipcontents) {
+            throw new moodle_exception("Error Unzipping file", 1);
+        }
+        $result = [];
+        foreach ($zipcontents as $contentname => $success) {
+            if ($success) {
+                $result[] = $this->folder.'/'.$contentname;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Import strings from a list of langfiles.
+     *
+     * @param string[] $langfiles an array with file paths
+     */
+    private function process_files(array $langfiles): void {
+        $parser = phpparser::get_instance();
+        foreach ($langfiles as $filepath) {
+            $component = $this->component_from_filepath($filepath);
+            if ($component) {
+                $strings = $parser->parse(file_get_contents($filepath));
+                $this->import_strings($strings, $component);
+            }
+        }
+    }
+
+    /**
+     * Try to get the component from a filepath.
+     *
+     * @param string $filepath the filepath
+     * @return stdCalss|null the DB record of that component
+     */
+    private function component_from_filepath(string $filepath) {
+        global $DB;
+
+        // Get component from filename.
+        $pathparts = pathinfo($filepath);
+        if (empty($pathparts['filename'])) {
+            throw new coding_exception("Cannot get filename from $filepath", 1);
+        }
+        $filename = $pathparts['filename'];
+
+        $normalized = core_component::normalize_component($filename);
+        if (count($normalized) == 1 || empty($normalized[1])) {
+            $componentname = $normalized[0];
+        } else {
+            $componentname = implode('_', $normalized);
+        }
+
+        $result = $DB->get_record('tool_customlang_components', ['name' => $componentname]);
+
+        if (!$result) {
+            $this->log[] = new logstatus('notice_missingcomponent', notification::NOTIFY_ERROR, null, $componentname);
+            return null;
+        }
+        return $result;
+    }
+
+    /**
+     * Import an array of strings into the customlang tables.
+     *
+     * @param langstring[] $strings the langstring to set
+     * @param stdClass $component the target component
+     */
+    private function import_strings(array $strings, stdClass $component): void {
+        global $DB;
+
+        foreach ($strings as $newstring) {
+            // Check current DB entry.
+            $customlang = $DB->get_record('tool_customlang', [
+                'componentid' => $component->id,
+                'stringid' => $newstring->id,
+                'lang' => $this->lng,
+            ]);
+            if (!$customlang) {
+                $customlang = null;
+            }
+
+            if ($this->can_save_string($customlang, $newstring, $component)) {
+                $customlang->local = $newstring->text;
+                $customlang->timecustomized = $newstring->timemodified;
+                $customlang->outdated = 0;
+                $customlang->modified = 1;
+                $DB->update_record('tool_customlang', $customlang);
+            }
+        }
+    }
+
+    /**
+     * Determine if a specific string can be saved based on the current importmode.
+     *
+     * @param stdClass $customlang customlang original record
+     * @param langstring $newstring the new strign to store
+     * @param stdClass $component the component target
+     * @return bool if the string can be stored
+     */
+    private function can_save_string(?stdClass $customlang, langstring $newstring, stdClass $component): bool {
+        $result = false;
+        $message = 'notice_success';
+        if (empty($customlang)) {
+            $message = 'notice_inexitentstring';
+            $this->log[] = new logstatus($message, notification::NOTIFY_ERROR, null, $component->name, $newstring);
+            return $result;
+        }
+
+        switch ($this->importmode) {
+            case self::IMPORTNEW:
+                $result = empty($customlang->local);
+                $warningmessage = 'notice_ignoreupdate';
+                break;
+            case self::IMPORTUPDATE:
+                $result = !empty($customlang->local);
+                $warningmessage = 'notice_ignorenew';
+                break;
+            case self::IMPORTALL:
+                $result = true;
+                break;
+        }
+        if ($result) {
+            $errorlevel = notification::NOTIFY_SUCCESS;
+        } else {
+            $errorlevel = notification::NOTIFY_ERROR;
+            $message = $warningmessage;
+        }
+        $this->log[] = new logstatus($message, $errorlevel, null, $component->name, $newstring);
+
+        return $result;
+    }
+}
diff --git a/admin/tool/customlang/classes/local/mlang/langstring.php b/admin/tool/customlang/classes/local/mlang/langstring.php
new file mode 100644 (file)
index 0000000..f380cd6
--- /dev/null
@@ -0,0 +1,177 @@
+<?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/>.
+
+/**
+ * Language string based on David Mudrak langstring from local_amos.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\local\mlang;
+
+use moodle_exception;
+use stdclass;
+
+/**
+ * Class containing a lang string cleaned.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Represents a single string
+ */
+class langstring {
+
+    /** @var string identifier */
+    public $id = null;
+
+    /** @var string */
+    public $text = '';
+
+    /** @var int the time stamp when this string was saved */
+    public $timemodified = null;
+
+    /** @var bool is deleted */
+    public $deleted = false;
+
+    /** @var stdclass extra information about the string */
+    public $extra = null;
+
+    /**
+     * Class constructor.
+     *
+     * @param string $id string identifier
+     * @param string $text string text
+     * @param int $timemodified
+     * @param int $deleted
+     * @param stdclass $extra
+     */
+    public function __construct(string $id, string $text = '', int $timemodified = null,
+            int $deleted = 0, stdclass $extra = null) {
+
+        if (is_null($timemodified)) {
+            $timemodified = time();
+        }
+        $this->id           = $id;
+        $this->text         = $text;
+        $this->timemodified = $timemodified;
+        $this->deleted      = $deleted;
+        $this->extra        = $extra;
+    }
+
+    /**
+     * Given a string text, returns it being formatted properly for storing in AMOS repository.
+     *
+     * Note: This method is taken directly from local_amos as it is highly tested and robust.
+     * The Moodle 1.x part is keep on puspose to make it easier the copy paste from both codes.
+     * This could change in the future when AMOS stop suporting the 1.x langstrings.
+     *
+     * We need to know for what branch the string should be prepared due to internal changes in
+     * format required by get_string()
+     * - for get_string() in Moodle 1.6 - 1.9 use $format == 1
+     * - for get_string() in Moodle 2.0 and higher use $format == 2
+     *
+     * Typical usages of this methods:
+     *  $t = langstring::fix_syntax($t);          // sanity new translations of 2.x strings
+     *  $t = langstring::fix_syntax($t, 1);       // sanity legacy 1.x strings
+     *  $t = langstring::fix_syntax($t, 2, 1);    // convert format of 1.x strings into 2.x
+     *
+     * Backward converting 2.x format into 1.x is not supported
+     *
+     * @param string $text string text to be fixed
+     * @param int $format target get_string() format version
+     * @param int $from which format version does the text come from, defaults to the same as $format
+     * @return string
+     */
+    public static function fix_syntax(string $text, int $format = 2, ?int $from = null): string {
+        if (is_null($from)) {
+            $from = $format;
+        }
+
+        // Common filter.
+        $clean = trim($text);
+        $search = [
+            // Remove \r if it is part of \r\n.
+            '/\r(?=\n)/',
+
+            // Control characters to be replaced with \n
+            // LINE TABULATION, FORM FEED, CARRIAGE RETURN, END OF TRANSMISSION BLOCK,
+            // END OF MEDIUM, SUBSTITUTE, BREAK PERMITTED HERE, NEXT LINE, START OF STRING,
+            // STRING TERMINATOR and Unicode character categorys Zl and Zp.
+            '/[\x{0B}-\r\x{17}\x{19}\x{1A}\x{82}\x{85}\x{98}\x{9C}\p{Zl}\p{Zp}]/u',
+
+            // Control characters to be removed
+            // NULL, ENQUIRY, ACKNOWLEDGE, BELL, SHIFT {OUT,IN}, DATA LINK ESCAPE,
+            // DEVICE CONTROL {ONE,TWO,THREE,FOUR}, NEGATIVE ACKNOWLEDGE, SYNCHRONOUS IDLE, ESCAPE,
+            // DELETE, PADDING CHARACTER, HIGH OCTET PRESET, NO BREAK HERE, INDEX,
+            // {START,END} OF SELECTED AREA, CHARACTER TABULATION {SET,WITH JUSTIFICATION},
+            // LINE TABULATION SET, PARTIAL LINE {FORWARD,BACKWARD}, REVERSE LINE FEED,
+            // SINGLE SHIFT {TWO,THREE}, DEVICE CONTROL STRING, PRIVATE USE {ONE,TWO},
+            // SET TRANSMIT STATE, MESSAGE WAITING, {START,END} OF GUARDED AREA,
+            // {SINGLE {GRAPHIC,} CHARACTER,CONTROL SEQUENCE} INTRODUCER, OPERATING SYSTEM COMMAND,
+            // PRIVACY MESSAGE, APPLICATION PROGRAM COMMAND, ZERO WIDTH {,NO-BREAK} SPACE,
+            // REPLACEMENT CHARACTER.
+            '/[\0\x{05}-\x{07}\x{0E}-\x{16}\x{1B}\x{7F}\x{80}\x{81}\x{83}\x{84}\x{86}-\x{93}\x{95}-\x{97}\x{99}-\x{9B}\x{9D}-\x{9F}\x{200B}\x{FEFF}\x{FFFD}]++/u',
+
+            // Remove trailing whitespace at the end of lines in a multiline string.
+            '/[ \t]+(?=\n)/',
+        ];
+        $replace = [
+            '',
+            "\n",
+            '',
+            '',
+        ];
+        $clean = preg_replace($search, $replace, $clean);
+
+        if (($format === 2) && ($from === 2)) {
+            // Sanity translations of 2.x strings.
+            $clean = preg_replace("/\n{3,}/", "\n\n\n", $clean); // Collapse runs of blank lines.
+
+        } else if (($format === 2) && ($from === 1)) {
+            // Convert 1.x string into 2.x format.
+            $clean = preg_replace("/\n{3,}/", "\n\n\n", $clean); // Collapse runs of blank lines.
+            $clean = preg_replace('/%+/', '%', $clean); // Collapse % characters.
+            $clean = str_replace('\$', '@@@___XXX_ESCAPED_DOLLAR__@@@', $clean); // Remember for later.
+            $clean = str_replace("\\", '', $clean); // Delete all slashes.
+            $clean = preg_replace('/(^|[^{])\$a\b(\->[a-zA-Z0-9_]+)?/', '\\1{$a\\2}', $clean); // Wrap placeholders.
+            $clean = str_replace('@@@___XXX_ESCAPED_DOLLAR__@@@', '$', $clean);
+            $clean = str_replace('&#36;', '$', $clean);
+
+        } else if (($format === 1) && ($from === 1)) {
+            // Sanity legacy 1.x strings.
+            $clean = preg_replace("/\n{3,}/", "\n\n", $clean); // Collapse runs of blank lines.
+            $clean = str_replace('\$', '@@@___XXX_ESCAPED_DOLLAR__@@@', $clean);
+            $clean = str_replace("\\", '', $clean); // Delete all slashes.
+            $clean = str_replace('$', '\$', $clean); // Escape all embedded variables.
+            // Unescape placeholders: only $a and $a->something are allowed. All other $variables are left escaped.
+            $clean = preg_replace('/\\\\\$a\b(\->[a-zA-Z0-9_]+)?/', '$a\\1', $clean); // Unescape placeholders.
+            $clean = str_replace('@@@___XXX_ESCAPED_DOLLAR__@@@', '\$', $clean);
+            $clean = str_replace('"', "\\\"", $clean); // Add slashes for ".
+            $clean = preg_replace('/%+/', '%', $clean); // Collapse % characters.
+            $clean = str_replace('%', '%%', $clean); // Duplicate %.
+
+        } else {
+            throw new moodle_exception('Unknown get_string() format version');
+        }
+        return $clean;
+    }
+}
diff --git a/admin/tool/customlang/classes/local/mlang/logstatus.php b/admin/tool/customlang/classes/local/mlang/logstatus.php
new file mode 100644 (file)
index 0000000..eca3a98
--- /dev/null
@@ -0,0 +1,92 @@
+<?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/>.
+
+/**
+ * Language string based on David Mudrak langstring from local_amos.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\local\mlang;
+
+use moodle_exception;
+use stdclass;
+
+/**
+ * Class containing a lang string cleaned.
+ *
+ * @package    tool_customlang
+ * @copyright  2020 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Represents a single string
+ */
+class logstatus {
+
+    /** @var langstring the current string */
+    public $langstring = null;
+
+    /** @var string the component */
+    public $component = null;
+
+    /** @var string the string ID */
+    public $stringid = null;
+
+    /** @var string the original filename */
+    public $filename = null;
+
+    /** @var int the error level */
+    public $errorlevel = null;
+
+    /** @var string the message identifier */
+    private $message;
+
+    /**
+     * Class creator.
+     *
+     * @param string $message the message identifier to display
+     * @param string $errorlevel the notice level
+     * @param string|null $filename the filename of this log
+     * @param string|null $component the component of this log
+     * @param langstring|null $langstring the langstring of this log
+     */
+    public function __construct(string $message, string $errorlevel, ?string $filename = null,
+            ?string $component = null, ?langstring $langstring = null) {
+
+        $this->filename = $filename;
+        $this->component = $component;
+        $this->langstring = $langstring;
+        $this->message = $message;
+        $this->errorlevel = $errorlevel;
+
+        if ($langstring) {
+            $this->stringid = $langstring->id;
+        }
+    }
+
+    /**
+     * Get the log message.
+     *
+     * @return string the log message.
+     */
+    public function get_message(): string {
+        return get_string($this->message, 'tool_customlang', $this);
+    }
+}
diff --git a/admin/tool/customlang/classes/local/mlang/phpparser.php b/admin/tool/customlang/classes/local/mlang/phpparser.php
new file mode 100644 (file)
index 0000000..c4c5407