Merge branch 'MDL-63149-master' of git://github.com/rezaies/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 26 Mar 2019 12:24:59 +0000 (13:24 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 26 Mar 2019 12:24:59 +0000 (13:24 +0100)
665 files changed:
.eslintignore
.stylelintignore
Gruntfile.js
admin/classes/form/testoutgoingmailconf_form.php [new file with mode: 0644]
admin/message.php
admin/roles/classes/check_users_selector.php
admin/settings/analytics.php
admin/settings/courses.php
admin/settings/messaging.php [new file with mode: 0644]
admin/settings/plugins.php
admin/settings/server.php
admin/settings/subsystems.php
admin/settings/top.php
admin/testoutgoingmailconf.php [new file with mode: 0644]
admin/tests/behat/behat_admin.php
admin/tool/analytics/amd/build/model.min.js
admin/tool/analytics/amd/src/model.js
admin/tool/analytics/classes/output/model_logs.php
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/cli/evaluate_model.php
admin/tool/analytics/lang/en/tool_analytics.php
admin/tool/analytics/model.php
admin/tool/analytics/templates/evaluation_mode_selection.mustache [new file with mode: 0644]
admin/tool/customlang/classes/output/renderer.php [new file with mode: 0644]
admin/tool/customlang/classes/output/translator.php [new file with mode: 0644]
admin/tool/customlang/renderer.php [deleted file]
admin/tool/customlang/styles.css [deleted file]
admin/tool/customlang/templates/translator.mustache [new file with mode: 0644]
admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
admin/tool/dataprivacy/tests/coverage.php [new file with mode: 0644]
admin/tool/lp/tests/externallib_test.php
admin/tool/lpimportcsv/classes/framework_importer.php
admin/tool/messageinbound/lang/en/tool_messageinbound.php
admin/tool/mobile/lib.php
admin/tool/monitor/lang/en/tool_monitor.php
admin/tool/policy/classes/output/page_agreedocs.php
admin/tool/policy/tests/behat/acceptances.feature
admin/tool/recyclebin/classes/course_bin.php
admin/tool/recyclebin/tests/course_bin_test.php
admin/tool/replace/lang/en/tool_replace.php
admin/tool/task/lang/en/tool_task.php
admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
admin/tool/usertours/classes/manager.php
admin/tool/usertours/db/upgrade.php
admin/tool/usertours/version.php
admin/tool/xmldb/lang/en/tool_xmldb.php
analytics/classes/classifier.php
analytics/classes/local/target/base.php
analytics/classes/manager.php
analytics/classes/model.php
analytics/classes/regressor.php
analytics/classes/stats.php [new file with mode: 0644]
analytics/tests/prediction_test.php
analytics/tests/stats_test.php [new file with mode: 0644]
analytics/upgrade.txt
auth/db/lang/en/auth_db.php
auth/ldap/lang/en/auth_ldap.php
auth/mnet/auth.php
auth/mnet/classes/task/cron_task.php [new file with mode: 0644]
auth/mnet/db/tasks.php [new file with mode: 0644]
auth/mnet/lang/en/auth_mnet.php
auth/mnet/version.php
auth/shibboleth/lang/en/auth_shibboleth.php
badges/index.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lang/en/deprecated.txt
blocks/myoverview/templates/nav-sort-selector.mustache
blocks/myoverview/templates/progress-bar.mustache
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/myoverview/tests/behat/block_myoverview_favourite.feature
blocks/recentlyaccessedcourses/tests/behat/block_recentlyaccessedcourses_dashboard.feature
blocks/recentlyaccesseditems/tests/behat/block_recentlyaccesseditems_dashboard.feature
blocks/timeline/templates/nav-day-filter.mustache
blocks/timeline/templates/nav-view-selector.mustache
blocks/timeline/tests/behat/block_timeline_courses.feature
blocks/timeline/tests/behat/block_timeline_dates.feature
blocks/timeline/tests/behat/block_timeline_pagelimit_persistence.feature
cache/stores/mongodb/MongoDB/BulkWriteResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/ChangeStream.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Client.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Collection.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Database.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/DeleteResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/BadMethodCallException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/Exception.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/InvalidArgumentException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/ResumeTokenException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/RuntimeException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/UnexpectedValueException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Exception/UnsupportedException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/Bucket.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/CollectionWrapper.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/Exception/CorruptFileException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/Exception/FileNotFoundException.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/ReadableStream.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/StreamWrapper.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/GridFS/WritableStream.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/InsertManyResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/InsertOneResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/MapReduceResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/BSONArray.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/BSONDocument.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/BSONIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/CachingIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/CollectionInfo.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/CollectionInfoCommandIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/CollectionInfoIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/DatabaseInfo.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/DatabaseInfoIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/DatabaseInfoLegacyIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/IndexInfo.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/IndexInfoIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/IndexInfoIteratorIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/IndexInput.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Model/TypeMapArrayIterator.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Aggregate.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/BulkWrite.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Count.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/CountDocuments.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/CreateCollection.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/CreateIndexes.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DatabaseCommand.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Delete.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DeleteMany.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DeleteOne.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Distinct.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DropCollection.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DropDatabase.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/DropIndexes.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/EstimatedDocumentCount.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Executable.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Explain.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Explainable.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Find.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/FindAndModify.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/FindOne.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/FindOneAndDelete.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/FindOneAndReplace.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/FindOneAndUpdate.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/InsertMany.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/InsertOne.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/ListCollections.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/ListDatabases.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/ListIndexes.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/MapReduce.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/ModifyCollection.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/ReplaceOne.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Update.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/UpdateMany.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/UpdateOne.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/Operation/Watch.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/UpdateResult.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/functions.php [new file with mode: 0644]
cache/stores/mongodb/MongoDB/readme_moodle.txt [new file with mode: 0644]
cache/stores/mongodb/addinstanceform.php
cache/stores/mongodb/lib.php
cache/stores/mongodb/thirdpartylibs.xml [new file with mode: 0644]
cache/upgrade.txt
calendar/classes/local/api.php
calendar/externallib.php
calendar/lib.php
calendar/tests/externallib_test.php
calendar/tests/local_api_test.php
comment/classes/external.php
comment/lib.php
comment/locallib.php
competency/classes/course_competency.php
competency/tests/external_test.php
completion/classes/progress.php
completion/tests/progress_test.php
config-dist.php
course/classes/search/course.php [moved from course/classes/search/mycourse.php with 95% similarity]
course/classes/search/customfield.php
course/externallib.php
course/format/social/format.php
course/format/social/tests/behat/social_adjust_discussion_count.feature
course/loginas.php
course/renderer.php
course/tests/behat/behat_course.php
course/tests/behat/category_role_assignment.feature
course/tests/behat/course_creation.feature
course/tests/behat/customfields_visibility.feature
course/tests/behat/navigate_course_list.feature
course/tests/courselib_test.php
course/tests/externallib_test.php
course/tests/search_test.php
course/upgrade.txt
customfield/field/checkbox/lang/en/customfield_checkbox.php
customfield/field/date/lang/en/customfield_date.php
customfield/field/select/lang/en/customfield_select.php
customfield/field/select/tests/behat/field.feature
customfield/field/text/lang/en/customfield_text.php
customfield/field/textarea/lang/en/customfield_textarea.php
customfield/tests/behat/edit_fields_settings.feature
enrol/externallib.php
enrol/ldap/lang/en/enrol_ldap.php
enrol/manual/lang/en/enrol_manual.php
enrol/self/lang/en/enrol_self.php
enrol/tests/enrollib_test.php
enrol/tests/externallib_test.php
enrol/upgrade.txt
files/classes/external/stored_file_exporter.php
files/renderer.php
grade/edit/tree/category_form.php
grade/edit/tree/item_form.php
grade/import/csv/classes/load_data.php
grade/import/csv/tests/load_data_test.php
group/lib.php
group/tests/behat/behat_groups.php
install/lang/hi/moodle.php
install/lang/hr/admin.php
install/lang/hr/error.php
install/lang/hr/install.php
lang/en/admin.php
lang/en/analytics.php
lang/en/availability.php
lang/en/backup.php
lang/en/completion.php
lang/en/course.php
lang/en/customfield.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/grades.php
lang/en/group.php
lang/en/hub.php
lang/en/message.php
lang/en/moodle.php
lang/en/plugin.php
lang/en/question.php
lang/en/role.php
lang/en/search.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/loadingicon.min.js [new file with mode: 0644]
lib/amd/build/templates.min.js
lib/amd/src/form-autocomplete.js
lib/amd/src/loadingicon.js [new file with mode: 0644]
lib/amd/src/templates.js
lib/behat/behat_base.php
lib/behat/classes/behat_config_util.php
lib/behat/classes/partial_named_selector.php
lib/behat/form_field/behat_form_filemanager.php
lib/behat/form_field/behat_form_passwordunmask.php
lib/behat/form_field/behat_form_select.php
lib/classes/component.php
lib/classes/external/exporter.php
lib/classes/external/paged_content_exporter.php [new file with mode: 0644]
lib/classes/hub/registration.php
lib/classes/message/manager.php
lib/classes/output/external.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/output/mustache_template_source_loader.php
lib/classes/output/notification.php
lib/classes/plugin_manager.php
lib/classes/task/clean_up_deleted_search_area_task.php [new file with mode: 0644]
lib/classes/useragent.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/dml/moodle_database.php
lib/dml/tests/dml_test.php
lib/dml/tests/pgsql_native_recordset_test.php
lib/editor/atto/plugins/media/lang/en/atto_media.php
lib/editor/atto/plugins/recordrtc/lib.php
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-debug.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-debug.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording.js
lib/editor/atto/plugins/recordrtc/yui/src/button/js/button.js
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/audiomodule.js
lib/editor/atto/plugins/recordrtc/yui/src/recording/js/videomodule.js
lib/enrollib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/form/tests/behat/modgrade_validation.feature
lib/ltiprovider/readme_moodle.txt
lib/ltiprovider/src/OAuth/OAuthRequest.php
lib/mlbackend/php/classes/processor.php
lib/mlbackend/python/classes/processor.php
lib/moodlelib.php
lib/outputrenderers.php
lib/phpunit/classes/coverage_info.php [new file with mode: 0644]
lib/phpunit/classes/util.php
lib/portfolio/constants.php
lib/portfoliolib.php
lib/templates/navbar.mustache
lib/templates/notification.mustache [new file with mode: 0644]
lib/tests/accesslib_test.php
lib/tests/behat/behat_app.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/tests/behat/locking.feature
lib/tests/behat/securelayout.feature [new file with mode: 0644]
lib/tests/coverage.php [new file with mode: 0644]
lib/tests/exporter_test.php
lib/tests/externallib_test.php
lib/tests/filelib_test.php
lib/tests/fixtures/securetestpage.php [new file with mode: 0644]
lib/tests/messagelib_test.php
lib/tests/moodlelib_test.php
lib/tests/useragent_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php
login/tests/lib_test.php
message/amd/build/message_drawer_events.min.js
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_conversation_constants.min.js
message/amd/build/message_drawer_view_conversation_patcher.min.js
message/amd/build/message_drawer_view_conversation_renderer.min.js
message/amd/build/message_drawer_view_conversation_state_manager.min.js
message/amd/build/message_drawer_view_overview_section.min.js
message/amd/build/message_drawer_view_settings.min.js
message/amd/build/message_repository.min.js
message/amd/src/message_drawer_events.js
message/amd/src/message_drawer_view_conversation.js
message/amd/src/message_drawer_view_conversation_constants.js
message/amd/src/message_drawer_view_conversation_patcher.js
message/amd/src/message_drawer_view_conversation_renderer.js
message/amd/src/message_drawer_view_conversation_state_manager.js
message/amd/src/message_drawer_view_overview_section.js
message/amd/src/message_drawer_view_settings.js
message/amd/src/message_repository.js
message/classes/api.php
message/classes/helper.php
message/classes/privacy/provider.php
message/defaultoutputs.php
message/externallib.php
message/renderer.php
message/templates/message_drawer_conversations_list.mustache
message/templates/message_drawer_lazy_load_list.mustache
message/templates/message_drawer_view_conversation_body_confirm_dialogue.mustache
message/templates/message_drawer_view_conversation_header_content_type_private.mustache
message/templates/message_drawer_view_conversation_header_content_type_private_no_controls.mustache
message/templates/message_drawer_view_conversation_header_content_type_public.mustache
message/templates/message_drawer_view_overview_section_messages.mustache
message/tests/api_test.php
message/tests/behat/behat_message.php
message/tests/behat/group_conversation.feature [new file with mode: 0644]
message/tests/externallib_test.php
message/tests/privacy_provider_test.php
mod/assign/feedback/editpdf/ajax.php
mod/assign/feedback/editpdf/backup/moodle2/backup_assignfeedback_editpdf_subplugin.class.php
mod/assign/feedback/editpdf/backup/moodle2/restore_assignfeedback_editpdf_subplugin.class.php
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/page_editor.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/classes/privacy/provider.php
mod/assign/feedback/editpdf/classes/renderer.php
mod/assign/feedback/editpdf/db/install.xml
mod/assign/feedback/editpdf/db/upgrade.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/pix/rotate_left.svg [new file with mode: 0644]
mod/assign/feedback/editpdf/pix/rotate_right.svg [new file with mode: 0644]
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/editpdf/tests/behat/behat_assignfeedback_editpdf.php
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/editpdf/version.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/comment.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/globals.js
mod/chat/classes/task/cron_task.php [new file with mode: 0644]
mod/chat/db/tasks.php [new file with mode: 0644]
mod/chat/lang/en/chat.php
mod/chat/lib.php
mod/chat/version.php
mod/choice/lang/en/choice.php
mod/choice/tests/behat/choice_availability.feature
mod/data/classes/privacy/provider.php
mod/data/lang/en/data.php
mod/data/tests/externallib_test.php
mod/feedback/lang/en/feedback.php
mod/feedback/tests/behat/behat_mod_feedback.php
mod/feedback/tests/behat/coursemapping.feature
mod/feedback/tests/behat/export_import.feature
mod/feedback/tests/behat/multichoice.feature
mod/feedback/tests/behat/question_types.feature
mod/feedback/tests/behat/question_types_non_anon.feature
mod/forum/amd/build/discussion_list.min.js [new file with mode: 0644]
mod/forum/amd/build/repository.min.js [new file with mode: 0644]
mod/forum/amd/build/selectors.min.js [new file with mode: 0644]
mod/forum/amd/build/subscription_toggle.min.js [new file with mode: 0644]
mod/forum/amd/src/discussion_list.js [new file with mode: 0644]
mod/forum/amd/src/repository.js [new file with mode: 0644]
mod/forum/amd/src/selectors.js [new file with mode: 0644]
mod/forum/amd/src/subscription_toggle.js [new file with mode: 0644]
mod/forum/classes/local/builders/exported_discussion_summaries.php [new file with mode: 0644]
mod/forum/classes/local/builders/exported_posts.php [new file with mode: 0644]
mod/forum/classes/local/container.php [new file with mode: 0644]
mod/forum/classes/local/data_mappers/legacy/author.php [new file with mode: 0644]
mod/forum/classes/local/data_mappers/legacy/discussion.php [new file with mode: 0644]
mod/forum/classes/local/data_mappers/legacy/forum.php [new file with mode: 0644]
mod/forum/classes/local/data_mappers/legacy/post.php [new file with mode: 0644]
mod/forum/classes/local/entities/author.php [new file with mode: 0644]
mod/forum/classes/local/entities/discussion.php [new file with mode: 0644]
mod/forum/classes/local/entities/discussion_summary.php [new file with mode: 0644]
mod/forum/classes/local/entities/forum.php [new file with mode: 0644]
mod/forum/classes/local/entities/post.php [new file with mode: 0644]
mod/forum/classes/local/entities/post_read_receipt_collection.php [new file with mode: 0644]
mod/forum/classes/local/entities/sorter.php [new file with mode: 0644]
mod/forum/classes/local/exporters/author.php [new file with mode: 0644]
mod/forum/classes/local/exporters/discussion.php [new file with mode: 0644]
mod/forum/classes/local/exporters/discussion_summaries.php [new file with mode: 0644]
mod/forum/classes/local/exporters/discussion_summary.php [new file with mode: 0644]
mod/forum/classes/local/exporters/forum.php [new file with mode: 0644]
mod/forum/classes/local/exporters/post.php [new file with mode: 0644]
mod/forum/classes/local/exporters/posts.php [new file with mode: 0644]
mod/forum/classes/local/factories/builder.php [new file with mode: 0644]
mod/forum/classes/local/factories/entity.php [new file with mode: 0644]
mod/forum/classes/local/factories/exporter.php [new file with mode: 0644]
mod/forum/classes/local/factories/legacy_data_mapper.php [new file with mode: 0644]
mod/forum/classes/local/factories/manager.php [new file with mode: 0644]
mod/forum/classes/local/factories/renderer.php [new file with mode: 0644]
mod/forum/classes/local/factories/url.php [new file with mode: 0644]
mod/forum/classes/local/factories/vault.php [new file with mode: 0644]
mod/forum/classes/local/managers/capability.php [new file with mode: 0644]
mod/forum/classes/local/renderers/discussion.php [new file with mode: 0644]
mod/forum/classes/local/renderers/discussion_list.php [new file with mode: 0644]
mod/forum/classes/local/renderers/posts.php [new file with mode: 0644]
mod/forum/classes/local/vaults/author.php [new file with mode: 0644]
mod/forum/classes/local/vaults/db_table_vault.php [new file with mode: 0644]
mod/forum/classes/local/vaults/discussion.php [new file with mode: 0644]
mod/forum/classes/local/vaults/discussion_list.php [new file with mode: 0644]
mod/forum/classes/local/vaults/forum.php [new file with mode: 0644]
mod/forum/classes/local/vaults/post.php [new file with mode: 0644]
mod/forum/classes/local/vaults/post_attachment.php [new file with mode: 0644]
mod/forum/classes/local/vaults/post_read_receipt_collection.php [new file with mode: 0644]
mod/forum/classes/local/vaults/preprocessors/extract_context.php [new file with mode: 0644]
mod/forum/classes/local/vaults/preprocessors/extract_record.php [new file with mode: 0644]
mod/forum/classes/local/vaults/preprocessors/extract_user.php [new file with mode: 0644]
mod/forum/classes/privacy/provider.php
mod/forum/db/caches.php [new file with mode: 0644]
mod/forum/db/services.php
mod/forum/deprecatedlib.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/post.php
mod/forum/search.php
mod/forum/templates/blog_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/discussion_subscription_toggle.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_nested_post.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_nested_posts.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_post.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_posts.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_threaded_post.mustache [new file with mode: 0644]
mod/forum/templates/forum_discussion_threaded_posts.mustache [new file with mode: 0644]
mod/forum/templates/forum_posts_with_context_links.mustache [new file with mode: 0644]
mod/forum/templates/frontpage_news_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/frontpage_social_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/news_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/qanda_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/single_discussion_list.mustache [new file with mode: 0644]
mod/forum/templates/social_discussion_list.mustache [new file with mode: 0644]
mod/forum/tests/behat/behat_mod_forum.php
mod/forum/tests/behat/discussion_display.feature
mod/forum/tests/behat/edit_tags.feature
mod/forum/tests/behat/forum_subscriptions_default.feature
mod/forum/tests/behat/groups_in_course_no_groups_in_forum.feature
mod/forum/tests/behat/no_groups_in_course.feature
mod/forum/tests/behat/post_to_multiple_groups.feature
mod/forum/tests/behat/separate_group_discussions.feature
mod/forum/tests/behat/separate_group_single_group_discussions.feature
mod/forum/tests/behat/visible_group_discussions.feature
mod/forum/tests/builders_exported_posts_test.php [new file with mode: 0644]
mod/forum/tests/coverage.php [new file with mode: 0644]
mod/forum/tests/entities_author_test.php [new file with mode: 0644]
mod/forum/tests/entities_discussion_summary_test.php [new file with mode: 0644]
mod/forum/tests/entities_discussion_test.php [new file with mode: 0644]
mod/forum/tests/entities_forum_test.php [new file with mode: 0644]
mod/forum/tests/entities_post_read_receipt_collection_test.php [new file with mode: 0644]
mod/forum/tests/entities_post_test.php [new file with mode: 0644]
mod/forum/tests/entities_sorter_test.php [new file with mode: 0644]
mod/forum/tests/exporters_author_test.php [new file with mode: 0644]
mod/forum/tests/exporters_discussion_test.php [new file with mode: 0644]
mod/forum/tests/exporters_forum_test.php [new file with mode: 0644]
mod/forum/tests/exporters_post_test.php [new file with mode: 0644]
mod/forum/tests/externallib_test.php
mod/forum/tests/generator/lib.php
mod/forum/tests/lib_test.php
mod/forum/tests/local_container_test.php [new file with mode: 0644]
mod/forum/tests/managers_capability_test.php [new file with mode: 0644]
mod/forum/tests/vaults_author_test.php [new file with mode: 0644]
mod/forum/tests/vaults_discussion_list_test.php [new file with mode: 0644]
mod/forum/tests/vaults_discussion_test.php [new file with mode: 0644]
mod/forum/tests/vaults_forum_test.php [new file with mode: 0644]
mod/forum/tests/vaults_post_attachment_test.php [new file with mode: 0644]
mod/forum/tests/vaults_post_read_receipt_collection_test.php [new file with mode: 0644]
mod/forum/tests/vaults_post_test.php [new file with mode: 0644]
mod/forum/upgrade.txt
mod/forum/user.php
mod/forum/version.php
mod/forum/view.php
mod/glossary/classes/privacy/provider.php
mod/lesson/lang/en/lesson.php
mod/lti/lang/en/lti.php
mod/lti/lib.php
mod/lti/tests/lib_test.php
mod/quiz/lib.php
mod/quiz/tests/lib_test.php
mod/scorm/lang/en/scorm.php
mod/wiki/edit_form.php
mod/wiki/lib.php
mod/wiki/tests/lib_test.php
mod/workshop/allocation/manual/lang/en/workshopallocation_manual.php
mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php
mod/workshop/allocation/random/lang/en/workshopallocation_random.php
mod/workshop/allocation/scheduled/classes/task/cron_task.php [new file with mode: 0644]
mod/workshop/allocation/scheduled/db/tasks.php [new file with mode: 0644]
mod/workshop/allocation/scheduled/lang/en/workshopallocation_scheduled.php
mod/workshop/allocation/scheduled/lib.php
mod/workshop/allocation/scheduled/version.php
mod/workshop/classes/task/cron_task.php [new file with mode: 0644]
mod/workshop/classes/task/legacy_workshop_allocation_cron.php [new file with mode: 0644]
mod/workshop/db/tasks.php [new file with mode: 0644]
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/tests/lib_test.php
mod/workshop/upgrade.txt
mod/workshop/version.php
notes/externallib.php
notes/tests/externallib_test.php
notes/upgrade.txt [new file with mode: 0644]
phpunit.xml.dist
pix/i/muted.png [new file with mode: 0644]
pix/i/muted.svg [new file with mode: 0644]
pix/t/sort_by.png [new file with mode: 0644]
pix/t/sort_by.svg [new file with mode: 0644]
privacy/tests/coverage.php [new file with mode: 0644]
question/format.php
question/format/aiken/format.php
question/format/aiken/tests/behat/aiken_export.feature [new file with mode: 0644]
question/format/aiken/tests/behat/aiken_import.feature [new file with mode: 0644]
question/format/aiken/tests/qformat_aiken_export_test.php [new file with mode: 0644]
question/format/gift/lang/en/qformat_gift.php
question/format/multianswer/format.php
question/format/multianswer/tests/fixtures/broken_multianswer_1.txt [new file with mode: 0644]
question/format/multianswer/tests/fixtures/broken_multianswer_2.txt [new file with mode: 0644]
question/format/multianswer/tests/fixtures/broken_multianswer_3.txt [new file with mode: 0644]
question/format/multianswer/tests/fixtures/broken_multianswer_4.txt [new file with mode: 0644]
question/format/multianswer/tests/multianswerformat_test.php
question/format/webct/lang/en/qformat_webct.php
question/format/xml/format.php
question/format/xml/tests/fixtures/broken_cloze_questions.xml [new file with mode: 0644]
question/format/xml/tests/qformat_xml_import_export_test.php
question/type/gapselect/lang/en/qtype_gapselect.php
question/type/multianswer/edit_multianswer_form.php
question/type/multianswer/lang/en/qtype_multianswer.php
question/type/multianswer/questiontype.php
question/type/multianswer/version.php
question/type/numerical/edit_numerical_form.php
question/type/numerical/questiontype.php
question/type/numerical/tests/form_test.php [deleted file]
question/type/numerical/tests/questiontype_test.php
question/upgrade.txt
rating/classes/privacy/provider.php
rating/tests/privacy_provider_test.php
repository/dropbox/classes/task/cron_task.php [new file with mode: 0644]
repository/dropbox/db/tasks.php [new file with mode: 0644]
repository/dropbox/lang/en/repository_dropbox.php
repository/dropbox/lib.php
repository/dropbox/version.php
repository/filesystem/classes/task/cron_task.php [new file with mode: 0644]
repository/filesystem/db/tasks.php [new file with mode: 0644]
repository/filesystem/lang/en/repository_filesystem.php
repository/filesystem/lib.php
repository/filesystem/version.php
repository/nextcloud/lang/en/repository_nextcloud.php
repository/tests/behat/behat_filepicker.php
repository/upload/tests/behat/behat_repository_upload.php
search/classes/base.php
search/classes/manager.php
search/classes/output/form/search.php
search/engine/solr/tests/engine_test.php
search/index.php
search/tests/base_test.php
search/tests/fixtures/testable_core_search.php
search/tests/manager_test.php
search/upgrade.txt
theme/boost/lang/en/theme_boost.php
theme/boost/scss/moodle/admin.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/filemanager.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
theme/boost/templates/navbar-secure.mustache
theme/boost/tests/behat/group_conversation.feature [new file with mode: 0644]
theme/bootstrapbase/lang/en/theme_bootstrapbase.php
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/templates/block_timeline/nav-day-filter.mustache
theme/bootstrapbase/templates/block_timeline/nav-view-selector.mustache
theme/bootstrapbase/templates/core_message/message_drawer_lazy_load_list.mustache
theme/bootstrapbase/templates/core_message/message_drawer_view_conversation_header_content_type_private.mustache
theme/bootstrapbase/templates/core_message/message_drawer_view_conversation_header_content_type_private_no_controls.mustache
theme/bootstrapbase/templates/core_message/message_drawer_view_conversation_header_content_type_public.mustache
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_admin.php
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_filepicker.php
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_repository_upload.php
theme/classic/classes/output/core_renderer.php [new file with mode: 0644]
theme/classic/classes/privacy/provider.php [new file with mode: 0644]
theme/classic/config.php [new file with mode: 0644]
theme/classic/lang/en/theme_classic.php [new file with mode: 0644]
theme/classic/layout/columns.php [new file with mode: 0644]
theme/classic/layout/contentonly.php [new file with mode: 0644]
theme/classic/layout/secure.php [new file with mode: 0644]
theme/classic/lib.php [new file with mode: 0644]
theme/classic/pix/favicon.ico [new file with mode: 0644]
theme/classic/pix/screenshot.png [new file with mode: 0644]
theme/classic/scss/bootstrap.scss [new file with mode: 0644]
theme/classic/scss/classic/body-background.scss [new file with mode: 0644]
theme/classic/scss/classic/navbar-dark.scss [new file with mode: 0644]
theme/classic/scss/classic/navbar-light.scss [new file with mode: 0644]
theme/classic/scss/classic/post.scss [new file with mode: 0644]
theme/classic/scss/classic/pre.scss [new file with mode: 0644]
theme/classic/scss/classicgrunt.scss [new file with mode: 0644]
theme/classic/scss/fontawesome.scss [new file with mode: 0644]
theme/classic/scss/moodle.scss [new file with mode: 0644]
theme/classic/scss/preset/default.scss [new file with mode: 0644]
theme/classic/scss/preset/plain.scss [new file with mode: 0644]
theme/classic/settings.php [new file with mode: 0644]
theme/classic/style/moodle.css [new file with mode: 0644]
theme/classic/templates/columns.mustache [new file with mode: 0644]
theme/classic/templates/contentonly.mustache [new file with mode: 0644]
theme/classic/templates/core/footer.mustache [new file with mode: 0644]
theme/classic/templates/core/full_header.mustache [new file with mode: 0644]
theme/classic/templates/navbar-secure.mustache [new file with mode: 0644]
theme/classic/templates/navbar.mustache [new file with mode: 0644]
theme/classic/templates/secure.mustache [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_admin.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_blocks.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_course.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_grade.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_mod_quiz.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_navigation.php [new file with mode: 0644]
theme/classic/tests/behat/behat_theme_classic_behat_repository_upload.php [new file with mode: 0644]
theme/classic/tests/behat/blacklist.json [new file with mode: 0644]
theme/classic/tests/behat/courseadministrationmenu.feature [new file with mode: 0644]
theme/classic/tests/behat/pageadministrationmenu.feature [new file with mode: 0644]
theme/classic/tests/scss_test.php [new file with mode: 0644]
theme/classic/version.php [new file with mode: 0644]
theme/clean/lang/en/theme_clean.php
user/externallib.php
user/lib.php
user/profile/field/menu/lang/en/profilefield_menu.php
user/tests/externallib_test.php
version.php
webservice/externallib.php
webservice/rest/locallib.php
webservice/tests/externallib_test.php
webservice/upgrade.txt

index 420eb21..9153e18 100644 (file)
@@ -6,6 +6,7 @@ vendor/
 admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
 auth/cas/CAS/
+cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
index be8cec8..d8056ab 100644 (file)
@@ -4,11 +4,13 @@ theme/bootstrapbase/style/
 theme/clean/style/custom.css
 theme/more/style/custom.css
 theme/boost/style/moodle.css
+theme/classic/style/moodle.css
 node_modules/
 vendor/
 admin/tool/policy/amd/src/jquery-eu-cookie-law-popup.js
 admin/tool/usertours/amd/src/tour.js
 auth/cas/CAS/
+cache/stores/mongodb/MongoDB/
 enrol/lti/ims-blti/
 filter/algebra/AlgParser.pm
 filter/tex/mimetex.*
index 29d271c..eb136bb 100644 (file)
@@ -140,11 +140,12 @@ module.exports = function(grunt) {
         sass: {
             dist: {
                 files: {
-                    "theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss"
+                    "theme/boost/style/moodle.css": "theme/boost/scss/preset/default.scss",
+                    "theme/classic/style/moodle.css": "theme/classic/scss/classicgrunt.scss"
                 }
             },
             options: {
-                includePaths: ["theme/boost/scss/"]
+                includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
             }
         },
         watch: {
@@ -226,7 +227,8 @@ module.exports = function(grunt) {
           'theme/bootstrapbase/style/',
           'theme/clean/style/custom.css',
           'theme/more/style/custom.css',
-          'theme/boost/style/moodle.css'
+          'theme/boost/style/moodle.css',
+          'theme/classic/style/moodle.css',
       ].concat(thirdPartyPaths);
       grunt.file.write('.stylelintignore', stylelintIgnores.join('\n'));
     };
diff --git a/admin/classes/form/testoutgoingmailconf_form.php b/admin/classes/form/testoutgoingmailconf_form.php
new file mode 100644 (file)
index 0000000..fbf8ac4
--- /dev/null
@@ -0,0 +1,59 @@
+<?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/>.
+
+/**
+ * Testing outgoing mail configuration form
+ *
+ * @package    core
+ * @copyright  2019 Victor Deniz <victor@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_admin\form;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * Test mail form
+ *
+ * @package    core
+ * @copyright 2019 Victor Deniz <victor@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testoutgoingmailconf_form extends \moodleform {
+    /**
+     * Add elements to form
+     */
+    public function definition() {
+        $mform = $this->_form;
+
+        // Recipient.
+        $options = ['maxlength' => '100', 'size' => '25'];
+        $mform->addElement('text', 'recipient', get_string('testoutgoingmailconf_toemail', 'admin'), $options);
+        $mform->setType('recipient', PARAM_EMAIL);
+        $mform->addRule('recipient', get_string('required'), 'required');
+
+        $buttonarray = array();
+        $buttonarray[] = $mform->createElement('submit', 'send', get_string('testoutgoingmailconf_sendtest', 'admin'));
+        $buttonarray[] = $mform->createElement('cancel');
+
+        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+        $mform->closeHeaderBefore('buttonar');
+
+    }
+}
index c10034a..9a43f61 100644 (file)
@@ -25,47 +25,101 @@ require_once(__DIR__ . '/../config.php');
 require_once($CFG->dirroot . '/message/lib.php');
 require_once($CFG->libdir.'/adminlib.php');
 
-// This is an admin page
+// This is an admin page.
 admin_externalpage_setup('managemessageoutputs');
 
-// Get the submitted params
-$disable    = optional_param('disable', 0, PARAM_INT);
-$enable     = optional_param('enable', 0, PARAM_INT);
+// Fetch processors.
+$allprocessors = get_message_processors();
+$processors = array_filter($allprocessors, function($processor) {
+    return $processor->enabled;
+});
+// Fetch message providers.
+$providers = get_message_providers();
+// Fetch the manage message outputs interface.
+$preferences = get_message_output_default_preferences();
 
-$headingtitle = get_string('managemessageoutputs', 'message');
+if (($form = data_submitted()) && confirm_sesskey()) {
+    $preferences = array();
+    // Prepare default message outputs settings.
+    foreach ($providers as $provider) {
+        $componentproviderbase = $provider->component.'_'.$provider->name;
+        $disableprovidersetting = $componentproviderbase.'_disable';
+        $providerdisabled = false;
+        if (!isset($form->$disableprovidersetting)) {
+            $providerdisabled = true;
+            $preferences[$disableprovidersetting] = 1;
+        } else {
+            $preferences[$disableprovidersetting] = 0;
+        }
 
-if (!empty($disable) && confirm_sesskey()) {
-    if (!$processor = $DB->get_record('message_processors', array('id'=>$disable))) {
-        print_error('outputdoesnotexist', 'message');
+        foreach (array('permitted', 'loggedin', 'loggedoff') as $setting) {
+            $value = null;
+            $componentprovidersetting = $componentproviderbase.'_'.$setting;
+            if ($setting == 'permitted') {
+                // If we deal with permitted select element, we need to create individual
+                // setting for each possible processor. Note that this block will
+                // always be processed first after entring parental foreach iteration
+                // so we can change form values on this stage.
+                foreach ($allprocessors as $processor) {
+                    $value = '';
+                    if (isset($form->{$componentprovidersetting}[$processor->name])) {
+                        $value = $form->{$componentprovidersetting}[$processor->name];
+                    }
+                    // Ensure that loggedin loggedoff options are set correctly for this permission.
+                    if (($value == 'disallowed') || $providerdisabled) {
+                        // It might be better to unset them, but I can't figure out why that cause error.
+                        $form->{$componentproviderbase.'_loggedin'}[$processor->name] = 0;
+                        $form->{$componentproviderbase.'_loggedoff'}[$processor->name] = 0;
+                    } else if ($value == 'forced') {
+                        $form->{$componentproviderbase.'_loggedin'}[$processor->name] = 1;
+                        $form->{$componentproviderbase.'_loggedoff'}[$processor->name] = 1;
+                    }
+                    // Record the site preference.
+                    $preferences[$processor->name.'_provider_'.$componentprovidersetting] = $value;
+                }
+            } else if (array_key_exists($componentprovidersetting, $form)) {
+                // We must be processing loggedin or loggedoff checkboxes. Store
+                // defained comma-separated processors as setting value.
+                // Using array_filter eliminates elements set to 0 above.
+                $value = join(',', array_keys(array_filter($form->{$componentprovidersetting})));
+                if (empty($value)) {
+                    $value = null;
+                }
+            }
+            if ($setting != 'permitted') {
+                // We have already recoded site preferences for 'permitted' type.
+                $preferences['message_provider_'.$componentprovidersetting] = $value;
+            }
+        }
+    }
+
+    // Update database.
+    $transaction = $DB->start_delegated_transaction();
+
+    // Save processors enabled/disabled status.
+    foreach ($allprocessors as $processor) {
+        $enabled = isset($form->{$processor->name});
+        \core_message\api::update_processor_status($processor, $enabled);
     }
-    \core_message\api::update_processor_status($processor, 0);     // Disable output.
-    core_plugin_manager::reset_caches();
-}
 
-if (!empty($enable) && confirm_sesskey()) {
-    if (!$processor = $DB->get_record('message_processors', array('id'=>$enable))) {
-        print_error('outputdoesnotexist', 'message');
+    foreach ($preferences as $name => $value) {
+        set_config($name, $value, 'message');
     }
-    \core_message\api::update_processor_status($processor, 1);      // Enable output.
+    $transaction->allow_commit();
+
     core_plugin_manager::reset_caches();
-}
 
-if ($disable || $enable) {
     $url = new moodle_url('message.php');
     redirect($url);
 }
+
 // Page settings
 $PAGE->set_context(context_system::instance());
+$PAGE->requires->js_init_call('M.core_message.init_defaultoutputs');
 
-// Grab the renderer
 $renderer = $PAGE->get_renderer('core', 'message');
 
-// Display the manage message outputs interface
-$processors = get_message_processors();
-$messageoutputs = $renderer->manage_messageoutputs($processors);
-
-// Display the page
+// Display the page.
 echo $OUTPUT->header();
-echo $OUTPUT->heading($headingtitle);
-echo $messageoutputs;
+echo $renderer->manage_messageoutput_settings($allprocessors, $processors, $providers, $preferences);
 echo $OUTPUT->footer();
index 5ac4c26..31929bd 100644 (file)
@@ -69,8 +69,11 @@ class core_role_check_users_selector extends user_selector_base {
 
         if ($coursecontext and $coursecontext != SITEID) {
             $sql1 = " FROM {user} u
-                      JOIN {user_enrolments} ue ON (ue.userid = u.id)
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
+                      JOIN (SELECT DISTINCT subu.id
+                              FROM {user} subu
+                              JOIN {user_enrolments} ue ON (ue.userid = subu.id)
+                              JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
+                           ) subq ON subq.id = u.id
                      WHERE $wherecondition";
             $params['courseid1'] = $coursecontext->instanceid;
 
index d58124c..5f43a52 100644 (file)
@@ -83,8 +83,9 @@ if ($hassiteconfig) {
         foreach ($alltimesplittings as $key => $timesplitting) {
             $timesplittingoptions[$key] = $timesplitting->get_name();
         }
-        $settings->add(new admin_setting_configmultiselect('analytics/timesplittings',
-            new lang_string('enabledtimesplittings', 'analytics'), new lang_string('timesplittingmethod_help', 'analytics'),
+        $settings->add(new admin_setting_configmultiselect('analytics/defaulttimesplittingsevaluation',
+            new lang_string('defaulttimesplittingmethods', 'analytics'),
+            new lang_string('defaulttimesplittingmethods_help', 'analytics'),
             $timesplittingdefaults, $timesplittingoptions)
         );
 
index 739c96e..f214ab4 100644 (file)
@@ -336,7 +336,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_modif_prev', new lang_string('skipmodifprev', 'backup'), new lang_string('skipmodifprevhelp', 'backup'), 0));
 
     // Automated defaults section.
-    $temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), ''));
+    $temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), new lang_string('recyclebin_desc', 'backup')));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', new lang_string('generalusers', 'backup'), new lang_string('configgeneralusers', 'backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), 1));
diff --git a/admin/settings/messaging.php b/admin/settings/messaging.php
new file mode 100644 (file)
index 0000000..2221e72
--- /dev/null
@@ -0,0 +1,78 @@
+<?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/>.
+
+/**
+ * Adds messaging related settings links for Messaging category to admin tree.
+ *
+ * @copyright 2019 Amaia Anabitarte <amaia@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+if ($hassiteconfig) {
+    $temp = new admin_settingpage('messages', new lang_string('messagingssettings', 'admin'));
+    $temp->add(new admin_setting_configcheckbox('messaging',
+        new lang_string('messaging', 'admin'),
+        new lang_string('configmessaging', 'admin'),
+        1));
+    $temp->add(new admin_setting_configcheckbox('messagingallusers',
+            new lang_string('messagingallusers', 'admin'),
+            new lang_string('configmessagingallusers', 'admin'),
+             0)
+    );
+    $temp->add(new admin_setting_configcheckbox('messagingdefaultpressenter',
+            new lang_string('messagingdefaultpressenter', 'admin'),
+            new lang_string('configmessagingdefaultpressenter', 'admin'),
+            1)
+    );
+    $options = array(
+        DAYSECS => new lang_string('secondstotime86400'),
+        WEEKSECS => new lang_string('secondstotime604800'),
+        2620800 => new lang_string('nummonths', 'moodle', 1),
+        7862400 => new lang_string('nummonths', 'moodle', 3),
+        15724800 => new lang_string('nummonths', 'moodle', 6),
+        0 => new lang_string('never')
+    );
+    $temp->add(new admin_setting_configselect(
+            'messagingdeletereadnotificationsdelay',
+            new lang_string('messagingdeletereadnotificationsdelay', 'admin'),
+            new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'),
+            604800,
+            $options)
+    );
+    $temp->add(new admin_setting_configselect(
+            'messagingdeleteallnotificationsdelay',
+            new lang_string('messagingdeleteallnotificationsdelay', 'admin'),
+            new lang_string('configmessagingdeleteallnotificationsdelay', 'admin'),
+            2620800,
+            $options)
+    );
+    $temp->add(new admin_setting_configcheckbox('messagingallowemailoverride',
+        new lang_string('messagingallowemailoverride', 'admin'),
+        new lang_string('configmessagingallowemailoverride', 'admin'),
+        0));
+    $ADMIN->add('messaging', $temp);
+    $ADMIN->add('messaging', new admin_page_managemessageoutputs());
+
+    // Notification outputs plugins.
+    $plugins = core_plugin_manager::instance()->get_plugins_of_type('message');
+    core_collator::asort_objects_by_property($plugins, 'displayname');
+    foreach ($plugins as $plugin) {
+        /** @var \core\plugininfo\message $plugin */
+        $plugin->load_settings($ADMIN, 'messaging', $hassiteconfig);
+    }
+}
index 616d10e..925829c 100644 (file)
@@ -81,17 +81,6 @@ if ($hassiteconfig) {
         $plugin->load_settings($ADMIN, 'blocksettings', $hassiteconfig);
     }
 
