Merge branch 'MDL-66230' of https://github.com/paulholden/moodle
authorAdrian Greeve <abgreeve@gmail.com>
Wed, 28 Aug 2019 06:54:36 +0000 (14:54 +0800)
committerAdrian Greeve <abgreeve@gmail.com>
Wed, 28 Aug 2019 06:54:36 +0000 (14:54 +0800)
605 files changed:
.eslintignore
.eslintrc
.stylelintignore
.travis.yml
Gruntfile.js
admin/classes/local/settings/filesize.php [new file with mode: 0644]
admin/cli/install.php
admin/cli/reset_password.php
admin/roles/allow.php
admin/roles/classes/allow_assign_page.php
admin/roles/classes/allow_override_page.php
admin/roles/classes/allow_role_page.php
admin/roles/classes/allow_switch_page.php
admin/roles/classes/allow_view_page.php
admin/roles/classes/define_role_table_advanced.php
admin/roles/define.php
admin/roles/override.php
admin/settings/courses.php
admin/settings/security.php
admin/settings/server.php
admin/templates/setting_configfilesize.mustache [new file with mode: 0644]
admin/tests/behat/manage_tokens.feature
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/classes/task/predict_models.php
admin/tool/analytics/classes/task/train_models.php
admin/tool/analytics/cli/evaluate_model.php
admin/tool/dataprivacy/classes/form/purpose.php
admin/tool/lp/amd/build/competencies.min.js
admin/tool/lp/amd/build/competencies.min.js.map
admin/tool/lp/amd/build/competencyactions.min.js
admin/tool/lp/amd/build/competencyactions.min.js.map
admin/tool/lp/amd/build/competencypicker.min.js
admin/tool/lp/amd/build/competencypicker.min.js.map
admin/tool/lp/amd/build/course_competency_settings.min.js
admin/tool/lp/amd/build/course_competency_settings.min.js.map
admin/tool/lp/amd/build/grade_dialogue.min.js
admin/tool/lp/amd/build/grade_dialogue.min.js.map
admin/tool/lp/amd/build/grade_user_competency_inline.min.js
admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map
admin/tool/lp/amd/src/competencies.js
admin/tool/lp/amd/src/competencyactions.js
admin/tool/lp/amd/src/competencypicker.js
admin/tool/lp/amd/src/course_competency_settings.js
admin/tool/lp/amd/src/grade_dialogue.js
admin/tool/lp/amd/src/grade_user_competency_inline.js
admin/tool/lp/tests/behat/framework_crud.feature
admin/tool/lp/tests/behat/template_crud.feature
admin/tool/mobile/classes/api.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/recyclebin/classes/category_bin.php
admin/tool/recyclebin/classes/course_bin.php
admin/tool/templatelibrary/amd/build/display.min.js
admin/tool/templatelibrary/amd/build/display.min.js.map
admin/tool/templatelibrary/amd/src/display.js
admin/tool/templatelibrary/classes/api.php
admin/tool/templatelibrary/classes/external.php
admin/tool/templatelibrary/version.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/tests/behat/upload_users.feature
admin/tool/usertours/classes/helper.php
admin/tool/usertours/classes/local/table/tour_list.php
admin/tool/usertours/classes/manager.php
admin/tool/usertours/classes/tour.php
admin/tool/usertours/lang/en/tool_usertours.php
admin/tool/usertours/tests/behat/duplicate_tour.feature [new file with mode: 0644]
analytics/classes/analysis.php
analytics/classes/local/analyser/base.php
analytics/classes/local/analyser/by_course.php
analytics/classes/local/analysis/result_file.php
analytics/tests/behat/manage_models.feature [new file with mode: 0644]
analytics/tests/fixtures/test_target_course_users.php
analytics/tests/prediction_test.php
auth/classes/output/login.php
auth/ldap/lang/en/auth_ldap.php
auth/ldap/lang/en/deprecated.txt [deleted file]
auth/oauth2/classes/auth.php
babel-plugin-add-module-to-define.js
backup/backup.php
backup/controller/backup_controller.class.php
backup/moodle2/backup_root_task.class.php
backup/util/dbops/backup_plan_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/backup_helper.class.php
backup/util/ui/backup_ui_stage.class.php
backup/util/ui/renderer.php
blocks/myoverview/amd/build/view.min.js
blocks/myoverview/amd/build/view.min.js.map
blocks/myoverview/amd/src/view.js
blocks/myoverview/classes/output/main.php
blocks/myoverview/lang/en/block_myoverview.php
blocks/myoverview/lib.php
blocks/myoverview/settings.php
blocks/myoverview/styles.css [new file with mode: 0644]
blocks/myoverview/templates/courses-view.mustache
blocks/myoverview/templates/nav-grouping-selector.mustache
blocks/myoverview/templates/view-cards.mustache
blocks/myoverview/templates/view-list.mustache
blocks/myoverview/templates/view-summary.mustache
blocks/myoverview/tests/behat/block_myoverview_adminsettings.feature [new file with mode: 0644]
blocks/myoverview/tests/behat/block_myoverview_dashboard.feature
blocks/myoverview/tests/behat/block_myoverview_hidden.feature
blocks/myoverview/tests/behat/block_myoverview_pagelimit_persistence.feature
blocks/myoverview/tests/behat/block_myoverview_progress.feature
blocks/myoverview/tests/privacy_test.php
blocks/myoverview/version.php
blocks/recentlyaccesseditems/db/upgrade.php
blocks/search_forums/templates/search_form.mustache
blocks/settings/templates/search_form.mustache
blog/rsslib.php
calendar/classes/local/event/forms/create.php
calendar/externallib.php
calendar/lib.php
calendar/renderer.php
comment/classes/external.php
comment/lib.php
comment/tests/externallib_test.php
comment/upgrade.txt [new file with mode: 0644]
composer.json
composer.lock
config-dist.php
course/classes/analytics/indicator/activities_due.php
course/classes/external/course_summary_exporter.php
course/externallib.php
course/lib.php
course/management.php
course/renderer.php
course/search.php
course/templates/activity_navigation.mustache
course/templates/course_search_form.mustache
course/templates/coursecard.mustache
course/tests/courselib_test.php
course/tests/indicators_test.php
course/upgrade.txt
dataformat/json/classes/writer.php
enrol/editenrolment.php
enrol/editenrolment_form.php
enrol/manual/ajax.php
enrol/manual/classes/enrol_users_form.php
enrol/manual/manage.php
enrol/manual/tests/behat/quickenrolment.feature
enrol/tests/enrollib_test.php
grade/grading/form/rubric/tests/behat/grade_calculation.feature
grade/grading/form/rubric/tests/behat/negative_points.feature
grade/lib.php
grade/report/history/templates/user_button.mustache
grade/report/history/tests/behat/basic_functionality.feature
grade/report/singleview/classes/local/ui/dropdown_attribute.php
grade/report/singleview/templates/bulk_insert.mustache
grade/report/singleview/templates/button.mustache
grade/report/singleview/templates/dropdown_attribute.mustache
grade/report/singleview/templates/text_attribute.mustache
grade/report/singleview/tests/behat/singleview.feature
grade/templates/edit_tree.mustache
grade/templates/weight_field.mustache
grade/templates/weight_override_field.mustache
install.php
install/lang/el/error.php
install/lang/el_wp/admin.php
install/lang/fr_wp/langconfig.php [new file with mode: 0644]
install/lang/pl/admin.php
install/lang/ro_wp/moodle.php [new file with mode: 0644]
install/lang/ta_lk/moodle.php
lang/en/access.php
lang/en/admin.php
lang/en/backup.php
lang/en/badges.php
lang/en/calendar.php
lang/en/completion.php
lang/en/deprecated.txt
lang/en/enrol.php
lang/en/hub.php
lang/en/install.php
lang/en/message.php
lang/en/moodle.php
lang/en/notes.php
lang/en/role.php
lib/accesslib.php
lib/adminlib.php
lib/amd/build/adapter.min.js
lib/amd/build/adapter.min.js.map
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-autocomplete.min.js.map
lib/amd/build/paged_content_factory.min.js
lib/amd/build/paged_content_factory.min.js.map
lib/amd/build/templates.min.js
lib/amd/build/templates.min.js.map
lib/amd/src/adapter.js
lib/amd/src/form-autocomplete.js
lib/amd/src/paged_content_factory.js
lib/amd/src/templates.js
lib/authlib.php
lib/behat/behat_base.php
lib/behat/classes/behat_config_util.php
lib/behat/classes/partial_named_selector.php
lib/classes/event/capability_assigned.php [new file with mode: 0644]
lib/classes/event/capability_unassigned.php [new file with mode: 0644]
lib/classes/event/context_locked.php [new file with mode: 0644]
lib/classes/event/context_unlocked.php [new file with mode: 0644]
lib/classes/event/role_allow_assign_updated.php
lib/classes/event/role_allow_override_updated.php
lib/classes/event/role_allow_switch_updated.php
lib/classes/event/role_allow_view_updated.php
lib/classes/event/role_capabilities_updated.php
lib/classes/event/role_updated.php [new file with mode: 0644]
lib/classes/output/external.php
lib/classes/output/mustache_template_finder.php
lib/classes/output/mustache_template_source_loader.php
lib/classes/plugin_manager.php
lib/classes/requirejs.php
lib/classes/task/task_log_cleanup_task.php
lib/classes/user.php
lib/db/messages.php
lib/db/services.php
lib/db/upgrade.php
lib/deprecatedlib.php
lib/dml/moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/tinymce/tests/behat/disablecontrol.feature
lib/enrollib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/file_system.php
lib/filestorage/file_system_filedir.php
lib/filestorage/tests/file_storage_test.php
lib/filestorage/tests/file_system_filedir_test.php
lib/filestorage/tests/file_system_test.php
lib/form/templates/element-radio.mustache
lib/installlib.php
lib/jssourcemap.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/phpmailer/COMMITMENT [new file with mode: 0644]
lib/phpmailer/LICENSE
lib/phpmailer/README.md
lib/phpmailer/README_MOODLE.txt
lib/phpmailer/VERSION
lib/phpmailer/changelog.md [deleted file]
lib/phpmailer/language/phpmailer.lang-ar.php
lib/phpmailer/language/phpmailer.lang-eo.php
lib/phpmailer/language/phpmailer.lang-es.php
lib/phpmailer/language/phpmailer.lang-hi.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-hu.php
lib/phpmailer/language/phpmailer.lang-id.php
lib/phpmailer/language/phpmailer.lang-it.php
lib/phpmailer/language/phpmailer.lang-mg.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-ms.php
lib/phpmailer/language/phpmailer.lang-nb.php
lib/phpmailer/language/phpmailer.lang-nl.php
lib/phpmailer/language/phpmailer.lang-sk.php
lib/phpmailer/language/phpmailer.lang-sl.php
lib/phpmailer/language/phpmailer.lang-sr.php [moved from lib/phpmailer/language/phpmailer.lang-rs.php with 79% similarity]
lib/phpmailer/language/phpmailer.lang-tl.php [new file with mode: 0644]
lib/phpmailer/language/phpmailer.lang-uk.php
lib/phpmailer/src/PHPMailer.php
lib/phpmailer/src/SMTP.php
lib/phpunit/tests/basic_test.php
lib/requirejs.php
lib/setup.php
lib/templates/custom_menu_item.mustache
lib/templates/filemanager_confirmdialog.mustache
lib/templates/filemanager_default_searchform.mustache
lib/templates/filemanager_fileselect.mustache
lib/templates/filemanager_modal_generallayout.mustache
lib/templates/filemanager_page_generallayout.mustache
lib/templates/filemanager_processexistingfile.mustache
lib/templates/filemanager_processexistingfilemultiple.mustache
lib/templates/filemanager_selectlayout.mustache
lib/templates/filemanager_uploadform.mustache
lib/templates/form_autocomplete_input.mustache
lib/templates/form_autocomplete_suggestions.mustache
lib/templates/full_header.mustache
lib/templates/loginform.mustache
lib/templates/paged_content_paging_bar.mustache
lib/templates/preferences_groups.mustache
lib/tests/accesslib_test.php
lib/tests/behat/app_behat_runtime.js
lib/tests/behat/behat_app.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/event_context_locked_test.php [new file with mode: 0644]
lib/tests/filelib_test.php
lib/tests/fixtures/upload_users_enrol_date_period.csv [new file with mode: 0644]
lib/tests/htmlpurifier_test.php
lib/tests/moodlelib_test.php
lib/tests/mustache_template_finder_test.php
lib/tests/outputcomponents_test.php
lib/tests/string_manager_standard_test.php
lib/tests/weblib_test.php
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/src/notification/js/ajaxexception.js
lib/yui/src/notification/js/dialogue.js
login/change_password_form.php
login/set_password_form.php
login/tests/lib_test.php
message/amd/build/message_drawer.min.js
message/amd/build/message_drawer.min.js.map
message/amd/build/message_drawer_view_contacts.min.js
message/amd/build/message_drawer_view_contacts.min.js.map
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_conversation.min.js.map
message/amd/build/message_drawer_view_conversation_constants.min.js
message/amd/build/message_drawer_view_conversation_constants.min.js.map
message/amd/build/message_drawer_view_conversation_renderer.min.js
message/amd/build/message_drawer_view_conversation_renderer.min.js.map
message/amd/build/message_drawer_view_conversation_state_manager.min.js
message/amd/build/message_drawer_view_conversation_state_manager.min.js.map
message/amd/src/message_drawer.js
message/amd/src/message_drawer_view_contacts.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_renderer.js
message/amd/src/message_drawer_view_conversation_state_manager.js
message/classes/api.php
message/classes/helper.php
message/externallib.php
message/index.php
message/pendingcontactrequests.php [new file with mode: 0644]
message/templates/message_drawer_view_conversation_body_confirm_dialogue.mustache
message/templates/message_index.mustache
message/tests/api_test.php
message/tests/behat/block_user.feature [new file with mode: 0644]
message/tests/behat/message_admin_settings.feature
message/tests/externallib_test.php
message/upgrade.txt
mod/assign/amd/build/override_form.min.js [new file with mode: 0644]
mod/assign/amd/build/override_form.min.js.map [new file with mode: 0644]
mod/assign/amd/src/override_form.js [new file with mode: 0644]
mod/assign/externallib.php
mod/assign/feedback/editpdf/ajax.php
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotation.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationstamp.js
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/override_form.php
mod/assign/overrideedit.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/file/lang/en/assignsubmission_file.php
mod/assign/submission/file/lang/en/deprecated.txt [deleted file]
mod/assign/submission/file/locallib.php
mod/assign/templates/grading_app.mustache
mod/assign/templates/override_form_user_defaults.mustache [new file with mode: 0644]
mod/assign/tests/behat/relative_dates.feature [new file with mode: 0644]
mod/assign/tests/externallib_test.php
mod/assign/tests/feedback_test.php [new file with mode: 0644]
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt
mod/book/lib.php
mod/book/tests/lib_test.php
mod/book/upgrade.txt
mod/data/lib.php
mod/data/locallib.php
mod/data/module.js [deleted file]
mod/data/view.php
mod/feedback/lib.php
mod/feedback/upgrade.txt
mod/forum/classes/local/factories/renderer.php
mod/forum/classes/output/big_search_form.php
mod/forum/deprecatedlib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/search.php
mod/forum/templates/big_search_form.mustache
mod/forum/templates/discussion_list.mustache
mod/forum/templates/quick_search_form.mustache
mod/forum/templates/social_discussion_list.mustache
mod/forum/tests/behat/advanced_search.feature
mod/forum/upgrade.txt
mod/forum/view.php
mod/glossary/lib.php
mod/glossary/tests/behat/categories.feature
mod/glossary/upgrade.txt
mod/lesson/lang/en/lesson.php
mod/lesson/locallib.php
mod/lesson/report.php
mod/lti/lib.php
mod/lti/upgrade.txt
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/lang/en/deprecated.txt
mod/quiz/lang/en/quiz.php
mod/quiz/review.php
mod/quiz/summary.php
mod/quiz/tests/attempt_test.php
mod/wiki/lib.php
mod/wiki/upgrade.txt
mod/workshop/lang/en/deprecated.txt [deleted file]
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/upgrade.txt
pix/s/angry.svg
pix/s/approve.svg
pix/s/biggrin.svg
pix/s/blackeye.svg
pix/s/blush.svg
pix/s/clown.svg
pix/s/cool.svg
pix/s/dead.svg
pix/s/egg.svg
pix/s/evil.svg
pix/s/heart.svg
pix/s/kiss.svg
pix/s/martin.svg
pix/s/mixed.svg
pix/s/no.svg
pix/s/sad.svg
pix/s/shy.svg
pix/s/sleepy.svg
pix/s/smiley.svg
pix/s/surprise.svg
pix/s/thoughtful.svg
pix/s/tongueout.svg
pix/s/wideeyes.svg
pix/s/wink.svg
pix/s/yes.svg
question/behaviour/interactive/behaviour.php
question/behaviour/interactive/renderer.php
question/behaviour/interactive/tests/walkthrough_test.php
question/format.php
question/tests/generator/lib.php
report/participation/index.php
repository/googledocs/lang/en/repository_googledocs.php
search/classes/manager.php
search/engine/solr/classes/engine.php
search/tests/behat/search_by_user.feature
search/tests/manager_test.php
theme/boost/amd/build/alert.min.js
theme/boost/amd/build/alert.min.js.map
theme/boost/amd/build/button.min.js
theme/boost/amd/build/button.min.js.map
theme/boost/amd/build/carousel.min.js
theme/boost/amd/build/carousel.min.js.map
theme/boost/amd/build/collapse.min.js
theme/boost/amd/build/collapse.min.js.map
theme/boost/amd/build/dropdown.min.js
theme/boost/amd/build/dropdown.min.js.map
theme/boost/amd/build/index.min.js [new file with mode: 0644]
theme/boost/amd/build/index.min.js.map [new file with mode: 0644]
theme/boost/amd/build/loader.min.js
theme/boost/amd/build/loader.min.js.map
theme/boost/amd/build/modal.min.js
theme/boost/amd/build/modal.min.js.map
theme/boost/amd/build/pending.min.js
theme/boost/amd/build/pending.min.js.map
theme/boost/amd/build/popover.min.js
theme/boost/amd/build/popover.min.js.map
theme/boost/amd/build/sanitizer.min.js [new file with mode: 0644]
theme/boost/amd/build/sanitizer.min.js.map [new file with mode: 0644]
theme/boost/amd/build/scrollspy.min.js
theme/boost/amd/build/scrollspy.min.js.map
theme/boost/amd/build/tab.min.js
theme/boost/amd/build/tab.min.js.map
theme/boost/amd/build/toast.min.js [new file with mode: 0644]
theme/boost/amd/build/toast.min.js.map [new file with mode: 0644]
theme/boost/amd/build/tooltip.min.js
theme/boost/amd/build/tooltip.min.js.map
theme/boost/amd/build/util.min.js
theme/boost/amd/build/util.min.js.map
theme/boost/amd/src/alert.js
theme/boost/amd/src/button.js
theme/boost/amd/src/carousel.js
theme/boost/amd/src/collapse.js
theme/boost/amd/src/dropdown.js
theme/boost/amd/src/index.js [new file with mode: 0644]
theme/boost/amd/src/loader.js
theme/boost/amd/src/modal.js
theme/boost/amd/src/pending.js
theme/boost/amd/src/popover.js
theme/boost/amd/src/sanitizer.js [new file with mode: 0644]
theme/boost/amd/src/scrollspy.js
theme/boost/amd/src/tab.js
theme/boost/amd/src/toast.js [new file with mode: 0644]
theme/boost/amd/src/tooltip.js
theme/boost/amd/src/util.js
theme/boost/readme_moodle.txt
theme/boost/scss/bootstrap/LICENSE [deleted file]
theme/boost/scss/bootstrap/_alert.scss
theme/boost/scss/bootstrap/_badge.scss
theme/boost/scss/bootstrap/_breadcrumb.scss
theme/boost/scss/bootstrap/_button-group.scss
theme/boost/scss/bootstrap/_buttons.scss
theme/boost/scss/bootstrap/_card.scss
theme/boost/scss/bootstrap/_carousel.scss
theme/boost/scss/bootstrap/_close.scss
theme/boost/scss/bootstrap/_code.scss
theme/boost/scss/bootstrap/_custom-forms.scss
theme/boost/scss/bootstrap/_dropdown.scss
theme/boost/scss/bootstrap/_forms.scss
theme/boost/scss/bootstrap/_functions.scss
theme/boost/scss/bootstrap/_images.scss
theme/boost/scss/bootstrap/_input-group.scss
theme/boost/scss/bootstrap/_jumbotron.scss
theme/boost/scss/bootstrap/_list-group.scss
theme/boost/scss/bootstrap/_mixins.scss
theme/boost/scss/bootstrap/_modal.scss
theme/boost/scss/bootstrap/_nav.scss
theme/boost/scss/bootstrap/_navbar.scss
theme/boost/scss/bootstrap/_pagination.scss
theme/boost/scss/bootstrap/_popover.scss
theme/boost/scss/bootstrap/_print.scss
theme/boost/scss/bootstrap/_progress.scss
theme/boost/scss/bootstrap/_reboot.scss
theme/boost/scss/bootstrap/_spinners.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_tables.scss
theme/boost/scss/bootstrap/_toasts.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/_tooltip.scss
theme/boost/scss/bootstrap/_transitions.scss
theme/boost/scss/bootstrap/_type.scss
theme/boost/scss/bootstrap/_utilities.scss
theme/boost/scss/bootstrap/_variables.scss
theme/boost/scss/bootstrap/bootstrap-grid.scss
theme/boost/scss/bootstrap/bootstrap-reboot.scss
theme/boost/scss/bootstrap/bootstrap.scss
theme/boost/scss/bootstrap/mixins/_badge.scss
theme/boost/scss/bootstrap/mixins/_border-radius.scss
theme/boost/scss/bootstrap/mixins/_box-shadow.scss
theme/boost/scss/bootstrap/mixins/_breakpoints.scss
theme/boost/scss/bootstrap/mixins/_buttons.scss
theme/boost/scss/bootstrap/mixins/_caret.scss
theme/boost/scss/bootstrap/mixins/_deprecate.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/mixins/_float.scss
theme/boost/scss/bootstrap/mixins/_forms.scss
theme/boost/scss/bootstrap/mixins/_gradients.scss
theme/boost/scss/bootstrap/mixins/_grid-framework.scss
theme/boost/scss/bootstrap/mixins/_grid.scss
theme/boost/scss/bootstrap/mixins/_hover.scss
theme/boost/scss/bootstrap/mixins/_image.scss
theme/boost/scss/bootstrap/mixins/_list-group.scss
theme/boost/scss/bootstrap/mixins/_nav-divider.scss
theme/boost/scss/bootstrap/mixins/_navbar-align.scss [deleted file]
theme/boost/scss/bootstrap/mixins/_pagination.scss
theme/boost/scss/bootstrap/mixins/_reset-text.scss
theme/boost/scss/bootstrap/mixins/_screen-reader.scss
theme/boost/scss/bootstrap/mixins/_size.scss
theme/boost/scss/bootstrap/mixins/_table-row.scss
theme/boost/scss/bootstrap/mixins/_text-emphasis.scss
theme/boost/scss/bootstrap/mixins/_text-hide.scss
theme/boost/scss/bootstrap/mixins/_transition.scss
theme/boost/scss/bootstrap/mixins/_visibility.scss
theme/boost/scss/bootstrap/utilities/_borders.scss
theme/boost/scss/bootstrap/utilities/_display.scss
theme/boost/scss/bootstrap/utilities/_embed.scss
theme/boost/scss/bootstrap/utilities/_flex.scss
theme/boost/scss/bootstrap/utilities/_float.scss
theme/boost/scss/bootstrap/utilities/_overflow.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_position.scss
theme/boost/scss/bootstrap/utilities/_shadows.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_sizing.scss
theme/boost/scss/bootstrap/utilities/_spacing.scss
theme/boost/scss/bootstrap/utilities/_stretched-link.scss [new file with mode: 0644]
theme/boost/scss/bootstrap/utilities/_text.scss
theme/boost/scss/bootstrap/utilities/_visibility.scss
theme/boost/scss/bootstrap/vendor/_rfs.scss [new file with mode: 0644]
theme/boost/scss/moodle/bs4alphacompat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/drawer.scss
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/modules.scss
theme/boost/style/moodle.css
theme/boost/tests/behat/settingstabs.feature [new file with mode: 0644]
theme/boost/thirdpartylibs.xml
theme/classic/style/moodle.css
theme/upgrade.txt
user/amd/build/participants.min.js
user/amd/build/participants.min.js.map
user/amd/build/status_field.min.js
user/amd/build/status_field.min.js.map
user/amd/src/participants.js
user/amd/src/status_field.js
user/classes/form/defaulthomepage_form.php [new file with mode: 0644]
user/classes/participants_table.php
user/defaulthomepage.php [new file with mode: 0644]
user/editadvanced_form.php
user/filters/date.php
user/index.php
user/lib.php
user/renderer.php
user/tests/behat/behat_user.php
user/tests/behat/bulk_editenrolment.feature
user/tests/behat/filter_participants.feature
user/tests/behat/set_default_homepage.feature
user/tests/behat/view_participants.feature
version.php

