Merge branch 'MDL-68200-master-take2' of git://github.com/rezaies/moodle
authorSara Arjona <sara@moodle.com>
Thu, 14 May 2020 11:10:01 +0000 (13:10 +0200)
committerSara Arjona <sara@moodle.com>
Thu, 14 May 2020 11:10:01 +0000 (13:10 +0200)
487 files changed:
.gherkin-lintrc
.nvmrc
Gruntfile.js
admin/cli/adhoc_task.php [new file with mode: 0644]
admin/cli/cfg.php
admin/cli/scheduled_task.php [new file with mode: 0644]
admin/settings/appearance.php
admin/settings/courses.php
admin/settings/h5p.php
admin/tests/behat/filter_users.feature
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js
admin/tool/lp/amd/build/menubar.min.js
admin/tool/lp/amd/build/menubar.min.js.map
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/db/services.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/lib.php
admin/tool/mobile/settings.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/version.php
admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js
admin/tool/policy/styles.css
admin/tool/task/classes/run_from_cli.php
admin/tool/task/cli/adhoc_task.php
admin/tool/task/cli/schedule_task.php
admin/tool/task/renderer.php
admin/tool/task/schedule_task.php
admin/tool/uploadcourse/tests/behat/create.feature
admin/tool/uploadcourse/tests/behat/update.feature
admin/tool/uploaduser/tests/behat/upload_users.feature
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/build/tour.min.js.map
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js
availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js
availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js
availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js
availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js
availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js
availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js
backup/moodle2/backup_course_task.class.php
backup/moodle2/backup_root_task.class.php
backup/moodle2/backup_settingslib.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_settingslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_test.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/dbops/restore_controller_dbops.class.php
backup/util/ui/tests/behat/import_contentbank_content.feature [new file with mode: 0644]
backup/util/ui/tests/behat/restore_moodle2_courses_settings.feature
backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js
backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js
badges/tests/behat/award_badge_groups.feature
blocks/navigation/amd/build/ajax_response_renderer.min.js
blocks/navigation/amd/build/ajax_response_renderer.min.js.map
blocks/recentlyaccesseditems/tests/behat/block_recentlyaccesseditems_dashboard.feature
blocks/settings/styles.css
calendar/amd/build/view_manager.min.js
calendar/amd/build/view_manager.min.js.map
contentbank/amd/build/search.min.js [new file with mode: 0644]
contentbank/amd/build/search.min.js.map [new file with mode: 0644]
contentbank/amd/build/selectors.min.js [new file with mode: 0644]
contentbank/amd/build/selectors.min.js.map [new file with mode: 0644]
contentbank/amd/src/search.js [new file with mode: 0644]
contentbank/amd/src/selectors.js [new file with mode: 0644]
contentbank/classes/contentbank.php
contentbank/classes/output/bankcontent.php
contentbank/contenttype/h5p/tests/behat/admin_upload_content.feature
contentbank/index.php
contentbank/templates/bankcontent.mustache
contentbank/templates/bankcontent/search.mustache [new file with mode: 0644]
contentbank/templates/bankcontent/toolbar.mustache [moved from contentbank/templates/toolbar.mustache with 91% similarity]
contentbank/tests/behat/access_permissions.feature
contentbank/tests/behat/events.feature
contentbank/tests/behat/search_content.feature [new file with mode: 0644]
contentbank/tests/content_test.php
contentbank/tests/contentbank_test.php
contentbank/tests/contenttype_test.php
contentbank/tests/external/delete_content_test.php
contentbank/tests/external/rename_content_test.php
contentbank/tests/privacy_test.php
course/amd/build/activitychooser.min.js
course/amd/build/activitychooser.min.js.map
course/amd/build/local/activitychooser/dialogue.min.js
course/amd/build/local/activitychooser/dialogue.min.js.map
course/amd/src/activitychooser.js
course/amd/src/local/activitychooser/dialogue.js
course/format/singleactivity/tests/behat/edit_format_course.feature
course/tests/behat/category_resort.feature
course/tests/behat/course_category_management_listing.feature
course/tests/behat/course_resort.feature
course/tests/behat/create_delete_course.feature
course/tests/behat/edit_settings.feature
course/yui/build/moodle-course-categoryexpander/moodle-course-categoryexpander-min.js
course/yui/build/moodle-course-coursebase/moodle-course-coursebase-min.js
course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js
course/yui/build/moodle-course-formatchooser/moodle-course-formatchooser-min.js
course/yui/build/moodle-course-management/moodle-course-management-debug.js
course/yui/build/moodle-course-management/moodle-course-management-min.js
course/yui/build/moodle-course-management/moodle-course-management.js
course/yui/build/moodle-course-util-base/moodle-course-util-base-min.js
course/yui/build/moodle-course-util-cm/moodle-course-util-cm-min.js
course/yui/build/moodle-course-util-section/moodle-course-util-section-min.js
course/yui/src/management/build.json
course/yui/src/management/js/category.js
course/yui/src/management/js/console.js
course/yui/src/management/js/course.js
course/yui/src/management/js/dd.js
course/yui/src/management/js/item.js
course/yui/src/management/js/shared.js [new file with mode: 0644]
customfield/field/date/tests/behat/field.feature
customfield/tests/behat/unique_field.feature
enrol/guest/tests/behat/guest_access.feature
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js
grade/grading/form/rubric/tests/behat/grade_calculation.feature
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js
grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js
grade/tests/behat/grade_override_letter.feature
group/tests/behat/create_groups.feature
group/tests/behat/group_description.feature
h5p/classes/core.php
h5p/classes/editor.php
h5p/classes/editor_ajax.php
h5p/classes/editor_framework.php
h5p/classes/helper.php
h5p/classes/local/library/autoloader.php
h5p/classes/local/library/handler.php
h5p/h5plib/v124/joubel/editor/readme_moodle.txt
h5p/h5plib/v124/lang/en/h5plib_v124.php
h5p/tests/editor_ajax_test.php
h5p/tests/editor_framework_test.php
h5p/tests/editor_test.php
h5p/tests/generator/lib.php
h5p/tests/h5p_core_test.php
h5p/tests/local/library/handler_test.php [new file with mode: 0644]
lang/en/admin.php
lang/en/backup.php
lang/en/cache.php
lang/en/contentbank.php
lib/adminlib.php
lib/amd/build/adapter.min.js
lib/amd/build/adapter.min.js.map
lib/amd/build/chart_base.min.js
lib/amd/build/chart_base.min.js.map
lib/amd/build/chart_series.min.js
lib/amd/build/chart_series.min.js.map
lib/amd/build/chartjs-lazy.min.js
lib/amd/build/chartjs-lazy.min.js.map
lib/amd/build/drawer.min.js
lib/amd/build/drawer.min.js.map
lib/amd/build/emoji/auto_complete.min.js
lib/amd/build/emoji/auto_complete.min.js.map
lib/amd/build/emoji/picker.min.js
lib/amd/build/emoji/picker.min.js.map
lib/amd/build/local/modal/alert.min.js
lib/amd/build/local/modal/alert.min.js.map
lib/amd/build/loglevel.min.js
lib/amd/build/loglevel.min.js.map
lib/amd/build/modal.min.js
lib/amd/build/modal.min.js.map
lib/amd/build/modal_cancel.min.js
lib/amd/build/modal_cancel.min.js.map
lib/amd/build/modal_factory.min.js
lib/amd/build/modal_factory.min.js.map
lib/amd/build/modal_save_cancel.min.js
lib/amd/build/modal_save_cancel.min.js.map
lib/amd/build/mustache.min.js
lib/amd/build/mustache.min.js.map
lib/amd/build/notification.min.js
lib/amd/build/notification.min.js.map
lib/amd/build/popper.min.js
lib/amd/build/popper.min.js.map
lib/amd/build/prefetch.min.js
lib/amd/build/prefetch.min.js.map
lib/amd/build/sortable_list.min.js
lib/amd/build/sortable_list.min.js.map
lib/amd/build/str.min.js
lib/amd/build/str.min.js.map
lib/amd/build/templates.min.js
lib/amd/build/templates.min.js.map
lib/amd/build/toast.min.js
lib/amd/build/toast.min.js.map
lib/amd/build/truncate.min.js
lib/amd/build/truncate.min.js.map
lib/amd/src/modal.js
lib/behat/classes/behat_core_generator.php
lib/classes/qrcode.php [new file with mode: 0644]
lib/classes/task/h5p_get_content_types_task.php
lib/classes/task/manager.php
lib/cronlib.php
lib/db/caches.php
lib/deprecatedlib.php
lib/editor/atto/plugins/accessibilitychecker/yui/build/moodle-atto_accessibilitychecker-button/moodle-atto_accessibilitychecker-button-min.js
lib/editor/atto/plugins/accessibilityhelper/yui/build/moodle-atto_accessibilityhelper-button/moodle-atto_accessibilityhelper-button-min.js
lib/editor/atto/plugins/align/yui/build/moodle-atto_align-button/moodle-atto_align-button-min.js
lib/editor/atto/plugins/backcolor/yui/build/moodle-atto_backcolor-button/moodle-atto_backcolor-button-min.js
lib/editor/atto/plugins/bold/yui/build/moodle-atto_bold-button/moodle-atto_bold-button-min.js
lib/editor/atto/plugins/charmap/yui/build/moodle-atto_charmap-button/moodle-atto_charmap-button-min.js
lib/editor/atto/plugins/clear/yui/build/moodle-atto_clear-button/moodle-atto_clear-button-min.js
lib/editor/atto/plugins/collapse/yui/build/moodle-atto_collapse-button/moodle-atto_collapse-button-min.js
lib/editor/atto/plugins/emojipicker/yui/build/moodle-atto_emojipicker-button/moodle-atto_emojipicker-button-min.js
lib/editor/atto/plugins/emoticon/yui/build/moodle-atto_emoticon-button/moodle-atto_emoticon-button-min.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js
lib/editor/atto/plugins/fontcolor/yui/build/moodle-atto_fontcolor-button/moodle-atto_fontcolor-button-min.js
lib/editor/atto/plugins/h5p/yui/build/moodle-atto_h5p-button/moodle-atto_h5p-button-min.js
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-beautify/moodle-atto_html-beautify-min.js
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-button/moodle-atto_html-button-min.js
lib/editor/atto/plugins/html/yui/build/moodle-atto_html-codemirror/moodle-atto_html-codemirror-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/indent/yui/build/moodle-atto_indent-button/moodle-atto_indent-button-min.js
lib/editor/atto/plugins/italic/yui/build/moodle-atto_italic-button/moodle-atto_italic-button-min.js
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-button/moodle-atto_managefiles-button-min.js
lib/editor/atto/plugins/managefiles/yui/build/moodle-atto_managefiles-usedfiles/moodle-atto_managefiles-usedfiles-min.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js
lib/editor/atto/plugins/noautolink/yui/build/moodle-atto_noautolink-button/moodle-atto_noautolink-button-min.js
lib/editor/atto/plugins/orderedlist/yui/build/moodle-atto_orderedlist-button/moodle-atto_orderedlist-button-min.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-button/moodle-atto_recordrtc-button-min.js
lib/editor/atto/plugins/recordrtc/yui/build/moodle-atto_recordrtc-recording/moodle-atto_recordrtc-recording-min.js
lib/editor/atto/plugins/rtl/yui/build/moodle-atto_rtl-button/moodle-atto_rtl-button-min.js
lib/editor/atto/plugins/strike/yui/build/moodle-atto_strike-button/moodle-atto_strike-button-min.js
lib/editor/atto/plugins/subscript/yui/build/moodle-atto_subscript-button/moodle-atto_subscript-button-min.js
lib/editor/atto/plugins/superscript/yui/build/moodle-atto_superscript-button/moodle-atto_superscript-button-min.js
lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button-min.js
lib/editor/atto/plugins/underline/yui/build/moodle-atto_underline-button/moodle-atto_underline-button-min.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js
lib/editor/atto/plugins/unorderedlist/yui/build/moodle-atto_unorderedlist-button/moodle-atto_unorderedlist-button-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/build/moodle-editor_atto-menu/moodle-editor_atto-menu-min.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js
lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin.js
lib/editor/atto/yui/build/moodle-editor_atto-rangy/moodle-editor_atto-rangy-min.js
lib/editor/atto/yui/src/editor/js/autosave.js
lib/editor/atto/yui/src/editor/js/clean.js
lib/editor/atto/yui/src/editor/js/editor-plugin-buttons.js
lib/editor/atto/yui/src/editor/js/selection.js
lib/editor/atto/yui/src/editor/js/styling.js
lib/editor/atto/yui/src/editor/js/toolbar-keyboardnav.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js
lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-min.js
lib/form/yui/src/dateselector/build.json
lib/form/yui/src/dateselector/js/calendar.js
lib/form/yui/src/dateselector/js/dateselector.js
lib/form/yui/src/dateselector/js/moodlecalendar.js
lib/form/yui/src/dateselector/js/shared.js [new file with mode: 0644]
lib/grouplib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/table/amd/build/dynamic.min.js
lib/table/amd/build/dynamic.min.js.map
lib/table/amd/src/dynamic.js
lib/tablelib.php
lib/tests/adminlib_test.php [new file with mode: 0644]
lib/tests/behat/securelayout.feature
lib/tests/grouplib_test.php
lib/tests/qrcode_test.php [new file with mode: 0644]
lib/upgrade.txt
lib/yui/build/moodle-core-actionmenu/moodle-core-actionmenu-min.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks-debug.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks-min.js
lib/yui/build/moodle-core-blocks/moodle-core-blocks.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
lib/yui/build/moodle-core-event/moodle-core-event-min.js
lib/yui/build/moodle-core-formchangechecker/moodle-core-formchangechecker-min.js
lib/yui/build/moodle-core-handlebars/moodle-core-handlebars-min.js
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js
lib/yui/build/moodle-core-lockscroll/moodle-core-lockscroll-min.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer-min.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-debug.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception-min.js
lib/yui/build/moodle-core-notification-ajaxexception/moodle-core-notification-ajaxexception.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-debug.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-min.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm.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/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js
lib/yui/build/moodle-core-notification/moodle-core-notification-min.js
lib/yui/build/moodle-core-popuphelp/moodle-core-popuphelp-min.js
lib/yui/build/moodle-core-tooltip/moodle-core-tooltip-min.js
lib/yui/build/moodle-core-widget-focusafterclose/moodle-core-widget-focusafterclose-min.js
lib/yui/src/blocks/build.json
lib/yui/src/blocks/js/blockregion.js
lib/yui/src/blocks/js/blocks.js
lib/yui/src/blocks/js/manager.js
lib/yui/src/blocks/js/shared.js [new file with mode: 0644]
lib/yui/src/languninstallconfirm/js/languninstallconfirm.js
lib/yui/src/notification/js/ajaxexception.js
lib/yui/src/notification/js/alert.js
lib/yui/src/notification/js/confirm.js
lib/yui/src/notification/js/dialogue.js
lib/yui/src/notification/js/exception.js
lib/yui/src/notification/js/info.js
lib/yui/src/notification/js/shared.js
media/player/videojs/amd/build/Youtube-lazy.min.js
media/player/videojs/amd/build/Youtube-lazy.min.js.map
media/player/videojs/amd/build/video-lazy.min.js
media/player/videojs/amd/build/video-lazy.min.js.map
media/player/videojs/amd/build/videojs-flash-lazy.min.js
media/player/videojs/amd/build/videojs-flash-lazy.min.js.map
message/amd/build/message_drawer_router.min.js
message/amd/build/message_drawer_router.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_patcher.min.js
message/amd/build/message_drawer_view_conversation_patcher.min.js.map
message/amd/build/message_drawer_view_search.min.js
message/amd/build/message_drawer_view_search.min.js.map
message/output/airnotifier/yui/build/moodle-message_airnotifier-toolboxes/moodle-message_airnotifier-toolboxes-min.js
message/output/popup/amd/build/notification_area_control_area.min.js
message/output/popup/amd/build/notification_area_control_area.min.js.map
message/tests/behat/message_manage_notification_preferences.feature
message/tests/behat/message_preferences.feature
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/annotationhighlight.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationline.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationoval.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationpen.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationrectangle.js
mod/assign/feedback/editpdf/yui/src/editor/js/annotationstamp.js
mod/assign/feedback/editpdf/yui/src/editor/js/comment.js
mod/assign/feedback/editpdf/yui/src/editor/js/commentsearch.js
mod/assign/feedback/editpdf/yui/src/editor/js/drawable.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/quickcommentlist.js
mod/assign/tests/behat/hide_grader.feature
mod/assign/yui/build/moodle-mod_assign-history/moodle-mod_assign-history-min.js
mod/data/tests/behat/add_entries.feature
mod/forum/amd/build/discussion_nested_v2.min.js
mod/forum/amd/build/discussion_nested_v2.min.js.map
mod/forum/amd/build/grades/expandconversation.min.js
mod/forum/amd/build/grades/expandconversation.min.js.map
mod/forum/amd/build/grades/grader.min.js
mod/forum/amd/build/grades/grader.min.js.map
mod/forum/amd/build/local/grades/grader.min.js
mod/forum/amd/build/local/grades/grader.min.js.map
mod/forum/amd/build/local/grades/local/grader/user_picker.min.js
mod/forum/amd/build/local/grades/local/grader/user_picker.min.js.map
mod/forum/amd/build/local/layout/fullscreen.min.js
mod/forum/amd/build/local/layout/fullscreen.min.js.map
mod/forum/tests/behat/add_forum_inline.feature
mod/forum/tests/behat/favourite_discussion.feature
mod/forum/tests/behat/inpage_reply.feature
mod/h5pactivity/backup/moodle2/backup_h5pactivity_stepslib.php
mod/h5pactivity/classes/local/attempt.php
mod/h5pactivity/classes/local/grader.php [new file with mode: 0644]
mod/h5pactivity/classes/local/manager.php [new file with mode: 0644]
mod/h5pactivity/classes/privacy/provider.php
mod/h5pactivity/classes/xapi/handler.php
mod/h5pactivity/db/install.xml
mod/h5pactivity/db/upgrade.php
mod/h5pactivity/lang/en/h5pactivity.php
mod/h5pactivity/lib.php
mod/h5pactivity/mod_form.php
mod/h5pactivity/tests/behat/define_settings.feature [new file with mode: 0644]
mod/h5pactivity/tests/behat/grading_attempts.feature [new file with mode: 0644]
mod/h5pactivity/tests/event/course_module_instance_list_viewed_test.php
mod/h5pactivity/tests/event/course_module_viewed_test.php
mod/h5pactivity/tests/event/statement_received_test.php
mod/h5pactivity/tests/generator/lib.php
mod/h5pactivity/tests/generator_test.php
mod/h5pactivity/tests/local/attempt_test.php
mod/h5pactivity/tests/local/grader_test.php [new file with mode: 0644]
mod/h5pactivity/tests/local/manager_test.php [new file with mode: 0644]
mod/h5pactivity/tests/privacy_test.php
mod/h5pactivity/tests/restore_test.php
mod/h5pactivity/tests/xapi/handler_test.php
mod/h5pactivity/version.php
mod/h5pactivity/view.php
mod/lesson/tests/behat/duplicate_lesson_page.feature
mod/lesson/tests/behat/import_fillintheblank_question.feature
mod/lesson/tests/behat/lesson_student_dashboard.feature
mod/lesson/tests/behat/teacher_grade_essays.feature
mod/quiz/accessrule/seb/tests/phpunit/backup_restore_test.php
mod/quiz/yui/build/moodle-mod_quiz-autosave/moodle-mod_quiz-autosave-min.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js
mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js
mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-min.js
mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-min.js
mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-min.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-min.js
mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-min.js
mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js
mod/quiz/yui/src/dragdrop/js/resource.js
mod/quiz/yui/src/toolboxes/js/resource.js
mod/quiz/yui/src/toolboxes/js/section.js
mod/resource/tests/behat/display_resource.feature
mod/scorm/tests/behat/completion_condition_require_status.feature
mod/scorm/tests/behat/multisco_review_mode.feature
mod/wiki/tests/behat/reset_wiki_comments_tags_files.feature
npm-shrinkwrap.json
package.json
pix/i/contentbank.png [new file with mode: 0644]
pix/i/contentbank.svg [new file with mode: 0644]
question/format/aiken/tests/behat/aiken_import.feature
question/type/ddimageortext/amd/build/question.min.js
question/type/ddimageortext/amd/build/question.min.js.map
question/type/ddmarker/amd/build/question.min.js
question/type/ddmarker/amd/build/question.min.js.map
question/type/ddmarker/styles.css
question/type/ddwtos/amd/build/ddwtos.min.js
question/type/ddwtos/amd/build/ddwtos.min.js.map
question/yui/build/moodle-question-chooser/moodle-question-chooser-min.js
question/yui/build/moodle-question-preview/moodle-question-preview-min.js
question/yui/build/moodle-question-searchform/moodle-question-searchform-min.js
report/configlog/classes/output/report_table.php
report/configlog/lang/en/report_configlog.php
report/configlog/tests/behat/view_report.feature
report/eventlist/yui/build/moodle-report_eventlist-eventfilter/moodle-report_eventlist-eventfilter-min.js
report/loglive/yui/build/moodle-report_loglive-fetchlogs/moodle-report_loglive-fetchlogs-min.js
repository/filepicker.js
repository/tests/behat/edit_file.feature [new file with mode: 0644]
repository/tests/behat/select_file.feature [new file with mode: 0644]
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/modal.min.js
theme/boost/amd/build/modal.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
theme/boost/amd/build/sanitizer.min.js.map
theme/boost/amd/build/scrollspy.min.js
theme/boost/amd/build/scrollspy.min.js.map
theme/boost/amd/build/tether.min.js
theme/boost/amd/build/tether.min.js.map
theme/boost/amd/build/toast.min.js
theme/boost/amd/build/toast.min.js.map
theme/boost/amd/build/tooltip.min.js
theme/boost/amd/build/tooltip.min.js.map
theme/boost/scss/moodle/chat.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/message.scss
theme/boost/scss/moodle/print.scss
theme/boost/scss/moodle/tables.scss
theme/boost/style/moodle.css
theme/boost/templates/navbar-secure.mustache
theme/boost/tests/behat/settingstabs.feature
theme/boost/tests/behat/tour_filter.feature
theme/classic/style/moodle.css
theme/classic/templates/navbar-secure.mustache
theme/upgrade.txt
user/amd/build/status_field.min.js
user/amd/build/status_field.min.js.map
user/classes/table/participants.php
user/classes/table/participants_search.php [new file with mode: 0644]
user/tests/behat/course_preference.feature
user/tests/behat/custom_profile_fields.feature
user/tests/behat/filter_idnumber.feature
user/tests/behat/filter_participants.feature
user/tests/behat/set_default_homepage.feature
user/tests/table/participants_search_test.php [new file with mode: 0644]
version.php