-    // message outputs
-    $ADMIN->add('modules', new admin_category('messageoutputs', new lang_string('messageoutputs', 'message')));
-    $ADMIN->add('messageoutputs', new admin_page_managemessageoutputs());
-    $ADMIN->add('messageoutputs', new admin_page_defaultmessageoutputs());
-    $plugins = core_plugin_manager::instance()->get_plugins_of_type('message');
-    core_collator::asort_objects_by_property($plugins, 'displayname');
-    foreach ($plugins as $plugin) {
-        /** @var \core\plugininfo\message $plugin */
-        $plugin->load_settings($ADMIN, 'messageoutputs', $hassiteconfig);
-    }
-
     // authentication plugins
     $ADMIN->add('modules', new admin_category('authsettings', new lang_string('authentication', 'admin')));
 
@@ -576,14 +565,18 @@ if ($hassiteconfig) {
     $temp->add(new admin_setting_configduration('searchindextime',
             new lang_string('searchindextime', 'admin'), new lang_string('searchindextime_desc', 'admin'),
             600));
+    $temp->add(new admin_setting_heading('searchcoursesheading', new lang_string('searchablecourses', 'admin'), ''));
     $options = [
         0 => new lang_string('searchallavailablecourses_off', 'admin'),
         1 => new lang_string('searchallavailablecourses_on', 'admin')
     ];
     $temp->add(new admin_setting_configselect('searchallavailablecourses',
             new lang_string('searchallavailablecourses', 'admin'),
-            new lang_string('searchallavailablecourses_desc', 'admin'),
+            new lang_string('searchallavailablecoursesdesc', 'admin'),
             0, $options));
+    $temp->add(new admin_setting_configcheckbox('searchincludeallcourses',
+        new lang_string('searchincludeallcourses', 'admin'), new lang_string('searchincludeallcourses_desc', 'admin'),
+        0));
 
     // Search display options.
     $temp->add(new admin_setting_heading('searchdisplay', new lang_string('searchdisplay', 'admin'), ''));
index 96c7871..8c47a56 100644 (file)
@@ -176,6 +176,8 @@ $ADMIN->add('server', $temp);
 
 $ADMIN->add('server', new admin_externalpage('environment', new lang_string('environment','admin'), "$CFG->wwwroot/$CFG->admin/environment.php"));
 $ADMIN->add('server', new admin_externalpage('phpinfo', new lang_string('phpinfo'), "$CFG->wwwroot/$CFG->admin/phpinfo.php"));
+$ADMIN->add('server', new admin_externalpage('testoutgoingmailconf', new lang_string('testoutgoingmailconf', 'admin'),
+            new moodle_url("$CFG->wwwroot/$CFG->admin/testoutgoingmailconf.php"), 'moodle/site:config', true));
 
 
 // "performance" settingpage
@@ -326,6 +328,10 @@ $temp->add(new admin_setting_configtextarea('allowedemaildomains',
         new lang_string('allowedemaildomains', 'admin'),
         new lang_string('configallowedemaildomains', 'admin'),
         ''));
+$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'),
+        new lang_string('testoutgoingmaildetail', 'admin', $link)));
 $temp->add(new admin_setting_heading('emaildoesnotfit', new lang_string('doesnotfit', 'admin'),
         new lang_string('doesnotfitdetail', 'admin')));
 $charsets = get_list_of_charsets();