index e645472..fe02a73 100644 (file)
@@ -74,10 +74,13 @@ theme/boost/amd/src/button.js
 theme/boost/amd/src/carousel.js
 theme/boost/amd/src/collapse.js
 theme/boost/amd/src/dropdown.js
+theme/boost/amd/src/index.js
 theme/boost/amd/src/modal.js
 theme/boost/amd/src/popover.js
+theme/boost/amd/src/sanitizer.js
 theme/boost/amd/src/scrollspy.js
 theme/boost/amd/src/tab.js
+theme/boost/amd/src/toast.js
 theme/boost/amd/src/tooltip.js
 theme/boost/amd/src/util.js
 theme/boost/amd/src/tether.js
index 8020549..2388717 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
       }
     },
     {
-      files: ["**/amd/src/*.js"],
+      files: ["**/amd/src/*.js", "**/amd/src/**/*.js"],
       // We support es6 now. Woot!
       env: {
         es6: true
index e0b754e..d3e4974 100644 (file)
@@ -75,10 +75,13 @@ theme/boost/amd/src/button.js
 theme/boost/amd/src/carousel.js
 theme/boost/amd/src/collapse.js
 theme/boost/amd/src/dropdown.js
+theme/boost/amd/src/index.js
 theme/boost/amd/src/modal.js
 theme/boost/amd/src/popover.js
+theme/boost/amd/src/sanitizer.js
 theme/boost/amd/src/scrollspy.js
 theme/boost/amd/src/tab.js
+theme/boost/amd/src/toast.js
 theme/boost/amd/src/tooltip.js
 theme/boost/amd/src/util.js
 theme/boost/amd/src/tether.js
index 56a20e8..90edc4f 100644 (file)
@@ -11,7 +11,10 @@ notifications:
 
 language: php
 
-dist: trusty
+dist: xenial
+
+services:
+    - mysql
 
 php:
     # We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
@@ -20,10 +23,6 @@ php:
 
 addons:
   postgresql: "9.6"
-  packages:
-    - mysql-server-5.6
-    - mysql-client-core-5.6
-    - mysql-client-5.6
 
 env:
     # Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
@@ -71,10 +70,10 @@ install:
         then
             sudo mkdir /mnt/ramdisk
             sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk
-            sudo stop mysql
+            sudo service mysql stop
             sudo mv /var/lib/mysql /mnt/ramdisk
             sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql
-            sudo start mysql
+            sudo service mysql restart
         fi
     - >
         if [ "$DB" = 'pgsql' ];
index 765a5a2..771bed1 100644 (file)
@@ -64,7 +64,14 @@ module.exports = function(grunt) {
     var inAMD = path.basename(cwd) == 'amd';
 
     // Globbing pattern for matching all AMD JS source files.
-    var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
+    var amdSrc = [];
+    if (inAMD) {
+        amdSrc.push(cwd + "/src/*.js");
+        amdSrc.push(cwd + "/src/**/*.js");
+    } else {
+        amdSrc.push("**/amd/src/*.js");
+        amdSrc.push("**/amd/src/**/*.js");
+    }
 
     /**
      * Function to generate the destination for the uglify task
diff --git a/admin/classes/local/settings/filesize.php b/admin/classes/local/settings/filesize.php
new file mode 100644 (file)
index 0000000..e5a6edb
--- /dev/null
@@ -0,0 +1,194 @@
+<?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/>.
+
+/**
+ * File size admin setting.
+ *
+ * @package    core_admin
+ * @copyright  2019 Shamim Rezaie <shamim@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_admin\local\settings;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/adminlib.php');
+
+/**
+ * An admin setting to support entering and displaying of file sizes in Bytes, KB, MB or GB.
+ *
+ * @copyright   2019 Shamim Rezaie <shamim@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class filesize extends \admin_setting {
+
+    /** @var int The byte unit. Number of bytes in a byte */
+    const UNIT_B = 1;
+
+    /** @var int The kilobyte unit (number of bytes in a kilobyte) */
+    const UNIT_KB = 1024;
+
+    /** @var int The megabyte unit (number of bytes in a megabyte) */
+    const UNIT_MB = 1048576;
+
+    /** @var int The gigabyte unit (number of bytes in a gigabyte) */
+    const UNIT_GB = 1073741824;
+
+    /** @var int default size unit */
+    protected $defaultunit;
+
+    /**
+     * Constructor
+     *
+     * @param string    $name           unique ascii name, either 'mysetting' for settings that in config,
+     *                                  or 'myplugin/mysetting' for ones in config_plugins.
+     * @param string    $visiblename    localised name
+     * @param string    $description    localised long description
+     * @param int|null  $defaultvalue   Value of the settings in bytes
+     * @param int|null  $defaultunit    GB, MB, etc. (in bytes)
+     */
+    public function __construct(string $name, string $visiblename, string $description,
+            int $defaultvalue = null, int $defaultunit = null) {
+
+        $defaultsetting = self::parse_bytes($defaultvalue);
+
+        if ($defaultunit && array_key_exists($defaultunit, self::get_units())) {
+            $this->defaultunit = $defaultunit;
+        } else {
+            $this->defaultunit = self::UNIT_MB;
+        }
+        parent::__construct($name, $visiblename, $description, $defaultsetting);
+    }
+
+    /**
+     * Returns selectable units.
+     *
+     * @return  array
+     */
+    protected static function get_units(): array {
+        return [
+            self::UNIT_GB => get_string('sizegb'),
+            self::UNIT_MB => get_string('sizemb'),
+            self::UNIT_KB => get_string('sizekb'),
+            self::UNIT_B  => get_string('sizeb'),
+        ];
+    }
+
+    /**
+     * Converts bytes to some more user friendly string.
+     *
+     * @param   int     $bytes  The number of bytes we want to convert from
+     * @return  string
+     */
+    protected static function get_size_text(int $bytes): string {
+        if (empty($bytes)) {
+            return get_string('none');
+        }
+        return display_size($bytes);
+    }
+
+    /**
+     * Finds suitable units for given file size.
+     *
+     * @param   int     $bytes  The number of bytes
+     * @return  array           Parsed file size in the format of ['v' => value, 'u' => unit]
+     */
+    protected static function parse_bytes(int $bytes): array {
+        foreach (self::get_units() as $unit => $unused) {
+            if ($bytes % $unit === 0) {
+                return ['v' => (int)($bytes / $unit), 'u' => $unit];
+            }
+        }
+        return ['v' => (int)$bytes, 'u' => self::UNIT_B];
+    }
+
+    /**
+     * Get the selected file size as array.
+     *
+     * @return  array|null  An array containing 'v' => xx, 'u' => xx, or null if not set
+     */
+    public function get_setting(): ?array {
+        $bytes = $this->config_read($this->name);
+        if (is_null($bytes)) {
+            return null;
+        }
+
+        return self::parse_bytes($bytes);
+    }
+
+    /**
+     * Store the file size as bytes.
+     *
+     * @param   array   $data   Must be form 'h' => xx, 'm' => xx
+     * @return  string          The error string if any
+     */
+    public function write_setting($data): string {
+        if (!is_array($data)) {
+            return '';
+        }
+
+        if (!is_numeric($data['v']) || $data['v'] < 0) {
+            return get_string('errorsetting', 'admin');
+        }
+
+        $bytes = $data['v'] * $data['u'];
+
+        $result = $this->config_write($this->name, $bytes);
+        return ($result ? '' : get_string('errorsetting', 'admin'));
+    }
+
+    /**
+     * Returns file size text+select fields.
+     *
+     * @param   array   $data   The current setting value. Must be form 'v' => xx, 'u' => xx.
+     * @param   string  $query  Admin search query to be highlighted.
+     * @return  string          File size text+select fields and wrapping div(s).
+     */
+    public function output_html($data, $query = ''): string {
+        global $OUTPUT;
+
+        $default = $this->get_defaultsetting();
+        if (is_number($default)) {
+            $defaultinfo = self::get_size_text($default);
+        } else if (is_array($default)) {
+            $defaultinfo = self::get_size_text($default['v'] * $default['u']);
+        } else {
+            $defaultinfo = null;
+        }
+
+        $inputid = $this->get_id() . 'v';
+        $units = self::get_units();
+        $defaultunit = $this->defaultunit;
+
+        $context = (object) [
+            'id' => $this->get_id(),
+            'name' => $this->get_full_name(),
+            'value' => $data['v'],
+            'options' => array_map(function($unit, $title) use ($data, $defaultunit) {
+                return [
+                    'value' => $unit,
+                    'name' => $title,
+                    'selected' => ($data['v'] == 0 && $unit == $defaultunit) || $unit == $data['u']
+                ];
+            }, array_keys($units), $units)
+        ];
+
+        $element = $OUTPUT->render_from_template('core_admin/setting_configfilesize', $context);
+
+        return format_admin_setting($this, $this->visiblename, $element, $this->description, $inputid, '', $defaultinfo, $query);
+    }
+}
index e9203ec..2dea6ca 100644 (file)
@@ -157,7 +157,6 @@ $CFG->lang                 = 'en';
 $CFG->dirroot              = dirname(dirname(__DIR__));
 $CFG->libdir               = "$CFG->dirroot/lib";
 $CFG->wwwroot              = "http://localhost";
-$CFG->httpswwwroot         = $CFG->wwwroot;
 $CFG->docroot              = 'http://docs.moodle.org';
 $CFG->running_installer    = true;
 $CFG->early_install_lang   = true;
@@ -389,8 +388,6 @@ if ($interactive) {
     }
 }
 $CFG->wwwroot       = $wwwroot;
-$CFG->httpswwwroot  = $CFG->wwwroot;
-
 
 //We need dataroot before lang download
 $CFG->dataroot = $options['dataroot'];
index 9d4dc14..8bd03eb 100644 (file)
@@ -94,7 +94,7 @@ if ($options['password'] == '' ) {
 
 $errmsg = '';//prevent eclipse warning
 if (!$options['ignore-password-policy'] ) {
-    if (!check_password_policy($password, $errmsg)) {
+    if (!check_password_policy($password, $errmsg, $user)) {
         cli_error(html_to_text($errmsg, 0));
     }
 }
index 88609cf..643e6ce 100644 (file)
@@ -46,25 +46,6 @@ $controller = new $classformode[$mode]();
 
 if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_sesskey()) {
     $controller->process_submission();
-    $event = null;
-    // Create event depending on mode.
-    switch ($mode) {
-        case 'assign':
-            $event = \core\event\role_allow_assign_updated::create(array('context' => $syscontext));
-            break;
-        case 'override':
-            $event = \core\event\role_allow_override_updated::create(array('context' => $syscontext));
-            break;
-        case 'switch':
-            $event = \core\event\role_allow_switch_updated::create(array('context' => $syscontext));
-            break;
-        case 'view':
-            $event = \core\event\role_allow_view_updated::create(array('context' => $syscontext));
-            break;
-    }
-    if ($event) {
-        $event->trigger();
-    }
     redirect($baseurl);
 }
 
index a89228f..002d944 100644 (file)
@@ -46,4 +46,8 @@ class core_role_allow_assign_page extends core_role_allow_role_page {
     public function get_intro_text() {
         return get_string('configallowassign', 'core_admin');
     }
+
+    protected function get_eventclass() {
+        return \core\event\role_allow_assign_updated::class;
+    }
 }
index 4b160ee..16226a6 100644 (file)
@@ -46,4 +46,8 @@ class core_role_allow_override_page extends core_role_allow_role_page {
     public function get_intro_text() {
         return get_string('configallowoverride2', 'core_admin');
     }
+
+    protected function get_eventclass() {
+        return \core\event\role_allow_override_updated::class;
+    }
 }
index c84d01a..376489f 100644 (file)
@@ -59,12 +59,36 @@ abstract class core_role_allow_role_page {
      */
     public function process_submission() {
         global $DB;
+
+        $context = context_system::instance();
+        $this->load_current_settings();
+
         // Delete all records, then add back the ones that should be allowed.
         $DB->delete_records($this->tablename);
         foreach ($this->roles as $fromroleid => $notused) {
             foreach ($this->roles as $targetroleid => $alsonotused) {
+                $isallowed = $this->allowed[$fromroleid][$targetroleid];
                 if (optional_param('s_' . $fromroleid . '_' . $targetroleid, false, PARAM_BOOL)) {
                     $this->set_allow($fromroleid, $targetroleid);
+                    // Only trigger events if this role allow relationship did not exist and the checkbox element
+                    // has been submitted.
+                    if (!$isallowed) {
+                        $eventclass = $this->get_eventclass();
+                        $eventclass::create([
+                            'context' => $context,
+                            'objectid' => $fromroleid,
+                            'other' => ['targetroleid' => $targetroleid, 'allow' => true]
+                        ])->trigger();
+                    }
+                } else if ($isallowed) {
+                    // When the user has deselect an existing role allow checkbox but it is in the list of roles
+                    // allowances.
+                    $eventclass = $this->get_eventclass();
+                    $eventclass::create([
+                        'context' => $context,
+                        'objectid' => $fromroleid,
+                        'other' => ['targetroleid' => $targetroleid, 'allow' => false]
+                    ])->trigger();
                 }
             }
         }
@@ -161,4 +185,10 @@ abstract class core_role_allow_role_page {
      * @return string
      */
     public abstract function get_intro_text();
+
+    /**
+     * Returns the allow class respective event class name.
+     * @return string
+     */
+    protected abstract function get_eventclass();
 }
index 5b22e1e..195aab7 100644 (file)
@@ -58,4 +58,8 @@ class core_role_allow_switch_page extends core_role_allow_role_page {
     public function get_intro_text() {
         return get_string('configallowswitch', 'core_admin');
     }
+
+    protected function get_eventclass() {
+        return \core\event\role_allow_switch_updated::class;
+    }
 }
index f1a1031..d331322 100644 (file)
@@ -74,4 +74,8 @@ class core_role_allow_view_page extends core_role_allow_role_page {
     public function get_intro_text() {
         return get_string('configallowview', 'core_admin');
     }
+
+    protected function get_eventclass() {
+        return \core\event\role_allow_view_updated::class;
+    }
 }
index d5f6c18..f4ab562 100644 (file)
@@ -434,7 +434,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     }
 
     public function save_changes() {
-        global $DB;
+        global $DB, $USER;
 
         if (!$this->roleid) {
             // Creating role.
@@ -444,6 +444,20 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             // Updating role.
             $DB->update_record('role', $this->role);
 
+            // Trigger role updated event.
+            \core\event\role_updated::create([
+                'userid' => $USER->id,
+                'objectid' => $this->role->id,
+                'context' => $this->context,
+                'other' => [
+                    'name' => $this->role->name,
+                    'shortname' => $this->role->shortname,
+                    'description' => $this->role->description,
+                    'archetype' => $this->role->archetype,
+                    'contextlevels' => $this->contextlevels
+                ]
+            ])->trigger();
+
             // This will ensure the course contacts cache is purged so name changes get updated in
             // the UI. It would be better to do this only when we know that fields affected are
             // updated. But thats getting into the weeds of the coursecat cache and role edits
@@ -473,10 +487,17 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
         $addfunction = "core_role_set_{$type}_allowed";
         $deltable = 'role_allow_'.$type;
         $field = 'allow'.$type;
+        $eventclass = "\\core\\event\\role_allow_" . $type . "_updated";
+        $context = context_system::instance();
 
         foreach ($current as $roleid) {
             if (!in_array($roleid, $wanted)) {
                 $DB->delete_records($deltable, array('roleid'=>$this->roleid, $field=>$roleid));
+                $eventclass::create([
+                    'context' => $context,
+                    'objectid' => $this->roleid,
+                    'other' => ['targetroleid' => $roleid, 'allow' => false]
+                ])->trigger();
                 continue;
             }
             $key = array_search($roleid, $wanted);
@@ -488,6 +509,14 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
                 $roleid = $this->roleid;
             }
             $addfunction($this->roleid, $roleid);
+
+            if (in_array($roleid, $wanted)) {
+                $eventclass::create([
+                    'context' => $context,
+                    'objectid' => $this->roleid,
+                    'other' => ['targetroleid' => $roleid, 'allow' => true]
+                ])->trigger();
+            }
         }
     }
 
@@ -623,7 +652,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
             echo "</label>\n";
         }
         if ($helpicon) {
-            echo '<span class="pull-xs-right text-nowrap">'.$helpicon.'</span>';
+            echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
         }
         echo '</div>';
         if (isset($this->errors[$name])) {
index 1a90cdd..fdf144d 100644 (file)
@@ -200,19 +200,6 @@ if (optional_param('cancel', false, PARAM_BOOL)) {
 if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey() && $definitiontable->is_submission_valid()) {
     $definitiontable->save_changes();
     $tableroleid = $definitiontable->get_role_id();
-    // Trigger event.
-    $event = \core\event\role_capabilities_updated::create(
-        array(
-            'context' => $systemcontext,
-            'objectid' => $tableroleid
-        )
-    );
-    $event->set_legacy_logdata(array(SITEID, 'role', $action, 'admin/roles/define.php?action=view&roleid=' . $tableroleid,
-        $definitiontable->get_role_name(), '', $USER->id));
-    if (!empty($role)) {
-        $event->add_record_snapshot('role', $role);
-    }
-    $event->trigger();
 
     if ($action === 'add') {
         redirect(new moodle_url('/admin/roles/define.php', array('action'=>'view', 'roleid'=>$definitiontable->get_role_id())));
index 878c820..f3d393b 100644 (file)
@@ -134,22 +134,6 @@ $overridestable->read_submitted_permissions();
 if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
     $overridestable->save_changes();
     $rolename = $overridableroles[$roleid];
-    // Trigger event.
-    $event = \core\event\role_capabilities_updated::create(
-        array(
-            'context' => $context,
-            'objectid' => $roleid,
-        )
-    );
-
-    $event->set_legacy_logdata(
-        array(
-            $course->id, 'role', 'override', 'admin/roles/override.php?contextid=' . $context->id . '&roleid=' . $roleid,
-            $rolename, '', $USER->id
-        )
-    );
-    $event->add_record_snapshot('role', $role);
-    $event->trigger();
 
     redirect($returnurl);
 }
index ace90d3..2525f22 100644 (file)
@@ -206,6 +206,11 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_blocks', new lang_string('generalblocks','backup'), new lang_string('configgeneralblocks','backup'), array('value'=>1, 'locked'=>0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock(
+            'backup/backup_general_files',
+            new lang_string('generalfiles', 'backup'),
+            new lang_string('configgeneralfiles', 'backup'),
+            array('value' => '1', 'locked' => 0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_filters', new lang_string('generalfilters','backup'), new lang_string('configgeneralfilters','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_comments', new lang_string('generalcomments','backup'), new lang_string('configgeneralcomments','backup'), array('value'=>1, 'locked'=>0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_badges', new lang_string('generalbadges','backup'), new lang_string('configgeneralbadges','backup'), array('value'=>1,'locked'=>0)));
@@ -341,6 +346,10 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $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));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_blocks', new lang_string('generalblocks','backup'), new lang_string('configgeneralblocks','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox(
+            'backup/backup_auto_files',
+            new lang_string('generalfiles', 'backup'),
+            new lang_string('configgeneralfiles', 'backup'), '1'));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_filters', new lang_string('generalfilters','backup'), new lang_string('configgeneralfilters','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_comments', new lang_string('generalcomments','backup'), new lang_string('configgeneralcomments','backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_badges', new lang_string('generalbadges','backup'), new lang_string('configgeneralbadges','backup'), 1));
index 564845b..1b91d6a 100644 (file)
@@ -1,4 +1,29 @@
 <?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 security related settings links for security category to admin tree.