index 9439956..50de756 100644 (file)
@@ -3,6 +3,8 @@
     "Feature": 0,
     "Background": 2,
     "Scenario": 2,
+    "Examples": 4,
+    "example": 6,
     "Step": 4,
     "given": 4,
     "and": 4
@@ -14,7 +16,9 @@
   "no-multiple-empty-lines": "on",
   "no-partially-commented-tag-lines": "on",
   "no-trailing-spaces": "on",
-  "no-unamed-features": "on",
-  "no-unamed-scenarios": "on",
-  "no-scenario-outlines-without-examples": "on"
+  "no-unnamed-features": "on",
+  "no-unnamed-scenarios": "on",
+  "no-scenario-outlines-without-examples": "on",
+  "no-examples-in-scenarios": "on",
+  "new-line-at-eof": ["on", "yes"]
 }
diff --git a/.nvmrc b/.nvmrc
index 7796292..01f1a56 100644 (file)
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v8.16.1
+v14.0.0
index 657bd77..a7b728f 100644 (file)
@@ -149,6 +149,7 @@ module.exports = function(grunt) {
     const watchmanClient = new watchman.Client();
     const fs = require('fs');
     const ComponentList = require(path.resolve('GruntfileComponents.js'));
+    const sass = require('node-sass');
 
     // Verify the node version is new enough.
     var expected = semver.validRange(grunt.file.readJSON('package.json').engines.node);
@@ -358,6 +359,7 @@ module.exports = function(grunt) {
                 }
             },
             options: {
+                implementation: sass,
                 includePaths: ["theme/boost/scss/", "theme/classic/scss/"]
             }
         },