index b559de3..de5f75b 100644 (file)
@@ -13,45 +13,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('enablewebservices', new lang_string('enablewebservices', 'admin'), new lang_string('configenablewebservices', 'admin'), 0));
 
-    $optionalsubsystems->add(new admin_setting_configcheckbox('messaging', new lang_string('messaging', 'admin'), new lang_string('configmessaging','admin'), 1));
-
-    $optionalsubsystems->add(new admin_setting_configcheckbox('messagingallusers',
-        new lang_string('messagingallusers', 'admin'),
-        new lang_string('configmessagingallusers', 'admin'),
-        0)
-    );
-
-    $optionalsubsystems->add(new admin_setting_configcheckbox('messagingdefaultpressenter',
-        new lang_string('messagingdefaultpressenter', 'admin'),
-        new lang_string('configmessagingdefaultpressenter', 'admin'),
-        1)
-    );
-
-    $options = array(
-        DAYSECS => new lang_string('secondstotime86400'),
-        WEEKSECS => new lang_string('secondstotime604800'),
-        2620800 => new lang_string('nummonths', 'moodle', 1),
-        7862400 => new lang_string('nummonths', 'moodle', 3),
-        15724800 => new lang_string('nummonths', 'moodle', 6),
-        0 => new lang_string('never')
-    );
-    $optionalsubsystems->add(new admin_setting_configselect(
-        'messagingdeletereadnotificationsdelay',
-        new lang_string('messagingdeletereadnotificationsdelay', 'admin'),
-        new lang_string('configmessagingdeletereadnotificationsdelay', 'admin'),
-        604800,
-        $options)
-    );
-    $optionalsubsystems->add(new admin_setting_configselect(
-        'messagingdeleteallnotificationsdelay',
-        new lang_string('messagingdeleteallnotificationsdelay', 'admin'),
-        new lang_string('configmessagingdeleteallnotificationsdelay', 'admin'),
-        2620800,
-        $options)
-    );
-
-    $optionalsubsystems->add(new admin_setting_configcheckbox('messagingallowemailoverride', new lang_string('messagingallowemailoverride', 'admin'), new lang_string('configmessagingallowemailoverride','admin'), 0));
-
     $optionalsubsystems->add(new admin_setting_configcheckbox('enablestats', new lang_string('enablestats', 'admin'), new lang_string('configenablestats', 'admin'), 0));
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('enablerssfeeds', new lang_string('enablerssfeeds', 'admin'), new lang_string('configenablerssfeeds', 'admin'), 0));
index 102b758..32d91c6 100644 (file)
@@ -33,6 +33,7 @@ $ADMIN->add('root', new admin_category('competencies', new lang_string('competen
 $ADMIN->add('root', new admin_category('badges', new lang_string('badges'), empty($CFG->enablebadges)));
 $ADMIN->add('root', new admin_category('location', new lang_string('location','admin')));
 $ADMIN->add('root', new admin_category('language', new lang_string('language')));
+$ADMIN->add('root', new admin_category('messaging', new lang_string('messagingcategory', 'admin')));
 $ADMIN->add('root', new admin_category('modules', new lang_string('plugins', 'admin')));
 $ADMIN->add('root', new admin_category('security', new lang_string('security','admin')));
 $ADMIN->add('root', new admin_category('appearance', new lang_string('appearance','admin')));
diff --git a/admin/testoutgoingmailconf.php b/admin/testoutgoingmailconf.php
new file mode 100644 (file)
index 0000000..ce5857f
--- /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/>.
+
+/**
+ * Test output mail configuration page
+ *
+ * @copyright 2019 Victor Deniz <victor@moodle.com>, based on Michael Milette <michael.milette@tngconsulting.ca> code
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+
+// This is an admin page.
+admin_externalpage_setup('testoutgoingmailconf');
+
+$headingtitle = get_string('testoutgoingmailconf', 'admin');
+$homeurl = new moodle_url('/admin/category.php', array('category' => 'email'));
+$returnurl = new moodle_url('/admin/testoutgoingconf.php');
+
+$form = new core_admin\form\testoutgoingmailconf_form(null, ['returnurl' => $returnurl]);
+if ($form->is_cancelled()) {
+    redirect($homeurl);
+}
+
+// Display the page.
+echo $OUTPUT->header();
+echo $OUTPUT->heading($headingtitle);
+
+$data = $form->get_data();
+if ($data) {
+    $emailuser = new stdClass();
+    $emailuser->email = $data->recipient;
+    $emailuser->id = -99;
+
+    $subject = get_string('testoutgoingmailconf_subject', 'admin', $SITE->fullname);
+    $messagetext = get_string('testoutgoingmailconf_message', 'admin');
+
+    // Manage Moodle debugging options.
+    $debuglevel = $CFG->debug;
+    $debugdisplay = $CFG->debugdisplay;
+    $debugsmtp = $CFG->debugsmtp;
+    $CFG->debugdisplay = true;
+    $CFG->debugsmtp = true;
+    $CFG->debug = 15;
+
+    // Send test email.
+    ob_start();
+    $success = email_to_user($emailuser, $USER, $subject, $messagetext);
+    $smtplog = ob_get_contents();
+    ob_end_clean();
+
+    // Restore Moodle debugging options.
+    $CFG->debug = $debuglevel;
+    $CFG->debugdisplay = $debugdisplay;
+    $CFG->debugsmtp = $debugsmtp;
+
+    if ($success) {
+        $msgparams = new stdClass();
+        $msgparams->fromemail = $USER->email;
+        $msgparams->toemail = $emailuser->email;
+        $msg = get_string('testoutgoingmailconf_sentmail', 'admin', $msgparams);
+        $notificationtype = 'notifysuccess';
+    } else {
+        $notificationtype = 'notifyproblem';
+        // No communication between Moodle and the SMTP server - no error output.
+        if (trim($smtplog) == false) {
+            $msg = get_string('testoutgoingmailconf_errorcommunications', 'admin');
+        } else {
+            $msg = $smtplog;
+        }
+    }
+
+    // Show result.
+    echo $OUTPUT->notification($msg, $notificationtype);
+}
+
+$form->display();
+echo $OUTPUT->footer();
index b2fd070..87a00b6 100644 (file)
@@ -58,12 +58,8 @@ class behat_admin extends behat_base {
             $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
 
             // Search by label.
-            $searchbox = $this->find_field(get_string('query', 'admin'));
-            $searchbox->setValue($label);
-            $submitsearch = $this->find('css', 'form input[type=submit][name=search]');
-            $submitsearch->press();
-
-            $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+            $this->execute('behat_forms::i_set_the_field_to', [get_string('query', 'admin'), $label]);
+            $this->execute("behat_forms::press_button", get_string('search', 'admin'));
 
             // Admin settings does not use the same DOM structure than other moodle forms
             // but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
index c66254a..180805d 100644 (file)
Binary files a/admin/tool/analytics/amd/build/model.min.js and b/admin/tool/analytics/amd/build/model.min.js differ
index 2f0b605..f39c577 100644 (file)
@@ -20,8 +20,8 @@
  * @copyright  2017 David Monllao
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_factory', 'core/modal_events'],
-    function($, Str, log, Notification, ModalFactory, ModalEvents) {
+define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_factory', 'core/modal_events', 'core/templates'],
+    function($, Str, log, Notification, ModalFactory, ModalEvents, Templates) {
 
     /**
      * List of actions that require confirmation and confirmation message.
@@ -94,10 +94,65 @@ define(['jquery', 'core/str', 'core/log', 'core/notification', 'core/modal_facto
                     modal.getRoot().on(ModalEvents.save, function() {
                         window.location.href = a.attr('href');
                     });
+                    modal.show();
+                    return modal;
+                }).fail(Notification.exception);
+            });
+        },
+
+        /**
+         * Displays a select-evaluation-mode choice.
+         *
+         * @param  {String}  actionId
+         * @param  {Boolean} trainedOnlyExternally
+         */
+        selectEvaluationMode: function(actionId, trainedOnlyExternally) {
+            $('[data-action-id="' + actionId + '"]').on('click', function(ev) {
+                ev.preventDefault();
+
+                var a = $(ev.currentTarget);
+
+                if (!trainedOnlyExternally) {
+                    // We can not evaluate trained models if the model was trained using data from this site.
+                    // Default to evaluate the model configuration if that is the case.
+                    window.location.href = a.attr('href');
+                    return;
+                }
+
+                var stringsPromise = Str.get_strings([
+                    {
+                        key: 'evaluatemodel',
+                        component: 'tool_analytics'
+                    }, {
+                        key: 'evaluationmode',
+                        component: 'tool_analytics'
+                    }
+                ]);
+                var modalPromise = ModalFactory.create({type: ModalFactory.types.SAVE_CANCEL});
+                var bodyPromise = Templates.render('tool_analytics/evaluation_mode_selection', {});
+
+                $.when(stringsPromise, modalPromise).then(function(strings, modal) {
+
+
+                    modal.getRoot().on(ModalEvents.hidden, modal.destroy.bind(modal));
+
+                    modal.setTitle(strings[1]);
+                    modal.setSaveButtonText(strings[0]);
+                    modal.setBody(bodyPromise);
+
+                    modal.getRoot().on(ModalEvents.save, function() {
+                        var evaluationMode = $("input[name='evaluationmode']:checked").val();
+                        if (evaluationMode == 'trainedmodel') {
+                            a.attr('href', a.attr('href') + '&mode=trainedmodel');
+                        }
+                        window.location.href = a.attr('href');
+                        return;
+                    });
+
                     modal.show();
                     return modal;
                 }).fail(Notification.exception);
             });
         }
     };
-});
+});
\ No newline at end of file
index df59038..2e3a552 100644 (file)
@@ -41,6 +41,11 @@ class model_logs extends \table_sql {
      */
     protected $model = null;
 
+    /**
+     * @var string|false
+     */
+    protected $evaluationmode = false;
+
     /**
      * Sets up the table_log parameters.
      *
@@ -57,21 +62,32 @@ class model_logs extends \table_sql {
         $this->set_attribute('class', 'modellog generaltable generalbox');
         $this->set_attribute('aria-live', 'polite');
 
-        $this->define_columns(array('time', 'version', 'indicators', 'timesplitting', 'accuracy', 'info', 'usermodified'));
+        $this->define_columns(array('time', 'version', 'evaluationmode', 'indicators', 'timesplitting',
+            'accuracy', 'info', 'usermodified'));
         $this->define_headers(array(
             get_string('time'),
             get_string('version'),
+            get_string('evaluationmode', 'tool_analytics'),
             get_string('indicators', 'tool_analytics'),
             get_string('timesplittingmethod', 'analytics'),
             get_string('accuracy', 'tool_analytics'),
             get_string('info', 'tool_analytics'),
             get_string('fullnameuser'),
         ));
+
+        $evaluationmodehelp = new \help_icon('evaluationmode', 'tool_analytics');
+        $this->define_help_for_headers([null, null, $evaluationmodehelp, null, null, null, null, null]);
+
         $this->pageable(true);
         $this->collapsible(false);
         $this->sortable(false);
         $this->is_downloadable(false);
 
+        $this->evaluationmode = optional_param('evaluationmode', false, PARAM_ALPHANUM);
+        if ($this->evaluationmode && $this->evaluationmode != 'configuration' && $this->evaluationmode != 'trainedmodel') {
+            $this->evaluationmode = '';
+        }
+
         $this->define_baseurl($PAGE->url);
     }
 
@@ -86,6 +102,15 @@ class model_logs extends \table_sql {
         return userdate($log->version, $recenttimestr);
     }
 
+    /**
+     * Generate the evaluation mode column.
+     *
+     * @param \stdClass $log log data.
+     * @return string HTML for the evaluationmode column
+     */
+    public function col_evaluationmode($log) {
+        return get_string('evaluationmodecol' . $log->evaluationmode, 'tool_analytics');
+    }
     /**
      * Generate the time column.
      *
index b3a1c43..351dae4 100644 (file)
@@ -187,15 +187,21 @@ class models_list implements \renderable, \templatable {
 
             // Evaluate machine-learning-based models.
             if (!$onlycli && $model->get_indicators() && !$model->is_static()) {
+
+                // Extra is_trained call as trained_locally returns false if the model has not been trained yet.
+                $trainedonlyexternally = !$model->trained_locally() && $model->is_trained();
+
+                $actionid = 'evaluate-' . $model->get_id();
+                $PAGE->requires->js_call_amd('tool_analytics/model', 'selectEvaluationMode', [$actionid, $trainedonlyexternally]);
                 $urlparams['action'] = 'evaluate';
                 $url = new \moodle_url('model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url, new \pix_icon('i/calc', get_string('evaluate', 'tool_analytics')),
-                    get_string('evaluate', 'tool_analytics'));
+                    get_string('evaluate', 'tool_analytics'), ['data-action-id' => $actionid]);
                 $actionsmenu->add($icon);
             }
 
             // Machine-learning-based models evaluation log.
-            if (!$model->is_static()) {
+            if (!$model->is_static() && $model->get_logs()) {
                 $urlparams['action'] = 'log';
                 $url = new \moodle_url('model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url, new \pix_icon('i/report', get_string('viewlog', 'tool_analytics')),
index 25d182f..2836ca7 100644 (file)
@@ -35,6 +35,8 @@ Options:
 --non-interactive      Not interactive questions
 --timesplitting        Restrict the evaluation to 1 single time splitting method (Optional)
 --filter               Analyser dependant. e.g. A courseid would evaluate the model using a single course (Optional)
+--mode                 'configuration' or 'trainedmodel'. You can only use mode=trainedmodel when the trained" .
+    " model was imported" . "
 --reuse-prev-analysed  Reuse recently analysed courses instead of analysing the whole site. Set it to false while" .
     " coding indicators. Defaults to true (Optional)" . "
 -h, --help             Print out this help
@@ -50,6 +52,7 @@ list($options, $unrecognized) = cli_get_params(
         'modelid'               => false,
         'list'                  => false,
         'timesplitting'         => false,
+        'mode'                  => 'configuration',
         'reuse-prev-analysed'   => true,
         'non-interactive'       => false,
         'filter'                => false
@@ -64,16 +67,30 @@ if ($options['help']) {
     exit(0);
 }
 
-if ($options['list'] || $options['modelid'] === false) {
+if ($options['list']) {
     \tool_analytics\clihelper::list_models();
     exit(0);
 }
 
+if ($options['modelid'] === false) {
+    // All actions but --list require a modelid.
+    echo $help;
+    exit(0);
+}
+
 // Reformat them as an array.
 if ($options['filter'] !== false) {
     $options['filter'] = explode(',', $options['filter']);
 }
 
+if ($options['mode'] !== 'configuration' && $options['mode'] !== 'trainedmodel') {
+    cli_error('Error: The provided mode is not supported');
+}
+
+if ($options['mode'] == 'trainedmodel' && $options['timesplitting']) {
+    cli_error('Sorry, no time splitting method can be specified when using \'trainedmodel\' mode.');
+}
+
 // We need admin permissions.
 \core\session\manager::set_user(get_admin());
 
@@ -89,6 +106,7 @@ $analyseroptions = array(
     'filter' => $options['filter'],
     'timesplitting' => $options['timesplitting'],
     'reuseprevanalysed' => $options['reuse-prev-analysed'],
+    'mode' => $options['mode'],
 );
 // Evaluate its suitability to predict accurately.
 $results = $model->evaluate($analyseroptions);
index 580d7b8..354dc96 100644 (file)
@@ -53,11 +53,23 @@ $string['erroronlycli'] = 'Execution only allowed via command line';
 $string['errortrainingdataexport'] = 'The model training data could not be exported';
 $string['evaluate'] = 'Evaluate';
 $string['evaluatemodel'] = 'Evaluate model';
+$string['evaluationmode'] = 'Evaluation mode';
+$string['evaluationmode_help'] = 'There are two evaluation modes:
+
+* Trained model -  Site data is used as testing data to evaluate the accuracy of the trained model.
+* Configuration - Site data is split into training and testing data, to both train and test the accuracy of the model configuration.
+
+Trained model is only available if a trained model has been imported into the site, and has not yet been re-trained using site data.';
+$string['evaluationmodeinfo'] = 'This model has been imported into the site. You can either evaluate the performance of the model, or you can evaluate the performance of the model configuration using site data.';
+$string['evaluationmodetrainedmodel'] = 'Evaluate the trained model';
+$string['evaluationmodecoltrainedmodel'] = 'Trained model';
+$string['evaluationmodecolconfiguration'] = 'Configuration';
+$string['evaluationmodeconfiguration'] = 'Evaluate the model configuration';
 $string['evaluationinbatches'] = 'The site contents are calculated and stored in batches. The evaluation process may be stopped at any time. The next time it is run, it will continue from the point when it was stopped.';
 $string['exportmodel'] = 'Export configuration';
 $string['exporttrainingdata'] = 'Export training data';
-$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) course duration splitting';
-$string['getpredictionsresults'] = 'Results using {$a->name} course duration splitting';
+$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) time-splitting method';
+$string['getpredictionsresults'] = 'Results using {$a->name} time-splitting method';
 $string['extrainfo'] = 'Info';
 $string['generalerror'] = 'Evaluation error. Status code {$a}';
 $string['getpredictions'] = 'Get predictions';
@@ -70,7 +82,7 @@ $string['ignoreversionmismatchescheckbox'] = 'Ignore the differences between thi
 $string['importedsuccessfully'] = 'The model has been successfully imported.';
 $string['insights'] = 'Insights';
 $string['invalidanalysables'] = 'Invalid site elements';
-$string['invalidanalysablesinfo'] = 'This pages lists this site analysable elements that can not be used by this prediction model. The listed elements can not be used neither to train the prediction model nor the prediction model can get predictions for them.';
+$string['invalidanalysablesinfo'] = 'This page lists analysable elements that can\'t be used by this prediction model. The listed elements can\'t be used either to train the prediction model nor can the prediction model obtain predictions for them.';
 $string['invalidanalysablestable'] = 'Invalid site analysable elements table';
 $string['invalidindicatorsremoved'] = 'A new model has been added. Indicators that do not work with the selected target have been automatically removed.';
 $string['invalidprediction'] = 'Invalid to get predictions';
@@ -104,7 +116,7 @@ $string['trainingprocessfinished'] = 'Training process finished';
 $string['trainingresults'] = 'Training results';
 $string['trainmodels'] = 'Train models';
 $string['versionnotsame'] = 'Imported file was from a different moodle version ({$a->importedversion}) than the current one ({$a->version})';
-$string['viewlog'] = 'Log';
+$string['viewlog'] = 'Evaluation log';
 $string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
 $string['weeksenddatedefault'] = 'End date automatically calculated from the course start date.';
 $string['privacy:metadata'] = 'The Analytic models plugin does not store any personal data.';
index 23d8454..a58b5d7 100644 (file)
@@ -119,7 +119,7 @@ switch ($action) {
             'id' => $model->get_id(),
             'trainedmodel' => $model->is_trained(),
             'indicators' => $model->get_potential_indicators(),
-            'timesplittings' => \core_analytics\manager::get_enabled_time_splitting_methods(),
+            'timesplittings' => \core_analytics\manager::get_all_time_splittings(),
             'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors()
         );
         $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
@@ -169,7 +169,13 @@ switch ($action) {
         // Web interface is used by people who can not use CLI nor code stuff, always use
         // cached stuff as they will change the model through the web interface as well
         // which invalidates the previously analysed stuff.
-        $results = $model->evaluate(array('reuseprevanalysed' => true));
+        $options = ['reuseprevanalysed' => true];
+
+        $mode = optional_param('mode', false, PARAM_ALPHANUM);
+        if ($mode == 'trainedmodel') {
+            $options['mode'] = 'trainedmodel';
+        }
+        $results = $model->evaluate($options);
         $renderer = $PAGE->get_renderer('tool_analytics');
         echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
         break;
diff --git a/admin/tool/analytics/templates/evaluation_mode_selection.mustache b/admin/tool/analytics/templates/evaluation_mode_selection.mustache
new file mode 100644 (file)
index 0000000..e9b32ce
--- /dev/null
@@ -0,0 +1,42 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template tool_analytics/evaluation_mode_selector
+
+    Evaluation mode selector.
+
+    The purpose of this template is to render the evaluation mode radio button.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Example context (json):
+    {
+    }
+}}
+<div class="box mb-4">{{#str}} evaluationmodeinfo, tool_analytics {{/str}}</div>
+<div class="form-check">
+    <input class="form-check-input" type="radio" name="evaluationmode" id="id-mode-trainedmodel" value="trainedmodel" checked>
+    <label class="form-check-label" for="id-mode-trainedmodel">{{#str}} evaluationmodetrainedmodel, tool_analytics {{/str}}</label>
+</div>
+<div class="form-check">
+    <input class="form-check-input" type="radio" name="evaluationmode" id="id-mode-configuration" value="configuration">
+    <label class="form-check-label" for="id-mode-configuration">{{#str}} evaluationmodeconfiguration, tool_analytics {{/str}}</label>
+</div>
\ No newline at end of file
diff --git a/admin/tool/customlang/classes/output/renderer.php b/admin/tool/customlang/classes/output/renderer.php
new file mode 100644 (file)
index 0000000..b183daf
--- /dev/null
@@ -0,0 +1,64 @@
+<?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/>.
+
+/**
+ * Renderer class for tool customlang
+ *
+ * @package     tool_customlang
+ * @category    output
+ * @copyright   2019 Bas Brands <bas@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * Renderer for the customlang tool.
+ *
+ * @copyright 2019 Bas Brands <bas@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends \plugin_renderer_base {
+
+    /**
+     * Defer to template.
+     *
+     * @param tool_customlang_translator $translator
+     * @return string Html for the translator
+     */
+    protected function render_tool_customlang_translator(\tool_customlang_translator $translator) {
+        $renderabletranslator = new translator($translator);
+        $templatevars = $renderabletranslator->export_for_template($this);
+        return $this->render_from_template('tool_customlang/translator', $templatevars);
+    }
+
+    /**
+     * Defer to template.
+     *
+     * @param tool_customlang_menu $menu
+     * @return string html the customlang menu buttons
+     */
+    protected function render_tool_customlang_menu(\tool_customlang_menu $menu) {
+        $output = '';
+        foreach ($menu->get_items() as $item) {
+            $output .= $this->single_button($item->url, $item->title, $item->method);
+        }
+        return $this->box($output, 'menu');
+    }
+}
diff --git a/admin/tool/customlang/classes/output/translator.php b/admin/tool/customlang/classes/output/translator.php
new file mode 100644 (file)
index 0000000..9b7ac7d
--- /dev/null
@@ -0,0 +1,93 @@
+<?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/>.
+
+/**
+ * customlang specific renderers.
+ *
+ * @package   tool_customlang
+ * @copyright 2019 Moodle
+ * @author    Bas Brands
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_customlang\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use renderable;
+use templatable;
+use renderer_base;
+use stdClass;
+
+/**
+ * Class containing data for customlang translator page
+ *
+ * @copyright  2019 Bas Brands
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class translator implements renderable, templatable {
+
+    /**
+     * @var tool_customlang_translator $translator object.
+     */
+    private $translator;
+
+    /**
+     * Construct this renderable.
+     *
+     * @param tool_customlang_translator $translator The translator object.
+     */
+    public function __construct(\tool_customlang_translator $translator) {
+        $this->translator = $translator;
+    }
+
+    /**
+     * Export the data.
+     *
+     * @param renderer_base $output
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+        $data = new stdClass();
+
+        $data->nostrings = $output->notification(get_string('nostringsfound', 'tool_customlang'));
+        $data->formurl = $this->translator->handler;
+        $data->currentpage = $this->translator->currentpage;
+        $data->sesskey = sesskey();
+        $data->strings = [];
+
+        if (!empty($this->translator->strings)) {
+            $data->hasstrings = true;
+            foreach ($this->translator->strings as $string) {
+                // Find strings that use placeholders.
+                if (preg_match('/\{\$a(->.+)?\}/', $string->master)) {
+                    $string->placeholderhelp = $output->help_icon('placeholder', 'tool_customlang',
+                            get_string('placeholderwarning', 'tool_customlang'));
+                }
+                if (!is_null($string->local) and $string->outdated) {
+                    $string->outdatedhelp = $output->help_icon('markinguptodate', 'tool_customlang');
+                    $string->checkupdated = true;
+                }
+                if ($string->original !== $string->master) {
+                    $string->showoriginalvsmaster = true;
+                }
+                $string->local = s($string->local);
+                $data->strings[] = $string;
+            }
+        }
+        return $data;
+    }
+}
\ No newline at end of file
diff --git a/admin/tool/customlang/renderer.php b/admin/tool/customlang/renderer.php
deleted file mode 100644 (file)
index aea7fd6..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?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/>.
-
-/**
- * Output rendering of Language customization admin tool
- *
- * @package    tool
- * @subpackage customlang
- * @copyright  2010 David Mudrak <david@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Rendering methods for the tool widgets
- */
-class tool_customlang_renderer extends plugin_renderer_base {
-
-    /**
-     * Renders customlang tool menu
-     *
-     * @return string HTML
-     */
-    protected function render_tool_customlang_menu(tool_customlang_menu $menu) {
-        $output = '';
-        foreach ($menu->get_items() as $item) {
-            $output .= $this->single_button($item->url, $item->title, $item->method);
-        }
-        return $this->box($output, 'menu');
-    }
-
-    /**
-     * Renders customlang translation table
-     *
-     * @param tool_customlang_translator $translator
-     * @return string HTML
-     */
-    protected function render_tool_customlang_translator(tool_customlang_translator $translator) {
-        $output = '';
-
-        if (empty($translator->strings)) {
-            return $this->notification(get_string('nostringsfound', 'tool_customlang'));
-        }
-
-        $table = new html_table();
-        $table->id = 'translator';
-        $table->head = array(
-            get_string('headingcomponent', 'tool_customlang'),
-            get_string('headingstringid', 'tool_customlang'),
-            get_string('headingstandard', 'tool_customlang'),
-            get_string('headinglocal', 'tool_customlang'),
-        );
-
-        foreach ($translator->strings as $string) {
-            $cells = array();
-            // component name
-            $cells[0] = new html_table_cell($string->component);
-            $cells[0]->attributes['class'] = 'component';
-            // string identification code
-            $cells[1] = new html_table_cell(html_writer::tag('div', s($string->stringid), array('class' => 'stringid')));
-            $cells[1]->attributes['class'] = 'stringid';
-            // master translation of the string
-            $master = html_writer::tag('div', s($string->master), array('class' => 'preformatted'));
-            $minheight = strlen($string->master) / 200;
-            if (preg_match('/\{\$a(->.+)?\}/', $string->master)) {
-                $master .= html_writer::tag('div', $this->help_icon('placeholder', 'tool_customlang',
-                        get_string('placeholderwarning', 'tool_customlang')), array('class' => 'placeholderinfo'));
-            }
-            $cells[2] = new html_table_cell($master);
-            $cells[2]->attributes['class'] = 'standard master';
-            // local customization of the string
-            $textareaattributes = array('name'=>'cust['.$string->id.']', 'cols'=>40, 'rows'=>3);
-            if ($minheight>1) {
-               $textareaattributes['style'] = 'min-height:' . (int) 4*$minheight . 'em;';
-            }
-            $textarea = html_writer::tag('textarea', s($string->local), $textareaattributes);
-            $cells[3] = new html_table_cell($textarea);
-            if (!is_null($string->local) and $string->outdated) {
-                $mark  = html_writer::empty_tag('input', array('type' => 'checkbox', 'id' => 'update_' . $string->id,
-                                                               'name' => 'updates[]', 'value' => $string->id));
-                $help  = $this->help_icon('markinguptodate', 'tool_customlang');
-                $mark .= html_writer::tag('label', get_string('markuptodate', 'tool_customlang') . $help,
-                                          array('for' => 'update_' . $string->id));
-                $mark  = html_writer::tag('div', $mark, array('class' => 'uptodatewrapper'));
-            } else {
-                $mark  = '';
-            }
-            $cells[3] = new html_table_cell($textarea."\n".$mark);
-            $cells[3]->attributes['class'] = 'local';
-            $cells[3]->id = 'id_'.$string->id;
-            if (!is_null($string->local)) {
-                $cells[3]->attributes['class'] .= ' customized';
-            }
-            if ($string->outdated) {
-                $cells[3]->attributes['class'] .= ' outdated';
-            }
-            if ($string->modified) {
-                $cells[3]->attributes['class'] .= ' modified';
-            }
-
-            if ($string->original !== $string->master) {
-                $cells[0]->rowspan = $cells[1]->rowspan = $cells[3]->rowspan = 2;
-            }
-
-            $row = new html_table_row($cells);
-            $table->data[] = $row;
-
-            if ($string->original !== $string->master) {
-                $cells = array();
-                // original of the string
-                $cells[2] = new html_table_cell(html_writer::tag('div', s($string->original), array('class' => 'preformatted')));
-                $cells[2]->attributes['class'] = 'standard original';
-                $row = new html_table_row($cells);
-                $table->data[] = $row;
-            }
-        }
-
-        $output .= html_writer::start_tag('form', array('method'=>'post', 'action'=>$translator->handler->out()));
-        $output .= html_writer::start_tag('div');
-        $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'translatorsubmitted', 'value'=>1));
-        $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
-        $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'p', 'value'=>$translator->currentpage));
-        $save1   = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecontinue',
-            'value' => get_string('savecontinue', 'tool_customlang'), 'class' => 'btn btn-secondary'));
-        $save2   = html_writer::empty_tag('input', array('type' => 'submit', 'name' => 'savecheckin',
-            'value' => get_string('savecheckin', 'tool_customlang'), 'class' => 'btn btn-secondary'));
-        $output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
-        $output .= html_writer::table($table);
-        $output .= html_writer::tag('fieldset', $save1 . ' ' . $save2, array('class' => 'buttonsbar'));
-        $output .= html_writer::end_tag('div');
-        $output .= html_writer::end_tag('form');
-
-        return $output;
-    }
-}
diff --git a/admin/tool/customlang/styles.css b/admin/tool/customlang/styles.css
deleted file mode 100644 (file)
index 9f9fa98..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-.path-admin-tool-customlang .langselectorbox,
-.path-admin-tool-customlang fieldset.buttonsbar,
-.path-admin-tool-customlang .menu {
-    margin: 5px auto;
-    text-align: center;
-}
-
-.path-admin-tool-customlang .menu .singlebutton,
-.path-admin-tool-customlang .menu .singlebutton form,
-.path-admin-tool-customlang .menu .singlebutton form div {
-    display: inline;
-}
-
-.path-admin-tool-customlang .mform.filterform {
-    width: 70%;
-    margin-left: auto;
-    margin-right: auto;
-}
-
-.path-admin-tool-customlang .mform.filterform .fitem .fitemtitle {
-    width: 30%;
-}
-
-.path-admin-tool-customlang .mform.filterform .fitem .felement {
-    width: 60%;
-    margin-left: 31%;
-}
-
-.path-admin-tool-customlang #translator {
-    width: 100%;
-}
-
-.path-admin-tool-customlang #translator .standard,
-.path-admin-tool-customlang #translator .local {
-    min-width: 35%;
-}
-
-.path-admin-tool-customlang #translator .customized {
-    background-color: #e7f1c3;
-}
-
-.path-admin-tool-customlang #translator .customized.outdated {
-    background-color: #f3f2aa;
-}
-
-.path-admin-tool-customlang #translator .modified {
-    background-color: #ffd3d9;
-}
-
-.path-admin-tool-customlang #translator .customized.modified {
-    background-color: #d2ebff;
-}
-
-.path-admin-tool-customlang #translator textarea {
-    width: 100%;
-    min-height: 4em;
-}
-
-.path-admin-tool-customlang #translator .placeholderinfo {
-    text-align: center;
-    border: 1px dotted #ddd;
-    background-color: #f6f6f6;
-    margin-top: 0.5em;
-}
-
-#page-admin-tool-customlang-index .continuebutton {
-    margin-top: 1em;
-}
-
-.path-admin-tool-customlang #translator .standard.master.cell.c2 {
-    word-break: break-all;
-}
diff --git a/admin/tool/customlang/templates/translator.mustache b/admin/tool/customlang/templates/translator.mustache
new file mode 100644 (file)
index 0000000..8864f83
--- /dev/null
@@ -0,0 +1,150 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template tool_customlang/translator
+
+    Template for the custom language translator page.
+
+    Classes required for JS:
+    -
+
+    Data attributes required for JS:
+    -
+
+    Context variables required for this template:
+    * strings
+
+    Example context (json):
+    {
+        "hasstrings": true,
+        "formurl": "admin/tool/customlang/edit.php?lng=en",
+        "currentpage": 0,
+        "sesskey" : "AZyeeQgmcs",
+        "strings": [
+            {
+                "id": 11,
+                "component": "core",
+                "componentid": 1,
+                "stringid": "course",
+                "original": "Course",
+                "master": "Cursus",
+                "local": "Hoofdstuk",
+                "outdated": 0,
+                "modified": 1
+            }
+        ]
+    }
+}}
+
+{{^hasstrings}}
+    {{{ nostrings }}}
+{{/hasstrings}}
+{{#hasstrings}}
+<form method="post" action="{{{formurl}}}">
+    <input type="hidden" name="translatorsubmitted" value="1">
+    <input type="hidden" name="sesskey" value="{{{ sesskey }}}">
+    <input type="hidden" name="p" value="{{ currentpage }}">
+
+    <fieldset class="m-a-1 m-3">
+        <button type="submit" name="savecontinue" class="btn btn-secondary">
+            {{#str}}savecontinue, tool_customlang{{/str}}
+        </button>
+        <button type="submit" name="savecheckin" class="btn btn-secondary">
+            {{#str}}savecheckin, tool_customlang{{/str}}
+        </button>
+    </fieldset>
+
+    <div class="list-group">
+        <div class="container-fluid d-none d-md-block list-group-item border-bottom-0">
+            <div class="row-fluid">
+                <div class="col-sm-4 col-md-2 span2">
+                    <strong>{{#str}}headingcomponent, tool_customlang{{/str}}</strong>
+                </div>
+                <div class="col-sm-4 col-md-2 span2">
+                    <strong>{{#str}}headingstringid, tool_customlang{{/str}}</strong>
+                </div>
+                <div class="col-sm-4 col-md-2 span2">
+                    <strong>{{#str}}headingstandard, tool_customlang{{/str}}</strong>
+                </div>
+                <div class="col-sm-12 col-md-6 span6">
+                    <span class="p-l-1 pl-3">
+                        <strong>{{#str}}headinglocal, tool_customlang{{/str}}</strong>
+                    </span>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="list-group">
+    {{#strings}}
+        <div class="container-fluid list-group-item
+                {{#local}}list-group-item-info{{/local}}
+                {{#outdated}}list-group-item-warning{{/outdated}}
+                {{#modified}}list-group-item-info{{/modified}}"
+            >
+            <div class="row-fluid ">
+                <div class="col-sm-4 col-md-2 span2">
+                    <div class="d-md-none">
+                        <strong>{{#str}}headingcomponent, tool_customlang{{/str}}</strong>
+                    </div>
+                    {{{ component }}}
+                </div>
+                <div class="col-sm-4 col-md-2 span2 text-break">
+                    <div class="d-md-none">
+                        <strong>{{#str}}headingstringid, tool_customlang{{/str}}</strong>
+                    </div>
+                    {{{ stringid }}}
+                </div>
+                <div class="col-sm-4 col-md-2 span2">
+                    <div class="d-md-none">
+                        <strong>{{#str}}headingstandard, tool_customlang{{/str}}</strong>
+                    </div>
+                    {{{ master }}}
+                    <div class="info">
+                        {{{ placeholderhelp }}}
+                        {{{ outdatedhelp}}}
+                    </div>
+                    {{#showoriginalvsmaster}}
+                    <div class="alert-secondary mt-3 m-t-1">
+                        {{{ original }}}
+                    </div>
+                    {{/showoriginalvsmaster}}
+                </div>
+                <div class="col-sm-12 col-md-6 mt-sm-3 mt-md-0 span6">
+                    <div class="d-md-none">
+                        <strong>{{#str}}headinglocal, tool_customlang{{/str}}</strong>
+                    </div>
+                    <div class="py-2 py-md-0 px-md-3">
+                        <textarea class="form-control w-100 border-box" name="cust[{{id}}]" cols="40" rows="3">{{{ local }}}</textarea>
+
+                        {{#checkupdated}}
+                        <div class="uptodatewrapper">
+                            <div class="form-check">
+                                <input id="update_{{id}}" class="form-check-input" name="updates[]" type="checkbox" value="{{id}}">
+                                <label for="update_{{id}}" class="form-check-label">{{#str}}markuptodate, tool_customlang{{/str}}</label>
+                                {{{ outdatedhelp }}}
+                            </div>
+                        </div>
+                        {{/checkupdated}}
+                    </div>
+                </div>
+            </div>
+        </div>
+    {{/strings}}
+    </div>
+</form>
+{{/hasstrings}}
index 86b1799..bb3fa70 100644 (file)
@@ -277,7 +277,7 @@ $string['resubmitrequestasnew'] = 'Resubmit as new request';
 $string['resubmitrequest'] = 'Resubmit {$a->type} request for {$a->username}';
 $string['resubmittedrequest'] = 'The existing {$a->type} request for {$a->username} was cancelled and resubmitted';
 $string['resultdeleted'] = 'You recently requested to have your account and personal data in {$a} to be deleted. This process has been completed and you will no longer be able to log in.';
-$string['resultdownloadready'] = 'Your copy of your personal data in {$a} that you recently requested is now available for download. Please click on the link below to go to the download page.';
+$string['resultdownloadready'] = 'Your copy of your personal data from {$a} that you recently requested is now available for download from the following link.';
 $string['reviewdata'] = 'Review data';
 $string['retentionperiod'] = 'Retention period';
 $string['retentionperiod_help'] = 'The retention period specifies the length of time that data should be kept for. When the retention period has expired, the data is flagged and listed for deletion, awaiting admin confirmation.';
diff --git a/admin/tool/dataprivacy/tests/coverage.php b/admin/tool/dataprivacy/tests/coverage.php
new file mode 100644 (file)
index 0000000..9af4b87
--- /dev/null
@@ -0,0 +1,49 @@
+<?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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Coverage information for the tool_dataprivacy plugin.
+ *
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+return new class extends phpunit_coverage_info {
+    /** @var array The list of folders relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfolders = [
+        'classes',
+    ];
+
+    /** @var array The list of files relative to the plugin root to whitelist in coverage generation. */
+    protected $whitelistfiles = [];
+
+    /** @var array The list of folders relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfolders = [
+    ];
+
+    /** @var array The list of files relative to the plugin root to excludelist in coverage generation. */
+    protected $excludelistfiles = [];
+};
index 138a420..44c9521 100644 (file)
@@ -116,7 +116,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
         $this->userrole = create_role('User role', 'lpuserrole', 'learning plan user role description');
 
         assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
-        assign_capability('moodle/competency:competencycompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
+        assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
         assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
         assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
         assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id);
index c8f9816..5371c48 100644 (file)
@@ -274,7 +274,6 @@ class framework_importer {
             // We are calling from browser, display progress bar.
             if ($this->useprogressbar === true) {
                 $this->progress = new \core\progress\display_if_slow(get_string('processingfile', 'tool_lpimportcsv'));
-                $this->progress->start_html();
             } else {
                 // Avoid html output on CLI scripts.
                 $this->progress = new \core\progress\none();
@@ -464,7 +463,6 @@ class framework_importer {
         $framework = api::create_framework($record);
         if ($this->useprogressbar === true) {
             $this->progress = new \core\progress\display_if_slow(get_string('importingfile', 'tool_lpimportcsv'));
-            $this->progress->start_html();
         } else {
             $this->progress = new \core\progress\none();
         }
index a9c378d..1711f5e 100644 (file)
@@ -109,7 +109,7 @@ $string['sslv3'] = 'SSLv2 (Force SSL Version 3)';
 $string['taskcleanup'] = 'Cleanup of unverified incoming email';
 $string['taskpickup'] = 'Incoming email pickup';
 $string['tls'] = 'TLS (TLS; started via protocol-level negotiation over unencrypted channel; RECOMMENDED way of initiating secure connection)';
-$string['tlsv1'] = 'TLSv1 (TLS direct version 1.x connection to server)';
+$string['tlsv1'] = 'TLSv1 (direct connection to TLS server version 1.x)';
 $string['validateaddress'] = 'Validate sender email address';
 $string['validateaddress_help'] = 'When a message is received from a user, Moodle attempts to validate the message by comparing the email address of the sender with the email address in their user profile.
 
index 0ecfeff..5646510 100644 (file)
@@ -66,6 +66,12 @@ function tool_mobile_create_app_download_url() {
     }
 
     $downloadurl = new moodle_url($mobilesettings->setuplink);
+
+    // Do not update the URL if it is a custom one (we may break it completely).
+    if ($mobilesettings->setuplink != 'https://download.moodle.org/mobile') {
+        return $downloadurl;
+    }
+
     $downloadurl->param('version', $CFG->version);
     $downloadurl->param('lang', current_language());
 
index 01b18fd..ef9052e 100644 (file)
@@ -64,11 +64,11 @@ $string['managerules'] = 'Event monitoring rules';
 $string['messageprovider:notification'] = 'Notifications of rule subscriptions';
 $string['messagetemplate'] = 'Notification message';
 $string['messagetemplate_help'] = 'A notification message is sent to subscribers once the notification threshold has been reached. It can include any or all of the following placeholders:
-<br /><br />
-* Link to the location of the event {link}<br />
-* Link to the area monitored {modulelink}<br />
-* Rule name {rulename}<br />
-* Description {description}<br />
+
+* Link to the location of the event {link}
+* Link to the area monitored {modulelink}
+* Rule name {rulename}
+* Description {description}
 * Event {eventname}';
 $string['messagetemplate_link'] = 'admin/tool/monitor/managerules';
 $string['moduleinstance'] = 'Instance';
index 9a62976..8681715 100644 (file)
@@ -301,6 +301,8 @@ class page_agreedocs implements renderable, templatable {
                 redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
             }
         } else {
+            // Update the policyagreed for the user to avoid infinite loop because there are no policies to-be-accepted.
+            api::update_policyagreed($userid);
             $this->redirect_to_previous_url();
         }
     }
index b35a20e..85df22d 100644 (file)
@@ -82,7 +82,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     And I press "Next"
     And I set the field "I agree to the This site policy" to "1"
     And I press "Next"
-    And I should not see "Next"
+    And I should see "Course overview"
     And I navigate to "Users > Privacy and policies > Manage policies" in site administration
     And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
     And I click on "Accept This site policy" "link" in the "User One" "table_row"
@@ -292,3 +292,23 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
     When I press "Give consent"
     Then "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
     And "Accepted on user's behalf" "text" should exist in the "User Two" "table_row"
+
+  Scenario: View acceptances made by users on their own after inactivating a policy
+    Given I log in as "user1"
+    And I should see "This site policy"
+    And I should not see "Course overview"
+    And I press "Next"
+    And I set the field "I agree to the This site policy" to "1"
+    And I press "Next"
+    And I should see "Course overview"
+    And I log out
+    And I log in as "admin"
+    And I navigate to "Users > Privacy and policies > Manage policies" in site administration
+    And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
+    And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
+    And I press "Continue"
+    And I click on "Set status to \"Inactive\"" "link" in the "This privacy policy" "table_row"
+    And I press "Continue"
+    And I log out
+    When I log in as "user1"
+    Then I should see "Course overview"
index fb5d779..d58d1a7 100644 (file)
@@ -119,7 +119,7 @@ class course_bin extends base_bin {
             $cm->id,
             \backup::FORMAT_MOODLE,
             \backup::INTERACTIVE_NO,
-            \backup::MODE_GENERAL,
+            \backup::MODE_AUTOMATED,
             $user->id
         );
         $controller->execute_plan();
@@ -224,7 +224,7 @@ class course_bin extends base_bin {
             $tempdir,
             $this->_courseid,
             \backup::INTERACTIVE_NO,
-            \backup::MODE_GENERAL,
+            \backup::MODE_AUTOMATED,
             $user->id,
             \backup::TARGET_EXISTING_ADDING
         );
@@ -344,4 +344,4 @@ class course_bin extends base_bin {
         $context = \context_course::instance($this->_courseid);
         return has_capability('tool/recyclebin:deleteitems', $context);
     }
-}
+}
\ No newline at end of file
index 9a4e0c3..9d99326 100644 (file)
@@ -55,7 +55,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
 
         $this->course = $this->getDataGenerator()->create_course();
         $this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
-            'course' => $this->course->id
+            'course' => $this->course->id, 'grade' => 100.0, 'sumgrades' => 1
         ));
     }
 
@@ -173,4 +173,99 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
         $deletedbook = reset($items);
         $this->assertEquals($book->name, $deletedbook->name);
     }
+
+    /**
+     * Tests that user data is restored when module is restored.
+     */
+    public function test_coursemodule_restore_with_userdata() {
+        $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
+        $this->setUser($student);
+
+        set_config('backup_auto_users', true, 'backup');
+        $this->create_quiz_attempt($this->quiz, $student);
+
+        // Delete quiz.
+        $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
+        course_delete_module($cm->id);
+        phpunit_util::run_all_adhoc_tasks();
+        $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
+        $this->assertEquals(0, count($quizzes));
+
+        // Restore quiz.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->restore_item($item);
+        }
+        $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
+        $this->assertEquals(1, count($quizzes));
+        $cm = array_pop($quizzes);
+
+        // Check if user quiz attempt data is restored.
+        $attempts = quiz_get_user_attempts($cm->instance, $student->id);
+        $this->assertEquals(1, count($attempts));
+        $attempt = array_pop($attempts);
+        $attemptobj = quiz_attempt::create($attempt->id);
+        $this->assertEquals($student->id, $attemptobj->get_userid());
+        $this->assertEquals(true, $attemptobj->is_finished());
+    }
+
+    /**
+     * Tests that user data is not restored when module is restored.
+     */
+    public function test_coursemodule_restore_without_userdata() {
+        $student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
+        $this->setUser($student);
+
+        set_config('backup_auto_users', false, 'backup');
+        $this->create_quiz_attempt($this->quiz, $student);
+
+        // Delete quiz.
+        $cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
+        course_delete_module($cm->id);
+        phpunit_util::run_all_adhoc_tasks();
+        $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
+        $this->assertEquals(0, count($quizzes));
+
+        // Restore quiz.
+        $recyclebin = new \tool_recyclebin\course_bin($this->course->id);
+        foreach ($recyclebin->get_items() as $item) {
+            $recyclebin->restore_item($item);
+        }
+        $quizzes = get_coursemodules_in_course('quiz', $this->course->id);
+        $this->assertEquals(1, count($quizzes));
+        $cm = array_pop($quizzes);
+
+        // Check if user quiz attempt data is restored.
+        $attempts = quiz_get_user_attempts($cm->instance, $student->id);
+        $this->assertEquals(0, count($attempts));
+    }
+
+    /**
+     * Add a question to quiz and create a quiz attempt.
+     * @param \stdClass $quiz Quiz
+     * @param \stdClass $student User
+     * @throws coding_exception
+     * @throws moodle_exception
+     */
+    private function create_quiz_attempt($quiz, $student) {
+        // Add Question.
+        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+        $cat = $questiongenerator->create_question_category();
+        $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
+        quiz_add_quiz_question($numq->id, $quiz);
+
+        // Create quiz attempt.
+        $quizobj = quiz::create($quiz->id, $student->id);
+        $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
+        $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
+        $timenow = time();
+        $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
+        quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
+        quiz_attempt_save_started($quizobj, $quba, $attempt);
+        $attemptobj = quiz_attempt::create($attempt->id);
+        $tosubmit = array(1 => array('answer' => '0'));
+        $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
+        $attemptobj = quiz_attempt::create($attempt->id);
+        $attemptobj->process_finish($timenow, false);
+    }
 }
index 3f5b0b9..6117521 100644 (file)
@@ -23,7 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['cannotfit'] = 'The replacement is longer than original and shortening is not allow, cannot continue.';
+$string['cannotfit'] = 'The replacement is longer than the original and shortening is not allowed; cannot continue.';
 $string['disclaimer'] = 'I understand the risks of this operation';
 $string['doit'] = 'Yes, do it!';
 $string['excludedtables'] = 'Several tables are not updated as part of the text replacement. These include configuration, log, events, and session tables.';
index a28b478..1255622 100644 (file)
@@ -50,14 +50,39 @@ $string['scheduledtaskchangesdisabled'] = 'Modifications to the list of schedule
 $string['taskdisabled'] = 'Task disabled';
 $string['tasklogs'] = 'Task logs';
 $string['taskscheduleday'] = 'Day';
-$string['taskscheduleday_help'] = 'Day of month field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every day</li><li><strong>*/2</strong> Every 2nd day</li><li><strong>1</strong> The first of every month</li><li><strong>1,15</strong> The first and fifteenth of every month</li></ul>';
+$string['taskscheduleday_help'] = 'Day of month field for task schedule. The field uses the same format as unix cron. Some examples are:
+
+* <strong>*</strong> Every day
+* <strong>*/2</strong> Every 2nd day
+* <strong>1</strong> The first of every month
+* <strong>1,15</strong> The first and fifteenth of every month';
 $string['taskscheduledayofweek'] = 'Day of week';
-$string['taskscheduledayofweek_help'] = 'Day of week field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every day</li><li><strong>0</strong> Every Sunday</li><li><strong>6</strong> Every Saturday</li><li><strong>1,5</strong> Every Monday and Friday</li></ul>';
+$string['taskscheduledayofweek_help'] = 'Day of week field for task schedule. The field uses the same format as unix cron. Some examples are:
+
+* <strong>*</strong> Every day
+* <strong>0</strong> Every Sunday
+* <strong>6</strong> Every Saturday
+* <strong>1,5</strong> Every Monday and Friday';
 $string['taskschedulehour'] = 'Hour';
-$string['taskschedulehour_help'] = 'Hour field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every hour</li><li><strong>*/2</strong> Every 2 hours</li><li><strong>2-10</strong> Every hour from 2am until 10am (inclusive)</li><li><strong>2,6,9</strong> 2am, 6am and 9am</li></ul>';
+$string['taskschedulehour_help'] = 'Hour field for task schedule. The field uses the same format as unix cron. Some examples are:
+
+* <strong>*</strong> Every hour
+* <strong>*/2</strong> Every 2 hours
+* <strong>2-10</strong> Every hour from 2am until 10am (inclusive)
+* <strong>2,6,9</strong> 2am, 6am and 9am';
 $string['taskscheduleminute'] = 'Minute';
-$string['taskscheduleminute_help'] = 'Minute field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every minute</li><li><strong>*/5</strong> Every 5 minutes</li><li><strong>2-10</strong> Every minute between 2 and 10 past the hour (inclusive)</li><li><strong>2,6,9</strong> 2 6 and 9 minutes past the hour</li></ul>';
+$string['taskscheduleminute_help'] = 'Minute field for task schedule. The field uses the same format as unix cron. Some examples are:
+
+* <strong>*</strong> Every minute
+* <strong>*/5</strong> Every 5 minutes
+* <strong>2-10</strong> Every minute between 2 and 10 past the hour (inclusive)
+* <strong>2,6,9</strong> 2, 6 and 9 minutes past the hour';
 $string['taskschedulemonth'] = 'Month';
-$string['taskschedulemonth_help'] = 'Month field for task schedule. The field uses the same format as unix cron. Some examples are:<br/><ul><li><strong>*</strong> Every month</li><li><strong>*/2</strong> Every second month</li><li><strong>1</strong> Every January</li><li><strong>1,5</strong> Every January and May</li></ul>';
+$string['taskschedulemonth_help'] = 'Month field for task schedule. The field uses the same format as unix cron. Some examples are:
+
+* <strong>*</strong> Every month
+* <strong>*/2</strong> Every second month
+* <strong>1</strong> Every January
+* <strong>1,5</strong> Every January and May';
 $string['privacy:metadata'] = 'The Scheduled task configuration plugin does not store any personal data.';
 $string['viewlogs'] = 'View logs for {$a}';
index c498b34..6c20d8f 100644 (file)
@@ -104,8 +104,7 @@ $string['reset_help'] = 'Whether to reset the course after creating/updating it.
 $string['result'] = 'Result';
 $string['restoreafterimport'] = 'Restore after import';
 $string['rowpreviewnum'] = 'Preview rows';
-$string['rowpreviewnum_help'] = 'Number of rows from the CSV file that will be previewed in the next page. This option exists in
-order to limit the next page size.';
+$string['rowpreviewnum_help'] = 'Number of rows from the CSV file that will be previewed on the following page. This option is for limiting the size of the following page.';
 $string['shortnametemplate'] = 'Template to generate a shortname';
 $string['shortnametemplate_help'] = 'The short name of the course is displayed in the navigation. You may use template syntax here (%f = fullname, %i = idnumber), or enter an initial value that is incremented.';
 $string['templatefile'] = 'Restore from this file after upload';
index 64955f2..f29f481 100644 (file)
@@ -796,8 +796,7 @@ class manager {
         // the format filename => version. The version value needs to
         // be increased if the tour has been updated.
         $shippedtours = [
-            '36_dashboard.json' => 3,
-            '36_messaging.json' => 3,
+            '36_dashboard.json' => 3
         ];
 
         // These are tours that we used to ship but don't ship any longer.
@@ -807,6 +806,12 @@ class manager {
             'boost_course_view.json' => 1,
         ];
 
+        if ($CFG->messaging) {
+            $shippedtours['36_messaging.json'] = 3;
+        } else {
+            $unshippedtours['36_messaging.json'] = 3;
+        }
+
         $existingtourrecords = $DB->get_recordset('tool_usertours_tours');
 
         // Get all of the existing shipped tours and check if they need to be
index a8582de..47e4994 100644 (file)
@@ -54,5 +54,12 @@ function xmldb_tool_usertours_upgrade($oldversion) {
     // Automatically generated Moodle v3.6.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2019030600) {
+        // Update the tours shipped with Moodle.
+        manager::update_shipped_tours();
+
+        upgrade_plugin_savepoint(true, 2019030600, 'tool', 'usertours');
+    }
+
     return true;
 }
index 2f00da1..c678cb9 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2018120300;            // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2019030600;            // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2018112800;            // Requires this Moodle version.
 $plugin->component = 'tool_usertours';      // Full name of the plugin (used for diagnostics).
index d2efeab..2abd947 100644 (file)
@@ -156,7 +156,7 @@ $string['newtable'] = 'New table';
 $string['newtablefrommysql'] = 'New table from MySQL';
 $string['new_table_from_mysql'] = 'New table from MySQL';
 $string['nofieldsspecified'] = 'No fields specified';
-$string['nomasterprimaryuniquefound'] = 'The column(s) that you foreign key references must be included in a primary or unique KEY in the referenced table. Note, the column being in a UNIQUE INDEX is not good enough.';
+$string['nomasterprimaryuniquefound'] = 'The column(s) that your foreign key references must be included in a primary or unique KEY in the referenced table. Note that the column being in a UNIQUE INDEX is not good enough.';
 $string['nomissingindexesfound'] = 'No missing indexes have been found, your DB doesn\'t need further actions.';
 $string['noreffieldsspecified'] = 'No reference fields specified';
 $string['noreftablespecified'] = 'Specified reference table not found';
@@ -221,6 +221,6 @@ $string['yeswrongdefaultsfound'] = '<p>Some inconsistent defaults have been foun
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more inconsistent defaults are found.</p>';
 $string['yeswrongintsfound'] = '<p>Some wrong integers have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them. Remember to backup your data first!</p>
 <p>After fixing them, it is highly recommended to execute this utility again to check that no more wrong integers are found.</p>';
-$string['yeswrongoraclesemanticsfound'] = '<p>Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all them. Remember to backup your data first!</p>
+$string['yeswrongoraclesemanticsfound'] = '<p>Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to convert them all. Remember to backup your data first!</p>
 <p>After doing that, it\'s highly recommended to execute this utility again to check that no more wrong semantics are found.</p>';
 $string['privacy:metadata'] = 'The XMLDB editor plugin does not store any personal data.';
index be0d3a0..919e9f6 100644 (file)
@@ -63,7 +63,9 @@ interface classifier extends predictor {
      * @param int $niterations
      * @param \stored_file $dataset
      * @param string $outputdir
+     * @param  string $trainedmodeldir
      * @return \stdClass
      */
-    public function evaluate_classification($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir);
+    public function evaluate_classification($uniqueid, $maxdeviation, $niterations, \stored_file $dataset,
+            $outputdir, $trainedmodeldir);
 }
index 638b18b..142518e 100644 (file)
@@ -196,7 +196,7 @@ abstract class base extends \core_analytics\calculable {
                 $message->component = 'moodle';
                 $message->name = 'insights';
 
-                $message->userfrom = get_admin();
+                $message->userfrom = \core_user::get_noreply_user();
                 $message->userto = $user;
 
                 $insighturl = new \moodle_url('/report/insights/insights.php?modelid=' . $modelid . '&contextid=' . $context->id);
index 1c10cde..8fcf6e5 100644 (file)
@@ -254,11 +254,26 @@ class manager {
     /**
      * Returns the enabled time splitting methods.
      *
+     * @deprecated since Moodle 3.7
+     * @todo MDL-65086 This will be deleted in Moodle 4.1
+     * @see \core_analytics\manager::get_time_splitting_methods_for_evaluation
      * @return \core_analytics\local\time_splitting\base[]
      */
     public static function get_enabled_time_splitting_methods() {
+        debugging('This function has been deprecated. You can use self::get_time_splitting_methods_for_evaluation if ' .
+            'you want to get the default time splitting methods for evaluation, or you can use self::get_all_time_splittings if ' .
+            'you want to get all the time splitting methods available on this site.');
+        return self::get_time_splitting_methods_for_evaluation();
+    }
+
+    /**
+     * Returns the default time splitting methods for model evaluation.
+     *
+     * @return \core_analytics\local\time_splitting\base[]
+     */
+    public static function get_time_splitting_methods_for_evaluation() {
 
-        if ($enabledtimesplittings = get_config('analytics', 'timesplittings')) {
+        if ($enabledtimesplittings = get_config('analytics', 'defaulttimesplittingsevaluation')) {
             $enabledtimesplittings = array_flip(explode(',', $enabledtimesplittings));
         }
 
index 3b6c6e4..3756b90 100644 (file)
@@ -289,7 +289,7 @@ class model {
                     $timesplitting = \core_analytics\manager::get_time_splitting($options['timesplitting']);
                     $timesplittings = array($timesplitting->get_id() => $timesplitting);
                 } else {
-                    $timesplittings = \core_analytics\manager::get_enabled_time_splitting_methods();
+                    $timesplittings = \core_analytics\manager::get_time_splitting_methods_for_evaluation();
                 }
             } else {
 
@@ -537,6 +537,29 @@ class model {
         }
 
         $options['evaluation'] = true;
+
+        if (empty($options['mode'])) {
+            $options['mode'] = 'configuration';
+        }
+
+        switch ($options['mode']) {
+            case 'trainedmodel':
+
+                // We are only interested on the time splitting method used by the trained model.
+                $options['timesplitting'] = $this->model->timesplitting;
+
+                // Provide the trained model directory to the ML backend if that is what we want to evaluate.
+                $trainedmodeldir = $this->get_output_dir(['execution']);
+                break;
+            case 'configuration':
+
+                $trainedmodeldir = false;
+                break;
+
+            default:
+                throw new \moodle_exception('errorunknownaction', 'analytics');
+        }
+
         $this->init_analyser($options);
 
         if (empty($this->get_indicators())) {
@@ -575,10 +598,10 @@ class model {
             // Evaluate the dataset, the deviation we accept in the results depends on the amount of iterations.
             if ($this->get_target()->is_linear()) {
                 $predictorresult = $predictor->evaluate_regression($this->get_unique_id(), self::ACCEPTED_DEVIATION,
-                self::EVALUATION_ITERATIONS, $dataset, $outputdir);
+                    self::EVALUATION_ITERATIONS, $dataset, $outputdir, $trainedmodeldir);
             } else {
                 $predictorresult = $predictor->evaluate_classification($this->get_unique_id(), self::ACCEPTED_DEVIATION,
-                self::EVALUATION_ITERATIONS, $dataset, $outputdir);
+                    self::EVALUATION_ITERATIONS, $dataset, $outputdir, $trainedmodeldir);
             }
 
             $result->status = $predictorresult->status;
@@ -596,7 +619,7 @@ class model {
                 $dir = $predictorresult->dir;
             }
 
-            $result->logid = $this->log_result($timesplitting->get_id(), $result->score, $dir, $result->info);
+            $result->logid = $this->log_result($timesplitting->get_id(), $result->score, $dir, $result->info, $options['mode']);
 
             $results[$timesplitting->get_id()] = $result;
         }
@@ -1462,6 +1485,29 @@ class model {
         return \core_analytics\dataset_manager::export_training_data($this->get_id(), $timesplittingid);
     }
 
+    /**
+     * Has the model been trained using data from this site?
+     *
+     * This method is useful to determine if a trained model can be evaluated as
+     * we can not use the same data for training and for evaluation.
+     *
+     * @return bool
+     */
+    public function trained_locally() : bool {
+        global $DB;
+
+        if (!$this->is_trained() || $this->is_static()) {
+            // Early exit.
+            return false;
+        }
+
+        if ($DB->record_exists('analytics_train_samples', ['modelid' => $this->model->id])) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Flag the provided file as used for training or prediction.
      *
@@ -1487,14 +1533,16 @@ class model {
      * @param float $score
      * @param string $dir
      * @param array $info
+     * @param string $evaluationmode
      * @return int The inserted log id
      */
-    protected function log_result($timesplittingid, $score, $dir = false, $info = false) {
+    protected function log_result($timesplittingid, $score, $dir = false, $info = false, $evaluationmode = 'configuration') {
         global $DB, $USER;
 
         $log = new \stdClass();
         $log->modelid = $this->get_id();
         $log->version = $this->model->version;
+        $log->evaluationmode = $evaluationmode;
         $log->target = $this->model->target;
         $log->indicators = $this->model->indicators;
         $log->timesplitting = $timesplittingid;
index c2d2a89..c8e0bcf 100644 (file)
@@ -63,7 +63,9 @@ interface regressor extends predictor {
      * @param int $niterations
      * @param \stored_file $dataset
      * @param string $outputdir
+     * @param  string $trainedmodeldir
      * @return \stdClass
      */
-    public function evaluate_regression($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir);
+    public function evaluate_regression($uniqueid, $maxdeviation, $niterations, \stored_file $dataset,
+            $outputdir, $trainedmodeldir);
 }
diff --git a/analytics/classes/stats.php b/analytics/classes/stats.php
new file mode 100644 (file)
index 0000000..0caf975
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// This file is part of Moodle - https://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/>.
+
+/**
+ * Provides the {@link \core_analytics\stats} class.
+ *
+ * @package     core_analytics
+ * @copyright   2019 David Mudrák <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_analytics;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Provides stats and meta information about the analytics usage on this site.
+ *
+ * @copyright 2019 David Mudrák <david@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class stats {
+
+    /**
+     * Return the number of models enabled on this site.
+     *
+     * @return int
+     */
+    public static function enabled_models() : int {
+        return count(manager::get_all_models(true));
+    }
+
+    /**
+     * Return the number of predictions generated by the system.
+     *
+     * @return int
+     */
+    public static function predictions() : int {
+        global $DB;
+
+        return $DB->count_records('analytics_predictions');
+    }
+
+    /**
+     * Return the number of suggested actions executed by users.
+     *
+     * @return int
+     */
+    public static function actions() : int {
+        global $DB;
+
+        return $DB->count_records('analytics_prediction_actions');
+    }
+
+    /**
+     * Return the number of suggested actions flagged as not useful.
+     *
+     * @return int
+     */
+    public static function actions_not_useful() : int {
+        global $DB;
+
+        return $DB->count_records('analytics_prediction_actions', ['actionname' => prediction::ACTION_NOT_USEFUL]);
+    }
+}
index 18582c3..7728648 100644 (file)
@@ -274,7 +274,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
      * test_ml_export_import
      *
      * @param string $predictionsprocessorclass The class name
-     * @dataProvider provider_ml_export_import
+     * @dataProvider provider_ml_processors
      */
     public function test_ml_export_import($predictionsprocessorclass) {
 
@@ -296,6 +296,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         $model->update(true, false, '\core\analytics\time_splitting\quarters', get_class($predictionsprocessor));
 
         $model->train();
+        $this->assertTrue($model->trained_locally());
 
         $this->generate_courses(10, ['visible' => 0]);
 
@@ -314,16 +315,18 @@ class core_analytics_prediction_testcase extends advanced_testcase {
             $this->assertEquals($importedmodelresults->predictions[$sampleid]->prediction, $prediction->prediction);
         }
 
+        $this->assertFalse($importmodel->trained_locally());
+
         set_config('enabled_stores', '', 'tool_log');
         get_log_manager(true);
     }
 
     /**
-     * provider_ml_export_import
+     * provider_ml_processors
      *
      * @return array
      */
-    public function provider_ml_export_import() {
+    public function provider_ml_processors() {
         $cases = [
             'case' => [],
         ];
@@ -425,14 +428,14 @@ class core_analytics_prediction_testcase extends advanced_testcase {
     /**
      * Basic test to check that prediction processors work as expected.
      *
-     * @dataProvider provider_ml_test_evaluation
+     * @dataProvider provider_ml_test_evaluation_configuration
      * @param string $modelquality
      * @param int $ncourses
      * @param array $expected
      * @param string $predictionsprocessorclass
      * @return void
      */
-    public function test_ml_evaluation($modelquality, $ncourses, $expected, $predictionsprocessorclass) {
+    public function test_ml_evaluation_configuration($modelquality, $ncourses, $expected, $predictionsprocessorclass) {
         $this->resetAfterTest(true);
         $this->setAdminuser();
         set_config('enabled_stores', 'logstore_standard', 'tool_log');
@@ -440,7 +443,7 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         $sometimesplittings = '\core\analytics\time_splitting\weekly,' .
             '\core\analytics\time_splitting\single_range,' .
             '\core\analytics\time_splitting\quarters';
-        set_config('timesplittings', $sometimesplittings, 'analytics');
+        set_config('defaulttimesplittingsevaluation', $sometimesplittings, 'analytics');
 
         if ($modelquality === 'perfect') {
             $model = $this->add_perfect_model();
@@ -473,6 +476,44 @@ class core_analytics_prediction_testcase extends advanced_testcase {
         get_log_manager(true);
     }
 
+    /**
+     * Tests the evaluation of already trained models.
+     *
+     * @dataProvider provider_ml_processors
+     * @param  string $predictionsprocessorclass
+     * @return null
+     */
+    public function test_ml_evaluation_trained_model($predictionsprocessorclass) {
+        $this->resetAfterTest(true);
+        $this->setAdminuser();
+        set_config('enabled_stores', 'logstore_standard', 'tool_log');
+
+        $model = $this->add_perfect_model();
+
+        // Generate training data.
+        $this->generate_courses(50);
+
+        // We repeat the test for all prediction processors.
+        $predictionsprocessor = \core_analytics\manager::get_predictions_processor($predictionsprocessorclass, false);
+        if ($predictionsprocessor->is_ready() !== true) {
+            $this->markTestSkipped('Skipping ' . $predictionsprocessorclass . ' as the predictor is not ready.');
+        }
+
+        $model->update(true, false, '\\core\\analytics\\time_splitting\\quarters', get_class($predictionsprocessor));
+        $model->train();
+
+        $zipfilename = 'model-zip-' . microtime() . '.zip';
+        $zipfilepath = $model->export_model($zipfilename);
+        $importmodel = \core_analytics\model::import_model($zipfilepath);
+
+        $results = $importmodel->evaluate(['mode' => 'trainedmodel']);
+        $this->assertEquals(0, $results['\\core\\analytics\\time_splitting\\quarters']->status);
+        $this->assertEquals(1, $results['\\core\\analytics\\time_splitting\\quarters']->score);
+
+        set_config('enabled_stores', '', 'tool_log');
+        get_log_manager(true);
+    }
+
     /**
      * test_read_indicator_calculations
      *
@@ -547,11 +588,11 @@ class core_analytics_prediction_testcase extends advanced_testcase {
     }
 
     /**
-     * provider_ml_test_evaluation
+     * provider_ml_test_evaluation_configuration
      *
      * @return array
      */
-    public function provider_ml_test_evaluation() {
+    public function provider_ml_test_evaluation_configuration() {
 
         $cases = array(
             'bad' => array(
diff --git a/analytics/tests/stats_test.php b/analytics/tests/stats_test.php
new file mode 100644 (file)
index 0000000..d92f403
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+// This file is part of Moodle - https://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/>.
+
+/**
+ * Provides the {@link analytics_stats_testcase} class.
+ *
+ * @package     core_analytics
+ * @category    test
+ * @copyright   2019 David Mudrák <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
+require_once(__DIR__ . '/fixtures/test_target_shortname.php');
+
+/**
+ * Unit tests for the analytics stats.
+ *
+ * @copyright 2019 David Mudrák <david@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class analytics_stats_testcase extends advanced_testcase {
+
+    /**
+     * Set up the test environment.
+     */
+    public function setUp() {
+
+        $this->setAdminUser();
+    }
+
+    /**
+     * Test the {@link \core_analytics\stats::enabled_models()} implementation.
+     */
+    public function test_enabled_models() {
+
+        $this->resetAfterTest(true);
+
+        // By default, sites have {@link \core\analytics\target\no_teaching} enabled.
+        $this->assertEquals(1, \core_analytics\stats::enabled_models());
+
+        $model = \core_analytics\model::create(
+            \core_analytics\manager::get_target('\core\analytics\target\course_dropout'),
+            [
+                \core_analytics\manager::get_indicator('\core\analytics\indicator\any_write_action'),
+            ]
+        );
+
+        // Purely adding a new model does not make it included in the stats.
+        $this->assertEquals(1, \core_analytics\stats::enabled_models());
+
+        // New models must be enabled to have them counted.
+        $model->enable('\core\analytics\time_splitting\quarters');
+        $this->assertEquals(2, \core_analytics\stats::enabled_models());
+    }
+
+    /**
+     * Test the {@link \core_analytics\stats::predictions()} implementation.
+     */
+    public function test_predictions() {
+
+        $this->resetAfterTest(true);
+
+        $model = \core_analytics\model::create(
+            \core_analytics\manager::get_target('test_target_shortname'),
+            [
+                \core_analytics\manager::get_indicator('test_indicator_fullname'),
+            ]
+        );
+
+        $model->enable('\core\analytics\time_splitting\no_splitting');
+
+        // Train the model.
+        $this->getDataGenerator()->create_course(['shortname' => 'a', 'fullname' => 'a', 'visible' => 1]);
+        $this->getDataGenerator()->create_course(['shortname' => 'b', 'fullname' => 'b', 'visible' => 1]);
+        $model->train();
+
+        // No predictions yet.
+        $this->assertEquals(0, \core_analytics\stats::predictions());
+
+        // Get one new prediction.
+        $this->getDataGenerator()->create_course(['shortname' => 'aa', 'fullname' => 'aa', 'visible' => 0]);
+        $result = $model->predict();
+
+        $this->assertEquals(1, count($result->predictions));
+        $this->assertEquals(1, \core_analytics\stats::predictions());
+
+        // Nothing changes if there is no new prediction.
+        $result = $model->predict();
+        $this->assertFalse(isset($result->predictions));
+        $this->assertEquals(1, \core_analytics\stats::predictions());
+
+        // Get two more predictions, we have three in total now.
+        $this->getDataGenerator()->create_course(['shortname' => 'bb', 'fullname' => 'bb', 'visible' => 0]);
+        $this->getDataGenerator()->create_course(['shortname' => 'cc', 'fullname' => 'cc', 'visible' => 0]);
+
+        $result = $model->predict();
+        $this->assertEquals(2, count($result->predictions));
+        $this->assertEquals(3, \core_analytics\stats::predictions());
+    }
+
+    /**
+     * Test the {@link \core_analytics\stats::actions()} and {@link \core_analytics\stats::actions_not_useful()} implementation.
+     */
+    public function test_actions() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $model = \core_analytics\model::create(
+            \core_analytics\manager::get_target('test_target_shortname'),
+            [
+                \core_analytics\manager::get_indicator('test_indicator_fullname'),
+            ]
+        );
+
+        $model->enable('\core\analytics\time_splitting\no_splitting');
+
+        // Train the model.
+        $this->getDataGenerator()->create_course(['shortname' => 'a', 'fullname' => 'a', 'visible' => 1]);
+        $this->getDataGenerator()->create_course(['shortname' => 'b', 'fullname' => 'b', 'visible' => 1]);
+        $model->train();
+
+        // Generate two predictions.
+        $this->getDataGenerator()->create_course(['shortname' => 'aa', 'fullname' => 'aa', 'visible' => 0]);
+        $this->getDataGenerator()->create_course(['shortname' => 'bb', 'fullname' => 'bb', 'visible' => 0]);
+        $model->predict();
+
+        list($p1, $p2) = array_values($DB->get_records('analytics_predictions'));
+
+        $p1 = new \core_analytics\prediction($p1, []);
+        $p2 = new \core_analytics\prediction($p2, []);
+
+        // No actions executed at the start.
+        $this->assertEquals(0, \core_analytics\stats::actions());
+        $this->assertEquals(0, \core_analytics\stats::actions_not_useful());
+
+        // The user has acknowledged the first prediction.
+        $p1->action_executed(\core_analytics\prediction::ACTION_FIXED, $model->get_target());
+        $this->assertEquals(1, \core_analytics\stats::actions());
+        $this->assertEquals(0, \core_analytics\stats::actions_not_useful());
+
+        // The user has marked the other prediction as not useful.
+        $p2->action_executed(\core_analytics\prediction::ACTION_NOT_USEFUL, $model->get_target());
+        $this->assertEquals(2, \core_analytics\stats::actions());
+        $this->assertEquals(1, \core_analytics\stats::actions_not_useful());
+    }
+}
index 550a3da..3187365 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in analytics sub system,
 information provided here is intended especially for developers.
 
+=== 3.7 ===
+
+* \core_analytics\regressor::evaluate_regression and \core_analytics\classifier::evaluate_classification
+  have been updated to include a new $trainedmodeldir param. This new param will be used to evaluate the
+  existing trained model.
+
 === 3.5 ===
 
 * There are two new methods for analysers, processes_user_data() and join_sample_user(). You
index c6c0145..d3f8a83 100644 (file)
@@ -33,7 +33,7 @@ $string['auth_dbextencodinghelp'] = 'Encoding used in external database';
 $string['auth_dbextrafields'] = 'These fields are optional.  You can choose to pre-fill some Moodle user fields with information from the <b>external database fields</b> that you specify here. <p>If you leave these blank, then defaults will be used.</p><p>In either case, the user will be able to edit all of these fields after they log in.</p>';
 $string['auth_dbfieldpass'] = 'Name of the field containing passwords';
 $string['auth_dbfieldpass_key'] = 'Password field';
-$string['auth_dbfielduser'] = 'Name of the field containing usernames';
+$string['auth_dbfielduser'] = 'Name of the field containing usernames. This field must be a varchar data type.';
 $string['auth_dbfielduser_key'] = 'Username field';
 $string['auth_dbhost'] = 'The computer hosting the database server. Use a system DSN entry if using ODBC. Use a PDO DSN entry if using PDO.';
 $string['auth_dbhost_key'] = 'Host';
index 76a4317..bbed21a 100644 (file)
@@ -164,10 +164,10 @@ $string['usernotfound'] = 'User not found in LDAP';
 $string['useracctctrlerror'] = 'Error getting userAccountControl for {$a}';
 
 $string['diag_genericerror'] = 'LDAP error {$a->code} reading {$a->subject}: {$a->message}.';
-$string['diag_toooldversion'] = 'Its is very unlikely a modern LDAP server uses LDAPv2 protocol. Wrong settings can corrupt values in user fields. Check with your LDAP administrator.';
+$string['diag_toooldversion'] = 'It is very unlikely a modern LDAP server uses LDAPv2 protocol. Wrong settings can corrupt values in user fields. Check with your LDAP administrator.';
 $string['diag_emptycontext'] = 'Empty context found.';
-$string['diag_contextnotfound'] = 'Context {$a} does not  exists or cannot be read by bind DN.';
-$string['diag_rolegroupnotfound'] = 'Group {$a->group} for role {$a->localname} does not exists or cannot be read by bind DN.';
+$string['diag_contextnotfound'] = 'Context {$a} doesn\'t exist or can\'t be read by bind DN.';
+$string['diag_rolegroupnotfound'] = 'Group {$a->group} for role {$a->localname} doesn\'t exist or can\'t be read by bind DN.';
 
 // Deprecated since Moodle 3.4.
 $string['auth_ldap_creators'] = 'List of groups or contexts whose members are allowed to create new courses. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
index b3b22f5..2744dcd 100644 (file)
@@ -737,25 +737,6 @@ class auth_plugin_mnet extends auth_plugin_base {
         return array('code' => 1, 'message' => $returnString, 'last log id' => $remoteclient->last_log_id);
     }
 
-    /**
-     * Cron function will be called automatically by cron.php every 5 minutes
-     *
-     * @return void
-     */
-    function cron() {
-        global $DB;
-
-        // run the keepalive client
-        $this->keepalive_client();
-
-        $random100 = rand(0,100);
-        if ($random100 < 10) {     // Approximately 10% of the time.
-            // nuke olden sessions
-            $longtime = time() - (1 * 3600 * 24);
-            $DB->delete_records_select('mnet_session', "expires < ?", array($longtime));
-        }
-    }
-
     /**
      * Cleanup any remote mnet_sessions, kill the local mnet_session data
      *
diff --git a/auth/mnet/classes/task/cron_task.php b/auth/mnet/classes/task/cron_task.php
new file mode 100644 (file)
index 0000000..dbd8e17
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace auth_mnet\task;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A schedule task for mnet cron.
+ *
+ * @package   auth_mnet
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cron_task extends \core\task\scheduled_task {
+    /**
+     * Get a descriptive name for this task (shown to admins).
+     *
+     * @return string
+     */
+    public function get_name() {
+        return get_string('crontask', 'auth_mnet');
+    }
+    /**
+     * Run auth mnet cron.
+     */
+    public function execute() {
+        global $DB, $CFG;
+
+        require_once($CFG->dirroot . '/auth/mnet/auth.php');
+        $mnetplugin = new \auth_plugin_mnet();
+        $mnetplugin->keepalive_client();
+
+        $random100 = rand(0,100);
+        if ($random100 < 10) {
+            $longtime = time() - DAYSECS;
+            $DB->delete_records_select('mnet_session', "expires < ?", [$longtime]);
+        }
+    }
+}
diff --git a/auth/mnet/db/tasks.php b/auth/mnet/db/tasks.php
new file mode 100644 (file)
index 0000000..e85dc3d
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+/**
+ * Definition of chat scheduled tasks.
+ *
+ * @package   auth_mnet
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+$tasks = array(
+    array(
+        'classname' => '\auth_mnet\task\cron_task',
+        'blocking' => 0,
+        'minute' => '*',
+        'hour' => '*',
+        'day' => '*',
+        'month' => '*',
+        'dayofweek' => '*'
+    )
+);
index a8c7ed3..4836214 100644 (file)
@@ -28,6 +28,7 @@ $string['auth_mnet_roamin'] = 'These host\'s users can roam in to your site';
 $string['auth_mnet_roamout'] = 'Your users can roam out to these hosts';
 $string['auth_mnet_rpc_negotiation_timeout'] = 'The timeout in seconds for authentication over the XMLRPC transport.';
 $string['auto_add_remote_users'] = 'Auto add remote users';
+$string['crontask'] = 'Background processing for MNET authentication';
 $string['rpc_negotiation_timeout'] = 'RPC negotiation timeout';
 $string['sso_idp_description'] = 'Publish this service to allow your users to roam to the {$a} site without having to re-login there. <ul><li><em>Dependency</em>: You must also <strong>subscribe</strong> to the SSO (Service Provider) service on {$a}.</li></ul><br />Subscribe to this service to allow authenticated users from {$a} to access your site without having to re-login. <ul><li><em>Dependency</em>: You must also <strong>publish</strong> the SSO (Service Provider) service to {$a}.</li></ul><br />';
 $string['sso_idp_name'] = 'SSO  (Identity Provider)';
index ae88a9f..93ddd7c 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2018120300;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2019030700;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2018112800;        // Requires this Moodle version
 $plugin->component = 'auth_mnet';       // Full name of the plugin (used for diagnostics)
index fd8b747..77d6ec4 100644 (file)
@@ -34,7 +34,7 @@ $string['auth_shibboleth_login'] = 'Shibboleth login';
 $string['auth_shibboleth_login_long'] = 'Login to Moodle via Shibboleth';
 $string['auth_shibboleth_manual_login'] = 'Manual login';
 $string['auth_shibboleth_select_member'] = 'I\'m a member of ...';
-$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organization from the drop down list:';
+$string['auth_shibboleth_select_organization'] = 'For authentication via Shibboleth, please select your organisation from the drop-down menu:';
 $string['auth_shib_convert_data'] = 'Data modification API';
 $string['auth_shib_convert_data_description'] = 'You can use this API to further modify the data provided by Shibboleth. Read the <a href="../auth/shibboleth/README.txt">README</a> for further instructions.';
 $string['auth_shib_convert_data_warning'] = 'The file does not exist or is not readable by the webserver process!';
index 77260f0..e0b38bd 100644 (file)
@@ -96,6 +96,7 @@ if (!has_any_capability(array(
         'moodle/badges:viewawarded',
         'moodle/badges:createbadge',
         'moodle/badges:awardbadge',
+        'moodle/badges:configurecriteria',
         'moodle/badges:configuremessages',
         'moodle/badges:configuredetails',
         'moodle/badges:deletebadge'), $PAGE->context)) {
index ed51a17..6a79717 100644 (file)
@@ -33,10 +33,10 @@ $string['aria:controls'] = 'Course overview controls';
 $string['aria:courseactions'] = 'Actions for current course';
 $string['aria:coursesummary'] = 'Course summary text:';
 $string['aria:courseprogress'] = 'Course progress:';
-$string['aria:displaydropdown'] = 'Display dropdown';
+$string['aria:displaydropdown'] = 'Display drop-down menu';
 $string['aria:favourites'] = 'Show starred courses';
 $string['aria:future'] = 'Show future courses';
-$string['aria:groupingdropdown'] = 'Grouping dropdown';
+$string['aria:groupingdropdown'] = 'Grouping drop-down menu';
 $string['aria:inprogress'] = 'Show in courses in progress';
 $string['aria:lastaccessed'] = 'Sort courses by last accessed date';
 $string['aria:list'] = 'Switch to list view';
@@ -44,11 +44,11 @@ $string['aria:title'] = 'Sort courses by course name';
 $string['aria:past'] = 'Show past courses';
 $string['aria:removefromfavourites'] = 'Remove star for';
 $string['aria:summary'] = 'Switch to summary view';
-$string['aria:sortingdropdown'] = 'Sorting dropdown';
+$string['aria:sortingdropdown'] = 'Sorting drop-down menu';
 $string['card'] = 'Card';
 $string['cards'] = 'Cards';
 $string['courseprogress'] = 'Course progress:';
-$string['complete'] = 'Complete';
+$string['completepercent'] = '{$a}% complete';
 $string['favourites'] = 'Starred';
 $string['future'] = 'Future';
 $string['inprogress'] = 'In progress';
@@ -93,4 +93,5 @@ $string['privacy:metadata:overviewlasttab'] = 'This stores the last tab selected
 $string['viewcourse'] = 'View course';
 
 // Deprecated since Moodle 3.7.
-$string['nocourses'] = 'No courses';
\ No newline at end of file
+$string['complete'] = 'complete';
+$string['nocourses'] = 'No courses';
index 4e22e76..fb6f86c 100644 (file)
@@ -13,4 +13,5 @@ sortbydates,block_myoverview
 timeline,block_myoverview
 viewcoursename,block_myoverview
 privacy:metadata:overviewlasttab,block_myoverview
-nocourses,block_myoverview
\ No newline at end of file
+nocourses,block_myoverview
+complete,block_myoverview
\ No newline at end of file
index 2567555..574b5e5 100644 (file)
 }}
 
 <div class="m-b-1 mr-1 d-flex align-items-center">
-    <div class="d-none d-md-inline-block mr-1">{{#str}} sortby, core {{/str}}</div>
     <div class="dropdown">
         <button id="sortingdropdown" type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown"  aria-haspopup="true" aria-expanded="false" aria-label="{{#str}} aria:sortingdropdown, block_myoverview {{/str}}">
-            <span data-active-item-text>
+            {{#pix}} t/sort_by {{/pix}}
+            <span class="d-sm-inline-block" data-active-item-text>
                 {{#title}}{{#str}} title, block_myoverview {{/str}}{{/title}}
                 {{#lastaccessed}}{{#str}} lastaccessed, block_myoverview {{/str}}{{/lastaccessed}}
             </span>
index 9bb0a57..02fa1f0 100644 (file)
@@ -30,5 +30,5 @@
 </div>
 <div class="small">
     <span class="sr-only">{{#str}}aria:courseprogress, block_myoverview{{/str}}</span>
-    <strong>{{progress}}%</strong> {{#str}}complete, block_myoverview{{/str}}
+    {{#str}}completepercent, block_myoverview, <strong>{{progress}}</strong>{{/str}}
 </div>
index 175d417..98ac925 100644 (file)
@@ -124,7 +124,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: List display  persistence
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     And I click on "List" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "List" in the "Course overview" "block"
@@ -132,7 +132,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: Cards display  persistence
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     And I click on "Card" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "Card" in the "Course overview" "block"
@@ -140,7 +140,7 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: Summary display  persistence
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     And I click on "Summary" "link" in the "Course overview" "block"
     And I reload the page
     Then I should see "Summary" in the "Course overview" "block"
@@ -206,18 +206,18 @@ Feature: The my overview block allows users to easily access their courses
 
   Scenario: Show course category in cards display
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     When I click on "Card" "link" in the "Course overview" "block"
     Then I should see "Category 1" in the "Course overview" "block"
 
   Scenario: Show course category in list display
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     When I click on "List" "link" in the "Course overview" "block"
     Then I should see "Category 1" in the "Course overview" "block"
 
   Scenario: Show course category in summary display
     Given I log in as "student1"
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     When I click on "Summary" "link" in the "Course overview" "block"
     Then I should see "Category 1" in the "Course overview" "block"
index 40a2442..9752f6f 100644 (file)
@@ -39,7 +39,7 @@ Feature: The my overview block allows users to favourite their courses
     When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
     And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
     And I reload the page
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     And I click on "List" "link" in the "Course overview" "block"
     And I reload the page
     Then "//li[contains(concat(' ', normalize-space(@class), ' '), 'list-group-item') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist
@@ -53,7 +53,7 @@ Feature: The my overview block allows users to favourite their courses
     When I click on ".coursemenubtn" "css_element" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
     And I click on "Star this course" "link" in the "//div[@class='card dashboard-card' and contains(.,'Course 5')]" "xpath_element"
     And I reload the page
-    And I click on "Display dropdown" "button" in the "Course overview" "block"
+    And I click on "Display drop-down menu" "button" in the "Course overview" "block"
     And I click on "Summary" "link" in the "Course overview" "block"
     And I reload the page
     Then "//div[contains(concat(' ', normalize-space(@class), ' '), 'course-summaryitem') and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist
index 2a32157..1361760 100644 (file)
@@ -34,6 +34,7 @@ Feature: The recently accessed courses block allows users to easily access their
     When I am on "Course 1" course homepage
     And I am on "Course 2" course homepage
     And I follow "Dashboard" in the user menu
+    And I change window size to "large"
     Then I should see "Course 1" in the "Recently accessed courses" "block"
     And I should see "Course 2" in the "Recently accessed courses" "block"
     And I should not see "Course 3" in the "Recently accessed courses" "block"
index 5aa383f..4c2de4f 100644 (file)
@@ -35,4 +35,5 @@ Feature: The recently accessed items block allows users to easily access their m
     Given I am on "Course 1" course homepage
     When  I follow "Test forum name"
     And I follow "Dashboard" in the user menu
+    And I change window size to "large"
     Then I should see "Test forum name" in the "Recently accessed items" "block"
\ No newline at end of file
index 938763b..52d92a6 100644 (file)
     {}
 }}
 <div data-region="day-filter" class="dropdown">
-    <button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+    <button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
+            aria-label="{{#str}} ariadayfilter, block_timeline {{/str}}" aria-controls="menudayfilter">
         {{#pix}} i/duration {{/pix}}
-        <span class="sr-only">
-            {{#str}} ariadayfilter, block_timeline {{/str}}
-            <span data-active-item-text>{{#str}} next30days, block_timeline {{/str}}</span>
+        <span class="sr-only" data-active-item-text>
+            {{#all}} {{#str}} all, core {{/str}} {{/all}}
+            {{#overdue}} {{#str}} overdue, block_timeline {{/str}} {{/overdue}}
+            {{#next7days}} {{#str}}next7days, block_timeline {{/str}} {{/next7days}}
+            {{#next30days}} {{#str}}next30days, block_timeline {{/str}} {{/next30days}}
+            {{#next3months}} {{#str}}next3months, block_timeline {{/str}} {{/next3months}}
+            {{#next6months}} {{#str}}next6months, block_timeline {{/str}} {{/next6months}}
         </span>
     </button>
-    <div role="menu" class="dropdown-menu" data-show-active-item>
+    <div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item>
         <a
             class="dropdown-item {{#all}} active {{/all}}"
             href="#"
             data-from="-14"
             data-filtername="all"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} all, core {{/str}}{{/str}}"
+            role="menuitem"
+            {{#all}}aria-current="true"{{/all}}
         >
             {{#str}} all, core {{/str}}
         </a>
             data-to="0"
             data-filtername="overdue"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} overdue, block_timeline {{/str}}{{/str}}"
+            role="menuitem"
+            {{#overdue}}aria-current="true"{{/overdue}}
         >
             {{#str}} overdue, block_timeline {{/str}}
         </a>
-        <div class="dropdown-divider"></div>
+        <div class="dropdown-divider" role="separator"></div>
         <h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
         <a
             class="dropdown-item {{#next7days}} active {{/next7days}}"
@@ -59,6 +68,8 @@
             data-to="7"
             data-filtername="next7days"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
+            role="menuitem"
+            {{#next7days}}aria-current="true"{{/next7days}}
         >
             {{#str}} next7days, block_timeline {{/str}}
         </a>
@@ -69,6 +80,8 @@
             data-to="30"
             data-filtername="next30days"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
+            role="menuitem"
+            {{#next30days}}aria-current="true"{{/next30days}}
         >
             {{#str}} next30days, block_timeline {{/str}}
         </a>
@@ -79,6 +92,8 @@
             data-to="90"
             data-filtername="next3months"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
+            role="menuitem"
+            {{#next3months}}aria-current="true"{{/next3months}}
         >
             {{#str}} next3months, block_timeline {{/str}}
         </a>
             data-to="180"
             data-filtername="next6months"
             aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
+            role="menuitem"
+            {{#next6months}}aria-current="true"{{/next6months}}
         >
             {{#str}} next6months, block_timeline {{/str}}
         </a>