+ *
+ * @copyright  1999 Martin Dougiamas  http://dougiamas.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+use core_admin\local\settings\filesize;
 
 if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
 
@@ -36,12 +61,9 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     // maxbytes set to 0 will allow the maximum server limit for uploads
     $temp->add(new admin_setting_configselect('maxbytes', new lang_string('maxbytes', 'admin'), new lang_string('configmaxbytes', 'admin'), 0, $max_upload_choices));
     // 100MB
-    $defaultuserquota = 104857600;
-    $params = new stdClass();
-    $params->bytes = $defaultuserquota;
-    $params->displaysize = display_size($defaultuserquota);
-    $temp->add(new admin_setting_configtext('userquota', new lang_string('userquota', 'admin'),
-                new lang_string('configuserquota', 'admin', $params), $defaultuserquota, PARAM_INT, 30));
+    $defaultuserquota = 100 * filesize::UNIT_MB;
+    $temp->add(new filesize('userquota', new lang_string('userquota', 'admin'),
+            new lang_string('userquota_desc', 'admin'), $defaultuserquota));
 
     $temp->add(new admin_setting_configcheckbox('allowobjectembed', new lang_string('allowobjectembed', 'admin'), new lang_string('configallowobjectembed', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('enabletrusttext', new lang_string('enabletrusttext', 'admin'), new lang_string('configenabletrusttext', 'admin'), 0));
index a869ce6..cfb2f21 100644 (file)
@@ -172,7 +172,10 @@ $temp->add(new admin_setting_configselect('tempdatafoldercleanup', new lang_stri
 
 $ADMIN->add('server', $temp);
 
-
+    $temp->add(new admin_setting_configduration('filescleanupperiod',
+        new lang_string('filescleanupperiod', 'admin'),
+        new lang_string('filescleanupperiod_help', 'admin'),
+        86400));
 
 $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"));
diff --git a/admin/templates/setting_configfilesize.mustache b/admin/templates/setting_configfilesize.mustache
new file mode 100644 (file)
index 0000000..4716c6e
--- /dev/null
@@ -0,0 +1,50 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core_admin/setting_configfilesize
+
+    Admin file size setting template.
+
+    Context variables required for this template:
+    * name - form element name
+    * options - list of options for units containing name, value, selected
+    * value - yes
+    * id - element id
+
+    Example context (json):
+    {
+        "name": "test",
+        "value": "5",
+        "id": "test0",
+        "options": [ { "name": "KB", "value": "1024", "selected": true } ]
+    }
+}}
+{{!
+    Setting configfilesize.
+}}
+<div class="form-filesize defaultsnext">
+    <div class="form-inline">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <label class="sr-only" for="{{id}}u">{{#str}}filesizeunits, admin{{/str}}</label>
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control custom-select">
+            {{#options}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/options}}
+        </select>
+    </div>
+</div>
+
index d30e230..dc1cd94 100644 (file)
@@ -9,6 +9,7 @@ Feature: Manage tokens
     | username  | password  | firstname | lastname |
     | testuser  | testuser  | Joe | Bloggs |
     | testuser2 | testuser2 | TestFirstname | TestLastname |
+    And I change window size to "small"
     And I log in as "admin"
     And I am on site homepage
 
index 63648a9..47cd54a 100644 (file)
@@ -198,7 +198,7 @@ class models_list implements \renderable, \templatable {
             // Get predictions.
             if (!$onlycli && $modeldata->enabled && !empty($modeldata->timesplitting)) {
                 $urlparams['action'] = 'scheduledanalysis';
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url,
                     new \pix_icon('i/notifications', get_string('executescheduledanalysis', 'tool_analytics')),
                     get_string('executescheduledanalysis', 'tool_analytics'));
@@ -225,7 +225,7 @@ class models_list implements \renderable, \templatable {
                 $evaluateparams = [$actionid, $trainedonlyexternally, $modeltimesplittingmethods];
                 $PAGE->requires->js_call_amd('tool_analytics/model', 'selectEvaluationOptions', $evaluateparams);
                 $urlparams['action'] = 'evaluate';
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/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'), ['data-action-id' => $actionid]);
                 $actionsmenu->add($icon);
@@ -234,7 +234,7 @@ class models_list implements \renderable, \templatable {
             // Machine-learning-based models evaluation log.
             if (!$model->is_static() && $model->get_logs()) {
                 $urlparams['action'] = 'log';
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url, new \pix_icon('i/report', get_string('viewlog', 'tool_analytics')),
                     get_string('viewlog', 'tool_analytics'));
                 $actionsmenu->add($icon);
@@ -242,7 +242,7 @@ class models_list implements \renderable, \templatable {
 
             // Edit model.
             $urlparams['action'] = 'edit';
-            $url = new \moodle_url('model.php', $urlparams);
+            $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
             $icon = new \action_menu_link_secondary($url, new \pix_icon('t/edit', get_string('edit')), get_string('edit'));
             $actionsmenu->add($icon);
 
@@ -259,7 +259,7 @@ class models_list implements \renderable, \templatable {
                     $icontype = 'i/checked';
                 }
                 $urlparams['action'] = $action;
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
                 $actionsmenu->add($icon);
             }
@@ -272,7 +272,7 @@ class models_list implements \renderable, \templatable {
 
                 if ($fullysetup || $istrained) {
 
-                    $url = new \moodle_url('model.php', $urlparams);
+                    $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                     // Clear the previous action param from the URL, we will set it in JS.
                     $url->remove_params('action');
 
@@ -300,7 +300,7 @@ class models_list implements \renderable, \templatable {
             $analyser = $model->get_analyser(['notimesplitting' => true]);
             if (!$analyser instanceof \core_analytics\local\analyser\sitewide) {
                 $urlparams['action'] = 'invalidanalysables';
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                 $pix = new \pix_icon('i/report', get_string('invalidanalysables', 'tool_analytics'));
                 $icon = new \action_menu_link_secondary($url, $pix, get_string('invalidanalysables', 'tool_analytics'));
                 $actionsmenu->add($icon);
@@ -311,7 +311,7 @@ class models_list implements \renderable, \templatable {
                 $actionid = 'clear-' . $model->get_id();
                 $PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'clear']);
                 $urlparams['action'] = 'clear';
-                $url = new \moodle_url('model.php', $urlparams);
+                $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
                 $icon = new \action_menu_link_secondary($url, new \pix_icon('e/cleanup_messy_code',
                     get_string('clearpredictions', 'tool_analytics')), get_string('clearpredictions', 'tool_analytics'),
                     ['data-action-id' => $actionid]);
@@ -322,7 +322,7 @@ class models_list implements \renderable, \templatable {
             $actionid = 'delete-' . $model->get_id();
             $PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'delete']);
             $urlparams['action'] = 'delete';
-            $url = new \moodle_url('model.php', $urlparams);
+            $url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
             $icon = new \action_menu_link_secondary($url, new \pix_icon('t/delete',
                 get_string('delete', 'tool_analytics')), get_string('delete', 'tool_analytics'),
                 ['data-action-id' => $actionid]);
index 6ea0d0d..4551e79 100644 (file)
@@ -59,6 +59,9 @@ class predict_models extends \core\task\scheduled_task {
         }
 
         foreach ($models as $model) {
+
+            $renderer = $PAGE->get_renderer('tool_analytics');
+
             $result = $model->predict();
 
             // Reset the page as some indicators may call external functions that overwrite the page context.
@@ -66,7 +69,6 @@ class predict_models extends \core\task\scheduled_task {
 
             if ($result) {
                 echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
-                $renderer = $PAGE->get_renderer('tool_analytics');
                 echo $renderer->render_get_predictions_results(false, array(), $result, $model->get_analyser()->get_logs());
             }
         }
index 67c0a3a..c9387d3 100644 (file)
@@ -70,6 +70,8 @@ class train_models extends \core\task\scheduled_task {
                 continue;
             }
 
+            $renderer = $PAGE->get_renderer('tool_analytics');
+
             $result = $model->train();
 
             // Reset the page as some indicators may call external functions that overwrite the page context.
@@ -77,8 +79,6 @@ class train_models extends \core\task\scheduled_task {
 
             if ($result) {
                 echo $OUTPUT->heading(get_string('modelresults', 'tool_analytics', $model->get_name()));
-
-                $renderer = $PAGE->get_renderer('tool_analytics');
                 echo $renderer->render_get_predictions_results($result, $model->get_analyser()->get_logs());
             }
         }
index 067320e..601599e 100644 (file)
@@ -102,6 +102,8 @@ if ($options['reuse-prev-analysed']) {
     mtrace(get_string('evaluationinbatches', 'tool_analytics'));
 }
 
+$renderer = $PAGE->get_renderer('tool_analytics');
+
 $analyseroptions = array(
     'filter' => $options['filter'],
     'timesplitting' => $options['analysisinterval'],
@@ -114,7 +116,6 @@ $results = $model->evaluate($analyseroptions);
 // Reset the page as some indicators may call external functions that overwrite the page context.
 \tool_analytics\output\helper::reset_page();
 
-$renderer = $PAGE->get_renderer('tool_analytics');
 echo $renderer->render_evaluate_results($results, $model->get_analyser()->get_logs());
 
 // Check that we have, at leasa,t 1 valid dataset (not necessarily good) to use.
index 72472e5..1de9317 100644 (file)
@@ -440,6 +440,9 @@ class purpose extends persistent {
         }
         if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
             $data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
+        } else {
+            // Nothing selected. Set default value of null.
+            $data->sensitivedatareasons = null;
         }
 
         // A single value.
index fd8cb9c..867ec94 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js and b/admin/tool/lp/amd/build/competencies.min.js differ
index aa66f0b..b7aa8c0 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencies.min.js.map and b/admin/tool/lp/amd/build/competencies.min.js.map differ
index 5314ff2..838c624 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js and b/admin/tool/lp/amd/build/competencyactions.min.js differ
index baafad8..3bdbdc7 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencyactions.min.js.map and b/admin/tool/lp/amd/build/competencyactions.min.js.map differ
index e9f3dff..ec38fee 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js and b/admin/tool/lp/amd/build/competencypicker.min.js differ
index 822d4ea..44b0812 100644 (file)
Binary files a/admin/tool/lp/amd/build/competencypicker.min.js.map and b/admin/tool/lp/amd/build/competencypicker.min.js.map differ
index dce39b1..496c31d 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js and b/admin/tool/lp/amd/build/course_competency_settings.min.js differ
index 329c557..9ab4d54 100644 (file)
Binary files a/admin/tool/lp/amd/build/course_competency_settings.min.js.map and b/admin/tool/lp/amd/build/course_competency_settings.min.js.map differ
index 9c0ecbd..806eeeb 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js and b/admin/tool/lp/amd/build/grade_dialogue.min.js differ
index 6f0202f..6af7b51 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_dialogue.min.js.map and b/admin/tool/lp/amd/build/grade_dialogue.min.js.map differ
index 4d52c20..ade6899 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js differ
index 4459d47..1095c0f 100644 (file)
Binary files a/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map and b/admin/tool/lp/amd/build/grade_user_competency_inline.min.js.map differ
index e3a5176..82052a4 100644 (file)
@@ -27,8 +27,9 @@ define(['jquery',
         'core/templates',
         'core/str',
         'tool_lp/competencypicker',
-        'tool_lp/dragdrop-reorder'],
-       function($, notification, ajax, templates, str, Picker, dragdrop) {
+        'tool_lp/dragdrop-reorder',
+        'core/pending'],
+       function($, notification, ajax, templates, str, Picker, dragdrop, Pending) {
 
     /**
      * Constructor
@@ -117,6 +118,7 @@ define(['jquery',
      * Pick a competency
      *
      * @method pickCompetency
+     * @return {Promise}
      */
     competencies.prototype.pickCompetency = function() {
         var self = this;
@@ -132,6 +134,7 @@ define(['jquery',
             self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
             self.pickerInstance.on('save', function(e, data) {
                 var compIds = data.competencyIds;
+                var pendingPromise = new Pending();
 
                 if (self.itemtype === "course") {
                     requests = [];
@@ -181,17 +184,20 @@ define(['jquery',
                     pagerender = 'tool_lp/plan_page';
                     pageregion = 'plan-page';
                 }
-                ajax.call(requests)[requests.length - 1].then(function(context) {
+                ajax.call(requests)[requests.length - 1]
+                .then(function(context) {
                     return templates.render(pagerender, context);
-                }).then(function(html, js) {
-                    $('[data-region="' + pageregion + '"]').replaceWith(html);
-                    templates.runTemplateJS(js);
+                })
+                .then(function(html, js) {
+                    templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
-        self.pickerInstance.display();
+        return self.pickerInstance.display();
     };
 
     /**
@@ -302,6 +308,7 @@ define(['jquery',
         if (localthis.itemtype == 'course') {
             // Course completion rule handling.
             $('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
+                var pendingPromise = new Pending();
                 var requests = [];
                 var pagerender = 'tool_lp/course_competencies_page';
                 var pageregion = 'coursecompetenciespage';
@@ -314,18 +321,24 @@ define(['jquery',
                       args: {courseid: localthis.itemid, moduleid: 0}}
                 ]);
 
-                requests[1].done(function(context) {
-                    templates.render(pagerender, context).done(function(html, js) {
-                        $('[data-region="' + pageregion + '"]').replaceWith(html);
-                        templates.runTemplateJS(js);
-                    }).fail(notification.exception);
-                }).fail(notification.exception);
+                requests[1].then(function(context) {
+                    return templates.render(pagerender, context);
+                })
+                .then(function(html, js) {
+                    return templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
         $('[data-region="actions"] button').click(function(e) {
+            var pendingPromise = new Pending();
             e.preventDefault();
-            localthis.pickCompetency();
+
+            localthis.pickCompetency()
+                .then(pendingPromise.resolve)
+                .catch();
         });
         $('[data-action="delete-competency-link"]').click(function(e) {
             e.preventDefault();
index 60c04da..78a7cd3 100644 (file)
@@ -33,8 +33,12 @@ define(['jquery',
         'tool_lp/menubar',
         'tool_lp/competencypicker',
         'tool_lp/competency_outcomes',
-        'tool_lp/competencyruleconfig'],
-       function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
+        'tool_lp/competencyruleconfig',
+        'core/pending',
+        ],
+       function(
+            $, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending
+        ) {
 
     // Private variables and functions.
     /** @var {Object} treeModel - This is an object representing the nodes in the tree. */
@@ -412,6 +416,7 @@ define(['jquery',
         if (!pickerInstance) {
             pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
             pickerInstance.on('save', function(e, data) {
+                var pendingPromise = new Pending();
                 var compIds = data.competencyIds;
 
                 var calls = [];
@@ -436,7 +441,9 @@ define(['jquery',
                     templates.runTemplateJS(js);
                     updatedRelatedCompetencies();
                     return;
-                }).catch(notification.exception);
+                })
+                .then(pendingPromise.resolve)
+                .catch(notification.exception);
             });
         }
 
index 23dce8a..c3cb0b1 100644 (file)
@@ -31,8 +31,10 @@ define(['jquery',
         'core/templates',
         'tool_lp/dialogue',
         'core/str',
-        'tool_lp/tree'],
-        function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
+        'tool_lp/tree',
+        'core/pending'
+        ],
+        function($, Notification, Ajax, Templates, Dialogue, Str, Tree, Pending) {
 
     /**
      * Competency picker class.
@@ -157,6 +159,7 @@ define(['jquery',
         // Add listener for add.
         self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
             e.preventDefault();
+            var pendingPromise = new Pending();
             if (!self._selectedCompetencies.length) {
                 return;
             }
@@ -168,7 +171,10 @@ define(['jquery',
                 self._trigger('save', {competencyId: self._selectedCompetencies[0]});
             }
 
+            // The dialogue here is a YUI dialogue and doesn't support Promises at all.
+            // However, it is typically synchronous so this shoudl suffice.
             self.close();
+            pendingPromise.resolve();
         });
 
         // The list of selected competencies will be modified while looping (because of the listeners above).
index ff21ade..fd497d2 100644 (file)
@@ -26,8 +26,10 @@ define(['jquery',
         'tool_lp/dialogue',
         'core/str',
         'core/ajax',
-        'core/templates'],
-       function($, notification, Dialogue, str, ajax, templates) {
+        'core/templates',
+        'core/pending'
+        ],
+       function($, notification, Dialogue, str, ajax, templates, Pending) {
 
     /**
      * Constructor
@@ -48,6 +50,7 @@ define(['jquery',
      * @method configureSettings
      */
     settingsMod.prototype.configureSettings = function(e) {
+        var pendingPromise = new Pending();
         var courseid = $(e.target).closest('a').data('courseid');
         var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
         var context = {
@@ -56,16 +59,21 @@ define(['jquery',
         };
         e.preventDefault();
 
-        templates.render('tool_lp/course_competency_settings', context).done(function(html) {
-            str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
-                this._dialogue = new Dialogue(
-                    title,
-                    html,
-                    this.addListeners.bind(this)
-                );
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        $.when(
+            str.get_string('configurecoursecompetencysettings', 'tool_lp'),
+            templates.render('tool_lp/course_competency_settings', context),
+        )
+        .then(function(title, templateResult) {
+            this._dialogue = new Dialogue(
+                title,
+                templateResult[0],
+                this.addListeners.bind(this)
+            );
+
+            return this._dialogue;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     /**
@@ -108,6 +116,7 @@ define(['jquery',
      * @method saveSettings
      */
     settingsMod.prototype.saveSettings = function(e) {
+        var pendingPromise = new Pending();
         e.preventDefault();
 
         var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
@@ -117,9 +126,12 @@ define(['jquery',
         ajax.call([
             {methodname: 'core_competency_update_course_competency_settings',
               args: {courseid: courseId, settings: settings}}
-        ])[0].done(function() {
-            this.refreshCourseCompetenciesPage();
-        }.bind(this)).fail(notification.exception);
+        ])[0]
+        .then(function() {
+            return this.refreshCourseCompetenciesPage();
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
 
     };
 
@@ -131,18 +143,23 @@ define(['jquery',
      */
     settingsMod.prototype.refreshCourseCompetenciesPage = function() {
         var courseId = this._find('input[name="courseid"]').val();
+        var pendingPromise = new Pending();
 
         ajax.call([
             {methodname: 'tool_lp_data_for_course_competencies_page',
               args: {courseid: courseId, moduleid: 0}}
-        ])[0].done(function(context) {
-            templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
-                $('[data-region="coursecompetenciespage"]').replaceWith(html);
-                templates.runTemplateJS(js);
-                this._dialogue.close();
-            }.bind(this)).fail(notification.exception);
-        }.bind(this)).fail(notification.exception);
-
+        ])[0]
+        .then(function(context) {
+            return templates.render('tool_lp/course_competencies_page', context);
+        })
+        .then(function(html, js) {
+            templates.replaceNode($('[data-region="coursecompetenciespage"]'), html, js);
+            this._dialogue.close();
+
+            return;
+        }.bind(this))
+        .then(pendingPromise.resolve)
+        .catch(notification.exception);
     };
 
     return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
index 576b511..80ef97c 100644 (file)
@@ -102,15 +102,20 @@ define(['jquery',
      * @return {Promise}
      */
     Grade.prototype.display = function() {
-        return this._render().then(function(html) {
-            return Str.get_string('rate', 'tool_lp').then(function(title) {
-                this._popup = new Dialogue(
-                    title,
-                    html,
-                    this._afterRender.bind(this)
-                );
-            }.bind(this));
-        }.bind(this)).fail(Notification.exception);
+        return $.when(
+            Str.get_string('rate', 'tool_lp'),
+            this._render()
+        )
+        .then(function(title, templateResult) {
+            this._popup = new Dialogue(
+                title,
+                templateResult[0],
+                this._afterRender.bind(this)
+            );
+
+            return this._popup;
+        }.bind(this))
+        .catch(Notification.exception);
     };
 
     /**
index 7a030b6..81e4f35 100644 (file)
@@ -95,7 +95,7 @@ define(['jquery',
             self = this;
 
         var promise = ScaleValues.get_values(self._scaleId);
-        promise.done(function(scalevalues) {
+        promise.then(function(scalevalues) {
             options.push({
                 value: '',
                 name: self._chooseStr
@@ -109,8 +109,13 @@ define(['jquery',
                 });
             }
 
-            self._dialogue = new GradeDialogue(options);
-            self._dialogue.on('rated', function(e, data) {
+            return options;
+        })
+        .then(function(options) {
+            return new GradeDialogue(options);
+        })
+        .then(function(dialogue) {
+            dialogue.on('rated', function(e, data) {
                 var args = self._args;
                 args.grade = data.rating;
                 args.note = data.note;
@@ -123,7 +128,15 @@ define(['jquery',
                     fail: notification.exception
                 }]);
             });
-        }).fail(notification.exception);
+
+            return dialogue;
+        })
+        .then(function(dialogue) {
+            self._dialogue = dialogue;
+
+            return;
+        })
+        .fail(notification.exception);
     };
 
     /** @type {Number} The scale id for this competency. */
index df34c22..168a028 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage competency frameworks
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new framework
index 0156243..a5386ff 100644 (file)
@@ -6,6 +6,7 @@ Feature: Manage plearning plan templates
 
   Background:
     Given I log in as "admin"
+    And I change window size to "small"
     And I am on site homepage
 
   Scenario: Create a new learning plan template
index 02027bd..978f950 100644 (file)
@@ -414,6 +414,7 @@ class api {
                 'NoDelegate_CoreRating' => new lang_string('ratings', 'rating'),
                 'NoDelegate_CoreTag' => new lang_string('tags'),
                 '$mmLoginEmailSignup' => new lang_string('startsignup'),
+                'NoDelegate_ForgottenPassword' => new lang_string('forgotten'),
                 'NoDelegate_ResponsiveMainMenuItems' => new lang_string('responsivemainmenuitems', 'tool_mobile'),
             ),
             "$mainmenu" => array(
index f3d1815..6bfea7f 100644 (file)
@@ -71,7 +71,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
 
         $expected = array(
             'wwwroot' => $CFG->wwwroot,
-            'httpswwwroot' => $CFG->httpswwwroot,
+            'httpswwwroot' => $CFG->wwwroot,
             'sitename' => external_format_string($SITE->fullname, $context->id, true),
             'guestlogin' => $CFG->guestloginbutton,
             'rememberusername' => $CFG->rememberusername,
index 6477c4d..b5bde99 100644 (file)
@@ -109,14 +109,15 @@ class category_bin extends base_bin {
         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
 
         // As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
-        // setting (storing backups @ real location. For recycle bin we want to ensure that
-        // backup files are always stored in Moodle file area. In order to achieve that, we
-        // hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
-        // operations.  See MDL-65218 for more information.
+        // settings (storing backups @ real location and potentially not including files).
+        // For recycle bin we want to ensure that backup files are always stored in Moodle file
+        // area and always contain the users' files. In order to achieve that, we hack the
+        // setting here via $CFG->forced_plugin_settings, so it won't interfere other operations.
+        // See MDL-65218 and MDL-35773 for more information.
         // This hack will be removed once recycle bin switches to use its own backup mode, with
-        // own preferences and 100% appart from MODLE_AUTOMATED.
+        // own preferences and 100% separate from MOODLE_AUTOMATED.
         // TODO: Remove this as part of MDL-65228.
-        $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
+        $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0, 'backup_auto_files' => 1];
 
         // Backup the course.
         $user = get_admin();
index deec65c..0353c2c 100644 (file)
@@ -113,14 +113,15 @@ class course_bin extends base_bin {
         }
 
         // As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
-        // setting (storing backups @ real location. For recycle bin we want to ensure that
-        // backup files are always stored in Moodle file area. In order to achieve that, we
-        // hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
-        // operations.  See MDL-65218 for more information.
+        // settings (storing backups @ real location and potentially not including files).
+        // For recycle bin we want to ensure that backup files are always stored in Moodle file
+        // area and always contain the users' files. In order to achieve that, we hack the
+        // setting here via $CFG->forced_plugin_settings, so it won't interfere other operations.
+        // See MDL-65218 and MDL-35773 for more information.
         // This hack will be removed once recycle bin switches to use its own backup mode, with
-        // own preferences and 100% appart from MODLE_AUTOMATED.
+        // own preferences and 100% separate from MOODLE_AUTOMATED.
         // TODO: Remove this as part of MDL-65228.
-        $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
+        $CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0, 'backup_auto_files' => 1];
 
         // Backup the activity.
         $user = get_admin();
index 76ec813..9ac8f6b 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/display.min.js and b/admin/tool/templatelibrary/amd/build/display.min.js differ
index 183fe03..abaf4bd 100644 (file)
Binary files a/admin/tool/templatelibrary/amd/build/display.min.js.map and b/admin/tool/templatelibrary/amd/build/display.min.js.map differ
index 3979366..222e4bd 100644 (file)
@@ -119,7 +119,7 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
     var loadTemplate = function(templateName) {
         var parts = templateName.split('/');
         var component = parts.shift();
-        var name = parts.shift();
+        var name = parts.join('/');
 
         var promises = ajax.call([{
             methodname: 'core_output_load_template',
index 7ae2b0e..f0d7a43 100644 (file)
@@ -99,13 +99,34 @@ class api {
 
         foreach ($templatedirs as $templatecomponent => $dirs) {
             foreach ($dirs as $dir) {
+                if (!is_dir($dir) || !is_readable($dir)) {
+                    continue;
+                }
+                $dir = realpath($dir);
+
                 // List it.
-                $files = glob($dir . '/*.mustache');
+                $directory = new \RecursiveDirectoryIterator($dir);
+                $files = new \RecursiveIteratorIterator($directory);
 
                 foreach ($files as $file) {
-                    $templatename = basename($file, '.mustache');
-                    if ($search == '' || strpos($templatename, $search) !== false) {
-                        $results[$templatecomponent . '/' . $templatename] = 1;
+                    if (!$file->isFile()) {
+                        continue;
+                    }
+                    $filename = substr($file->getRealpath(), strlen($dir) + 1);
+                    if (strpos($templatecomponent, 'theme_') === 0) {
+                        if (strpos($filename, '/') !== false && strpos($filename, 'local/') !== 0) {
+                            // Skip any template in a sub-directory of a theme which is not in a local directory.
+                            // These are theme overrides of core templates.
+                            // Note: There is a rare edge case where a theme may override a template and then have additional
+                            // dependant templates and these will not be shown.
+                            continue;
+                        }
+                    }
+                    $templatename = str_replace('.mustache', '', $filename);
+                    $componenttemplatename = "{$templatecomponent}/{$templatename}";
+
+                    if ($search == '' || strpos($componenttemplatename, $search) !== false) {
+                        $results[$componenttemplatename] = 1;
                     }
                 }
             }
@@ -152,5 +173,4 @@ class api {
         return $templatestr;
     }
 
-
 }
index f1c72f5..edda9f2 100644 (file)
@@ -105,7 +105,7 @@ class external extends external_api {
     public static function load_canonical_template_parameters() {
         return new external_function_parameters(
                 array('component' => new external_value(PARAM_COMPONENT, 'component containing the template'),
-                      'template' => new external_value(PARAM_ALPHANUMEXT, 'name of the template'))
+                      'template' => new external_value(PARAM_SAFEPATH, 'name of the template'))
             );
     }
 
index c8d1519..625a195 100644 (file)
@@ -21,6 +21,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019052002; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019051100; // Requires this Moodle version.
 $plugin->component = 'tool_templatelibrary'; // Full name of the plugin (used for diagnostics).
index 019da51..dcf16e4 100644 (file)
@@ -668,7 +668,7 @@ if ($formdata = $mform2->is_cancelled()) {
                     // Check for passwords that we want to force users to reset next
                     // time they log in.
                     $errmsg = null;
-                    $weak = !check_password_policy($user->password, $errmsg);
+                    $weak = !check_password_policy($user->password, $errmsg, $user);
                     if ($resetpasswords == UU_PWRESET_ALL or ($resetpasswords == UU_PWRESET_WEAK and $weak)) {
                         if ($weak) {
                             $weakpasswords++;
@@ -809,7 +809,7 @@ if ($formdata = $mform2->is_cancelled()) {
                     }
                 } else {
                     $errmsg = null;
-                    $weak = !check_password_policy($user->password, $errmsg);
+                    $weak = !check_password_policy($user->password, $errmsg, $user);
                     if ($resetpasswords == UU_PWRESET_ALL or ($resetpasswords == UU_PWRESET_WEAK and $weak)) {
                         if ($weak) {
                             $weakpasswords++;
@@ -1039,6 +1039,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 if ($roleid) {
                     // Find duration and/or enrol status.
                     $timeend = 0;
+                    $timestart = $today;
                     $status = null;
 
                     if (isset($user->{'enrolstatus'.$i})) {
@@ -1054,16 +1055,23 @@ if ($formdata = $mform2->is_cancelled()) {
                         }
                     }
 
+                    if (!empty($user->{'enroltimestart'.$i})) {
+                        $parsedtimestart = strtotime($user->{'enroltimestart'.$i});
+                        if ($parsedtimestart !== false) {
+                            $timestart = $parsedtimestart;
+                        }
+                    }
+
                     if (!empty($user->{'enrolperiod'.$i})) {
                         $duration = (int)$user->{'enrolperiod'.$i} * 60*60*24; // convert days to seconds
                         if ($duration > 0) { // sanity check
-                            $timeend = $today + $duration;
+                            $timeend = $timestart + $duration;
                         }
                     } else if ($manualcache[$courseid]->enrolperiod > 0) {
-                        $timeend = $today + $manualcache[$courseid]->enrolperiod;
+                        $timeend = $timestart + $manualcache[$courseid]->enrolperiod;
                     }
 
-                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $today, $timeend, $status);
+                    $manual->enrol_user($manualcache[$courseid], $user->id, $roleid, $timestart, $timeend, $status);
 
                     $a = new stdClass();
                     $a->course = $shortname;
index e389f80..46b8bc4 100644 (file)
@@ -204,7 +204,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
             // hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field
             $newfield = $lcfield;
 
-        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus)\d+$/', $lcfield)) {
+        } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus|enroltimestart)\d+$/', $lcfield)) {
             // special fields for enrolments
             $newfield = $lcfield;
 
index ffd9140..7ac75ba 100644 (file)
@@ -145,3 +145,29 @@ Feature: Upload users
     And I should see "Users created: 4"
     And I press "Continue"
     And I log out
+
+  @javascript
+  Scenario: Upload users setting their enrol date and period
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Maths    | math102   | 0        |
+    # Upload the users.
+    And I change window size to "large"
+    And I log in as "admin"
+    And I navigate to "Users > Accounts > Upload users" in site administration
+    When I upload "lib/tests/fixtures/upload_users_enrol_date_period.csv" file to "File" filemanager
+    And I press "Upload users"
+    Then I should see "Upload users preview"
+    And I press "Upload users"
+    # Check user enrolment start date and period
+    And I am on "Maths" course homepage
+    Then I navigate to course participants
+    And I click on "Manual enrolments" "link" in the "Student One" "table_row"
+    Then I should see "1 January 2019" in the "Enrolment starts" "table_row"
+    And I should not see "Enrolment ends"
+    And I click on "Close" "button"
+    And I click on "Manual enrolments" "link" in the "Student Two" "table_row"
+    Then I should see "2 January 2020" in the "Enrolment starts" "table_row"
+    And I should see "12 January 2020" in the "Enrolment ends" "table_row"
+    And I click on "Close" "button"
+    And I log out
index 1e4cdbb..df04ed9 100644 (file)
@@ -203,6 +203,21 @@ class helper {
         return $link;
     }
 
+    /**
+     * Get the link used to duplicate the tour.
+     *
+     * @param   int         $tourid     The ID of the tour to duplicate.
+     * @return  moodle_url              The URL.
+     */
+    public static function get_duplicate_tour_link($tourid) {
+        $link = new \moodle_url('/admin/tool/usertours/configure.php', [
+                'action'    => manager::ACTION_DUPLICATETOUR,
+                'id'        => $tourid,
+        ]);
+
+        return $link;
+    }
+
     /**
      * Get the link used to delete the tour.
      *
index 10de685..3611de8 100644 (file)
@@ -138,6 +138,7 @@ class tour_list extends \flexible_table {
 
         $actions[] = helper::format_icon_link($tour->get_view_link(), 't/viewdetails', get_string('view'));
         $actions[] = helper::format_icon_link($tour->get_edit_link(), 't/edit', get_string('edit'));
+        $actions[] = helper::format_icon_link($tour->get_duplicate_link(), 't/copy', get_string('duplicate'));
         $actions[] = helper::format_icon_link($tour->get_export_link(), 't/export',
                 get_string('exporttour', 'tool_usertours'), 'tool_usertours');
         $actions[] = helper::format_icon_link($tour->get_delete_link(), 't/delete', get_string('delete'), null, [
index f29f481..33cd752 100644 (file)
@@ -78,6 +78,11 @@ class manager {
      */
     const ACTION_VIEWTOUR = 'viewtour';
 
+    /**
+     * @var ACTION_DUPLICATETOUR     The action to duplicate the tour.
+     */
+    const ACTION_DUPLICATETOUR = 'duplicatetour';
+
     /**
      * @var ACTION_NEWSTEP The action to create a new step.
      */
@@ -163,6 +168,10 @@ class manager {
                 $this->view_tour(required_param('id', PARAM_INT));
                 break;
 
+            case self::ACTION_DUPLICATETOUR:
+                $this->duplicate_tour(required_param('id', PARAM_INT));
+                break;
+
             case self::ACTION_HIDETOUR:
                 $this->hide_tour(required_param('id', PARAM_INT));
                 break;
@@ -486,6 +495,39 @@ class manager {
         $this->footer();
     }
 
+    /**
+     * Duplicate an existing tour.
+     *
+     * @param   int         $tourid     The ID of the tour to duplicate.
+     */
+    protected function duplicate_tour($tourid) {
+        $tour = helper::get_tour($tourid);
+
+        $export = $tour->to_record();
+        // Remove the id.
+        unset($export->id);
+
+        // Set the version.
+        $export->version = get_config('tool_usertours', 'version');
+
+        $export->name = get_string('duplicatetour_name', 'tool_usertours', $export->name);
+
+        // Step export.
+        $export->steps = [];
+        foreach ($tour->get_steps() as $step) {
+            $record = $step->to_record();
+            unset($record->id);
+            unset($record->tourid);
+
+            $export->steps[] = $record;
+        }
+
+        $exportstring = json_encode($export);
+        $newtour = self::import_tour_from_json($exportstring);
+
+        redirect($newtour->get_view_link());
+    }
+
     /**
      * Show the tour.
      *
index 5717e24..4c4a201 100644 (file)
@@ -356,6 +356,15 @@ class tour {
         return helper::get_export_tour_link($this->id);
     }
 
+    /**
+     * The link to duplicate this tour.
+     *
+     * @return  moodle_url
+     */
+    public function get_duplicate_link() {
+        return helper::get_duplicate_tour_link($this->id);
+    }
+
     /**
      * The link to remove this tour.
      *
index f61d13c..b3b22cf 100644 (file)
@@ -44,6 +44,8 @@ $string['cssselector'] = 'CSS selector';
 $string['defaultvalue'] = 'Default ({$a})';
 $string['delay'] = 'Delay before showing the step';
 $string['done'] = 'Done';
+$string['duplicatetour'] = 'Duplicate tour';
+$string['duplicatetour_name'] = '{$a} (copy)';
 $string['editstep'] = 'Editing "{$a}"';
 $string['tourisenabled'] = 'Tour is enabled';
 $string['enabled'] = 'Enabled';
diff --git a/admin/tool/usertours/tests/behat/duplicate_tour.feature b/admin/tool/usertours/tests/behat/duplicate_tour.feature
new file mode 100644 (file)
index 0000000..8f80853
--- /dev/null
@@ -0,0 +1,24 @@
+@tool @tool_usertours
+Feature: Duplicate a user tour
+  As an administrator
+  I want to duplicate a user tour
+
+  @javascript
+  Scenario: Tour can be duplicated
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+    And I log in as "admin"
+    And I add a new user tour with:
+      | Name                | First tour |
+      | Description         | My first tour |
+      | Apply to URL match  | /my/% |
+      | Tour is enabled     | 0 |
+    And I add steps to the "First tour" tour:
+      | targettype                  | Title             | Content |
+      | Display in middle of page   | Welcome           | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful |
+    And I open the User tour settings page
+    And I should see "1" occurrences of "First tour" in the "admintable" "table"
+    And I click on "Duplicate" "link" in the "My first tour" "table_row"
+    And I open the User tour settings page
+    Then I should see "1" occurrences of "First tour (copy)" in the "admintable" "table"
index 32b5abe..ddb9c6f 100644 (file)
@@ -138,6 +138,9 @@ class analysis {
                 }
             }
         }
+
+        // Force GC to clean up the indicator instances used during the last iteration.
+        $this->analyser->instantiate_indicators();
     }
 
     /**
@@ -474,6 +477,11 @@ class analysis {
                 list($samplesfeatures, $newindicatorcalculations, $indicatornotnulls) = $rangeindicator->calculate($sampleids,
                     $this->analyser->get_samples_origin(), $range['start'], $range['end'], $prevcalculations);
 
+                // Free memory ASAP.
+                unset($rangeindicator);
+                gc_collect_cycles();
+                gc_mem_caches();
+
                 // Copy the features data to the dataset.
                 foreach ($samplesfeatures as $analysersampleid => $features) {
 
@@ -503,7 +511,7 @@ class analysis {
                         $indcalc->endtime = $range['end'];
                         $indcalc->sampleid = $sampleid;
                         $indcalc->sampleorigin = $this->analyser->get_samples_origin();
-                        $indcalc->indicator = $rangeindicator->get_id();
+                        $indcalc->indicator = $indicator->get_id();
                         $indcalc->value = $calculatedvalue;
                         $indcalc->timecreated = $timecreated;
                         $newcalculations[] = $indcalc;
index 185f573..0065f15 100644 (file)
@@ -246,6 +246,11 @@ abstract class base {
         foreach ($this->indicators as $key => $indicator) {
             $this->indicators[$key] = call_user_func(array($indicator, 'instance'));
         }
+
+        // Free memory ASAP.
+        gc_collect_cycles();
+        gc_mem_caches();
+
         return $this->indicators;
     }
 
index 99e70c1..c0d308b 100644 (file)
@@ -50,12 +50,11 @@ abstract class by_course extends base {
         if (!empty($this->options['filter'])) {
             $courses = array();
             foreach ($this->options['filter'] as $courseid) {
-                $courses[$courseid] = new \stdClass();
-                $courses[$courseid]->id = $courseid;
+                $courses[$courseid] = intval($courseid);
             }
 
             list($coursesql, $courseparams) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
-            $sql .= " AND c.id IN $coursesql";
+            $sql .= " AND c.id $coursesql";
             $params = $params + $courseparams;
         }
 
index 7a6b61e..16ef9f1 100644 (file)
@@ -78,9 +78,9 @@ class result_file extends result {
         // if this analyser was analysed less that 1 week ago we skip generating a new one. This
         // helps scale the evaluation process as sites with tons of courses may need a lot of time to
         // complete an evaluation.
-        if (!empty($options['evaluation']) && !empty($options['reuseprevanalysed'])) {
+        if (!empty($this->options['evaluation']) && !empty($this->options['reuseprevanalysed'])) {
 
-            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->analyser->get_modelid(),
+            $previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->modelid,
                 $analysable->get_id(), $timesplitting->get_id());
             // 1 week is a partly random time interval, no need to worry about DST.
             $boundary = time() - WEEKSECS;
diff --git a/analytics/tests/behat/manage_models.feature b/analytics/tests/behat/manage_models.feature
new file mode 100644 (file)
index 0000000..f8f4826
--- /dev/null
@@ -0,0 +1,162 @@
+@core @core_analytics @javascript
+Feature: Manage analytics models
+  In order to manage analytics models
+  As a manager
+  I need to create and use a model
+
+  Background:
+    Given the following config values are set as admin:
+      | onlycli  | 0 | analytics |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+      | manager1 | Manager   | 1        | manager1@example.com |
+      | student0 | Student   | 0        | student0@example.com |
+      | student1 | Student   | 1        | student1@example.com |
+      | student2 | Student   | 2        | student2@example.com |
+      | student3 | Student   | 3        | student3@example.com |
+      | student4 | Student   | 4        | student4@example.com |
+      | student5 | Student   | 5        | student5@example.com |
+      | student6 | Student   | 6        | student6@example.com |
+    And the following "system role assigns" exist:
+      | user     | course               | role      |
+      | manager1 | Acceptance test site | manager   |
+    And the following "courses" exist:
+      | fullname | shortname | category | enddate         | startdate        | enablecompletion |
+      | Course 1 | C1        | 0        | ## yesterday ## | ## 2 days ago ## | 1                |
+      | Course 2 | C2        | 0        | ## yesterday ## | ## 2 days ago ## | 1                |
+      | Course 3 | C3        | 0        | ## tomorrow  ## | ## 2 days ago ## | 1                |
+    And the following "course enrolments" exist:
+      | user     | course | role           | timeend         | timestart         |
+      | teacher1 | C1     | editingteacher | ## 1 day ago ## | ## 2 days ago ##  |
+      | student0 | C1     | student        | ## 1 day ago ## | ## 2 days ago ##  |
+      | student1 | C1     | student        | ## 1 day ago ## | ## 2 days ago ##  |
+      | student2 | C1     | student        | ## 1 day ago ## | ## 2 days ago ##  |
+      | teacher1 | C2     | editingteacher | ## 1 day ago ## | ## 2 days ago ##  |
+      | student3 | C2     | student        | ## 1 day ago ## | ## 2 days ago ##  |
+      | student4 | C2     | student        | ## 1 day ago ## | ## 2 days ago ##  |
+      | teacher1 | C3     | editingteacher | 0               | ## 2 days ago ##  |
+      | manager1 | C3     | manager        | 0               | ## 2 days ago ##  |
+      | student5 | C3     | student        | 0               | ## 2 days ago ##  |
+      | student6 | C3     | student        | 0               | ## 2 days ago ##  |
+    And the following "activities" exist:
+      | activity   | name      | intro   | course | idnumber    | section | completion | completionview |
+      | assign     | assign1   | A1 desc | C1     | assign1     | 0       | 2          | 1              |
+      | assign     | assign2   | A2 desc | C2     | assign2     | 0       | 2          | 1              |
+      | assign     | assign3   | A3 desc | C3     | assign3     | 0       | 2          | 1              |
+    And the following "analytics model" exist:
+      | target                                   | indicators                                 | timesplitting                               | enabled |
+      | \core_course\analytics\target\course_completion | \core\analytics\indicator\any_write_action,\core\analytics\indicator\read_actions |  \core\analytics\time_splitting\single_range | true    |
+    And I log in as "manager1"
+    And I navigate to "Analytics > Analytics models" in site administration
+
+  Scenario: Create a model
+    When I open the action menu in ".top-nav" "css_element"
+    And I choose "Create model" in the open action menu
+    And I set the field "Enabled" to "Enable"
+    And I select "__core_course__analytics__target__course_completion" from the "target" singleselect
+    And I open the autocomplete suggestions list
+    And I click on "Read actions amount" item in the autocomplete list
+    And I open the autocomplete suggestions list
+    And I click on "Any write action in the course" item in the autocomplete list
+    And I select "__core__analytics__time_splitting__single_range" from the "timesplitting" singleselect
+    And I press "Save changes"
+    Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
+
+  Scenario: Evaluate a model
+    Given I am on "Course 1" course homepage
+    And I navigate to "Course completion" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Assignment - assign1 | 1 |
+    And I click on "Save changes" "button"
+    And I am on "Course 2" course homepage
+    And I navigate to "Course completion" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Assignment - assign2 | 1 |
+    And I click on "Save changes" "button"
+    And I am on "Course 3" course homepage
+    And I navigate to "Course completion" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Assignment - assign3 | 1 |
+    And I click on "Save changes" "button"
+    And I am on site homepage
+    And I navigate to "Analytics > Analytics models" in site administration
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluate" in the open action menu
+    And I press "Evaluate"
+    And I should see "Evaluate model"
+    And I press "Continue"
+    # Evaluation log
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Evaluation log" in the open action menu
+    And I should see "Configuration"
+    And I click on "View" "link"
+    And I should see "Log extra info"
+    And I click on "Close" "button"
+    And I click on "Analytics models" "link"
+    # Execute scheduled analysis
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Execute scheduled analysis" in the open action menu
+    And I should see "Training results"
+    And I press "Continue"
+    # Check notifications
+    Then I should see "1" in the "#nav-notification-popover-container [data-region='count-container']" "css_element"
+    And I open the notification popover
+    And I click on "View full notification" "link" in the ".popover-region-notifications" "css_element"
+    And I should see "Students at risk in Course 3 course"
+    When I am on site homepage
+    And I navigate to "Analytics > Analytics models" in site administration
+    # View predictions
+    When I select "C3" from the "contextid" singleselect
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "View prediction details" in the open action menu
+    And I should see "Prediction details"
+    And I should see "Any write action"
+    And I should see "Read actions amount"
+    And I open the action menu in "Student 6" "table_row"
+    And I choose "Acknowledged" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "View prediction details" in the open action menu
+    And I open the action menu in "Student 5" "table_row"
+    And I choose "Not useful" in the open action menu
+    And I should see "No insights reported"
+    # Clear predictions
+    When I am on site homepage
+    And I navigate to "Analytics > Analytics models" in site administration
+    And I should see "No insights reported" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Clear predictions" in the open action menu
+    And I press "Clear predictions"
+    Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
+
+  Scenario: Edit a model
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Edit" in the open action menu
+    And I click on "Read actions amount" "text" in the ".form-autocomplete-selection" "css_element"
+    And I press "Save changes"
+    And I should not see "Read actions amount"
+
+  Scenario: Disable a model
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Disable" in the open action menu
+    Then I should see "Disabled model" in the "Students at risk of not meeting the course completion conditions" "table_row"
+
+  Scenario: Export model
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Export" in the open action menu
+    And I click on "Actions" "link" in the "Students at risk of not meeting the course completion conditions" "table_row"
+    And following "Export" should download between "100" and "500" bytes
+
+  Scenario: Check invalid site elements
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Invalid site elements" in the open action menu
+    Then I should see "Invalid analysable elements"
+
+  Scenario: Delete model
+    When I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
+    And I choose "Delete" in the open action menu
+    And I click on "Delete" "button" in the "Confirm" "dialogue"
+    Then I should not see "Students at risk of not meeting the course completion conditions"
index 8907a3c..7be0a87 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once(__DIR__ . '/test_target_shortname.php');
+require_once(__DIR__ . '/test_target_site_users.php');
 
 /**
  * Test target.
index 4294cad..7fbbdc6 100644 (file)
@@ -478,6 +478,13 @@ class core_analytics_prediction_testcase extends advanced_testcase {
             $message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting];
             $filtered = $result->status & $expected[$timesplitting];
             $this->assertEquals($expected[$timesplitting], $filtered, $message);
+
+            $options = ['evaluation' => true, 'reuseprevanalysed' => true];
+            $result = new \core_analytics\local\analysis\result_file($model->get_id(), true, $options);
+            $timesplittingobj = \core_analytics\manager::get_time_splitting($timesplitting);
+            $analysable = new \core_analytics\site();
+            $cachedanalysis = $result->retrieve_cached_result($timesplittingobj, $analysable);
+            $this->assertInstanceOf(\stored_file::class, $cachedanalysis);
         }
 
         set_config('enabled_stores', '', 'tool_log');
index 7034e97..c98048b 100644 (file)
@@ -72,6 +72,8 @@ class login implements renderable, templatable {
     public $username;
     /** @var string The csrf token to limit login to requests that come from the login form. */
     public $logintoken;
+    /** @var string Maintenance message, if Maintenance is enabled. */
+    public $maintenance;
 
     /**
      * Constructor.
@@ -109,6 +111,10 @@ class login implements renderable, templatable {
             $this->instructions = get_string('loginsteps', 'core', 'signup.php');
         }
 
+        if ($CFG->maintenance_enabled == true && !empty($CFG->maintenance_message)) {
+            $this->maintenance = $CFG->maintenance_message;
+        }
+
         // Identity providers.
         $this->identityproviders = \auth_plugin_base::get_identity_providers($authsequence);
         $this->logintoken = \core\session\manager::get_login_token();
@@ -145,6 +151,7 @@ class login implements renderable, templatable {
         $data->signupurl = $this->signupurl->out(false);
         $data->username = $this->username;
         $data->logintoken = $this->logintoken;
+        $data->maintenance = format_text($this->maintenance, FORMAT_MOODLE);
 
         return $data;
     }
index 7a063ab..da0f7e9 100644 (file)
@@ -164,8 +164,4 @@ $string['diag_toooldversion'] = 'It is very unlikely a modern LDAP server uses L
 $string['diag_emptycontext'] = 'Empty context found.';
 $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\'';
-$string['auth_ldap_creators_key'] = 'Creators';
 $string['privacy:metadata'] = 'The LDAP server authentication plugin does not store any personal data.';
diff --git a/auth/ldap/lang/en/deprecated.txt b/auth/ldap/lang/en/deprecated.txt
deleted file mode 100644 (file)
index fa73d1e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-auth_ldap_creators,auth_ldap
-auth_ldap_creators_key,auth_ldap
index 448c00e..1d1ff6f 100644 (file)
@@ -36,6 +36,7 @@ use core\oauth2\client;
 
 require_once($CFG->libdir.'/authlib.php');
 require_once($CFG->dirroot.'/user/lib.php');
+require_once($CFG->dirroot.'/user/profile/lib.php');
 
 /**
  * Plugin for oauth2 authentication.
index 2169dae..133df3f 100644 (file)
@@ -36,6 +36,7 @@
 
 module.exports = ({ template, types }) => {
     const fs = require('fs');
+    const path = require('path');
     const glob = require('glob');
     const cwd = process.cwd();
 
@@ -72,7 +73,7 @@ module.exports = ({ template, types }) => {
             var rawContents = fs.readFileSync(file);
             var subplugins = JSON.parse(rawContents);
 
-            for (const [component, path] of Object.entries(subplugins)) {
+            for (const [component, path] of Object.entries(subplugins.plugintypes)) {
                 if (path) {
                     moodlePlugins[path] = component;
                 }
@@ -92,7 +93,7 @@ module.exports = ({ template, types }) => {
      */
     function getModuleNameFromFileName(searchFileName) {
         searchFileName = fs.realpathSync(searchFileName);
-        const relativeFileName = searchFileName.replace(`${cwd}/`, '');
+        const relativeFileName = searchFileName.replace(`${cwd}${path.sep}`, '').replace(/\\/g, '/');
         const [componentPath, file] = relativeFileName.split('/amd/src/');
         const fileName = file.replace('.js', '');
 
@@ -202,4 +203,4 @@ module.exports = ({ template, types }) => {
             }
         }
     };
-};
\ No newline at end of file
+};
index e0606d1..5090f39 100644 (file)
@@ -150,6 +150,12 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
     $loghtml = '';
     if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
 
+        // Before we perform the backup check settings to see if user
+        // or setting defaults are set to exclude files from the backup.
+        if ($backup->get_setting_value('files') == 0) {
+            $renderer->set_samesite_notification();
+        }
+
         if ($backupmode != backup::MODE_ASYNC) {
             // Synchronous backup handling.
 
@@ -180,6 +186,7 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
             // Hide the progress display and first backup step bar (the 'finished' step will show next).
             echo html_writer::end_div();
             echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
+
         } else {
             // Async backup handling.
             $backup->get_controller()->finish_ui();
@@ -203,6 +210,7 @@ if (!async_helper::is_async_pending($id, 'course', 'backup')) {
                     'restoreurl' => $restoreurl->out(),
                     'headingident' => 'backup'
             );
+
             echo $renderer->render_from_template('core/async_backup_status', $progresssetup);
         }
 
index 9ac5f5b..cf5fcf9 100644 (file)
@@ -271,6 +271,37 @@ class backup_controller extends base_controller {
         return $this->includefiles;
     }
 
+    /**
+     * Returns the default value for $this->includefiles before we consider any settings.
+     *
+     * @return bool
+     * @throws dml_exception
+     */
+    protected function get_include_files_default() : bool {
+        // We normally include files.
+        $includefiles = true;
+
+        // In an import, we don't need to include files.
+        if ($this->get_mode() === backup::MODE_IMPORT) {
+            $includefiles = false;
+        }
+
+        // When a backup is intended for the same site, we don't need to include the files.
+        // Note, this setting is only used for duplication of an entire course.
+        if ($this->get_mode() === backup::MODE_SAMESITE) {
+            $includefiles = false;
+        }
+
+        // If backup is automated and we have set auto backup config to exclude
+        // files then set them to be excluded here.
+        $backupautofiles = (bool) get_config('backup', 'backup_auto_files');
+        if ($this->get_mode() === backup::MODE_AUTOMATED && !$backupautofiles) {
+            $includefiles = false;
+        }
+
+        return $includefiles;
+    }
+
     public function get_operation() {
         return $this->operation;
     }
@@ -326,6 +357,12 @@ class backup_controller extends base_controller {
         // Basic/initial prevention against time/memory limits
         core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
         raise_memory_limit(MEMORY_EXTRA);
+
+        // If the controller has decided that we can include files, then check the setting, otherwise do not include files.
+        if ($this->get_include_files()) {
+            $this->set_include_files((bool) $this->get_plan()->get_setting('files')->get_value());
+        }
+
         // If this is not a course backup, or single activity backup (e.g. duplicate) inform the plan we are not
         // including all the activities for sure. This will affect any
         // task/step executed conditionally to stop including information
@@ -386,35 +423,19 @@ class backup_controller extends base_controller {
         $this->log('applying plan defaults', backup::LOG_DEBUG);
         backup_controller_dbops::apply_config_defaults($this);
         $this->set_status(backup::STATUS_CONFIGURED);
-        $this->set_include_files();
+        $this->set_include_files($this->get_include_files_default());
     }
 
     /**
      * Set the initial value for the include_files setting.
      *
+     * @param bool $includefiles
      * @see backup_controller::get_include_files for further information on the purpose of this setting.
-     * @return int Indicates whether files should be included in backups.
      */
-    protected function set_include_files() {
-        // We normally include files.
-        $includefiles = true;
-
-        // In an import, we don't need to include files.
-        if ($this->get_mode() === backup::MODE_IMPORT) {
-            $includefiles = false;
-        }
-
-        // When a backup is intended for the same site, we don't need to include the files.
-        // Note, this setting is only used for duplication of an entire course.
-        if ($this->get_mode() === backup::MODE_SAMESITE) {
-            $includefiles = false;
-        }
-
-        $this->includefiles = (int) $includefiles;
+    protected function set_include_files(bool $includefiles) {
         $this->log("setting file inclusion to {$this->includefiles}", backup::LOG_DEBUG);
-        return $this->includefiles;
+        $this->includefiles = (int) $includefiles;
     }
-
 }
 
 /*
index 03e989d..ee434c1 100644 (file)
@@ -110,6 +110,12 @@ class backup_root_task extends backup_task {
         $this->add_setting($blocks);
         $this->converter_deps($blocks, $converters);
 
+        // Define files.
+        $files = new backup_generic_setting('files', base_setting::IS_BOOLEAN, true);
+        $files->set_ui(new backup_setting_ui_checkbox($files, get_string('rootsettingfiles', 'backup')));
+        $this->add_setting($files);
+        $this->converter_deps($files, $converters);
+
         // Define filters
         $filters = new backup_generic_setting('filters', base_setting::IS_BOOLEAN, true);
         $filters->set_ui(new backup_setting_ui_checkbox($filters, get_string('rootsettingfilters', 'backup')));
index 615f802..d3a650b 100644 (file)
@@ -201,7 +201,8 @@ abstract class backup_plan_dbops extends backup_dbops {
     * @param bool $useidonly only use the ID in the file name
     * @return string The filename to use
     */
-    public static function get_default_backup_filename($format, $type, $id, $users, $anonymised, $useidonly = false) {
+    public static function get_default_backup_filename($format, $type, $id, $users, $anonymised,
+            $useidonly = false, $files = true) {
         global $DB;
 
         // Calculate backup word
@@ -251,6 +252,11 @@ abstract class backup_plan_dbops extends backup_dbops {
             $info = '-an';
         }
 
+        // Indicate if backup doesn't contain files.
+        if (!$files) {
+            $info .= '-nf';
+        }
+
         return $backupword . '-' . $format . '-' . $type . '-' .
                $name . '-' . $date . $info . '.mbz';
     }
index 7ef9117..0911c68 100644 (file)
@@ -1054,17 +1054,40 @@ abstract class restore_dbops {
                     // Create the file in the filepool if it does not exist yet.
                     if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) {
 
-                        // Even if a file has been deleted since the backup was made, the file metadata will remain in the
-                        // files table, and the file will not be moved to the trashdir.
-                        // Files are not cleared from the files table by cron until several days after deletion.
+                        // Even if a file has been deleted since the backup was made, the file metadata may remain in the
+                        // files table, and the file will not yet have been moved to the trashdir. e.g. a draft file version.
+                        // Try to recover from file table first.
                         if ($foundfiles = $DB->get_records('files', array('contenthash' => $file->contenthash), '', '*', 0, 1)) {
                             // Only grab one of the foundfiles - the file content should be the same for all entries.
                             $foundfile = reset($foundfiles);
                             $fs->create_file_from_storedfile($file_record, $foundfile->id);
                         } else {
-                            // A matching existing file record was not found in the database.
-                            $results[] = self::get_missing_file_result($file);
-                            continue;
+                            $filesystem = $fs->get_file_system();
+                            $restorefile = $file;
+                            $restorefile->contextid = $newcontextid;
+                            $restorefile->itemid = $rec->newitemid;
+                            $storedfile = new stored_file($fs, $restorefile);
+
+                            // Ok, let's try recover this file.
+                            // 1. We check if the file can be fetched locally without attempting to fetch
+                            //    from the trash.
+                            // 2. We check if we can get the remote filepath for the specified stored file.
+                            // 3. We check if the file can be fetched from the trash.
+                            // 4. All failed, say we couldn't find it.
+                            if ($filesystem->is_file_readable_locally_by_storedfile($storedfile)) {
+                                $localpath = $filesystem->get_local_path_from_storedfile($storedfile);
+                                $fs->create_file_from_pathname($file, $localpath);
+                            } else if ($filesystem->is_file_readable_remotely_by_storedfile($storedfile)) {
+                                $url = $filesystem->get_remote_path_from_storedfile($storedfile);
+                                $fs->create_file_from_url($file, $url);
+                            } else if ($filesystem->is_file_readable_locally_by_storedfile($storedfile, true)) {
+                                $localpath = $filesystem->get_local_path_from_storedfile($storedfile, true);
+                                $fs->create_file_from_pathname($file, $localpath);
+                            } else {
+                                // A matching file was not found.
+                                $results[] = self::get_missing_file_result($file);
+                                continue;
+                            }
                         }
                     }
                 }
index 980baf4..68ff822 100644 (file)
@@ -415,8 +415,9 @@ abstract class backup_cron_automated_helper {
             $id = $bc->get_id();
             $users = $bc->get_plan()->get_setting('users')->get_value();
             $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value();
+            $incfiles = (bool)$config->backup_auto_files;
             $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type,
-                    $id, $users, $anonymised));
+                    $id, $users, $anonymised, false, $incfiles));
 
             $bc->set_status(backup::STATUS_AWAITING);
 
index fa3bc79..167eae7 100644 (file)
@@ -287,7 +287,15 @@ abstract class backup_helper {
             $config = get_config('backup');
             $dir = $config->backup_auto_destination;
             if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
-                $filedest = $dir.'/'.backup_plan_dbops::get_default_backup_filename($format, $backuptype, $courseid, $hasusers, $isannon, !$config->backup_shortname);
+                $filedest = $dir.'/'
+                        .backup_plan_dbops::get_default_backup_filename(
+                                $format,
+                                $backuptype,
+                                $courseid,
+                                $hasusers,
+                                $isannon,
+                                !$config->backup_shortname,
+                                (bool)$config->backup_auto_files);
                 // first try to move the file, if it is not possible copy and delete instead
                 if (@rename($filepath, $filedest)) {
                     return null;
index 1e1caf8..aff941b 100644 (file)
@@ -155,7 +155,9 @@ class backup_ui_stage_initial extends backup_ui_stage {
                             $this->ui->get_type(),
                             $this->ui->get_controller_id(),
                             $this->ui->get_setting_value('users'),
-                            $this->ui->get_setting_value('anonymize')
+                            $this->ui->get_setting_value('anonymize'),
+                            false,
+                            (bool)$this->ui->get_setting_value('files')
                         );
                         $setting->set_value($filename);
                     }
@@ -457,7 +459,16 @@ class backup_ui_stage_confirmation extends backup_ui_stage {
                         $id = $this->ui->get_controller_id();
                         $users = $this->ui->get_setting_value('users');
                         $anonymised = $this->ui->get_setting_value('anonymize');
-                        $setting->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised));
+                        $files = (bool)$this->ui->get_setting_value('files');
+                        $filename = backup_plan_dbops::get_default_backup_filename(
+                                $format,
+                                $type,
+                                $id,
+                                $users,
+                                $anonymised,
+                                false,
+                                $files);
+                        $setting->set_value($filename);
                     }
                     $form->add_setting($setting, $task);
                     break;
@@ -628,6 +639,7 @@ class backup_ui_stage_complete extends backup_ui_stage_final {
         if (!empty($this->results['missing_files_in_pool'])) {
             $output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
         }
+        $output .= $renderer->get_samesite_notification();
         $output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
         $output .= $renderer->continue_button($restorerul);
         $output .= $renderer->box_end();
index 4ee83e9..a46499f 100644 (file)
@@ -43,6 +43,13 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
  */
 class core_backup_renderer extends plugin_renderer_base {
 
+    /**
+     * Same site notification display.
+     *
+     * @var string
+     */
+    private $samesitenotification = '';
+
     /**
      * Renderers a progress bar for the backup or restore given the items that make it up.
      *
@@ -80,6 +87,22 @@ class core_backup_renderer extends plugin_renderer_base {
         return $out;
     }
 
+    /**
+     * Set the same site backup notification.
+     *
+     */
+    public function set_samesite_notification() {
+        $this->samesitenotification = $this->output->notification(get_string('samesitenotification', 'backup'), 'info');
+    }
+
+    /**
+     * Get the same site backup notification.
+     *
+     */
+    public function get_samesite_notification() {
+        return $this->samesitenotification;
+    }
+
     /**
      * Prints a dependency notification
      *
index 57e14e5..01b71bc 100644 (file)
Binary files a/blocks/myoverview/amd/build/view.min.js and b/blocks/myoverview/amd/build/view.min.js differ
index 8529c62..5de1f87 100644 (file)
Binary files a/blocks/myoverview/amd/build/view.min.js.map and b/blocks/myoverview/amd/build/view.min.js.map differ
index 0dc37ba..40d48b9 100644 (file)
@@ -67,7 +67,17 @@ function(
         NOCOURSES: 'core_course/no-courses'
     };
 
-    var NUMCOURSES_PERPAGE = [12, 24, 48];
+    var GROUPINGS = {
+        GROUPING_ALLINCLUDINGHIDDEN: 'allincludinghidden',
+        GROUPING_ALL: 'all',
+        GROUPING_INPROGRESS: 'inprogress',
+        GROUPING_FUTURE: 'future',
+        GROUPING_PAST: 'past',
+        GROUPING_FAVOURITES: 'favourites',
+        GROUPING_HIDDEN: 'hidden'
+    };
+
+    var NUMCOURSES_PERPAGE = [12, 24, 48, 96, 0];
 
     var loadedPages = [];
 
@@ -252,15 +262,104 @@ function(
         }).catch(Notification.exception);
     };
 
+    /**
+     * Get the action menu item
+     *
+     * @param {Object} root  root The course overview container
+     * @param {Number} courseId Course id.
+     * @return {Object} The hide course menu item.
+     */
+    var getHideCourseMenuItem = function(root, courseId) {
+        return root.find('[data-action="hide-course"][data-course-id="' + courseId + '"]');
+    };
+
+    /**
+     * Get the action menu item
+     *
+     * @param {Object} root  root The course overview container
+     * @param {Number} courseId Course id.
+     * @return {Object} The show course menu item.
+     */
+    var getShowCourseMenuItem = function(root, courseId) {
+        return root.find('[data-action="show-course"][data-course-id="' + courseId + '"]');
+    };
+
+    /**
+     * Hide course
+     *
+     * @param  {Object} root The course overview container
+     * @param  {Number} courseId Course id number
+     */
+    var hideCourse = function(root, courseId) {
+        var hideAction = getHideCourseMenuItem(root, courseId);
+        var showAction = getShowCourseMenuItem(root, courseId);
+        var filters = getFilterValues(root);
+
+        setCourseHiddenState(courseId, true);
+
+        // Remove the course from this view as it is now hidden and thus not covered by this view anymore.
+        // Do only if we are not in "All" view mode where really all courses are shown.
+        if (filters.grouping != GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {
+            hideElement(root, courseId);
+        }
+
+        hideAction.addClass('hidden');
+        showAction.removeClass('hidden');
+    };
+
+    /**
+     * Show course
+     *
+     * @param  {Object} root The course overview container
+     * @param  {Number} courseId Course id number
+     */
+    var showCourse = function(root, courseId) {
+        var hideAction = getHideCourseMenuItem(root, courseId);
+        var showAction = getShowCourseMenuItem(root, courseId);
+        var filters = getFilterValues(root);
+
+        setCourseHiddenState(courseId, null);
+
+        // Remove the course from this view as it is now shown again and thus not covered by this view anymore.
+        // Do only if we are not in "All" view mode where really all courses are shown.
+        if (filters.grouping != GROUPINGS.GROUPING_ALLINCLUDINGHIDDEN) {
+            hideElement(root, courseId);
+        }
+
+        hideAction.removeClass('hidden');
+        showAction.addClass('hidden');
+    };
+
+    /**
+     * Set the courses hidden status and push to repository
+     *
+     * @param  {Number} courseId Course id to favourite.
+     * @param  {Bool} status new hidden status.
+     * @return {Promise} Repository promise.
+     */
+    var setCourseHiddenState = function(courseId, status) {
+
+        // If the given status is not hidden, the preference has to be deleted with a null value.
+        if (status === false) {
+            status = null;
+        }
+        return Repository.updateUserPreferences({
+            preferences: [
+                {
+                    type: 'block_myoverview_hidden_course_' + courseId,
+                    value: status
+                }
+            ]
+        });
+    };
+
     /**
      * Reset the loadedPages dataset to take into account the hidden element
      *
      * @param {Object} root The course overview container
-     * @param {Object} target The course that you want to hide
+     * @param {Number} id The course id number
      */
-    var hideElement = function(root, target) {
-        var id = getCourseId(target);
-
+    var hideElement = function(root, id) {
         var pagingBar = root.find('[data-region="paging-bar"]');
         var jumpto = parseInt(pagingBar.attr('data-active-page-number'));
 
@@ -421,19 +520,24 @@ function(
     var initializePagedContent = function(root) {
         namespace = "block_myoverview_" + root.attr('id') + "_" + Math.random();
 
-        var itemsPerPage = NUMCOURSES_PERPAGE;
         var pagingLimit = parseInt(root.find(Selectors.courseView.region).attr('data-paging'), 10);
-        if (pagingLimit) {
-            itemsPerPage = NUMCOURSES_PERPAGE.map(function(value) {
-                var active = false;
-                if (value == pagingLimit) {
-                    active = true;
-                }
+        var itemsPerPage = NUMCOURSES_PERPAGE.map(function(value) {
+            var active = false;
+            if (value == pagingLimit) {
+                active = true;
+            }
+
+            return {
+                value: value,
+                active: active
+            };
+        });
 
-                return {
-                    value: value,
-                    active: active
-                };
+        // Filter out all pagination options which are too large for the amount of courses user is enrolled in.
+        var totalCourseCount = parseInt(root.find(Selectors.courseView.region).attr('data-totalcoursecount'), 10);
+        if (totalCourseCount) {
+            itemsPerPage = itemsPerPage.filter(function(pagingOption) {
+                return pagingOption.value < totalCourseCount;
             });
         }
 
@@ -448,7 +552,7 @@ function(
 
                 pagesData.forEach(function(pageData) {
                     var currentPage = pageData.pageNumber;
-                    var limit = pageData.limit;
+                    var limit = (pageData.limit > 0) ? pageData.limit : 0;
 
                     // Reset local variables if limits have changed
                     if (lastLimit != limit) {
@@ -491,7 +595,7 @@ function(
                             }
                         } else {
                             nextPageStart = pageData.limit;
-                            pageCourses = courses.slice(0, pageData.limit);
+                            pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses;
                         }
 
                         // Finished setting up the current page
@@ -500,7 +604,7 @@ function(
                         };
 
                         // Set up the next page
-                        var remainingCourses = courses.slice(nextPageStart, courses.length);
+                        var remainingCourses = nextPageStart ? courses.slice(nextPageStart, courses.length) : [];
                         if (remainingCourses.length) {
                             loadedPages[currentPage + 1] = {
                                 courses: remainingCourses
@@ -565,38 +669,15 @@ function(
 
         root.on(CustomEvents.events.activate, SELECTORS.ACTION_HIDE_COURSE, function(e, data) {
             var target = $(e.target).closest(SELECTORS.ACTION_HIDE_COURSE);
-            var id = getCourseId(target);
-
-            var request = {
-                preferences: [
-                    {
-                        type: 'block_myoverview_hidden_course_' + id,
-                        value: true
-                    }
-                ]
-            };
-            Repository.updateUserPreferences(request);
-
-            hideElement(root, target);
+            var courseId = getCourseId(target);
+            hideCourse(root, courseId);
             data.originalEvent.preventDefault();
         });
 
         root.on(CustomEvents.events.activate, SELECTORS.ACTION_SHOW_COURSE, function(e, data) {
             var target = $(e.target).closest(SELECTORS.ACTION_SHOW_COURSE);
-            var id = getCourseId(target);
-
-            var request = {
-                preferences: [
-                    {
-                        type: 'block_myoverview_hidden_course_' + id,
-                        value: null
-                    }
-                ]
-            };
-
-            Repository.updateUserPreferences(request);
-
-            hideElement(root, target);
+            var courseId = getCourseId(target);
+            showCourse(root, courseId);
             data.originalEvent.preventDefault();
         });
     };
index 8df2b47..5f4a033 100644 (file)
@@ -81,6 +81,55 @@ class main implements renderable, templatable {
      */
     private $layouts;
 
+    /**
+     * Store a course grouping option setting
+     *
+     * @var boolean
+     */
+    private $displaygroupingallincludinghidden;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingall;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupinginprogress;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingfuture;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingpast;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupingstarred;
+
+    /**
+     * Store a course grouping option setting.
+     *
+     * @var boolean
+     */
+    private $displaygroupinghidden;
+
     /**
      * main constructor.
      * Initialize the user preferences
@@ -92,19 +141,89 @@ class main implements renderable, templatable {
      * @throws \dml_exception
      */
     public function __construct($grouping, $sort, $view, $paging) {
-        $this->grouping = $grouping ? $grouping : BLOCK_MYOVERVIEW_GROUPING_ALL;
+        // Get plugin config.
+        $config = get_config('block_myoverview');
+
+        // Build the course grouping option name to check if the given grouping is enabled afterwards.
+        $groupingconfigname = 'displaygrouping'.$grouping;
+        // Check the given grouping and remember it if it is enabled.
+        if ($grouping && $config->$groupingconfigname == true) {
+            $this->grouping = $grouping;
+
+            // Otherwise fall back to another grouping in a reasonable order.
+            // This is done to prevent one-time UI glitches in the case when a user has chosen a grouping option previously which
+            // was then disabled by the admin in the meantime.
+        } else if ($config->displaygroupingall == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALL;
+        } else if ($config->displaygroupingallincludinghidden == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_ALLINCLUDINGHIDDEN;
+        } else if ($config->displaygroupinginprogress == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_INPROGRESS;
+        } else if ($config->displaygroupingfuture == true) {
+            $this->grouping = BLOCK_MYOVERVIEW_GROUPING_FUTURE;
+        } else if ($config->displaygroupingpast == true)&nb