@@ -527,23 +529,28 @@ module.exports = function(grunt) {
         const options = grunt.config('gherkinlint.options');
 
         // Grab the gherkin-lint linter and required scaffolding.
-        const linter = require('gherkin-lint/src/linter.js');
-        const featureFinder = require('gherkin-lint/src/feature-finder.js');
-        const configParser = require('gherkin-lint/src/config-parser.js');
-        const formatter = require('gherkin-lint/src/formatters/stylish.js');
+        const linter = require('gherkin-lint/dist/linter.js');
+        const featureFinder = require('gherkin-lint/dist/feature-finder.js');
+        const configParser = require('gherkin-lint/dist/config-parser.js');
+        const formatter = require('gherkin-lint/dist/formatters/stylish.js');
 
         // Run the linter.
-        const results = linter.lint(
+        return linter.lint(
             featureFinder.getFeatureFiles(grunt.file.expand(options.files)),
             configParser.getConfiguration(configParser.defaultConfigFileName)
-        );
-
-        // Print the results out uncondtionally.
-        formatter.printResults(results);
-
-        // Report on the results.
-        // The done function takes a bool whereby a falsey statement causes the task to fail.
-        done(results.every(result => result.errors.length === 0));
+        )
+        .then(results => {
+            // Print the results out uncondtionally.
+            formatter.printResults(results);
+
+            return results;
+        })
+        .then(results => {
+            // Report on the results.
+            // The done function takes a bool whereby a falsey statement causes the task to fail.
+            return results.every(result => result.errors.length === 0);
+        })
+        .then(done); // eslint-disable-line promise/no-callback-in-promise
     };
 
     tasks.startup = function() {
diff --git a/admin/cli/adhoc_task.php b/admin/cli/adhoc_task.php
new file mode 100644 (file)
index 0000000..b0ed21d
--- /dev/null
@@ -0,0 +1,122 @@
+<?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/>.
+
+/**
+ * Task executor for adhoc tasks.
+ *
+ * @package    core
+ * @subpackage cli
+ * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__ . '/../../config.php');
+require_once("{$CFG->libdir}/clilib.php");
+require_once("{$CFG->libdir}/cronlib.php");
+
+list($options, $unrecognized) = cli_get_params(
+    [
+        'execute' => false,
+        'help' => false,
+        'keep-alive' => 0,
+        'showsql' => false,
+        'showdebugging' => false,
+        'ignorelimits' => false,
+    ], [
+        'h' => 'help',
+        'e' => 'execute',
+        'k' => 'keep-alive',
+        'i' => 'ignorelimits',
+    ]
+);
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help'] or empty($options['execute'])) {
+    $help = <<<EOT
+Ad hoc cron tasks.
+
+Options:
+ -h, --help                Print out this help
+     --showsql             Show sql queries before they are executed
+     --showdebugging       Show developer level debugging information
+ -e, --execute             Run all queued adhoc tasks
+ -k, --keep-alive=N        Keep this script alive for N seconds and poll for new adhoc tasks
+ -i  --ignorelimits        Ignore task_adhoc_concurrency_limit and task_adhoc_max_runtime limits
+
+Example:
+\$sudo -u www-data /usr/bin/php admin/cli/adhoc_task.php --execute
+
+EOT;
+
+    echo $help;
+    die;
+}
+
+if ($options['showdebugging']) {
+    set_debugging(DEBUG_DEVELOPER, true);
+}
+
+if ($options['showsql']) {
+    $DB->set_debug(true);
+}
+
+if (CLI_MAINTENANCE) {
+    echo "CLI maintenance mode active, cron execution suspended.\n";
+    exit(1);
+}
+
+if (moodle_needs_upgrading()) {
+    echo "Moodle upgrade pending, cron execution suspended.\n";
+    exit(1);
+}
+
+if (empty($options['execute'])) {
+    exit(0);
+}
+if (empty($options['keep-alive'])) {
+    $options['keep-alive'] = 0;
+}
+
+if (!empty($CFG->showcronsql)) {
+    $DB->set_debug(true);
+}
+if (!empty($CFG->showcrondebugging)) {
+    set_debugging(DEBUG_DEVELOPER, true);
+}
+
+$checklimits = empty($options['ignorelimits']);
+
+core_php_time_limit::raise();
+
+// Increase memory limit.
+raise_memory_limit(MEMORY_EXTRA);
+
+// Emulate normal session - we use admin account by default.
+cron_setup_user();
+
+$humantimenow = date('r', time());
+$keepalive = (int)$options['keep-alive'];
+
+\core\local\cli\shutdown::script_supports_graceful_exit();
+
+mtrace("Server Time: {$humantimenow}\n");
+cron_run_adhoc_tasks(time(), $keepalive, $checklimits);
index 7f56b92..35045ca 100644 (file)
@@ -124,7 +124,12 @@ if ($options['unset'] || $options['set'] !== null) {
         cli_error('The configuration variable is hard-set in the config.php, unable to change.', 4);
     }
 
-    set_config($options['name'], $options['set'], $options['component']);
+    $new = $options['set'];
+    $old = get_config($options['component'], $options['name']);
+    if ($new !== $old) {
+        set_config($options['name'], $options['set'], $options['component']);
+        add_to_config_log($options['name'], $old, $new, $options['component']);
+    }
     exit(0);
 }
 
diff --git a/admin/cli/scheduled_task.php b/admin/cli/scheduled_task.php
new file mode 100644 (file)
index 0000000..f825f46
--- /dev/null
@@ -0,0 +1,151 @@
+<?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/>.
+
+/**
+ * CLI task execution.
+ *
+ * @package    core
+ * @subpackage cli
+ * @copyright  2014 Petr Skoda
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__ . '/../../config.php');
+require_once("$CFG->libdir/clilib.php");
+require_once("$CFG->libdir/cronlib.php");
+
+list($options, $unrecognized) = cli_get_params(
+    array('help' => false, 'list' => false, 'execute' => false, 'showsql' => false, 'showdebugging' => false),
+    array('h' => 'help')
+);
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help'] or (!$options['list'] and !$options['execute'])) {
+    $help =
+    "Scheduled cron tasks.
+
+    Options:
+    --execute=\\some\\task  Execute scheduled task manually
+    --list                List all scheduled tasks
+    --showsql             Show sql queries before they are executed
+    --showdebugging       Show developer level debugging information
+    -h, --help            Print out this help
+
+    Example:
+    \$sudo -u www-data /usr/bin/php admin/cli/scheduled_task.php --execute=\\core\\task\\session_cleanup_task
+
+    ";
+
+    echo $help;
+    die;
+}
+
+if ($options['showdebugging']) {
+    set_debugging(DEBUG_DEVELOPER, true);
+}
+
+if ($options['showsql']) {
+    $DB->set_debug(true);
+}
+if ($options['list']) {
+    cli_heading("List of scheduled tasks ($CFG->wwwroot)");
+
+    $shorttime = get_string('strftimedatetimeshort');
+
+    $tasks = \core\task\manager::get_all_scheduled_tasks();
+    echo str_pad(get_string('scheduledtasks', 'tool_task'), 50, ' ') . ' ' . str_pad(get_string('runpattern', 'tool_task'), 17, ' ')
+        . ' ' . str_pad(get_string('lastruntime', 'tool_task'), 40, ' ') . get_string('nextruntime', 'tool_task') . "\n";
+    foreach ($tasks as $task) {
+        $class = '\\' . get_class($task);
+        $schedule = $task->get_minute() . ' '
+            . $task->get_hour() . ' '
+            . $task->get_day() . ' '
+            . $task->get_day_of_week() . ' '
+            . $task->get_month() . ' '
+            . $task->get_day_of_week();
+        $nextrun = $task->get_next_run_time();
+        $lastrun = $task->get_last_run_time();
+
+        $plugininfo = core_plugin_manager::instance()->get_plugin_info($task->get_component());
+        $plugindisabled = $plugininfo && $plugininfo->is_enabled() === false && !$task->get_run_if_component_disabled();
+
+        if ($plugindisabled) {
+            $nextrun = get_string('plugindisabled', 'tool_task');
+        } else if ($task->get_disabled()) {
+            $nextrun = get_string('taskdisabled', 'tool_task');
+        } else if ($nextrun > time()) {
+            $nextrun = userdate($nextrun);
+        } else {
+            $nextrun = get_string('asap', 'tool_task');
+        }
+
+        if ($lastrun) {
+            $lastrun = userdate($lastrun);
+        } else {
+            $lastrun = get_string('never');
+        }
+
+        echo str_pad($class, 50, ' ') . ' ' . str_pad($schedule, 17, ' ') .
+            ' ' . str_pad($lastrun, 40, ' ') . ' ' . $nextrun . "\n";
+    }
+    exit(0);
+}
+
+if ($execute = $options['execute']) {
+    if (!$task = \core\task\manager::get_scheduled_task($execute)) {
+        mtrace("Task '$execute' not found");
+        exit(1);
+    }
+
+    if (moodle_needs_upgrading()) {
+        mtrace("Moodle upgrade pending, cannot execute tasks.");
+        exit(1);
+    }
+
+    // Increase memory limit.
+    raise_memory_limit(MEMORY_EXTRA);
+
+    // Emulate normal session - we use admin account by default.
+    cron_setup_user();
+
+    // Execute the task.
+    \core\local\cli\shutdown::script_supports_graceful_exit();
+    $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
+    if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) {
+        mtrace('Cannot obtain cron lock');
+        exit(129);
+    }
+    if (!$lock = $cronlockfactory->get_lock('\\' . get_class($task), 10)) {
+        $cronlock->release();
+        mtrace('Cannot obtain task lock');
+        exit(130);
+    }
+
+    $task->set_lock($lock);
+    if (!$task->is_blocking()) {
+        $cronlock->release();
+    } else {
+        $task->set_cron_lock($cronlock);
+    }
+
+    cron_run_inner_scheduled_task($task);
+}
index c9d406a..4aa50b4 100644 (file)
@@ -25,6 +25,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
     $temp->add(new admin_setting_configcheckbox('allowcohortthemes',  new lang_string('allowcohortthemes', 'admin'), new lang_string('configallowcohortthemes', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowthemechangeonurl',  new lang_string('allowthemechangeonurl', 'admin'), new lang_string('configallowthemechangeonurl', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('allowuserblockhiding', new lang_string('allowuserblockhiding', 'admin'), new lang_string('configallowuserblockhiding', 'admin'), 1));
+    $temp->add(new admin_setting_configcheckbox('langmenuinsecurelayout',
+        new lang_string('langmenuinsecurelayout', 'admin'),
+        new lang_string('langmenuinsecurelayout_desc', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('logininfoinsecurelayout',
+        new lang_string('logininfoinsecurelayout', 'admin'),
+        new lang_string('logininfoinsecurelayout_desc', 'admin'), 0));
     $temp->add(new admin_setting_configtextarea('custommenuitems', new lang_string('custommenuitems', 'admin'),
         new lang_string('configcustommenuitems', 'admin'), '', PARAM_RAW, '50', '10'));
     $temp->add(new admin_setting_configtextarea(
index 841b72b..8936f7c 100644 (file)
@@ -235,6 +235,11 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
             new lang_string('generalgroups', 'backup'), new lang_string('configgeneralgroups', 'backup'),
             array('value' => 1, 'locked' => 0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), array('value'=>1, 'locked'=>0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_contentbankcontent',
+        new lang_string('generalcontentbankcontent', 'backup'),
+        new lang_string('configgeneralcontentbankcontent', 'backup'),
+        ['value' => 1, 'locked' => 0])
+    );
 
     $ADMIN->add('backups', $temp);
 
@@ -256,6 +261,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
             new lang_string('generalgroups', 'backup'), new lang_string('configgeneralgroups', 'backup'),
             array('value' => 1, 'locked' => 0)));
     $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_import_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), array('value'=>1, 'locked'=>0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock(
+        'backup/backup_import_contentbankcontent',
+        new lang_string('generalcontentbankcontent', 'backup'),
+        new lang_string('configgeneralcontentbankcontent', 'backup'),
+        ['value' => 1, 'locked' => 0])
+    );
 
     $ADMIN->add('backups', $temp);
 
@@ -375,6 +386,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_groups', new lang_string('generalgroups', 'backup'),
             new lang_string('configgeneralgroups', 'backup'), 1));
     $temp->add(new admin_setting_configcheckbox('backup/backup_auto_competencies', new lang_string('generalcompetencies','backup'), new lang_string('configgeneralcompetencies','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox(
+        'backup/backup_auto_contentbankcontent',
+        new lang_string('generalcontentbankcontent', 'backup'),
+        new lang_string('configgeneralcontentbankcontent', 'backup'),
+        1)
+    );
 
     //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_messages', new lang_string('messages', 'message'), new lang_string('backupmessageshelp','message'), 0));
     //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_blogs', new lang_string('blogs', 'blog'), new lang_string('backupblogshelp','blog'), 0));
@@ -435,6 +452,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
     $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_competencies',
         new lang_string('generalcompetencies', 'backup'),
         new lang_string('configrestorecompetencies', 'backup'), array('value' => 1, 'locked' => 0)));
+    $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_contentbankcontent',
+        new lang_string('generalcontentbankcontent', 'backup'),
+        new lang_string('configrestorecontentbankcontent', 'backup'), array('value' => 1, 'locked' => 0)));
 
     // Restore defaults when merging into another course.
     $temp->add(new admin_setting_heading('mergerestoredefaults', new lang_string('mergerestoredefaults', 'backup'), ''));
index 3b9302d..a512853 100644 (file)
@@ -33,7 +33,7 @@ $ADMIN->add('h5p', new admin_externalpage('h5pmanagelibraries', get_string('h5pm
     new moodle_url('/h5p/libraries.php'), ['moodle/site:config', 'moodle/h5p:updatelibraries']));
 
 // H5P settings.
-$defaulth5plib = \core_h5p\local\library\autoloader::get_default_handler();
+$defaulth5plib = \core_h5p\local\library\autoloader::get_default_handler_library();
 if (!empty($defaulth5plib)) {
     // As for now this page only has this setting, it will be hidden if there isn't any H5P libraries handler defined.
     $settings = new admin_settingpage('h5psettings', new lang_string('h5psettings', 'core_h5p'));
index 51e7960..c11b613 100644 (file)
@@ -115,4 +115,4 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
     And I set the field "id_department" to "red"
     And I press "Add filter"
     And I should see "User One"
-    And I should not see "User Two"
\ No newline at end of file
+    And I should not see "User Two"
index 697a214..6520ca2 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js differ
index b00dbac..e71b3c8 100644 (file)
Binary files a/admin/tool/lp/amd/build/menubar.min.js and b/admin/tool/lp/amd/build/menubar.min.js differ
index 209a5c7..6c52abb 100644 (file)
Binary files a/admin/tool/lp/amd/build/menubar.min.js.map and b/admin/tool/lp/amd/build/menubar.min.js.map differ
index 25b8682..2ef194d 100644 (file)
Binary files a/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js and b/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js differ
index 1136bd0..914ef5f 100644 (file)
@@ -31,6 +31,8 @@ use moodle_url;
 use moodle_exception;
 use lang_string;
 use curl;
+use core_qrcode;
+use stdClass;
 
 /**
  * API exposed by tool_mobile, to be used mostly by external functions and the plugin settings.
@@ -51,6 +53,14 @@ class api {
     const LOGIN_KEY_TTL = 60;
     /** @var string URL of the Moodle Apps Portal */
     const MOODLE_APPS_PORTAL_URL = 'https://apps.moodle.com';
+    /** @var int seconds a QR login key will expire. */
+    const LOGIN_QR_KEY_TTL = 600;
+    /** @var int QR code disabled value */
+    const QR_CODE_DISABLED = 0;
+    /** @var int QR code type URL value */
+    const QR_CODE_URL = 1;
+    /** @var int QR code type login value */
+    const QR_CODE_LOGIN = 2;
 
     /**
      * Returns a list of Moodle plugins supporting the mobile app.
@@ -336,6 +346,7 @@ class api {
 
     /**
      * Creates an auto-login key for the current user, this key is restricted by time and ip address.
+     * This key is used for automatically login the user in the site when the Moodle app opens the site in a mobile browser.
      *
      * @return string the key
      * @since Moodle 3.2
@@ -351,6 +362,24 @@ class api {
         return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
     }
 
+    /**
+     * Creates a QR login key for the current user, this key is restricted by time and ip address.
+     * This key is used for automatically login the user in the site when the user scans a QR code in the Moodle app.
+     *
+     * @return string the key
+     * @since Moodle 3.9
+     */
+    public static function get_qrlogin_key() {
+        global $USER;
+        // Delete previous keys.
+        delete_user_key('tool_mobile', $USER->id);
+
+        // Create a new key.
+        $iprestriction = getremoteaddr(null);
+        $validuntil = time() + self::LOGIN_QR_KEY_TTL;
+        return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
+    }
+
     /**
      * Get a list of the Mobile app features.
      *
@@ -601,4 +630,31 @@ class api {
 
         return $warnings;
     }
+
+    /**
+     * Generates a QR code with the site URL or for automatic login from the mobile app.
+     *
+     * @param  stdClass $mobilesettings tool_mobile settings
+     * @return string base64 data image contents, null if qr disabled
+     */
+    public static function generate_login_qrcode(stdClass $mobilesettings) {
+        global $CFG, $USER;
+
+        if ($mobilesettings->qrcodetype == static::QR_CODE_DISABLED) {
+            return null;
+        }
+
+        $urlscheme = !empty($mobilesettings->forcedurlscheme) ? $mobilesettings->forcedurlscheme : 'moodlemobile';
+        $data = $urlscheme . '://' . $CFG->wwwroot;
+
+        if ($mobilesettings->qrcodetype == static::QR_CODE_LOGIN) {
+            $qrloginkey = static::get_qrlogin_key();
+            $data .= '?qrlogin=' . $qrloginkey . '&userid=' . $USER->id;
+        }
+
+        $qrcode = new core_qrcode($data);
+        $imagedata = 'data:image/png;base64,' . base64_encode($qrcode->getBarcodePngData(5, 5));
+
+        return $imagedata;
+    }
 }
index fe1dad0..fa13085 100644 (file)
@@ -39,6 +39,7 @@ use context_system;
 use moodle_exception;
 use moodle_url;
 use core_text;
+use core_user;
 use coding_exception;
 
 /**
@@ -593,4 +594,102 @@ class external extends external_api {
              )
         ]);
     }
+
+    /**
+     * Returns description of get_tokens_for_qr_login() parameters.
+     *
+     * @return external_function_parameters
+     * @since  Moodle 3.9
+     */
+    public static function get_tokens_for_qr_login_parameters() {
+        return new external_function_parameters (
+            [
+                'qrloginkey' => new external_value(PARAM_ALPHANUMEXT, 'The user key for validating the request.'),
+                'userid' => new external_value(PARAM_INT, 'The user the key belongs to.'),
+            ]
+        );
+    }
+
+    /**
+     * Returns a WebService token (and private token) for QR login
+     *
+     * @param string $qrloginkey the user key generated and embedded into the QR code for validating the request
+     * @param int $userid the user the key belongs to
+     * @return array with the tokens and warnings
+     * @since  Moodle 3.9
+     */
+    public static function get_tokens_for_qr_login($qrloginkey, $userid) {
+        global $PAGE, $DB;
+
+        $params = self::validate_parameters(self::get_tokens_for_qr_login_parameters(),
+            ['qrloginkey' => $qrloginkey, 'userid' => $userid]);
+
+        $context = context_system::instance();
+        // We need this to make work the format text functions.
+        $PAGE->set_context($context);
+
+        $qrcodetype = get_config('tool_mobile', 'qrcodetype');
+        if ($qrcodetype != api::QR_CODE_LOGIN) {
+            throw new moodle_exception('qrcodedisabled', 'tool_mobile');
+        }
+
+        // Only requests from the Moodle mobile or desktop app. This enhances security to avoid any type of XSS attack.
+        // This code goes intentionally here and not inside the check_autologin_prerequisites() function because it
+        // is used by other PHP scripts that can be opened in any browser.
+        if (!\core_useragent::is_moodle_app()) {
+            throw new moodle_exception('apprequired', 'tool_mobile');
+        }
+        api::check_autologin_prerequisites($params['userid']);  // Checks https, avoid site admins using this...
+
+        // Validate and delete the key.
+        $key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
+        delete_user_key('tool_mobile', $params['userid']);
+
+        // Double check key belong to user.
+        if ($key->userid != $params['userid']) {
+            throw new moodle_exception('invalidkey');
+        }
+
+        // Key validated, check user.
+        $user = core_user::get_user($key->userid, '*', MUST_EXIST);
+        core_user::require_active_user($user, true, true);
+
+        // Generate WS tokens.
+        \core\session\manager::set_user($user);
+
+        // Check if the service exists and is enabled.
+        $service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
+        if (empty($service)) {
+            // will throw exception if no token found
+            throw new moodle_exception('servicenotavailable', 'webservice');
+        }
+
+        // Get an existing token or create a new one.
+        $token = external_generate_token_for_current_user($service);
+        $privatetoken = $token->privatetoken; // Save it here, the next function removes it.
+        external_log_token_request($token);
+
+        $result = [
+            'token' => $token->token,
+            'privatetoken' => $privatetoken ?: '',
+            'warnings' => [],
+        ];
+        return $result;
+    }
+
+    /**
+     * Returns description of get_tokens_for_qr_login() result value.
+     *
+     * @return external_description
+     * @since  Moodle 3.9
+     */
+    public static function get_tokens_for_qr_login_returns() {
+        return new external_single_structure(
+            [
+                'token' => new external_value(PARAM_ALPHANUM, 'A valid WebService token for the official mobile app service.'),
+                'privatetoken' => new external_value(PARAM_ALPHANUM, 'Private token used for auto-login processes.'),
+                'warnings' => new external_warnings(),
+            ]
+        );
+    }
 }
index d53f7b4..530267a 100644 (file)
@@ -78,5 +78,14 @@ $functions = array(
         'type'        => 'write',
         'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
-);
 
+    'tool_mobile_get_tokens_for_qr_login' => array(
+        'classname'   => 'tool_mobile\external',
+        'methodname'  => 'get_tokens_for_qr_login',
+        'description' => 'Returns a WebService token (and private token) for QR login.',
+        'type'        => 'read',
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
+        'ajax'          => true,
+        'loginrequired' => false,
+    ),
+);
index ccc6867..789cfd2 100644 (file)
@@ -96,6 +96,15 @@ $string['oauth2identityproviders'] = 'OAuth 2 identity providers';
 $string['offlineuse'] = 'Offline use';
 $string['pluginname'] = 'Moodle app tools';
 $string['pluginnotenabledorconfigured'] = 'Plugin not enabled or configured.';
+$string['qrcodedisabled'] = 'Access via QR code disabled';
+$string['qrcodeformobileappaccess'] = 'QR code for mobile app access';
+$string['qrcodeformobileapploginabout'] = 'Scan the QR code with your mobile app and you will be automatically logged in. The QR code will expire in {$a} minutes.';
+$string['qrcodeformobileappurlabout'] = 'Scan the QR code with your mobile app to fill in the site URL in your app.';
+$string['qrsiteadminsnotallowed'] = 'For security reasons login via QR code is not allowed for site administrators or if you are logged in as another user.';
+$string['qrcodetype'] = 'QR code access';
+$string['qrcodetype_desc'] = 'A QR code can be provided for mobile app users to scan and either have the site URL filled in or be automatically logged in without having to enter their credentials.';
+$string['qrcodetypeurl'] = 'QR code with site URL';
+$string['qrcodetypelogin'] = 'QR code with automatic login';
 $string['readingthisemailgettheapp'] = 'Reading this in an email? <a href="{$a}">Download the mobile app and receive notifications on your mobile device</a>.';
 $string['remoteaddons'] = 'Remote add-ons';
 $string['selfsignedoruntrustedcertificatewarning'] = 'It seems that the HTTPS certificate is self-signed or not trusted. The mobile app will only work with trusted sites.';
@@ -108,3 +117,4 @@ $string['getmoodleonyourmobile'] = 'Get the mobile app';
 $string['privacy:metadata:preference:tool_mobile_autologin_request_last'] = 'The date of the last auto-login key request. Between each request 6 minutes are required.';
 $string['privacy:metadata:core_userkey'] = 'User\'s keys used to create auto-login key for the current user.';
 $string['responsivemainmenuitems'] = 'Responsive menu items';
+$string['viewqrcode'] = 'View QR code';
index 74b3c0b..43d6cc2 100644 (file)
@@ -126,24 +126,64 @@ function tool_mobile_myprofile_navigation(\core_user\output\myprofile\tree $tree
         return;
     }
 
-    if (!$url = tool_mobile_create_app_download_url()) {
-        return;
+    $newnodes = [];
+    $mobilesettings = get_config('tool_mobile');
+
+    // Check if we should display a QR code.
+    if (!empty($mobilesettings->qrcodetype)) {
+        $mobileqr = null;
+        $qrcodeforappstr = get_string('qrcodeformobileappaccess', 'tool_mobile');
+
+        if ($mobilesettings->qrcodetype == tool_mobile\api::QR_CODE_LOGIN && is_https()) {
+
+            if (is_siteadmin() || \core\session\manager::is_loggedinas()) {
+                $mobileqr = get_string('qrsiteadminsnotallowed', 'tool_mobile');
+            } else {
+                $qrcodeimg = tool_mobile\api::generate_login_qrcode($mobilesettings);
+
+                $minutes = tool_mobile\api::LOGIN_QR_KEY_TTL / MINSECS;
+                $mobileqr = html_writer::tag('p', get_string('qrcodeformobileapploginabout', 'tool_mobile', $minutes));
+                $mobileqr .= html_writer::link('#qrcode', get_string('viewqrcode', 'tool_mobile'),
+                    ['class' => 'btn btn-primary mt-2', 'data-toggle' => 'collapse',
+                    'role' => 'button', 'aria-expanded' => 'false']);
+                $mobileqr .= html_writer::div(html_writer::img($qrcodeimg, $qrcodeforappstr), 'collapse mt-4', ['id' => 'qrcode']);
+            }
+
+        } else if ($mobilesettings->qrcodetype == tool_mobile\api::QR_CODE_URL) {
+            $qrcodeimg = tool_mobile\api::generate_login_qrcode($mobilesettings);
+
+            $mobileqr = get_string('qrcodeformobileappurlabout', 'tool_mobile');
+            $mobileqr .= html_writer::div(html_writer::img($qrcodeimg, $qrcodeforappstr));
+        }
+
+        if ($mobileqr) {
+            $newnodes[] = new core_user\output\myprofile\node('mobile', 'mobileappqr', $qrcodeforappstr, null, null, $mobileqr);
+        }
     }
 
+    // Check if the user is using the app, encouraging him to use it otherwise.
     $userhastoken = tool_mobile_user_has_token($user->id);
-
-    $mobilecategory = new core_user\output\myprofile\category('mobile', get_string('mobileapp', 'tool_mobile'),
-            'loginactivity');
-    $tree->add_category($mobilecategory);
+    $mobilestrconnected = null;
 
     if ($userhastoken) {
-        $mobilestr = get_string('mobileappconnected', 'tool_mobile');
-    } else {
-        $mobilestr = get_string('mobileappenabled', 'tool_mobile', $url->out());
+        $mobilestrconnected = get_string('mobileappconnected', 'tool_mobile');
+    } else if ($url = tool_mobile_create_app_download_url()) {
+         $mobilestrconnected = get_string('mobileappenabled', 'tool_mobile', $url->out());
     }
 
-    $node = new  core_user\output\myprofile\node('mobile', 'mobileappnode', $mobilestr, null);
-    $tree->add_node($node);
+    if ($mobilestrconnected) {
+        $newnodes[] = new core_user\output\myprofile\node('mobile', 'mobileappnode', $mobilestrconnected, null);
+    }
+
+    // Add nodes, if any.
+    if (!empty($newnodes)) {
+        $mobilecat = new core_user\output\myprofile\category('mobile', get_string('mobileapp', 'tool_mobile'), 'loginactivity');
+        $tree->add_category($mobilecat);
+
+        foreach ($newnodes as $node) {
+            $tree->add_node($node);
+        }
+    }
 }
 
 /**
index 05ed4aa..732bd71 100644 (file)
@@ -58,6 +58,9 @@ if ($hassiteconfig) {
 
         // Type of login.
         $temp = new admin_settingpage('mobileauthentication', new lang_string('mobileauthentication', 'tool_mobile'));
+
+        $temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesauth', '', $featuresnotice));
+
         $options = array(
             tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
             tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
@@ -67,6 +70,15 @@ if ($hassiteconfig) {
                     new lang_string('typeoflogin', 'tool_mobile'),
                     new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
 
+        $options = [
+            tool_mobile\api::QR_CODE_DISABLED => new lang_string('qrcodedisabled', 'tool_mobile'),
+            tool_mobile\api::QR_CODE_URL => new lang_string('qrcodetypeurl', 'tool_mobile'),
+            tool_mobile\api::QR_CODE_LOGIN => new lang_string('qrcodetypelogin', 'tool_mobile'),
+        ];
+        $temp->add(new admin_setting_configselect('tool_mobile/qrcodetype',
+                    new lang_string('qrcodetype', 'tool_mobile'),
+                    new lang_string('qrcodetype_desc', 'tool_mobile'), tool_mobile\api::QR_CODE_LOGIN, $options));
+
         $temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
                     new lang_string('forcedurlscheme_key', 'tool_mobile'),
                     new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
index 9534f05..7b05175 100644 (file)
@@ -600,4 +600,129 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $expected = format_text($expected, $course->summaryformat, ['para' => false, 'filter' => true]);
         $this->assertEquals($expected, $data->courses[0]->summary);
     }
+
+    /*
+     * Test get_tokens_for_qr_login.
+     */
+    public function test_get_tokens_for_qr_login() {
+        global $DB, $CFG, $USER;
+
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $qrloginkey = api::get_qrlogin_key();
+
+        // Generate new tokens, the ones we expect to receive.
+        $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
+        $token = external_generate_token_for_current_user($service);
+
+        // Fake the app.
+        core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
+                'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
+
+        $result = external::get_tokens_for_qr_login($qrloginkey, $USER->id);
+        $result = external_api::clean_returnvalue(external::get_tokens_for_qr_login_returns(), $result);
+
+        $this->assertEmpty($result['warnings']);
+        $this->assertEquals($token->token, $result['token']);
+        $this->assertEquals($token->privatetoken, $result['privatetoken']);
+
+        // Now, try with an invalid key.
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('invalidkey', 'error'));
+        $result = external::get_tokens_for_qr_login(random_string('64'), $user->id);
+    }
+
+    /**
+     * Test get_tokens_for_qr_login missing QR code enabled.
+     */
+    public function test_get_tokens_for_qr_login_missing_enableqr() {
+        global $CFG, $USER;
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        set_config('qrcodetype', tool_mobile\api::QR_CODE_DISABLED, 'tool_mobile');
+
+        $this->expectExceptionMessage(get_string('qrcodedisabled', 'tool_mobile'));
+        $result = external::get_tokens_for_qr_login('', $USER->id);
+    }
+
+    /**
+     * Test get_tokens_for_qr_login missing ws.
+     */
+    public function test_get_tokens_for_qr_login_missing_ws() {
+        global $CFG;
+        $this->resetAfterTest(true);
+
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        // Fake the app.
+        core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
+            'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
+
+        // Need to disable webservices to verify that's checked.
+        $CFG->enablewebservices = 0;
+        $CFG->enablemobilewebservice = 0;
+
+        $this->setAdminUser();
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('enablewsdescription', 'webservice'));
+        $result = external::get_tokens_for_qr_login('', $user->id);
+    }
+
+    /**
+     * Test get_tokens_for_qr_login missing https.
+     */
+    public function test_get_tokens_for_qr_login_missing_https() {
+        global $CFG, $USER;
+
+        // Fake the app.
+        core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
+            'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
+
+        // Need to simulate a non HTTPS site here.
+        $CFG->wwwroot = str_replace('https:', 'http:', $CFG->wwwroot);
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('httpsrequired', 'tool_mobile'));
+        $result = external::get_tokens_for_qr_login('', $USER->id);
+    }
+
+    /**
+     * Test get_tokens_for_qr_login missing admin.
+     */
+    public function test_get_tokens_for_qr_login_missing_admin() {
+        global $CFG, $USER;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        // Fake the app.
+        core_useragent::instance(true, 'Mozilla/5.0 (Linux; Android 7.1.1; Moto G Play Build/NPIS26.48-43-2; wv) ' .
+            'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/71.0.3578.99 Mobile Safari/537.36 MoodleMobile');
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('autologinnotallowedtoadmins', 'tool_mobile'));
+        $result = external::get_tokens_for_qr_login('', $USER->id);
+    }
+
+    /**
+     * Test get_tokens_for_qr_login missing app_request.
+     */
+    public function test_get_tokens_for_qr_login_missing_app_request() {
+        global $CFG, $USER;
+
+        $this->resetAfterTest(true);
+        $this->setAdminUser();
+
+        $this->expectException('moodle_exception');
+        $this->expectExceptionMessage(get_string('apprequired', 'tool_mobile'));
+        $result = external::get_tokens_for_qr_login('', $USER->id);
+    }
 }
index 392d5b2..afba9ff 100644 (file)
@@ -23,7 +23,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2019111800; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019111801; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019111200; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(
index bf74086..2e8537e 100644 (file)
Binary files a/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js and b/admin/tool/monitor/yui/build/moodle-tool_monitor-dropdown/moodle-tool_monitor-dropdown-min.js differ
index 52bf795..da4ef21 100644 (file)
@@ -26,7 +26,7 @@
     font-weight: 100;
     line-height: 1;
     color: #a2a2a2;
-    filter: alpha(opacity=20);
+    opacity: 0.2;
     position: absolute;
     font-family: helvetica, arial, verdana, sans-serif;
     top: 0;
index 06e24ef..9b91a14 100644 (file)
@@ -17,6 +17,9 @@
 /**
  * Form for scheduled tasks admin pages.
  *
+ * @deprecated since Moodle 3.9 MDL-63580. Please use the \core\task\manager.
+ * @todo final deprecation. To be removed in Moodle 4.3 MDL-63594.
+ *
  * @package    tool_task
  * @copyright  2018 Toni Barbera <toni@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -55,7 +58,9 @@ class run_from_cli {
      * @return bool
      */
     public static function is_runnable():bool {
-        return self::find_php_cli_path() !== false;
+        debugging('run_from_cli class is deprecated. Please use \core\task\manager::run_from_cli() instead.',
+            DEBUG_DEVELOPER);
+        return \core\task\manager::is_runnable();
     }
 
     /**
@@ -66,30 +71,8 @@ class run_from_cli {
      * @throws \moodle_exception
      */
     public static function execute(\core\task\task_base $task):bool {
-        global $CFG;
-
-        if (!self::is_runnable()) {
-            $redirecturl = new \moodle_url('/admin/settings.php', ['section' => 'systempaths']);
-            throw new \moodle_exception('cannotfindthepathtothecli', 'tool_task', $redirecturl->out());
-        } else {
-            // Shell-escaped path to the PHP binary.
-            $phpbinary = escapeshellarg(self::find_php_cli_path());
-
-            // Shell-escaped path CLI script.
-            $pathcomponents = [$CFG->dirroot, $CFG->admin, 'tool', 'task', 'cli', 'schedule_task.php'];
-            $scriptpath     = escapeshellarg(implode(DIRECTORY_SEPARATOR, $pathcomponents));
-
-            // Shell-escaped task name.
-            $classname = get_class($task);
-            $taskarg   = escapeshellarg("--execute={$classname}");
-
-            // Build the CLI command.
-            $command = "{$phpbinary} {$scriptpath} {$taskarg}";
-
-            // Execute it.
-            passthru($command);
-        }
-
-        return true;
+        debugging('run_from_cli class is deprecated. Please use \core\task\manager::run_from_cli() instead.',
+            DEBUG_DEVELOPER);
+        return \core\task\manager::run_from_cli($task);
     }
 }
index 1a04ba8..07cded8 100644 (file)
@@ -17,6 +17,9 @@
 /**
  * Task executor for adhoc tasks.
  *
+ * @deprecated since Moodle 3.9 MDL-63580. Please use the admin/cli/adhoc_task.php.
+ * @todo final deprecation. To be removed in Moodle 4.3 MDL-63594.
+ *
  * @package    tool_task
  * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -44,6 +47,8 @@ list($options, $unrecognized) = cli_get_params(
     ]
 );
 
+debugging('admin/tool/task/cli/adhoc_task.php is deprecated. Please use admin/cli/adhoc_task.php instead.', DEBUG_DEVELOPER);
+
 if ($unrecognized) {
     $unrecognized = implode("\n  ", $unrecognized);
     cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
index 85d7a4f..073075e 100644 (file)
@@ -17,6 +17,9 @@
 /**
  * CLI task execution.
  *
+ * @deprecated since Moodle 3.9 MDL-63580. Please use the admin/cli/schedule_task.php.
+ * @todo final deprecation. To be removed in Moodle 4.3 MDL-63594.
+ *
  * @package    tool_task
  * @copyright  2014 Petr Skoda
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -33,6 +36,8 @@ list($options, $unrecognized) = cli_get_params(
     array('h' => 'help')
 );
 
+debugging('admin/tool/task/cli/schedule_task.php is deprecated. Please use admin/cli/scheduled_task.php instead.', DEBUG_DEVELOPER);
+
 if ($unrecognized) {
     $unrecognized = implode("\n  ", $unrecognized);
     cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
@@ -65,6 +70,7 @@ if ($options['showdebugging']) {
 if ($options['showsql']) {
     $DB->set_debug(true);
 }
+
 if ($options['list']) {
     cli_heading("List of scheduled tasks ($CFG->wwwroot)");
 
index 0a01d0a..a003eab 100644 (file)
@@ -75,7 +75,7 @@ class tool_task_renderer extends plugin_renderer_base {
         $data = [];
         $yes = get_string('yes');
         $no = get_string('no');
-        $canruntasks = tool_task\run_from_cli::is_runnable();
+        $canruntasks = \core\task\manager::is_runnable();
         foreach ($tasks as $task) {
             $classname = get_class($task);
             $defaulttask = \core\task\manager::get_default_scheduled_task($classname, false);
index b404a82..1b0de98 100644 (file)
@@ -88,7 +88,7 @@ echo html_writer::start_tag('pre');
 $CFG->mtrace_wrapper = 'tool_task_mtrace_wrapper';
 
 // Run the specified task (this will output an error if it doesn't exist).
-\tool_task\run_from_cli::execute($task);
+\core\task\manager::run_from_cli($task);
 
 echo html_writer::end_tag('pre');
 
index a17fc2f..75ae6cd 100644 (file)
@@ -104,4 +104,4 @@ Feature: An admin can create courses using a CSV file
     And I should see "Field 2: 1 June 2020"
     And I should see "Field 3: b"
     And I should see "Field 4: Hello"
-    And I should see "Field 5: Some text"
\ No newline at end of file
+    And I should see "Field 5: Some text"
index dbca1f4..3d86dc6 100644 (file)
@@ -56,4 +56,4 @@ Feature: An admin can update courses using a CSV file
     And I should see "Field 2: Tuesday, 1 October 2019, 2:00"
     And I should see "Field 3: b"
     And I should see "Field 4: Hello"
-    And I should see "Field 5: Goodbye"
\ No newline at end of file
+    And I should see "Field 5: Goodbye"
index fc55002..d21da4e 100644 (file)
@@ -187,4 +187,4 @@ Feature: Upload users
     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
\ No newline at end of file
+    And I log out
index 72237ea..5be936a 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js and b/admin/tool/usertours/amd/build/tour.min.js differ
index 125bbab..bc48f5c 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js.map and b/admin/tool/usertours/amd/build/tour.min.js.map differ
index 10be26c..67acacb 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js differ
index 6a9564d..42a4d65 100644 (file)
Binary files a/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js and b/availability/condition/date/yui/build/moodle-availability_date-form/moodle-availability_date-form-min.js differ
index 50c96e9..9f523aa 100644 (file)
Binary files a/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js and b/availability/condition/grade/yui/build/moodle-availability_grade-form/moodle-availability_grade-form-min.js differ
index 52658fe..bc86932 100644 (file)
Binary files a/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js and b/availability/condition/group/yui/build/moodle-availability_group-form/moodle-availability_group-form-min.js differ
index ebd80d8..4430bd6 100644 (file)
Binary files a/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js and b/availability/condition/grouping/yui/build/moodle-availability_grouping-form/moodle-availability_grouping-form-min.js differ
index f870dda..945137a 100644 (file)
Binary files a/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js and b/availability/condition/profile/yui/build/moodle-availability_profile-form/moodle-availability_profile-form-min.js differ
index 6411461..dd09dd5 100644 (file)
Binary files a/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js and b/availability/yui/build/moodle-core_availability-form/moodle-core_availability-form-min.js differ
index d9060e3..d2ac8c5 100644 (file)
@@ -139,6 +139,11 @@ class backup_course_task extends backup_task {
         // Migrate the already exported inforef entries to final ones
         $this->add_step(new move_inforef_annotations_to_final('migrate_inforef'));
 
+        // Generate the content bank file (conditionally).
+        if ($this->get_setting_value('contentbankcontent')) {
+            $this->add_step(new backup_contentbankcontent_structure_step('course_contentbank', 'contentbank.xml'));
+        }
+
         // At the end, mark it as built
         $this->built = true;
     }
index ee434c1..4771c39 100644 (file)
@@ -179,5 +179,10 @@ class backup_root_task extends backup_task {
         $customfields = new backup_customfield_setting('customfield', base_setting::IS_BOOLEAN, true);
         $customfields->set_ui(new backup_setting_ui_checkbox($customfields, get_string('rootsettingcustomfield', 'backup')));
         $this->add_setting($customfields);
+
+        // Define content bank content inclusion setting.
+        $contentbank = new backup_contentbankcontent_setting('contentbankcontent', base_setting::IS_BOOLEAN, true);
+        $contentbank->set_ui(new backup_setting_ui_checkbox($contentbank, get_string('rootsettingcontentbankcontent', 'backup')));
+        $this->add_setting($contentbank);
     }
 }
index 9125673..59ea252 100644 (file)
@@ -199,3 +199,9 @@ class backup_activity_included_setting extends activity_backup_setting {}
  * user information or no, depends of @backup_users_setting
  */
 class backup_activity_userinfo_setting extends activity_backup_setting {}
+
+/**
+ * Root setting to control if backup will include content bank content or no
+ */
+class backup_contentbankcontent_setting extends backup_generic_setting {
+}
index ac8c917..cf0115a 100644 (file)
@@ -2740,3 +2740,34 @@ class backup_completion_defaults_structure_step extends backup_structure_step {
 
     }
 }
+
+/**
+ * Structure step in charge of constructing the contentbank.xml file for all the contents found in a given context
+ */
+class backup_contentbankcontent_structure_step extends backup_structure_step {
+
+    /**
+     * Define structure for content bank step
+     */
+    protected function define_structure() {
+
+        // Define each element separated.
+        $contents = new backup_nested_element('contents');
+        $content = new backup_nested_element('content', ['id'], [
+            'name', 'contenttype', 'instanceid', 'configdata', 'usercreated', 'usermodified', 'timecreated', 'timemodified']);
+
+        // Build the tree.
+        $contents->add_child($content);
+
+        // Define sources.
+        $content->set_source_table('contentbank_content', ['contextid' => backup::VAR_CONTEXTID]);
+
+        // Define annotations.
+        $content->annotate_ids('user', 'usercreated');
+        $content->annotate_ids('user', 'usermodified');
+        $content->annotate_files('contentbank', 'public', 'id');
+
+        // Return the root element (contents).
+        return $contents;
+    }
+}
index 6bcf826..be4f3f9 100644 (file)
@@ -126,6 +126,11 @@ class restore_course_task extends restore_task {
         // Activity completion defaults.
         $this->add_step(new restore_completion_defaults_structure_step('course_completion_defaults', 'completiondefaults.xml'));
 
+        // Content bank content (conditionally).
+        if ($this->get_setting_value('contentbankcontent')) {
+            $this->add_step(new restore_contentbankcontent_structure_step('course_contentbank', 'contentbank.xml'));
+        }
+
         // At the end, mark it as built
         $this->built = true;
     }
index 3069a0b..632985f 100644 (file)
@@ -290,5 +290,17 @@ class restore_root_task extends restore_task {
         $customfields = new restore_customfield_setting('customfields', base_setting::IS_BOOLEAN, $defaultvalue);
         $customfields->set_ui(new backup_setting_ui_checkbox($customfields, get_string('rootsettingcustomfield', 'backup')));
         $this->add_setting($customfields);
+
+        // Define Content bank content.
+        $defaultvalue = false;
+        $changeable = false;
+        if (isset($rootsettings['contentbankcontent']) && $rootsettings['contentbankcontent']) { // Only enabled when available.
+            $defaultvalue = true;
+            $changeable = true;
+        }
+        $contents = new restore_contentbankcontent_setting('contentbankcontent', base_setting::IS_BOOLEAN, $defaultvalue);
+        $contents->set_ui(new backup_setting_ui_checkbox($contents, get_string('rootsettingcontentbankcontent', 'backup')));
+        $contents->get_ui()->set_changeable($changeable);
+        $this->add_setting($contents);
     }
 }
index daa3fec..61124de 100644 (file)
@@ -236,3 +236,9 @@ class restore_activity_included_setting extends restore_activity_generic_setting
  * user information or no, depends of @restore_users_setting
  */
 class restore_activity_userinfo_setting extends restore_activity_generic_setting {}
+
+/**
+ * root setting to control if restore will create content bank content or no
+ */
+class restore_contentbankcontent_setting extends restore_generic_setting {
+}
index 10199e0..223b849 100644 (file)
@@ -3984,6 +3984,60 @@ class restore_activity_grade_history_structure_step extends restore_structure_st
     }
 }
 
+/**
+ * This structure steps restores the content bank content
+ */
+class restore_contentbankcontent_structure_step extends restore_structure_step {
+
+    /**
+     * Define structure for content bank step
+     */
+    protected function define_structure() {
+
+        $paths = [];
+        $paths[] = new restore_path_element('contentbankcontent', '/contents/content');
+
+        return $paths;
+    }
+
+    /**
+     * Define data processed for content bank
+     *
+     * @param mixed  $data
+     */
+    public function process_contentbankcontent($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+
+        $params = [
+            'name'           => $data->name,
+            'contextid'      => $this->task->get_contextid(),
+            'contenttype'    => $data->contenttype,
+            'instanceid'     => $data->instanceid,
+            'timecreated'    => $data->timecreated,
+        ];
+        $exists = $DB->record_exists('contentbank_content', $params);
+        if (!$exists) {
+            $params['configdata'] = $data->configdata;
+            $params['usercreated'] = $this->get_mappingid('user', $data->usercreated);
+            $params['usermodified'] = $this->get_mappingid('user', $data->usermodified);
+            $params['timemodified'] = time();
+            $newitemid = $DB->insert_record('contentbank_content', $params);
+            $this->set_mapping('contentbank_content', $oldid, $newitemid, true);
+        }
+    }
+
+    /**
+     * Define data processed after execute for content bank
+     */
+    protected function after_execute() {
+        // Add related files.
+        $this->add_related_files('contentbank', 'public', 'contentbank_content');
+    }
+}
+
 /**
  * This structure steps restores one instance + positions of one block
  * Note: Positions corresponding to one existing context are restored
index 6649934..b4fed99 100644 (file)
@@ -1063,4 +1063,32 @@ class core_backup_moodle2_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test the content bank content through a backup and restore.
+     */
+    public function test_contentbank_content_backup() {
+        global $DB, $USER, $CFG;
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+        $generator = $this->getDataGenerator();
+        $cbgenerator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+
+        // Create course and add content bank content.
+        $course = $generator->create_course();
+        $context = context_course::instance($course->id);
+        $filepath = $CFG->dirroot . '/h5p/tests/fixtures/filltheblanks.h5p';
+        $contents = $cbgenerator->generate_contentbank_data('contenttype_h5p', 2, $USER->id, $context, true, $filepath);
+        $this->assertEquals(2, $DB->count_records('contentbank_content'));
+
+        // Do backup and restore.
+        $newcourseid = $this->backup_and_restore($course);
+
+        // Confirm that values were transferred correctly into content bank on new course.
+        $newcontext = context_course::instance($newcourseid);
+
+        $this->assertEquals(4, $DB->count_records('contentbank_content'));
+        $this->assertEquals(2, $DB->count_records('contentbank_content', ['contextid' => $newcontext->id]));
+    }
 }
index ce9c1ba..9e09f55 100644 (file)
@@ -564,7 +564,8 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_general_histories'          => 'grade_histories',
                         'backup_general_questionbank'       => 'questionbank',
                         'backup_general_groups'             => 'groups',
-                        'backup_general_competencies'       => 'competencies'
+                        'backup_general_competencies'       => 'competencies',
+                        'backup_general_contentbankcontent' => 'contentbankcontent',
                 );
                 self::apply_admin_config_defaults($controller, $settings, true);
                 break;
@@ -577,7 +578,8 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_import_calendarevents'     => 'calendarevents',
                         'backup_import_questionbank'       => 'questionbank',
                         'backup_import_groups'             => 'groups',
-                        'backup_import_competencies'       => 'competencies'
+                        'backup_import_competencies'       => 'competencies',
+                        'backup_import_contentbankcontent' => 'contentbankcontent',
                 );
                 self::apply_admin_config_defaults($controller, $settings, true);
                 if ((!$controller->get_interactive()) &&
@@ -608,7 +610,8 @@ abstract class backup_controller_dbops extends backup_dbops {
                         'backup_auto_histories'          => 'grade_histories',
                         'backup_auto_questionbank'       => 'questionbank',
                         'backup_auto_groups'             => 'groups',
-                        'backup_auto_competencies'       => 'competencies'
+                        'backup_auto_competencies'       => 'competencies',
+                        'backup_auto_contentbankcontent' => 'contentbankcontent'
                 );
                 self::apply_admin_config_defaults($controller, $settings, false);
                 break;
index 6649777..148655b 100644 (file)
@@ -157,7 +157,8 @@ abstract class restore_controller_dbops extends restore_dbops {
             'restore_general_histories'          => 'grade_histories',
             'restore_general_questionbank'       => 'questionbank',
             'restore_general_groups'             => 'groups',
-            'restore_general_competencies'       => 'competencies'
+            'restore_general_competencies'       => 'competencies',
+            'restore_general_contentbankcontent' => 'contentbankcontent'
         );
         self::apply_admin_config_defaults($controller, $settings, true);
 
diff --git a/backup/util/ui/tests/behat/import_contentbank_content.feature b/backup/util/ui/tests/behat/import_contentbank_content.feature
new file mode 100644 (file)
index 0000000..68f2fb3
--- /dev/null
@@ -0,0 +1,45 @@
+@core @core_backup @core_contentbank
+Feature: Import course content bank content
+  In order to import content from a course contentbank
+  As a teacher
+  I need to confirm that errors will not happen
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+      | Course 2 | C2        | 0        |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | teacher1 | C2 | editingteacher |
+    And the following "contentbank content" exist:
+      | course| contenttype     | user     | contentname       |
+      | C1    | contenttype_h5p | teacher1 | ipsums.h5p        |
+    And I log in as "teacher1"
+
+  Scenario: Import content bank content to another course
+    Given I am on "Course 2" course homepage
+    And I click on "Content bank" "link"
+    And I should not see "ipsums.h5p"
+    When I import "Course 1" course into "Course 2" course using this options:
+    And I click on "Content bank" "link"
+    Then I should see "ipsums.h5p"
+    And I am on "Course 1" course homepage
+    And I click on "Content bank" "link"
+    And I should see "ipsums.h5p"
+
+  Scenario: User could configure not to import content bank
+    Given I am on "Course 2" course homepage
+    And I click on "Content bank" "link"
+    And I should not see "ipsums.h5p"
+    When I import "Course 1" course into "Course 2" course using this options:
+      | Initial | Include content bank content | 0 |
+    And I click on "Content bank" "link"
+    Then I should not see "ipsums.h5p"
+    And I am on "Course 1" course homepage
+    And I click on "Content bank" "link"
+    And I should see "ipsums.h5p"
index d4cf469..41ad459 100644 (file)
@@ -122,4 +122,4 @@ Feature: Restore Moodle 2 course backups with different user data settings
       | Settings |  Include enrolled users | 0 |
     Then I should see "Test database name"
     When I follow "Test database name"
-    Then I should not see "Student entry"
\ No newline at end of file
+    Then I should not see "Student entry"
index 129195e..db4d0c0 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js and b/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js differ
index 20654af..74d17a3 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js and b/backup/util/ui/yui/build/moodle-backup-confirmcancel/moodle-backup-confirmcancel-min.js differ
index 67c5b6e..76b29b6 100644 (file)
@@ -126,4 +126,4 @@ Feature: Award badges with separate groups
     And I follow "Course Badge"
     And I press "Award badge"
     # Teacher 2 shouldn't be able to go further
-    Then I should see "Sorry, but you need to be part of a group to see this page."
\ No newline at end of file
+    Then I should see "Sorry, but you need to be part of a group to see this page."
index e60aad8..dead3b8 100644 (file)
Binary files a/blocks/navigation/amd/build/ajax_response_renderer.min.js and b/blocks/navigation/amd/build/ajax_response_renderer.min.js differ
index 5154d5b..2bc3203 100644 (file)
Binary files a/blocks/navigation/amd/build/ajax_response_renderer.min.js.map and b/blocks/navigation/amd/build/ajax_response_renderer.min.js.map differ
index 4c2de4f..ae73b42 100644 (file)
@@ -36,4 +36,4 @@ Feature: The recently accessed items block allows users to easily access their m
     When  I follow "Test forum name"
     And I follow "Dashboard" in the user menu
     And I change window size to "large"
-    Then I should see "Test forum name" in the "Recently accessed items" "block"
\ No newline at end of file
+    Then I should see "Test forum name" in the "Recently accessed items" "block"
index 3d7e1d1..63c032d 100644 (file)
@@ -61,5 +61,3 @@
 .block_settings .block_tree  [aria-hidden="true"]:not(.icon) {
     display: none;
 }
-
-
index 9ffad4e..c095ee1 100644 (file)
Binary files a/calendar/amd/build/view_manager.min.js and b/calendar/amd/build/view_manager.min.js differ
index ca2d1fb..e40ea6f 100644 (file)
Binary files a/calendar/amd/build/view_manager.min.js.map and b/calendar/amd/build/view_manager.min.js.map differ
diff --git a/contentbank/amd/build/search.min.js b/contentbank/amd/build/search.min.js
new file mode 100644 (file)
index 0000000..9090c9b
Binary files /dev/null and b/contentbank/amd/build/search.min.js differ
diff --git a/contentbank/amd/build/search.min.js.map b/contentbank/amd/build/search.min.js.map
new file mode 100644 (file)
index 0000000..a3ddca1
Binary files /dev/null and b/contentbank/amd/build/search.min.js.map differ
diff --git a/contentbank/amd/build/selectors.min.js b/contentbank/amd/build/selectors.min.js
new file mode 100644 (file)
index 0000000..c7322b9
Binary files /dev/null and b/contentbank/amd/build/selectors.min.js differ
diff --git a/contentbank/amd/build/selectors.min.js.map b/contentbank/amd/build/selectors.min.js.map
new file mode 100644 (file)
index 0000000..99b3b56
Binary files /dev/null and b/contentbank/amd/build/selectors.min.js.map differ
diff --git a/contentbank/amd/src/search.js b/contentbank/amd/src/search.js
new file mode 100644 (file)
index 0000000..bd5cb55
--- /dev/null
@@ -0,0 +1,160 @@
+// 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/>.
+
+/**
+ * Search methods for finding contents in the content bank.
+ *
+ * @module     core_contentbank/search
+ * @package    core_contentbank
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import $ from 'jquery';
+import selectors from 'core_contentbank/selectors';
+import {get_string as getString} from 'core/str';
+import Pending from 'core/pending';
+import {debounce} from 'core/utils';
+
+/**
+ * Set up the search.
+ *
+ * @method init
+ */
+export const init = () => {
+    const pendingPromise = new Pending();
+
+    const root = $(selectors.elements.main);
+    registerListenerEvents(root);
+
+    pendingPromise.resolve();
+};
+
+/**
+ * Register contentbank search related event listeners.
+ *
+ * @method registerListenerEvents
+ * @param {Object} root The root element for the contentbank.
+ */
+const registerListenerEvents = (root) => {
+
+    const searchInput = root.find(selectors.elements.searchinput)[0];
+
+    root.on('click', selectors.actions.search, function(e) {
+        e.preventDefault();
+        toggleSearchResultsView(root, searchInput.value);
+    });
+
+    root.on('click', selectors.actions.clearSearch, function(e) {
+        e.preventDefault();
+        searchInput.value = "";
+        searchInput.focus();
+        toggleSearchResultsView(root, searchInput.value);
+    });
+
+    // The search input is also triggered.
+    searchInput.addEventListener('input', debounce(() => {
+        // Display the search results.
+        toggleSearchResultsView(root, searchInput.value);
+    }, 300));
+
+};
+
+/**
+ * Toggle (display/hide) the search results depending on the value of the search query.
+ *
+ * @method toggleSearchResultsView
+ * @param {HTMLElement} body The root element for the contentbank.
+ * @param {String} searchQuery The search query.
+ */
+const toggleSearchResultsView = async(body, searchQuery) => {
+    const clearSearchButton = body.find(selectors.elements.clearsearch)[0];
+    const searchIcon = body.find(selectors.elements.searchicon)[0];
+
+    const navbarBreadcrumb = body.find(selectors.elements.cbnavbarbreadcrumb)[0];
+    const navbarTotal = body.find(selectors.elements.cbnavbartotalsearch)[0];
+    // Update the results.
+    const filteredContents = filterContents(body, searchQuery);
+    if (searchQuery.length > 0) {
+        // As the search query is present, search results should be displayed.
+
+        // Display the "clear" search button in the activity chooser search bar.
+        searchIcon.classList.add('d-none');
+        clearSearchButton.classList.remove('d-none');
+
+        // Change the cb-navbar to display total items found.
+        navbarBreadcrumb.classList.add('d-none');
+        navbarTotal.innerHTML = await getString('itemsfound', 'core_contentbank', filteredContents.length);
+        navbarTotal.classList.remove('d-none');
+    } else {
+        // As search query is not present, the search results should be removed.
+
+        // Hide the "clear" search button in the activity chooser search bar.
+        clearSearchButton.classList.add('d-none');
+        searchIcon.classList.remove('d-none');
+
+        // Display again the breadcrumb in the navbar.
+        navbarBreadcrumb.classList.remove('d-none');
+        navbarTotal.classList.add('d-none');
+    }
+};
+
+/**
+ * Return the list of contents which have a name that matches the given search term.
+ *
+ * @method filterContents
+ * @param {HTMLElement} body The root element for the contentbank.
+ * @param {String} searchTerm The search term to match.
+ * @return {Array}
+ */
+const filterContents = (body, searchTerm) => {
+    const contents = Array.from(body.find(selectors.elements.cbfile));
+    const searchResults = [];
+    contents.forEach((content) => {
+        const contentName = content.getAttribute('data-file');
+        if (searchTerm === '' || contentName.toLowerCase().includes(searchTerm.toLowerCase())) {
+            // The content matches the search criteria so it should be displayed and hightlighted.
+            searchResults.push(content);
+            const contentNameElement = content.querySelector(selectors.regions.cbcontentname);
+            contentNameElement.innerHTML = highlight(contentName, searchTerm);
+            content.classList.remove('d-none');
+        } else {
+            content.classList.add('d-none');
+        }
+    });
+
+    return searchResults;
+};
+
+/**
+ * Highlight a given string in a text.
+ *
+ * @method highlight
+ * @param  {String} text The whole text.
+ * @param  {String} highlightText The piece of text to highlight.
+ * @return {String}
+ */
+const highlight = (text, highlightText) => {
+    let result = text;
+    if (highlightText !== '') {
+        const pos = text.toLowerCase().indexOf(highlightText.toLowerCase());
+        if (pos > -1) {
+            result = text.substr(0, pos) + '<span class="matchtext">' + text.substr(pos, highlightText.length) + '</span>' +
+                text.substr(pos + highlightText.length);
+        }
+    }
+
+    return result;
+};
diff --git a/contentbank/amd/src/selectors.js b/contentbank/amd/src/selectors.js
new file mode 100644 (file)
index 0000000..080f85f
--- /dev/null
@@ -0,0 +1,54 @@
+// 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/>.
+
+/**
+ * Define all of the selectors we will be using on the contentbank interface.
+ *
+ * @module     core_contentbank/selectors
+ * @package    core_contentbank
+ * @copyright  2020 Sara Arjona <sara@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * A small helper function to build queryable data selectors.
+ *
+ * @method getDataSelector
+ * @param {String} name
+ * @param {String} value
+ * @return {string}
+ */
+const getDataSelector = (name, value) => {
+    return `[data-${name}="${value}"]`;
+};
+
+export default {
+    regions: {
+        cbcontentname: getDataSelector('region', 'cb-content-name'),
+    },
+    actions: {
+        search: getDataSelector('action', 'searchcontent'),
+        clearSearch: getDataSelector('action', 'clearsearchcontent'),
+    },
+    elements: {
+        cbfile: '.cb-file',
+        cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',
+        cbnavbartotalsearch: '.cb-navbar-totalsearch',
+        clearsearch: '.input-group-append .clear-icon',
+        main: '#region-main',
+        searchicon: '.input-group-append .search-icon',
+        searchinput: '#searchinput',
+    },
+};
index 35499ce..197a36c 100644 (file)
@@ -42,7 +42,7 @@ class contentbank {
      *
      * @return string[] Array of contentbank contenttypes.
      */
-    private function get_enabled_content_types(): array {
+    public function get_enabled_content_types(): array {
         $enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
         $types = [];
         foreach ($enabledtypes as $name) {
@@ -159,20 +159,28 @@ class contentbank {
      * Find the contents with %$search% in the contextid defined.
      * If contextid and search are empty, all contents are returned.
      * In all the cases, only the contents for the enabled contentbank-type plugins are returned.
+     * No content-type permissions are validated here. It is the caller responsability to check that the user can access to them.
+     * The only validation done here is, for each content, a call to the method $content->is_view_allowed().
      *
      * @param  string|null $search Optional string to search (for now it will search only into the name).
      * @param  int $contextid Optional contextid to search.
+     * @param  array $contenttypenames Optional array with the list of content-type names to search.
      * @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid.
      */
-    public function search_contents(?string $search = null, ?int $contextid = 0): array {
+    public function search_contents(?string $search = null, ?int $contextid = 0, ?array $contenttypenames = null): array {
         global $DB;
 
         $contents = [];
 
         // Get only contents for enabled content-type plugins.
-        $contenttypes = array_map(function($contenttypename) {
-            return "contenttype_$contenttypename";
-        }, $this->get_enabled_content_types());
+        $contenttypes = [];
+        $enabledcontenttypes = $this->get_enabled_content_types();
+        foreach ($enabledcontenttypes as $contenttypename) {
+            if (empty($contenttypenames) || in_array($contenttypename, $contenttypenames)) {
+                $contenttypes[] = "contenttype_$contenttypename";
+            }
+        }
+
         if (empty($contenttypes)) {
             // Early return if there are no content-type plugins enabled.
             return $contents;
@@ -193,7 +201,7 @@ class contentbank {
             $params['name'] = '%' . $DB->sql_like_escape($search) . '%';
         }
 
-        $records = $DB->get_records_select('contentbank_content', $sql, $params);
+        $records = $DB->get_records_select('contentbank_content', $sql, $params, 'name ASC');
         foreach ($records as $record) {
             $contentclass = "\\$record->contenttype\\content";
             $content = new $contentclass($record);
index ac1a855..471266e 100644 (file)
@@ -72,22 +72,22 @@ class bankcontent implements renderable, templatable {
      * @return stdClass
      */
     public function export_for_template(renderer_base $output): stdClass {
+        global $PAGE;
+
+        $PAGE->requires->js_call_amd('core_contentbank/search', 'init');
+
         $data = new stdClass();
         $contentdata = array();
         foreach ($this->contents as $content) {
             $record = $content->get_content();
-            $managerclass = $content->get_content_type().'\\contenttype';
-            if (class_exists($managerclass)) {
-                $manager = new $managerclass($this->context);
-                if ($manager->can_access()) {
-                    $name = $content->get_name();
-                    $contentdata[] = array(
-                        'name' => $name,
-                        'link' => $manager->get_view_url($record),
-                        'icon' => $manager->get_icon($name)
-                    );
-                }
-            }
+            $contenttypeclass = $content->get_content_type().'\\contenttype';
+            $contenttype = new $contenttypeclass($this->context);
+            $name = $content->get_name();
+            $contentdata[] = array(
+                'name' => $name,
+                'link' => $contenttype->get_view_url($record),
+                'icon' => $contenttype->get_icon($name)
+            );
         }
         $data->contents = $contentdata;
         $data->tools = $this->toolbar;
index 61f28ea..ad9c5c5 100644 (file)
@@ -70,4 +70,4 @@ Feature: H5P file upload to content bank for admins
     And I add the "Navigation" block if not present
     And I expand "Site pages" node
     And I click on "Content bank" "link"
-    And I should not see "filltheblanks.h5p"
\ No newline at end of file
+    And I should not see "filltheblanks.h5p"
index bbf9786..f4f101b 100644 (file)
@@ -46,9 +46,19 @@ $PAGE->set_title($title);
 $PAGE->set_heading($title);
 $PAGE->set_pagetype('contenbank');
 
-// Get all contents managed by active plugins to render.
+// Get all contents managed by active plugins where the user has permission to render them.
 $cb = new \core_contentbank\contentbank();
-$foldercontents = $cb->search_contents($search, $contextid);
+$contenttypes = [];
+$enabledcontenttypes = $cb->get_enabled_content_types();
+foreach ($enabledcontenttypes as $contenttypename) {
+    $contenttypeclass = "\\contenttype_$contenttypename\\contenttype";
+    $contenttype = new $contenttypeclass($context);
+    if ($contenttype->can_access()) {
+        $contenttypes[] = $contenttypename;
+    }
+}
+
+$foldercontents = $cb->search_contents($search, $contextid, $contenttypes);
 
 // Get the toolbar ready.
 $toolbar = array ();
index 2212b81..396c297 100644 (file)
     }
 
 }}
-{{>core_contentbank/toolbar}}
+<div class="d-flex justify-content-between flex-column flex-sm-row">
+    <div class="cb-search-container mb-2">
+        {{>core_contentbank/bankcontent/search}}
+    </div>
+    <div class="cb-toolbar-container mb-2">
+        {{>core_contentbank/bankcontent/toolbar}}
+    </div>
+</div>
 <div class="content-bank-container pb-3 border">
     <div class="content-bank">
         <div class="cb-navbar bg-light p-2 border-bottom">
-            {{#pix}} i/folder {{/pix}}
+            <div class="cb-navbar-breadbrumb">
+                {{#pix}} i/folder {{/pix}}
+            </div>
+            <div class="cb-navbar-totalsearch d-none">
+            </div>
         </div>
         <div class="cb-content-wrapper d-flex flex-wrap p-2">
         {{#contents}}
-            <div class="cb-file position-relative mb-2">
+            <div class="cb-file position-relative mb-2" data-file="{{{name}}}">
                 <div class="p-2">
                     <div class="cb-thumbnail mb-1 text-center">
                         {{{ icon }}}
@@ -60,7 +71,7 @@
                     {{#link}}
                         <a href="{{{ link }}}" class="stretched-link" title="{{{name}}}">
                     {{/link}}
-                            <span class="cb-name word-break-all clamp-2 text-center" >
+                            <span class="cb-name word-break-all clamp-2 text-center" data-region="cb-content-name">
                                 {{{ name }}}
                             </span>
                     {{#link}}
diff --git a/contentbank/templates/bankcontent/search.mustache b/contentbank/templates/bankcontent/search.mustache
new file mode 100644 (file)
index 0000000..8d02863
--- /dev/null
@@ -0,0 +1,51 @@
+{{!
+    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_contentbank/bankcontent/search
+
+    Example context (json):
+    {}
+
+}}
+<div class="searchbar input-group" role="search">
+    <label for="searchinput">
+        <span class="sr-only">{{#str}} searchcontentbankbyname, contentbank {{/str}}</span>
+    </label>
+    <input type="text"
+           id="searchinput"
+           class="form-control searchinput border-right-0"
+           placeholder="{{#str}} search, core {{/str}}"
+           name="search"
+           autocomplete="off"
+    >
+    <div class="input-group-append">
+        <div class="input-group-text bg-transparent">
+            <div class="search-icon">
+                <button class="btn p-0 align-baseline icon-no-margin" data-action="searchcontent"
+                    aria-label="{{#str}} search, core {{/str}}">
+                    <span class="d-flex" aria-hidden="true">{{#pix}} a/search, core {{/pix}}</span>
+                </button>
+            </div>
+            <div class="clear-icon d-none">
+                <button class="btn p-0 align-baseline icon-no-margin" data-action="clearsearchcontent"
+                    aria-label="{{#str}} clearsearch, core {{/str}}">
+                    <span class="d-flex" aria-hidden="true">{{#pix}} e/cancel_solid_circle, core {{/pix}}</span>
+                </button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
similarity index 91%
rename from contentbank/templates/toolbar.mustache
rename to contentbank/templates/bankcontent/toolbar.mustache
index 9ab8024..88f4a4c 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core_contentbank/toolbar
+    @template core_contentbank/bankcontent/toolbar
 
     Example context (json):
     {
@@ -38,7 +38,7 @@
         {{#tools}}
             {{#link}}<a href="{{{ link }}}" title="{{{ name }}}">{{/link}}
                 <div class="cb-tool icon-no-margin btn btn-secondary btn-lg">
-                    {{#pix}} {{{ icon }}} {{/pix}}
+                    {{#pix}} {{{ icon }}} {{/pix}} <span class="sr-only">{{{ name }}}</span>
                 </div>
             {{#link}}</a>{{/link}}
         {{/tools}}
index de1fde0..1b7ad49 100644 (file)
@@ -26,4 +26,4 @@ Feature: Access permission to content Bank
 
   Scenario: Editing teachers can't access content bank at system level
     Given I log in as "teacher1"
-    Then "Content bank" "link" should not exist
\ No newline at end of file
+    Then "Content bank" "link" should not exist
index c520199..708a9a8 100644 (file)
@@ -70,4 +70,4 @@ Feature: Confirm content bank events are triggered
     And I set the field "Content name" to "New name"
     And I click on "Rename" "button"
     And I navigate to "Reports > Live logs" in site administration
-    Then I should see "Content updated"
\ No newline at end of file
+    Then I should see "Content updated"
diff --git a/contentbank/tests/behat/search_content.feature b/contentbank/tests/behat/search_content.feature
new file mode 100644 (file)
index 0000000..940415f
--- /dev/null
@@ -0,0 +1,51 @@
+@core @core_contentbank @contentbank_h5p @_file_upload @javascript
+Feature: Search content in the content bank
+  In order to find easily content in the content bank
+  As an admin
+  I need to be able to search content in the content bank
+
+  Background:
+    Given the following "contentbank content" exist:
+        | contextid | contenttype       | user  | contentname          |
+        | 1         | contenttype_h5p   | admin | santjordi.h5p        |
+        | 1         | contenttype_h5p   | admin | santjordi_rose.h5p   |
+        | 1         | contenttype_h5p   | admin | SantJordi_book       |
+        | 1         | contenttype_h5p   | admin | Dragon_santjordi.h5p |
+        | 1         | contenttype_h5p   | admin | princess.h5p         |
+        | 1         | contenttype_h5p   | admin | mathsbook.h5p        |
+        | 1         | contenttype_h5p   | admin | historybook.h5p      |
+        | 1         | contenttype_h5p   | admin | santvicenc.h5p       |
+
+  Scenario: Admins can search content in the content bank
+    Given I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I expand "Site pages" node
+    And I click on "Content bank" "link"
+    And I should see "santjordi.h5p"
+    And "Clear search input" "button" should not exist
+    And I should not see "items found"
+    When I set the field "Search" to "book"
+    Then "Clear search input" "button" should exist
+    And I should see "3 items found"
+    And I should see "SantJordi_book"
+    And I should see "mathsbook.h5p"
+    And I should see "historybook.h5p"
+    And I set the field "Search" to "sant"
+    And "Clear search input" "button" should exist
+    And I should see "5 items found"
+    And I set the field "Search" to "santjordi"
+    And I should see "4 items found"
+    And I should see "santjordi.h5p"
+    And I should see "santjordi_rose.h5p"
+    And I should see "SantJordi_book"
+    And I should see "Dragon_santjordi.h5p"
+    And I click on "Clear search input" "button"
+    And "Clear search input" "button" should not exist
+    And I should not see "items found"
+    And I set the field "Search" to ".h5p"
+    And "Clear search input" "button" should exist
+    And I should see "7 items found"
+    And I set the field "Search" to "friend"
+    And I should see "0 items found"
index 2fcd66c..75d44c9 100644 (file)
 
 namespace core_contentbank;
 
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
-require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
-
 use stdClass;
 use context_system;
 use contenttype_testable\contenttype as contenttype;
+
 /**
  * Test for content bank contenttype class.
  *
@@ -46,6 +41,16 @@ use contenttype_testable\contenttype as contenttype;
  */
 class core_contenttype_content_testcase extends \advanced_testcase {
 
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setupBeforeClass(): void {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
     /**
      * Tests for behaviour of get_name().
      *
index e420456..1999559 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-defined('MOODLE_INTERNAL') || die();
+namespace core_contentbank;
 
-global $CFG;
-require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+use advanced_testcase;
+use context_course;
+use context_system;
 
 /**
  * Test for extensions manager.
@@ -38,6 +39,16 @@ require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.p
  * @coversDefaultClass \core_contentbank\contentbank
  */
 class core_contentbank_testcase extends advanced_testcase {
+
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setupBeforeClass(): void {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+    }
+
     /**
      * Data provider for test_get_extension_supporter.
      *
@@ -62,7 +73,7 @@ class core_contentbank_testcase extends advanced_testcase {
     public function test_get_extension(string $filename, string $expected) {
         $this->resetAfterTest();
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
 
         $extension = $cb->get_extension($filename);
         $this->assertEquals($expected, $extension);
@@ -93,7 +104,7 @@ class core_contentbank_testcase extends advanced_testcase {
     public function test_get_extension_supporter_for_admins(array $supporters, string $extension, string $expected) {
         $this->resetAfterTest();
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
         $expectedsupporters = [$extension => $expected];
 
         $systemcontext = context_system::instance();
@@ -117,7 +128,7 @@ class core_contentbank_testcase extends advanced_testcase {
     public function test_get_extension_supporter_for_users(array $supporters, string $extension, string $expected) {
         $this->resetAfterTest();
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
         $systemcontext = context_system::instance();
 
         // Set a user with no permissions.
@@ -142,7 +153,7 @@ class core_contentbank_testcase extends advanced_testcase {
     public function test_get_extension_supporter_for_teachers(array $supporters, string $extension, string $expected) {
         $this->resetAfterTest();
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
         $expectedsupporters = [$extension => $expected];
 
         $course = $this->getDataGenerator()->create_course();
@@ -168,7 +179,7 @@ class core_contentbank_testcase extends advanced_testcase {
     public function test_get_extension_supporter(array $supporters, string $extension, string $expected) {
         $this->resetAfterTest();
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
         $systemcontext = context_system::instance();
         $this->setAdminUser();
 
@@ -185,7 +196,8 @@ class core_contentbank_testcase extends advanced_testcase {
      * @param  int $expectedresult Expected result.
      * @param  array $contexts List of contexts where to create content.
      */
-    public function test_search_contents(?string $search, string $where, int $expectedresult, array $contexts = []): void {
+    public function test_search_contents(?string $search, string $where, int $expectedresult, array $contexts = [],
+            array $contenttypes = null): void {
         global $DB;
 
         $this->resetAfterTest();
@@ -218,8 +230,8 @@ class core_contentbank_testcase extends advanced_testcase {
         }
 
         // Search for some content.
-        $cb = new \core_contentbank\contentbank();
-        $contents = $cb->search_contents($search, $contextid);
+        $cb = new contentbank();
+        $contents = $cb->search_contents($search, $contextid, $contenttypes);
 
         $this->assertCount($expectedresult, $contents);
         if (!empty($contents) && !empty($search)) {
@@ -321,6 +333,13 @@ class core_contentbank_testcase extends advanced_testcase {
                 0,
                 []
             ],
+            'Search with unexisting content-type' => [
+                null,
+                'course',
+                0,
+                ['system', 'category', 'course'],
+                ['contenttype_unexisting'],
+            ],
         ];
     }
 
@@ -350,7 +369,7 @@ class core_contentbank_testcase extends advanced_testcase {
         $fs = get_file_storage();
         $dummyh5pfile = $fs->create_file_from_string($dummyh5p, 'Dummy H5Pcontent');
 
-        $cb = new \core_contentbank\contentbank();
+        $cb = new contentbank();
         $content = $cb->create_content_from_file($systemcontext, $USER->id, $dummyh5pfile);
 
         $this->assertEquals('contenttype_h5p', $content->get_content_type());
index e74bd1b..d5cf377 100644 (file)
 
 namespace core_contentbank;
 
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
-require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
-
 use stdClass;
 use context_system;
 use contenttype_testable\contenttype as contenttype;
@@ -64,6 +58,16 @@ class core_contenttype_contenttype_testcase extends \advanced_testcase {
     /** @var contenttype The contenttype instance. */
     protected $contenttype;
 
+    /**
+     * Setup to ensure that fixtures are loaded.
+     */
+    public static function setupBeforeClass(): void {
+        global $CFG;
+
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.php');
+        require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+    }
+
     /**
      * Tests get_contenttype_name result.
      *
index fd35f45..262e7e6 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-namespace core_contentbank;
+namespace core_contentbank\external;
 
 defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
 
-use core_contentbank\external\delete_content;
 use dml_missing_record_exception;
 use external_api;
 use externallib_advanced_testcase;
index 4b1bf6b..6a9ea67 100644 (file)
@@ -24,7 +24,7 @@
  * @since      Moodle 3.9
  */
 
-namespace core_contentbank;
+namespace core_contentbank\external;
 
 defined('MOODLE_INTERNAL') || die();
 
@@ -33,9 +33,6 @@ require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_contenttype.p
 require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 
-use core_contentbank\external\delete_content;
-use core_contentbank\external\external;
-use core_contentbank\external\rename_content;
 use external_api;
 
 /**
index 09c3f0f..e737117 100644 (file)
@@ -25,8 +25,6 @@
 
 namespace core_contentbank;
 
-defined('MOODLE_INTERNAL') || die();
-
 use stdClass;
 use context_system;
 use context_coursecat;
index d9441d4..b3f553f 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js and b/course/amd/build/activitychooser.min.js differ
index 3348e19..78345bc 100644 (file)
Binary files a/course/amd/build/activitychooser.min.js.map and b/course/amd/build/activitychooser.min.js.map differ
index 92a8164..abfc5d3 100644 (file)
Binary files a/course/amd/build/local/activitychooser/dialogue.min.js and b/course/amd/build/local/activitychooser/dialogue.min.js differ
index 6a85405..355a979 100644 (file)
Binary files a/course/amd/build/local/activitychooser/dialogue.min.js.map and b/course/amd/build/local/activitychooser/dialogue.min.js.map differ
index d3b83d0..a66560e 100644 (file)
@@ -78,7 +78,6 @@ const registerListenerEvents = (courseId) => {
     events.forEach((event) => {
         document.addEventListener(event, async(e) => {
             if (e.target.closest(selectors.elements.sectionmodchooser)) {
-                const data = await fetchModuleData();
                 // We need to know who called this.
                 // Standard courses use the ID in the main section info.
                 const sectionDiv = e.target.closest(selectors.elements.section);
@@ -86,11 +85,31 @@ const registerListenerEvents = (courseId) => {
                 const button = e.target.closest(selectors.elements.sectionmodchooser);
                 // If we don't have a section ID use the fallback ID.
                 const caller = sectionDiv || button;
-                const favouriteFunction = partiallyAppliedFavouriteManager(data, caller.dataset.sectionid);
+
+                // We want to show the modal instantly but loading whilst waiting for our data.
+                let bodyPromiseResolver;
+                const bodyPromise = new Promise(resolve => {
+                    bodyPromiseResolver = resolve;
+                });
+
+                const sectionModal = buildModal(bodyPromise);
+
+                // Now we have a modal we should start fetching data.
+                const data = await fetchModuleData();
+
+                // Apply the section id to all the module instance links.
                 const builtModuleData = sectionIdMapper(data, caller.dataset.sectionid);
-                const sectionModal = await modalBuilder(builtModuleData);
 
-                ChooserDialogue.displayChooser(caller, sectionModal, builtModuleData, favouriteFunction);
+                ChooserDialogue.displayChooser(
+                    sectionModal,
+                    builtModuleData,
+                    partiallyAppliedFavouriteManager(data, caller.dataset.sectionid),
+                );
+
+                bodyPromiseResolver(await Templates.render(
+                    'core_course/activitychooser',
+                    templateDataBuilder(builtModuleData)
+                ));
             }
         });
     });
@@ -102,7 +121,7 @@ const registerListenerEvents = (courseId) => {
  *
  * @method sectionIdMapper
  * @param {Object} webServiceData Our original data from the Web service call
- * @param {Array} id The ID of the section we need to append to the links
+ * @param {Number} id The ID of the section we need to append to the links
  * @return {Array} [modules] with URL's built
  */
 const sectionIdMapper = (webServiceData, id) => {
@@ -114,15 +133,6 @@ const sectionIdMapper = (webServiceData, id) => {
     return newData.content_items;
 };
 
-/**
- * Build a modal on demand to save page load times
- *
- * @method modalBuilder
- * @param {Array} data our array of modules with section ID's applied in the URL field
- * @return {Object} Our modal that we are going to show the user
- */
-const modalBuilder = data => buildModal(templateDataBuilder(data));
-
 /**
  * Given an array of modules we want to figure out where & how to place them into our template object
  *
@@ -158,18 +168,22 @@ const templateDataBuilder = (data) => {
  * Given an object we want to build a modal ready to show
  *
  * @method buildModal
- * @param {Object} data The template data which contains arrays of modules
- * @return {Object} The modal for the calling section with everything already set up
+ * @param {Promise} bodyPromise
+ * @return {Object} The modal ready to display immediately and render body in later.
  */
-const buildModal = data => {
+const buildModal = bodyPromise => {
     return ModalFactory.create({
         type: ModalFactory.types.DEFAULT,
         title: getString('addresourceoractivity'),
-        body: Templates.render('core_course/activitychooser', data),
+        body: bodyPromise,
         large: true,
         templateContext: {
             classes: 'modchooser'
         }
+    })
+    .then(modal => {
+        modal.show();
+        return modal;
     });
 };
 
@@ -240,6 +254,7 @@ const partiallyAppliedFavouriteManager = (moduleData, sectionId) => {
             if (favourite) {
                 result.favourite = true;
 
+                // eslint-disable-next-line camelcase
                 newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);
 
                 const builtFaves = sectionIdMapper(newFaves, sectionId);
index 6a476cf..42a19b3 100644 (file)
@@ -42,6 +42,7 @@ import {debounce} from 'core/utils';
 const showModuleHelp = (carousel, moduleData) => {
     const help = carousel.find(selectors.regions.help)[0];
     help.innerHTML = '';
+    help.classList.add('m-auto');
 
     // Add a spinner.
     const spinnerPromise = addIconToContainer(help);
@@ -483,6 +484,37 @@ const searchModules = (modules, searchTerm) => {
     return searchResults;
 };
 
+/**
+ * Set up our tabindex information across the chooser.
+ *
+ * @method setupKeyboardAccessibility
+ * @param {Promise} modal Our created modal for the section
+ * @param {Map} mappedModules A map of all of the built module information
+ */
+const setupKeyboardAccessibility = (modal, mappedModules) => {
+    modal.getModal()[0].tabIndex = -1;
+
+    modal.getBodyPromise().then(body => {
+        $(selectors.elements.tab).on('shown.bs.tab', (e) => {
+            const activeSectionId = e.target.getAttribute("href");
+            const activeSectionChooserOptions = body[0]
+                .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));
+            const firstChooserOption = activeSectionChooserOptions
+                .querySelector(selectors.regions.chooserOption.container);
+            const prevActiveSectionId = e.relatedTarget.getAttribute("href");
+            const prevActiveSectionChooserOptions = body[0]
+                .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));
+
+            // Disable the focus of every chooser option in the previous active section.
+            disableFocusAllChooserOptions(prevActiveSectionChooserOptions);
+            // Enable the focus of the first chooser option in the current active section.
+            toggleFocusableChooserOption(firstChooserOption, true);
+            initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);
+        });
+        return;
+    }).catch(Notification.exception);
+};
+
 /**
  * Disable the focus of all chooser options in a specific container (section).
  *
@@ -500,13 +532,11 @@ const disableFocusAllChooserOptions = (sectionChooserOptions) => {
  * Display the module chooser.
  *
  * @method displayChooser
- * @param {HTMLElement} origin The calling button
- * @param {Object} modal Our created modal for the section
+ * @param {Promise} modalPromise Our created modal for the section
  * @param {Array} sectionModules An array of all of the built module information
  * @param {Function} partialFavourite Partially applied function we need to manage favourite status
  */
-export const displayChooser = (origin, modal, sectionModules, partialFavourite) => {
-
+export const displayChooser = (modalPromise, sectionModules, partialFavourite) => {
     // Make a map so we can quickly fetch a specific module's object for either rendering or searching.
     const mappedModules = new Map();
     sectionModules.forEach((module) => {
@@ -514,39 +544,18 @@ export const displayChooser = (origin, modal, sectionModules, partialFavourite)
     });
 
     // Register event listeners.
-    registerListenerEvents(modal, mappedModules, partialFavourite);
+    modalPromise.then(modal => {
+        registerListenerEvents(modal, mappedModules, partialFavourite);
 
-    // We want to focus on the action select when the dialog is closed.
-    modal.getRoot().on(ModalEvents.hidden, () => {
-        modal.destroy();
-    });
+        // We want to focus on the first chooser option element as soon as the modal is opened.
+        setupKeyboardAccessibility(modal, mappedModules);
 
-    // We want to focus on the first chooser option element as soon as the modal is opened.
-    modal.getRoot().on(ModalEvents.shown, () => {
-        modal.getModal()[0].tabIndex = -1;
-
-        modal.getBodyPromise()
-        .then(body => {
-            $(selectors.elements.tab).on('shown.bs.tab', (e) => {
-                const activeSectionId = e.target.getAttribute("href");
-                const activeSectionChooserOptions = body[0]
-                    .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));
-                const firstChooserOption = activeSectionChooserOptions
-                    .querySelector(selectors.regions.chooserOption.container);
-                const prevActiveSectionId = e.relatedTarget.getAttribute("href");
-                const prevActiveSectionChooserOptions = body[0]
-                    .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));
-
-                // Disable the focus of every chooser option in the previous active section.
-                disableFocusAllChooserOptions(prevActiveSectionChooserOptions);
-                // Enable the focus of the first chooser option in the current active section.
-                toggleFocusableChooserOption(firstChooserOption, true);
-                initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);
-            });
-            return;
-        })
-        .catch(Notification.exception);
-    });
+        // We want to focus on the action select when the dialog is closed.
+        modal.getRoot().on(ModalEvents.hidden, () => {
+            modal.destroy();
+        });
 
-    modal.show();
+        return modal;
+    })
+    .catch();
 };
index 3372196..15e206f 100644 (file)
@@ -24,4 +24,4 @@ Feature: Edit format course to Single Activity format
     And I press "Update format"
     Then I should see "Forum" in the "Type of activity" "field"
     And I press "Save and display"
-    And I should see "Adding a new Forum"
\ No newline at end of file
+    And I should see "Adding a new Forum"
index 615928e..27c6dfb 100644 (file)
@@ -23,12 +23,12 @@ Feature: Test we can resort categories in the management interface.
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   Scenario Outline: Test bulk sorting current category.
     Given the following "categories" exist:
@@ -52,12 +52,12 @@ Feature: Test we can resort categories in the management interface.
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   Scenario Outline: Test resorting subcategories.
     Given the following "categories" exist:
@@ -79,12 +79,12 @@ Feature: Test we can resort categories in the management interface.
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   @javascript
   Scenario Outline: Test resorting subcategories with JS enabled.
@@ -109,12 +109,12 @@ Feature: Test we can resort categories in the management interface.
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   # The scenario below this is the same but with JS enabled.
   Scenario: Test moving categories up and down by one.
index eb3995d..18319fb 100644 (file)
@@ -259,12 +259,12 @@ Feature: Course category management interface performs as expected
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "Sort by Category name ascending"       | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Category name descending"      | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Category ID number ascending"  | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Category ID number descending" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   @javascript
   Scenario Outline: Sub categories are displayed correctly when resorted
@@ -289,12 +289,12 @@ Feature: Course category management interface performs as expected
     And I should see category listing <cat1> before <cat2>
     And I should see category listing <cat2> before <cat3>
 
-  Examples:
-    | sortby | cat1 | cat2 | cat3 |
-    | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+    Examples:
+      | sortby | cat1 | cat2 | cat3 |
+      | "resortbyname"         | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "resortbynamedesc"     | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "resortbyidnumber"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "resortbyidnumberdesc" | "Applied sciences"        | "Social studies"          | "Extended social studies" |
 
   @javascript
   Scenario Outline: Test courses are displayed correctly after being resorted.
@@ -330,16 +330,16 @@ Feature: Course category management interface performs as expected
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
 
-  Examples:
-    | sortby | course1 | course2 | course3 |
-    | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
-    | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
-    | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
+    Examples:
+      | sortby | course1 | course2 | course3 |
+      | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
+      | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+      | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
 
   @javascript
   Scenario: Test course pagination
index 54a52ec..a49f9f5 100644 (file)
@@ -36,16 +36,16 @@ Feature: Test we can resort course in the management interface.
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
 
-  Examples:
-    | sortby | course1 | course2 | course3 |
-    | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
-    | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
-    | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
+    Examples:
+      | sortby | course1 | course2 | course3 |
+      | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
+      | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+      | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
 
   @javascript
   Scenario Outline: Resort courses with JavaScript enabled.
@@ -90,16 +90,16 @@ Feature: Test we can resort course in the management interface.
     And I should see course listing <course1> before <course2>
     And I should see course listing <course2> before <course3>
 
-  Examples:
-    | sortby | course1 | course2 | course3 |
-    | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
-    | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
-    | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
-    | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
-    | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
-    | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
-    | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
+    Examples:
+      | sortby | course1 | course2 | course3 |
+      | "Sort by Course full name ascending"     | "Applied sciences"        | "Extended social studies" | "Social studies" |
+      | "Sort by Course full name descending"    | "Social studies"          | "Extended social studies" | "Applied sciences" |
+      | "Sort by Course short name ascending"    | "Extended social studies" | "Applied sciences"        | "Social studies" |
+      | "Sort by Course short name descending"   | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course ID number ascending"     | "Extended social studies" | "Social studies"          | "Applied sciences" |
+      | "Sort by Course ID number descending"    | "Applied sciences"        | "Social studies"          | "Extended social studies" |
+      | "Sort by Course time created ascending"  | "Social studies"          | "Applied sciences"        | "Extended social studies" |
+      | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences"        | "Social studies" |
 
   Scenario: Test moving courses up and down by one.
     Given the following "categories" exist:
index fba44dd..fde8963 100644 (file)
@@ -101,4 +101,4 @@ Feature: Test we can both create and delete a course.
     # Redirect
     And I should see the "Course categories and courses" management page
     And I should see "Cat 1" in the "#category-listing" "css_element"
-    And I should see "Test course 2: create another course" in the "#course-listing" "css_element"
\ No newline at end of file
+    And I should see "Test course 2: create another course" in the "#course-listing" "css_element"
index e2ffa84..4d46d50 100644 (file)
@@ -53,4 +53,4 @@ Feature: Edit course settings
       | Course short name | Edited course shortname |
       | Course summary | Edited course summary |
     And I press "Save and return"
-    Then I should see the "Course categories and courses" management page
\ No newline at end of file
+    Then I should see the "Course categories and courses" management page
index f655b42..d179445 100644 (file)
Binary files a/course/yui/build/moodle-course-categoryexpander/moodle-course-categoryexpander-min.js and b/course/yui/build/moodle-course-categoryexpander/moodle-course-categoryexpander-min.js differ
index 5f78e62..ec29709 100644 (file)
Binary files a/course/yui/build/moodle-course-coursebase/moodle-course-coursebase-min.js and b/course/yui/build/moodle-course-coursebase/moodle-course-coursebase-min.js differ
index 04e39ef..add1a4d 100644 (file)
Binary files a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js and b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js differ
index eba4700..2b4cf0c 100644 (file)
Binary files a/course/yui/build/moodle-course-formatchooser/moodle-course-formatchooser-min.js and b/course/yui/build/moodle-course-formatchooser/moodle-course-formatchooser-min.js differ
index 7606d81..2c484cd 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-debug.js and b/course/yui/build/moodle-course-management/moodle-course-management-debug.js differ
index 8514547..efd525a 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-min.js and b/course/yui/build/moodle-course-management/moodle-course-management-min.js differ
index e491ffd..1d86485 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management.js and b/course/yui/build/moodle-course-management/moodle-course-management.js differ
index 2d1cba0..b33d223 100644 (file)
Binary files a/course/yui/build/moodle-course-util-base/moodle-course-util-base-min.js and b/course/yui/build/moodle-course-util-base/moodle-course-util-base-min.js differ
index aa48612..d780808 100644 (file)
Binary files a/course/yui/build/moodle-course-util-cm/moodle-course-util-cm-min.js and b/course/yui/build/moodle-course-util-cm/moodle-course-util-cm-min.js differ
index fe8775f..eed1442 100644 (file)
Binary files a/course/yui/build/moodle-course-util-section/moodle-course-util-section-min.js and b/course/yui/build/moodle-course-util-section/moodle-course-util-section-min.js differ
index e2ffc8f..dae3424 100644 (file)
@@ -3,6 +3,7 @@
   "builds": {
     "moodle-course-management": {
       "jsfiles": [
+        "shared.js",
         "console.js",
         "dd.js",
         "item.js",
@@ -11,4 +12,4 @@
       ]
     }
   }
-}
\ No newline at end of file
+}
index bd5f247..ed3f72c 100644 (file)
@@ -1,5 +1,3 @@
-/* global Item */
-
 /**
  * A managed category.
  *
@@ -8,9 +6,9 @@
  * @constructor
  * @extends Item
  */
-function Category() {
+Category = function() {
     Category.superclass.constructor.apply(this, arguments);
-}
+};
 Category.NAME = 'moodle-course-management-category';
 Category.CSS_PREFIX = 'management-category';
 Category.ATTRS = {
index 4c7c034..be21064 100644 (file)
@@ -1,5 +1,3 @@
-/* global DragDrop, Category, Course */
-
 /**
  * Provides drop down menus for list of action links.
  *
@@ -16,9 +14,9 @@
  * @constructor
  * @extends Base
  */
-function Console() {
+Console = function() {
     Console.superclass.constructor.apply(this, arguments);
-}
+};
 Console.NAME = 'moodle-course-management';
 Console.CSS_PREFIX = 'management';
 Console.ATTRS = {
index 60ee26f..bd1a682 100644 (file)
@@ -1,5 +1,3 @@
-/* global Item */
-
 /**
  * A managed course.
  *
@@ -8,9 +6,9 @@
  * @constructor
  * @extends Item
  */
-function Course() {
+Course = function() {
     Course.superclass.constructor.apply(this, arguments);
-}
+};
 Course.NAME = 'moodle-course-management-course';
 Course.CSS_PREFIX = 'management-course';
 Course.ATTRS = {
index 850d6a7..60500e6 100644 (file)
@@ -1,5 +1,3 @@
-/* global Console */
-
 /**
  * Drag and Drop handler
  *
@@ -8,9 +6,9 @@
  * @constructor
  * @extends Base
  */
-function DragDrop(config) {
+DragDrop = function(config) {
     Console.superclass.constructor.apply(this, [config]);
-}
+};
 DragDrop.NAME = 'moodle-course-management-dd';
 DragDrop.CSS_PREFIX = 'management-dd';
 DragDrop.ATTRS = {
index 590bbf3..46e9406 100644 (file)
@@ -6,9 +6,9 @@
  * @constructor
  * @extends Base
  */
-function Item() {
+Item = function() {
     Item.superclass.constructor.apply(this, arguments);
-}
+};
 Item.NAME = 'moodle-course-management-item';
 Item.CSS_PREFIX = 'management-item';
 Item.ATTRS = {
diff --git a/course/yui/src/management/js/shared.js b/course/yui/src/management/js/shared.js
new file mode 100644 (file)
index 0000000..2c001c3
--- /dev/null
@@ -0,0 +1,5 @@
+var Category;
+var Console;
+var Course;
+var DragDrop;
+var Item;
index a897294..f0bcbf3 100644 (file)
@@ -102,4 +102,4 @@ Feature: Managers can manage course custom fields date
     And I expand all fieldsets
     Then "#id_customfield_testfield_hour" "css_element" should not be visible
     Then "#id_customfield_testfield_minute" "css_element" should not be visible
-    And I log out
\ No newline at end of file
+    And I log out
index e834eaa..c48acae 100644 (file)
@@ -71,4 +71,4 @@ Feature: Uniqueness The course custom fields can be mandatory or not
     And I set the following fields to these values:
       | Test field |  |
     And I press "Save and display"
-    Then I should not see "This value is already used"
\ No newline at end of file
+    Then I should not see "This value is already used"
index af6c0f7..93742ba 100644 (file)
@@ -47,4 +47,4 @@ Feature: Guest users can auto-enrol themself in courses where guest access is al
     And I set the following fields to these values:
       | Password | moodle_rules |
     And I press "Submit"
-    And I should see "Test forum name"
\ No newline at end of file
+    And I should see "Test forum name"
index c0608af..de39bb3 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js differ
index dec529d..0e36236 100644 (file)
Binary files a/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js and b/filter/mathjaxloader/yui/build/moodle-filter_mathjaxloader-loader/moodle-filter_mathjaxloader-loader-min.js differ
index 0e264b6..7eb1613 100644 (file)
@@ -4,7 +4,7 @@ Feature: Converting rubric score to grades
   As a teacher
   I need to be able to use different grade settings
 
-  Scenario Outline:
+  Scenario Outline: Convert rubric scores to grades.
     Given the following "users" exist:
       | username | firstname | lastname | email |
       | teacher1 | Teacher | 1 | teacher1@example.com |
index 0f85187..568a518 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js differ
index 22251a2..ff31a3a 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js differ
index 895b917..d2ef47a 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js differ
index 3576f0a..cdd3d81 100644 (file)
@@ -12,7 +12,6 @@
 //
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-/* global SELECTORS */
 
 /**
  * @module moodle-gradereport_grader-gradereporttable
index d2fa0b0..66faed6 100644 (file)
Binary files a/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js and b/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js differ
index da25498..69a7ec6 100644 (file)
@@ -61,12 +61,12 @@ Feature: Grade letters can be overridden
       | <high6> | <low6> | <letter6> |
 
     Examples:
-    | l1 | b1    | l2 | b2    | l3 | b3    | l4 | b4    | l5 | b5    | l6 | b6    | l7 | b7 | l8 | b8   | l9 | b9 | high1    | low1     | letter1 | high2   | low2    | letter2 | high3    | low3    | letter3 | high4    | low4    | letter4 | high5    | low5    | letter5 | high6    | low6    | letter6 |
-    | Z  | 95    | Y  | 85    | X  | 75    | W  | 65    | V  | 55    | U  | 45    |    |    |    |      |    |    | 100.00 % | 95.00 %  | Z       | 94.99 % | 85.00 % | Y       | 84.99 %  | 75.00 % | X       | 74.99 %  | 65.00 % | W       | 64.99 %  | 55.00 % | V       | 54.99 %  | 45.00 % | U       |
-    | 5  | 100   | 4  | 80    | 3  | 60    | 2  | 40    | 1  | 20    | 0  | 0     |    |    |    |      |    |    | 100.00 % | 100.00 % | 5       | 99.99 % | 80.00 % | 4       | 79.99 %  | 60.00 % | 3       | 59.99 %  | 40.00 % | 2       | 39.99 %  | 20.00 % | 1       | 19.99 %  | 0.00 %  | 0       |
-    | A  | 95.25 | B  | 76.75 | C  | 50.01 | D  | 40    | F  | 0.01  | F- | 0     |    |    |    |      |    |    | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
-    |    |       |    |       |    |       | A  | 95.25 | B  | 76.75 | C  | 50.01 | D  | 40 | F  | 0.01 | F- | 0  | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
-    |    |       | A  | 95.25 | B  | 76.75 | C  | 50.01 |    |       |    |       | D  | 40 | F  | 0.01 | F- | 0  | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
+      | l1 | b1    | l2 | b2    | l3 | b3    | l4 | b4    | l5 | b5    | l6 | b6    | l7 | b7 | l8 | b8   | l9 | b9 | high1    | low1     | letter1 | high2   | low2    | letter2 | high3    | low3    | letter3 | high4    | low4    | letter4 | high5    | low5    | letter5 | high6    | low6    | letter6 |
+      | Z  | 95    | Y  | 85    | X  | 75    | W  | 65    | V  | 55    | U  | 45    |    |    |    |      |    |    | 100.00 % | 95.00 %  | Z       | 94.99 % | 85.00 % | Y       | 84.99 %  | 75.00 % | X       | 74.99 %  | 65.00 % | W       | 64.99 %  | 55.00 % | V       | 54.99 %  | 45.00 % | U       |
+      | 5  | 100   | 4  | 80    | 3  | 60    | 2  | 40    | 1  | 20    | 0  | 0     |    |    |    |      |    |    | 100.00 % | 100.00 % | 5       | 99.99 % | 80.00 % | 4       | 79.99 %  | 60.00 % | 3       | 59.99 %  | 40.00 % | 2       | 39.99 %  | 20.00 % | 1       | 19.99 %  | 0.00 %  | 0       |
+      | A  | 95.25 | B  | 76.75 | C  | 50.01 | D  | 40    | F  | 0.01  | F- | 0     |    |    |    |      |    |    | 100.00 % | 95.25 %  | A       | 95.24 % | 76.75 % | B       | 76.74 %  | 50.01 % | C       | 50.00 %  | 40.00 % | D       | 39.99 %  | 0.01 %  | F       | 0.00 %   | 0.00 %  | F-      |
+