Merge branch 'MDL-63674-master' of git://github.com/rezaies/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 27 Feb 2019 05:01:08 +0000 (13:01 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 27 Feb 2019 05:01:08 +0000 (13:01 +0800)
368 files changed:
admin/cli/install_database.php
admin/settings.php
admin/templates/setting.mustache
admin/templates/setting_configcolourpicker.mustache
admin/templates/setting_configduration.mustache
admin/templates/setting_configfile.mustache
admin/templates/setting_configmultiselect.mustache
admin/templates/setting_configmultiselect_optgroup.mustache
admin/templates/setting_configselect.mustache
admin/templates/setting_configselect_optgroup.mustache
admin/templates/setting_configtext.mustache
admin/templates/setting_configtextarea.mustache
admin/templates/setting_configtime.mustache
admin/templates/setting_courselist_frontpage.mustache
admin/templates/setting_description.mustache
admin/templates/setting_devicedetectregex.mustache
admin/templates/setting_emoticons.mustache
admin/templates/setting_gradecat_combo.mustache
admin/templates/settings.mustache
admin/templates/settings_search_results.mustache
admin/tests/behat/behat_admin.php
admin/tool/behat/tests/fixtures/core/behat_test_context_1.php
admin/tool/behat/tests/fixtures/core/behat_test_context_2.php
admin/tool/behat/tests/fixtures/theme/defaulttheme/behat_theme_defaulttheme_test_context_1.php
admin/tool/behat/tests/fixtures/theme/nofeatures/behat_theme_nofeatures_behat_test_context_2.php
admin/tool/behat/tests/fixtures/theme/nofeatures/behat_theme_nofeatures_test_context_1.php
admin/tool/behat/tests/fixtures/theme/withfeatures/behat_theme_withfeatures_behat_test_context_1.php
admin/tool/behat/tests/fixtures/theme/withfeatures/behat_theme_withfeatures_test_context_2.php
admin/tool/behat/tests/manager_util_test.php
admin/tool/behat/upgrade.txt
admin/tool/lp/templates/progress_bar.mustache
admin/tool/task/tests/behat/clear_fail_delay.feature
admin/tool/task/tests/behat/manage_tasks.feature
admin/tool/usertours/amd/build/managesteps.min.js
admin/tool/usertours/amd/build/managetours.min.js
admin/tool/usertours/amd/src/managesteps.js
admin/tool/usertours/amd/src/managetours.js
admin/tool/usertours/classes/helper.php
admin/tool/usertours/templates/tourstep.mustache
blocks/admin_bookmarks/tests/behat/bookmark_admin_pages.feature
blocks/myoverview/amd/build/view.min.js
blocks/myoverview/amd/src/view.js
blocks/recentlyaccessedcourses/amd/build/main.min.js
blocks/recentlyaccessedcourses/amd/src/main.js
blocks/recentlyaccessedcourses/classes/output/main.php
blocks/recentlyaccessedcourses/templates/course-card.mustache [new file with mode: 0644]
blocks/recentlyaccessedcourses/templates/no-courses.mustache [deleted file]
blocks/recentlyaccessedcourses/templates/recentlyaccessedcourses-view.mustache
blocks/search_forums/templates/search_form.mustache
blocks/settings/renderer.php
blocks/settings/templates/search_form.mustache [moved from theme/boost/templates/block_settings/search_form.mustache with 100% similarity]
blocks/tests/behat/behat_blocks.php
calendar/templates/minicalendar_day_link.mustache
course/classes/management_renderer.php
course/renderer.php
course/templates/course_search_form.mustache [moved from theme/boost/templates/course_search_form.mustache with 100% similarity]
course/templates/coursecard.mustache [new file with mode: 0644]
course/templates/coursecards.mustache
course/tests/behat/behat_course.php
enrol/self/tests/self_test.php
files/renderer.php
grade/grading/form/guide/templates/comment_chooser.mustache
grade/report/history/classes/output/renderer.php
grade/report/history/templates/user_button.mustache [moved from theme/boost/templates/gradereport_history/user_button.mustache with 100% similarity]
grade/report/singleview/templates/bulk_insert.mustache
grade/report/singleview/templates/button.mustache
grade/report/singleview/templates/dropdown_attribute.mustache
grade/report/singleview/templates/text_attribute.mustache
grade/tests/behat/behat_grade.php
grade/tests/importlib_test.php
grades/templates/edit_tree.mustache [moved from theme/boost/templates/core_grades/edit_tree.mustache with 100% similarity]
grades/templates/weight_field.mustache [moved from theme/boost/templates/core_grades/weight_field.mustache with 100% similarity]
grades/templates/weight_override_field.mustache [moved from theme/boost/templates/core_grades/weight_override_field.mustache with 100% similarity]
lang/en/admin.php
lang/en/message.php
lib/adminlib.php
lib/amd/build/paged_content_paging_bar.min.js
lib/amd/build/showhidesettings.min.js [new file with mode: 0644]
lib/amd/src/paged_content_paging_bar.js
lib/amd/src/showhidesettings.js [new file with mode: 0644]
lib/behat/classes/behat_config_util.php
lib/behat/classes/behat_context_helper.php
lib/behat/core_behat_file_helper.php [moved from lib/behat/behat_files.php with 94% similarity]
lib/outputrenderers.php
lib/templates/action_menu.mustache
lib/templates/action_menu_link.mustache
lib/templates/action_menu_trigger.mustache
lib/templates/auth_digital_minor_page.mustache
lib/templates/auth_verify_age_location_page.mustache
lib/templates/availability_info.mustache
lib/templates/block.mustache [moved from theme/boost/templates/core/block.mustache with 100% similarity]
lib/templates/chooser.mustache
lib/templates/columns-1to1to1.mustache
lib/templates/columns-1to2.mustache
lib/templates/columns-2to1.mustache
lib/templates/custom_menu_item.mustache [moved from theme/boost/templates/core/custom_menu_item.mustache with 100% similarity]
lib/templates/dataformat_selector.mustache
lib/templates/filemanager_confirmdialog.mustache [moved from theme/boost/templates/core/filemanager_confirmdialog.mustache with 100% similarity]
lib/templates/filemanager_default_searchform.mustache [moved from theme/boost/templates/core/filemanager_default_searchform.mustache with 100% similarity]
lib/templates/filemanager_fileselect.mustache [moved from theme/boost/templates/core/filemanager_fileselect.mustache with 100% similarity]
lib/templates/filemanager_loginform.mustache [moved from theme/boost/templates/core/filemanager_loginform.mustache with 100% similarity]
lib/templates/filemanager_modal_generallayout.mustache [moved from theme/boost/templates/core/filemanager_modal_generallayout.mustache with 100% similarity]
lib/templates/filemanager_page_generallayout.mustache [moved from theme/boost/templates/core/filemanager_page_generallayout.mustache with 100% similarity]
lib/templates/filemanager_processexistingfile.mustache [moved from theme/boost/templates/core/filemanager_processexistingfile.mustache with 100% similarity]
lib/templates/filemanager_processexistingfilemultiple.mustache [moved from theme/boost/templates/core/filemanager_processexistingfilemultiple.mustache with 100% similarity]
lib/templates/filemanager_selectlayout.mustache [moved from theme/boost/templates/core/filemanager_selectlayout.mustache with 100% similarity]
lib/templates/filemanager_uploadform.mustache [moved from theme/boost/templates/core/filemanager_uploadform.mustache with 100% similarity]
lib/templates/form_autocomplete_input.mustache
lib/templates/form_autocomplete_selection.mustache
lib/templates/full_header.mustache [moved from theme/boost/templates/header.mustache with 100% similarity]
lib/templates/help_icon.mustache
lib/templates/initials_bar.mustache
lib/templates/loginform.mustache
lib/templates/modal.mustache
lib/templates/modal_backdrop.mustache
lib/templates/navbar.mustache [moved from theme/boost/templates/core/navbar.mustache with 100% similarity]
lib/templates/notification_error.mustache
lib/templates/notification_info.mustache
lib/templates/notification_success.mustache
lib/templates/notification_warning.mustache
lib/templates/preferences_groups.mustache [moved from theme/boost/templates/core/preferences_groups.mustache with 100% similarity]
lib/templates/progress_bar.mustache
lib/templates/select_time.mustache
lib/templates/settings_link_page.mustache
lib/templates/settings_link_page_single.mustache
lib/templates/signup_form_layout.mustache
lib/templates/single_button.mustache
lib/templates/skip_links.mustache
lib/templates/tabtree.mustache [moved from theme/boost/templates/core/tabtree.mustache with 100% similarity]
lib/tests/behat/behat_action_menu.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_navigation.php
lib/tests/weblib_test.php
lib/upgrade.txt
lib/weblib.php
message/amd/build/message_area.min.js [deleted file]
message/amd/build/message_area_actions.min.js [deleted file]
message/amd/build/message_area_contacts.min.js [deleted file]
message/amd/build/message_area_events.min.js [deleted file]
message/amd/build/message_area_messages.min.js [deleted file]
message/amd/build/message_area_profile.min.js [deleted file]
message/amd/build/message_area_search.min.js [deleted file]
message/amd/build/message_area_tabs.min.js [deleted file]
message/amd/build/message_drawer.min.js
message/amd/build/message_drawer_router.min.js
message/amd/build/message_drawer_view_contact.min.js
message/amd/build/message_drawer_view_contacts.min.js
message/amd/build/message_drawer_view_contacts_section_contacts.min.js
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_group_info.min.js
message/amd/build/message_drawer_view_overview.min.js
message/amd/build/message_drawer_view_overview_section.min.js
message/amd/build/message_drawer_view_search.min.js
message/amd/build/message_drawer_view_settings.min.js
message/amd/build/message_preferences.min.js [deleted file]
message/amd/build/message_user_button.min.js [new file with mode: 0644]
message/amd/src/message_area.js [deleted file]
message/amd/src/message_area_actions.js [deleted file]
message/amd/src/message_area_contacts.js [deleted file]
message/amd/src/message_area_events.js [deleted file]
message/amd/src/message_area_messages.js [deleted file]
message/amd/src/message_area_profile.js [deleted file]
message/amd/src/message_area_search.js [deleted file]
message/amd/src/message_area_tabs.js [deleted file]
message/amd/src/message_drawer.js
message/amd/src/message_drawer_router.js
message/amd/src/message_drawer_view_contact.js
message/amd/src/message_drawer_view_contacts.js
message/amd/src/message_drawer_view_contacts_section_contacts.js
message/amd/src/message_drawer_view_conversation.js
message/amd/src/message_drawer_view_group_info.js
message/amd/src/message_drawer_view_overview.js
message/amd/src/message_drawer_view_overview_section.js
message/amd/src/message_drawer_view_search.js
message/amd/src/message_drawer_view_settings.js
message/amd/src/message_preferences.js [deleted file]
message/amd/src/message_user_button.js [new file with mode: 0644]
message/classes/api.php
message/classes/helper.php
message/classes/output/messagearea/contact.php
message/classes/output/messagearea/contacts.php
message/classes/output/messagearea/message.php
message/classes/output/messagearea/message_area.php
message/classes/output/messagearea/message_search_results.php [deleted file]
message/classes/output/messagearea/messages.php
message/classes/output/messagearea/profile.php
message/classes/output/messagearea/user_search_results.php
message/externallib.php
message/index.php
message/lib.php
message/templates/message_area.mustache [deleted file]
message/templates/message_area_contact.mustache [deleted file]
message/templates/message_area_contacts.mustache [deleted file]
message/templates/message_area_contacts_area.mustache [deleted file]
message/templates/message_area_message.mustache [deleted file]
message/templates/message_area_message_search_results.mustache [deleted file]
message/templates/message_area_messages_area.mustache [deleted file]
message/templates/message_area_profile.mustache [deleted file]
message/templates/message_area_response.mustache [deleted file]
message/templates/message_area_user_search_results.mustache [deleted file]
message/templates/message_drawer.mustache
message/templates/message_drawer_view_overview_body.mustache
message/templates/message_drawer_view_overview_footer.mustache [new file with mode: 0644]
message/templates/message_drawer_view_overview_section.mustache
message/templates/message_index.mustache [new file with mode: 0644]
message/templates/message_preferences.mustache [deleted file]
message/templates/message_preferences_component.mustache [deleted file]
message/templates/message_preferences_notification_processor.mustache [deleted file]
message/tests/behat/message_admin_settings.feature [new file with mode: 0644]
message/tests/externallib_test.php
message/upgrade.txt
mod/assign/classes/task/cron_task.php [new file with mode: 0644]
mod/assign/db/tasks.php [new file with mode: 0644]
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/templates/grading_actions.mustache
mod/assign/templates/grading_navigation.mustache
mod/assign/templates/grading_navigation_user_selector.mustache
mod/assign/version.php
mod/feedback/amd/build/edit.min.js
mod/feedback/amd/src/edit.js
mod/forum/templates/big_search_form.mustache
mod/forum/templates/quick_search_form.mustache
mod/lesson/locallib.php
mod/quiz/accessrule/timelimit/rule.php
mod/quiz/accessrule/timelimit/tests/rule_test.php
mod/quiz/classes/task/legacy_quiz_accessrules_cron.php [moved from message/classes/output/renderer.php with 54% similarity]
mod/quiz/classes/task/legacy_quiz_reports_cron.php [moved from theme/boost/classes/output/gradereport_history_renderer.php with 58% similarity]
mod/quiz/classes/task/update_overdue_attempts.php [new file with mode: 0644]
mod/quiz/db/tasks.php [new file with mode: 0644]
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/report/statistics/classes/task/quiz_statistics_cleanup.php [new file with mode: 0644]
mod/quiz/report/statistics/db/tasks.php [new file with mode: 0644]
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/lib.php
mod/quiz/report/statistics/version.php
mod/quiz/tests/behat/behat_mod_quiz.php
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/wiki/lib.php
mod/wiki/version.php
mod/workshop/mod_form.php
mod/workshop/tests/behat/grade_to_pass.feature
question/renderer.php
repository/tests/behat/behat_filepicker.php
repository/upload/tests/behat/behat_repository_upload.php
theme/boost/amd/build/drawer.min.js
theme/boost/amd/src/drawer.js
theme/boost/classes/output/core/files_renderer.php [deleted file]
theme/boost/classes/output/core_renderer.php
theme/boost/classes/output/core_renderer_maintenance.php [deleted file]
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/message.scss
theme/boost/style/moodle.css
theme/boost/templates/block_search_forums/search_form.mustache [deleted file]
theme/boost/templates/core/action_menu_trigger.mustache [deleted file]
theme/boost/templates/core/auth_digital_minor_page.mustache [deleted file]
theme/boost/templates/core/auth_verify_age_location_page.mustache [deleted file]
theme/boost/templates/core/availability_info.mustache [deleted file]
theme/boost/templates/core/help_icon.mustache [deleted file]
theme/boost/templates/core/loginform.mustache [deleted file]
theme/boost/templates/core/settings_link_page.mustache [deleted file]
theme/boost/templates/core/signup_form_layout.mustache [deleted file]
theme/boost/templates/core_admin/setting_configduration.mustache [deleted file]
theme/boost/templates/core_admin/setting_configmultiselect_optgroup.mustache [deleted file]
theme/boost/templates/core_admin/setting_configtime.mustache [deleted file]
theme/boost/templates/core_calendar/minicalendar_day_link.mustache [deleted file]
theme/boost/templates/gradereport_singleview/button.mustache [deleted file]
theme/boost/templates/mod_forum/quick_search_form.mustache [deleted file]
theme/boost/tests/behat/behat_theme_boost_behat_blocks.php [deleted file]
theme/boost/tests/behat/behat_theme_boost_behat_navigation.php [deleted file]
theme/boost/upgrade.txt
theme/boost/version.php
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/renderers.php
theme/bootstrapbase/renderers/block_settings_renderer.php [moved from theme/boost/classes/output/block_settings_renderer.php with 60% similarity]
theme/bootstrapbase/renderers/core/course_renderer.php [moved from theme/boost/classes/output/core/course_renderer.php with 62% similarity]
theme/bootstrapbase/renderers/core/files_renderer.php [new file with mode: 0644]
theme/bootstrapbase/renderers/core_course/management/renderer.php [moved from theme/boost/classes/output/core_course/management/renderer.php with 56% similarity]
theme/bootstrapbase/renderers/core_question/bank_renderer.php [moved from theme/boost/classes/output/core_question/bank_renderer.php with 62% similarity]
theme/bootstrapbase/renderers/core_renderer.php
theme/bootstrapbase/renderers/gradereport_history_renderer.php [new file with mode: 0644]
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/templates/block_search_forums/search_form.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/action_menu.mustache [moved from theme/boost/templates/core/action_menu.mustache with 55% similarity]
theme/bootstrapbase/templates/core/action_menu_item.mustache [moved from theme/boost/templates/core/action_menu_item.mustache with 100% similarity]
theme/bootstrapbase/templates/core/action_menu_link.mustache [moved from theme/boost/templates/core/action_menu_link.mustache with 88% similarity]
theme/bootstrapbase/templates/core/action_menu_trigger.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/auth_digital_minor_page.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/auth_verify_age_location_page.mustache [moved from theme/boost/templates/core_admin/setting_configfile.mustache with 57% similarity]
theme/bootstrapbase/templates/core/chooser.mustache [moved from theme/boost/templates/core/chooser.mustache with 94% similarity]
theme/bootstrapbase/templates/core/columns-1to1to1.mustache [moved from theme/boost/templates/core/columns-1to1to1.mustache with 86% similarity]
theme/bootstrapbase/templates/core/columns-1to2.mustache [moved from theme/boost/templates/core/columns-1to2.mustache with 88% similarity]
theme/bootstrapbase/templates/core/columns-2to1.mustache [moved from theme/boost/templates/core/columns-2to1.mustache with 88% similarity]
theme/bootstrapbase/templates/core/dataformat_selector.mustache [moved from theme/boost/templates/core/dataformat_selector.mustache with 68% similarity]
theme/bootstrapbase/templates/core/form_autocomplete_input.mustache [moved from theme/boost/templates/core/form_autocomplete_input.mustache with 83% similarity]
theme/bootstrapbase/templates/core/form_autocomplete_selection.mustache [moved from theme/boost/templates/core/form_autocomplete_selection.mustache with 83% similarity]
theme/bootstrapbase/templates/core/help_icon.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/initials_bar.mustache [moved from theme/boost/templates/core/initials_bar.mustache with 75% similarity]
theme/bootstrapbase/templates/core/loginform.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/modal.mustache [moved from theme/boost/templates/core/modal.mustache with 53% similarity]
theme/bootstrapbase/templates/core/modal_backdrop.mustache [moved from theme/boost/templates/core/modal_backdrop.mustache with 91% similarity]
theme/bootstrapbase/templates/core/notification_error.mustache [moved from theme/boost/templates/core/notification_error.mustache with 84% similarity]
theme/bootstrapbase/templates/core/notification_info.mustache [moved from theme/boost/templates/core/notification_info.mustache with 88% similarity]
theme/bootstrapbase/templates/core/notification_success.mustache [moved from theme/boost/templates/core/notification_success.mustache with 88% similarity]
theme/bootstrapbase/templates/core/notification_warning.mustache [moved from theme/boost/templates/core/notification_warning.mustache with 88% similarity]
theme/bootstrapbase/templates/core/progress_bar.mustache [moved from theme/boost/templates/core/progress_bar.mustache with 67% similarity]
theme/bootstrapbase/templates/core/select_time.mustache [moved from theme/boost/templates/core/select_time.mustache with 92% similarity]
theme/bootstrapbase/templates/core/settings_link_page.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/settings_link_page_single.mustache [moved from theme/boost/templates/core/settings_link_page_single.mustache with 96% similarity]
theme/bootstrapbase/templates/core/signup_form_layout.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core/single_button.mustache [moved from theme/boost/templates/core/single_button.mustache with 97% similarity]
theme/bootstrapbase/templates/core/skip_links.mustache [moved from theme/boost/templates/core/skip_links.mustache with 60% similarity]
theme/bootstrapbase/templates/core_admin/setting.mustache [moved from theme/boost/templates/core_admin/setting.mustache with 50% similarity]
theme/bootstrapbase/templates/core_admin/setting_configcolourpicker.mustache [moved from theme/boost/templates/core_admin/setting_configcolourpicker.mustache with 67% similarity]
theme/bootstrapbase/templates/core_admin/setting_configduration.mustache [moved from theme/boost/templates/core_admin/setting_configselect.mustache with 53% similarity]
theme/bootstrapbase/templates/core_admin/setting_configfile.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core_admin/setting_configmultiselect.mustache [moved from theme/boost/templates/core_admin/setting_configmultiselect.mustache with 60% similarity]
theme/bootstrapbase/templates/core_admin/setting_configmultiselect_optgroup.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core_admin/setting_configselect.mustache [moved from theme/boost/templates/core_admin/setting_configselect_optgroup.mustache with 62% similarity]
theme/bootstrapbase/templates/core_admin/setting_configselect_optgroup.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core_admin/setting_configtext.mustache [moved from theme/boost/templates/core_admin/setting_configtext.mustache with 52% similarity]
theme/bootstrapbase/templates/core_admin/setting_configtextarea.mustache [moved from theme/boost/templates/core_admin/setting_configtextarea.mustache with 56% similarity]
theme/bootstrapbase/templates/core_admin/setting_configtime.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/core_admin/setting_courselist_frontpage.mustache [moved from theme/boost/templates/core_admin/setting_courselist_frontpage.mustache with 57% similarity]
theme/bootstrapbase/templates/core_admin/setting_description.mustache [moved from theme/boost/templates/core_admin/setting_description.mustache with 85% similarity]
theme/bootstrapbase/templates/core_admin/setting_devicedetectregex.mustache [moved from theme/boost/templates/core_admin/setting_devicedetectregex.mustache with 69% similarity]
theme/bootstrapbase/templates/core_admin/setting_emoticons.mustache [moved from theme/boost/templates/core_admin/setting_emoticons.mustache with 80% similarity]
theme/bootstrapbase/templates/core_admin/setting_gradecat_combo.mustache [moved from theme/boost/templates/core_admin/setting_gradecat_combo.mustache with 69% similarity]
theme/bootstrapbase/templates/core_admin/settings.mustache [moved from theme/boost/templates/core_admin/settings.mustache with 62% similarity]
theme/bootstrapbase/templates/core_admin/settings_search_results.mustache [moved from theme/boost/templates/core_admin/settings_search_results.mustache with 91% similarity]
theme/bootstrapbase/templates/core_message/message_drawer.mustache
theme/bootstrapbase/templates/core_message/message_drawer_view_overview_section.mustache
theme/bootstrapbase/templates/core_message/message_index.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/gradereport_singleview/bulk_insert.mustache [moved from theme/boost/templates/gradereport_singleview/bulk_insert.mustache with 92% similarity]
theme/bootstrapbase/templates/gradereport_singleview/button.mustache [moved from message/templates/message_area_messages.mustache with 89% similarity]
theme/bootstrapbase/templates/gradereport_singleview/dropdown_attribute.mustache [moved from theme/boost/templates/gradereport_singleview/dropdown_attribute.mustache with 89% similarity]
theme/bootstrapbase/templates/gradereport_singleview/text_attribute.mustache [moved from theme/boost/templates/gradereport_singleview/text_attribute.mustache with 89% similarity]
theme/bootstrapbase/templates/gradingform_guide/comment_chooser.mustache [moved from theme/boost/templates/gradingform_guide/comment_chooser.mustache with 87% similarity]
theme/bootstrapbase/templates/mod_assign/grading_actions.mustache [moved from theme/boost/templates/mod_assign/grading_actions.mustache with 75% similarity]
theme/bootstrapbase/templates/mod_assign/grading_navigation.mustache [moved from theme/boost/templates/mod_assign/grading_navigation.mustache with 90% similarity]
theme/bootstrapbase/templates/mod_assign/grading_navigation_user_selector.mustache [moved from theme/boost/templates/mod_assign/grading_navigation_user_selector.mustache with 57% similarity]
theme/bootstrapbase/templates/mod_forum/big_search_form.mustache [moved from theme/boost/templates/mod_forum/big_search_form.mustache with 64% similarity]
theme/bootstrapbase/templates/mod_forum/quick_search_form.mustache [new file with mode: 0644]
theme/bootstrapbase/templates/tool_lp/progress_bar.mustache [moved from theme/boost/templates/tool_lp/progress_bar.mustache with 69% similarity]
theme/bootstrapbase/templates/tool_usertours/tourstep.mustache [moved from theme/boost/templates/tool_usertours/tourstep.mustache with 66% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_action_menu.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_action_menu.php with 71% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_admin.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_admin.php with 68% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_blocks.php [new file with mode: 0644]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_course.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_course.php with 68% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_deprecated.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_deprecated.php with 59% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_filepicker.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_filepicker.php with 57% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_grade.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_grade.php with 72% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_mod_quiz.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_mod_quiz.php with 79% similarity]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_navigation.php [new file with mode: 0644]
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_repository_upload.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_repository_upload.php with 74% similarity]
theme/bootstrapbase/tests/behat/blacklist.json [new file with mode: 0644]
theme/bootstrapbase/tests/behat/theme_bootstrapbase_behat_file_helper.php [moved from theme/boost/tests/behat/behat_theme_boost_behat_files.php with 73% similarity]
theme/bootstrapbase/upgrade.txt
theme/bootstrapbase/version.php
theme/upgrade.txt

index 0dd6122..8b5fe93 100644 (file)
@@ -82,11 +82,6 @@ require_once($CFG->libdir.'/installlib.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/componentlib.class.php');
 
-// make sure no tables are installed yet
-if ($DB->get_tables() ) {
-    cli_error(get_string('clitablesexist', 'install'));
-}
-
 $CFG->early_install_lang = true;
 get_string_manager(true);
 
@@ -109,12 +104,17 @@ list($options, $unrecognized) = cli_get_params(
     )
 );
 
-
+// We show help text even if tables are installed.
 if ($options['help']) {
     echo $help;
     die;
 }
 
+// Make sure no tables are installed yet.
+if ($DB->get_tables() ) {
+    cli_error(get_string('clitablesexist', 'install'));
+}
+
 if (!$options['agree-license']) {
     cli_error('You have to agree to the license. --help prints out the help'); // TODO: localize
 }
index ecd2455..0872e03 100644 (file)
@@ -156,4 +156,11 @@ $PAGE->requires->yui_module('moodle-core-formchangechecker',
 );
 $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
 
+if ($settingspage->has_dependencies()) {
+    $opts = [
+        'dependencies' => $settingspage->get_dependencies_for_javascript()
+    ];
+    $PAGE->requires->js_call_amd('core/showhidesettings', 'init', [$opts]);
+}
+
 echo $OUTPUT->footer();
index ed17246..af78e7e 100644 (file)
@@ -29,6 +29,7 @@
     * element - The Element HTML
     * forceltr - Force this element to be displayed LTR
     * default - Default value
+    * dependenton - optional message listing the settings this one is dependent on
 
     Example context (json):
     {
         "default": "Default value"
     }
 }}
-<div class="form-item clearfix" id="{{id}}">
-    <div class="form-label">
+{{!
+    Setting.
+}}
+<div class="form-item row" id="{{id}}">
+    <div class="form-label col-sm-3 text-sm-right">
         <label {{#labelfor}}for="{{labelfor}}"{{/labelfor}}>
             {{{title}}}
             {{#override}}
                 <div class="form-warning">{{warning}}</div>
             {{/warning}}
         </label>
-        <span class="form-shortname">{{{name}}}</span>
+        <span class="form-shortname d-block small text-muted">{{{name}}}</span>
     </div>
-    <div class="form-setting">
+    <div class="form-setting col-sm-9">
         {{#error}}
             <div><span class="error">{{error}}</span></div>
         {{/error}}
         {{{element}}}
         {{#default}}
-            <div class="form-defaultinfo {{#forceltr}}text-ltr{{/forceltr}}">{{{default}}}</div>
+            <div class="form-defaultinfo text-muted {{#forceltr}}text-ltr{{/forceltr}}">{{{default}}}</div>
         {{/default}}
+        <div class="form-description mt-3">{{{description}}}</div>
+        {{#dependenton}}<div class="form-dependenton mb-4 text-muted">{{{.}}}</div>{{/dependenton}}
     </div>
-    <div class="form-description">{{{description}}}</div>
 </div>
index eb8c487..3de5aac 100644 (file)
         "haspreviewconfig": false
     }
 }}
+{{!
+    Setting configcolourpicker.
+}}
 <div class="form-colourpicker defaultsnext">
     <div class="admin_colourpicker clearfix">
         {{#icon}}
             {{>core/pix_icon}}
         {{/icon}}
     </div>
-    <input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="text-ltr">
+    <input type="text" name="{{name}}" id="{{id}}" value="{{value}}" size="12" class="form-control text-ltr">
     {{#haspreviewconfig}}
         <input type="button" id="{{id}}_preview" value={{#quote}}{{#str}}preview{{/str}}{{/quote}} class="admin_colourpicker_preview">
     {{/haspreviewconfig}}
index 3bad98c..d2faa4a 100644 (file)
         "options": [ { "name": "Minutes", "value": "mins", "selected": true } ]
     }
 }}
+{{!
+    Setting configduration.
+}}
 <div class="form-duration defaultsnext">
-    <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="text-ltr">
-    <label class="accesshide" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
-    <select id="{{id}}u" name="{{name}}[u]">
-        {{#options}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
-        {{/options}}
-    </select>
+    <div class="form-inline">
+        <input type="text" size="5" id="{{id}}v" name="{{name}}[v]" value="{{value}}" class="form-control text-ltr">
+        <label class="sr-only" for="{{id}}u">{{#str}}durationunits, admin{{/str}}</label>
+        <select id="{{id}}u" name="{{name}}[u]" class="form-control">
+            {{#options}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/options}}
+        </select>
+    </div>
 </div>
 
index f249fe6..1f0b5d5 100644 (file)
         "valid": false
     }
 }}
+{{!
+    Setting configfile.
+}}
 <div class="form-file defaultsnext">
-    <input type="text" name="{{name}}" id="{{id}}" size="{{size}}" value="{{value}}" class="text-ltr" {{#readonly}}readonly{{/readonly}}>
-    {{#showvalidity}}
-        {{#valid}}
-            <span class="pathok">&#x2714;</span>
-        {{/valid}}
-        {{^valid}}
-            <span class="patherror">&#x2718;</span>
-        {{/valid}}
-    {{/showvalidity}}
+    <div class="form-inline">
+        <input type="text" name="{{name}}" id="{{id}}" size="{{size}}" value="{{value}}" class="form-control text-ltr" {{#readonly}}readonly{{/readonly}}>
+        {{#showvalidity}}
+            {{#valid}}
+                <span class="text-success">&#x2714;</span>
+            {{/valid}}
+            {{^valid}}
+                <span class="text-danger">&#x2718;</span>
+            {{/valid}}
+        {{/showvalidity}}
+    </div>
 </div>
 
index 7465a7b..49eb55c 100644 (file)
                      { "name": "Option 2", "value": "V", "selected": true } ]
     }
 }}
+{{!
+    Setting configmultiselect.
+}}
 <div class="form-select">
     <input type="hidden" name="{{name}}[xxxxx]" value="1">
-    <select id="{{id}}" name="{{name}}[]" size="{{size}}" multiple>
+    <select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index 9fa61c5..20d66e1 100644 (file)
         ]
     }
 }}
+{{!
+    Setting configmultiselect with optgroup support.
+}}
 <div class="form-select">
     <input type="hidden" name="{{name}}[xxxxx]" value="1">
-    <select id="{{id}}" name="{{name}}[]" size="{{size}}" multiple>
-        {{#options}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
-        {{/options}}
-        {{#optgroups}}
+    <select id="{{id}}" name="{{name}}[]" size="{{size}}" class="form-control" multiple>
+    {{#options}}
+        <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+    {{/options}}
+    {{#optgroups}}
             <optgroup label="{{label}}">
                 {{#options}}
                     <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
index a90c260..9b6c6e1 100644 (file)
         ]
     }
 }}
+{{!
+    Setting configselect.
+}}
 <div class="form-select defaultsnext">
-    <select id="{{id}}" name="{{name}}">
+    <select id="{{id}}" name="{{name}}" class="custom-select">
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index ea5da12..95f818b 100644 (file)
         ]
     }
 }}
+{{!
+    Setting configselect with optgroup support.
+}}
 <div class="form-select defaultsnext">
-    <select id="{{id}}" name="{{name}}">
+    <select id="{{id}}" name="{{name}}" class="custom-select">
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
@@ -66,3 +69,4 @@
     </select>
 </div>
 
+
index 236228a..a31f73a 100644 (file)
@@ -37,6 +37,9 @@
         "attributes": [ { "name": "readonly", "value": "readonly" } ]
     }
 }}
+{{!
+    Setting configtext.
+}}
 <div class="form-text defaultsnext">
-    <input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="{{#forceltr}}text-ltr{{/forceltr}}"{{#attributes}} {{name}}="{{value}}"{{/attributes}}>
+    <input type="text" name="{{name}}" value="{{value}}" size="{{size}}" id="{{id}}" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">
 </div>
index ec6ded3..002b0cd 100644 (file)
@@ -36,6 +36,9 @@
         "id": "test0"
     }
 }}
+{{!
+    Setting configtextarea.
+}}
 <div class="form-textarea">
-    <textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="{{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
+    <textarea rows="{{rows}}" cols="{{cols}}" id="{{id}}" name="{{name}}" spellcheck="true" class="form-control {{#forceltr}}text-ltr{{/forceltr}}">{{value}}</textarea>
 </div>
index b86b691..90d37b3 100644 (file)
         ]
     }
 }}
-<div class="form-time defaultsnext text-ltr">
-    <label class="accesshide" for="{{id}}h">{{#str}}hours{{/str}}</label>
-    <select id="{{id}}h" name="{{name}}[h]">
-        {{#hours}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
-        {{/hours}}
-    </select>:
-    <label class="accesshide" for="{{id}}m">{{#str}}minutes{{/str}}</label>
-    <select id="{{id}}m" name="{{name}}[m]">
-        {{#minutes}}
-            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
-        {{/minutes}}
-    </select>
+{{!
+    Setting configtime.
+}}
+<div class="form-time defaultsnext">
+    <div class="form-inline text-ltr">
+        <label class="sr-only" for="{{id}}h">{{#str}}hours{{/str}}</label>
+        <select id="{{id}}h" name="{{name}}[h]" class="custom-select">
+            {{#hours}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/hours}}
+        </select>:
+        <label class="sr-only" for="{{id}}m">{{#str}}minutes{{/str}}</label>
+        <select id="{{id}}m" name="{{name}}[m]" class="custom-select">
+            {{#minutes}}
+                <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+            {{/minutes}}
+        </select>
+    </div>
 </div>
 
index 2fe18cc..ef89725 100644 (file)
         ]
     }
 }}
+{{!
+    Setting courselist_frontpage.
+}}
 <div class="form-group">
     {{#selects}}
-        <select id="{{id}}{{key}}" name="{{name}}[]" class="form-select">
+        <select id="{{id}}{{key}}" name="{{name}}[]" class="custom-select">
             {{#options}}
                 <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
             {{/options}}
index c6d0e57..09bda33 100644 (file)
 {{!
     Setting description.
 }}
-<div class="form-item form-horizontal clearfix">
-    <div class="form-label">
+<div class="form-item row">
+    <div class="form-label col-sm-3 text-sm-right">
         <label>
             {{{title}}}
         </label>
-        <span class="form-shortname ">{{{name}}}</span>
     </div>
-    <div class="controls felement fstatic">{{{description}}}</div>
-</div>
\ No newline at end of file
+    <div class="form-setting col-sm-9">
+        <div class="form-description">{{{description}}}</div>
+    </div>
+</div>
index cc88862..97f97f2 100644 (file)
         ]
     }
 }}
-<table class="generaltable">
+{{!
+    Setting devicedetectregex.
+}}
+<table class="table table-striped">
     <thead>
         <tr>
             <th>{{#str}}devicedetectregexexpression, admin{{/str}}</th>
         {{#expressions}}
             <tr>
                 <td class="c{{index}}">
-                    <input type="text" name="{{name}}[expression{{index}}]" class="form-text text-ltr" value="{{expression}}">
+                    <input type="text" name="{{name}}[expression{{index}}]" class="form-control" value="{{expression}}">
                 </td>
                 <td class="c{{index}}">
-                    <input type="text" name="{{name}}[value{{index}}]" class="form-text text-ltr" value="{{value}}">
+                    <input type="text" name="{{name}}[value{{index}}]" class="form-control" value="{{value}}">
                 </td>
             </tr>
         {{/expressions}}
index 4c36174..8a245dd 100644 (file)
@@ -32,6 +32,9 @@
         ]
     }
 }}
+{{!
+    Setting emoticons.
+}}
 <div class="form-group">
     <table id="emoticonsetting" class="admintable generaltable">
         <thead>
@@ -48,7 +51,7 @@
                 <tr>
                     {{#fields}}
                         <td class="c{{index}}">
-                            <input type="text" name="{{name}}[{{field}}]" class="form-text text-ltr" value="{{value}}">
+                            <input type="text" name="{{name}}[{{field}}]" class="form-text form-control text-ltr" value="{{value}}">
                         </td>
                     {{/fields}}
                     <td>
index d64d75f..9e71c27 100644 (file)
         "advanced": true
     }
 }}
+{{!
+    Setting configselect.
+}}
 <div class="form-group">
-    <select id="{{id}}" name="{{name}}[value]" class="form-select">
+    <select id="{{id}}" name="{{name}}[value]" class="form-select custom-select">
         {{#options}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/options}}
index 0097305..0067f8e 100644 (file)
         "showsave": true
     }
 }}
+{{!
+    Settings.
+}}
 <form action="{{actionurl}}" method="post" id="adminsettings">
-    <div class="settingsform clearfix">
+    <div class="settingsform">
         {{#params}}
             <input type="hidden" name="{{name}}" value="{{value}}">
             <input type="hidden" name="action" value="save-settings">
         {{/title}}
         {{{settings}}}
         {{#showsave}}
-            <div class="form-buttons">
-                <input type="submit" class="form-submit" value={{#quote}}{{#str}}savechanges, admin{{/str}}{{/quote}}>
+            <div class="row">
+                <div class="offset-sm-3 col-sm-3">
+                    <button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
+                </div>
             </div>
         {{/showsave}}
     </div>
index df82b0f..6653154 100644 (file)
                 </fieldset>
             {{/results}}
             {{#showsave}}
-                <div class="form-buttons">
-                    <input type="submit" class="form-submit" value={{#quote}}{{#str}}savechanges, admin{{/str}}{{/quote}}>
+                <div class="row">
+                    <div class="offset-sm-3 col-sm-3">
+                        <button type="submit" class="btn btn-primary">{{#str}}savechanges, admin{{/str}}</button>
+                    </div>
                 </div>
             {{/showsave}}
         {{/hasresults}}
index 73ce572..b2fd070 100644 (file)
@@ -55,16 +55,12 @@ class behat_admin extends behat_base {
 
         foreach ($data as $label => $value) {
 
-            // We expect admin block to be visible, otherwise go to homepage.
-            if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
-                $this->getSession()->visit($this->locate_path('/'));
-                $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-            }
+            $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]);
 
             // Search by label.
-            $searchbox = $this->find_field(get_string('searchinsettings', 'admin'));
+            $searchbox = $this->find_field(get_string('query', 'admin'));
             $searchbox->setValue($label);
-            $submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
+            $submitsearch = $this->find('css', 'form input[type=submit][name=search]');
             $submitsearch->press();
 
             $this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
@@ -78,21 +74,24 @@ class behat_admin extends behat_base {
 
             // Single element settings.
             try {
-                $fieldxpath = "//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
-                    "[@id=//label[contains(normalize-space(.), $label)]/@for or " .
-                    "@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
+                $fieldxpath = "//*[self::input | self::textarea | self::select]" .
+                        "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
+                        "[@id=//label[contains(normalize-space(.), $label)]/@for or " .
+                        "@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
                 $fieldnode = $this->find('xpath', $fieldxpath, $exception);
 
-                $formfieldtypenode = $this->find('xpath', $fieldxpath . "/ancestor::div[@class='form-setting']" .
-                    "/child::div[contains(concat(' ', @class, ' '),  ' form-')]/child::*/parent::div");
+                $formfieldtypenode = $this->find('xpath', $fieldxpath .
+                        "/ancestor::div[contains(concat(' ', @class, ' '), ' form-setting ')]" .
+                        "/child::div[contains(concat(' ', @class, ' '),  ' form-')]/child::*/parent::div");
 
             } catch (ElementNotFoundException $e) {
 
                 // Multi element settings, interacting only the first one.
-                $fieldxpath = "//*[label[normalize-space(.)= $label]|span[normalize-space(.)= $label]]/" .
-                    "ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
-                    "/descendant::div[@class='form-group']/descendant::*[self::input | self::textarea | self::select]" .
-                    "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
+                $fieldxpath = "//*[label[contains(., $label)]|span[contains(., $label)]]" .
+                        "/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
+                        "/descendant::div[contains(concat(' ', @class, ' '), ' form-group ')]" .
+                        "/descendant::*[self::input | self::textarea | self::select]" .
+                        "[not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
                 $fieldnode = $this->find('xpath', $fieldxpath);
 
                 // It is the same one that contains the type.
@@ -101,6 +100,7 @@ class behat_admin extends behat_base {
 
             // Getting the class which contains the field type.
             $classes = explode(' ', $formfieldtypenode->getAttribute('class'));
+            $type = false;
             foreach ($classes as $class) {
                 if (substr($class, 0, 5) == 'form-') {
                     $type = substr($class, 5);
index 01c0396..c8819d6 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 /**
  * Test context 1
index 0dbdbeb..9b423ae 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 /**
  * Test context 2
index ff164cd..f0d851c 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
 
 /**
  * Default Theme test context 1
index 710da0a..5db9807 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../behat_test_context_1.php');
+require_once(__DIR__ . '/../../core/behat_test_context_1.php');
 
 /**
  * Theme test context 1
@@ -33,6 +33,6 @@ require_once(__DIR__ . '/../behat_test_context_1.php');
  * @copyright  2016 Rajesh Taneja <rajesh@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_theme_nofeatures_behat_test_context_1 extends behat_test_context_1 {
+class behat_theme_nofeatures_behat_test_context_2 extends behat_test_context_2 {
 
 }
\ No newline at end of file
index 87fe40c..2d8d121 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
 
 /**
  * Theme test context 2
@@ -33,6 +33,6 @@ require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
  * @copyright  2016 Rajesh Taneja <rajesh@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_theme_nofeatures_test_context_2 extends behat_base {
+class behat_theme_nofeatures_test_context_1 extends behat_base {
 
 }
\ No newline at end of file
index 3941071..1111532 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../behat_test_context_1.php');
+require_once(__DIR__ . '/../../core/behat_test_context_1.php');
 
 /**
  * Theme test context 1
index 23eedff..2265320 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../../lib/behat/behat_base.php');
 
 /**
  * Theme test context 2
index 2d10776..98ccd03 100644 (file)
@@ -108,7 +108,6 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             array('nofeatures', __DIR__.'/fixtures/theme/nofeatures'),
             array('defaulttheme', __DIR__.'/fixtures/theme/defaulttheme'),
         );
-
         // List of themes is const for test.
         if ($notheme) {
             $themelist = array('defaulttheme');
@@ -116,6 +115,13 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             $themelist = array('withfeatures', 'nofeatures', 'defaulttheme');
         }
 
+        $thememap = [];
+        foreach ($themelist as $themename) {
+            $mock = $this->getMockBuilder('theme_config');
+            $mock->disableOriginalConstructor();
+            $thememap[] = [$themename, $mock->getMock()];
+        }
+
         $behatconfigutil->expects($this->any())
             ->method('get_list_of_themes')
             ->will($this->returnValue($themelist));
@@ -125,6 +131,11 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             ->method('get_theme_test_directory')
             ->will($this->returnValueMap($map));
 
+        // Theme directory for testing.
+        $behatconfigutil->expects($this->any())
+                ->method('get_theme_config')
+                ->will($this->returnValueMap($thememap));
+
         $behatconfigutil->expects($this->any())
             ->method('get_default_theme')
             ->will($this->returnValue('defaulttheme'));
@@ -138,7 +149,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_single_run() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -177,7 +188,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_single_run_no_theme() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -231,7 +242,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_parallel_run() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -334,7 +345,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_parallel_run_optimize_tags() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -479,7 +490,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
         $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
-            'get_default_theme'));
+            'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -543,7 +554,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
         $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
-            'get_default_theme'));
+            'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -621,7 +632,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_core_features_to_include_in_specified_theme() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme', 'get_theme_config'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
index 525aa94..36b1184 100644 (file)
@@ -1,5 +1,8 @@
 This files describes API changes in the tool_behat code.
 
+=== 3.7 ===
+* Behat will now look for behat step definitions in the current
+  theme and any parents the theme may have.
 === 2.7 ===
 * Constants behat_base::cap_allow, behat_base::cap_prevent and
   behat_base::cap_prohibit have been removed in favour of the
index 118f761..b7029db 100644 (file)
@@ -20,7 +20,6 @@
     Moodle progress bar template for tool_lp.
 
     The purpose of this template is to render a progress bar with a brief description.
-    Inherits core/columns-1to2.
 
     Classes required for JS:
     * none
     }
 
 }}
-<div class="row-fluid rtl-compatible">
-    <div class="span4">
-        <div class="progresstext">
-            {{$progresstext}}{{progresstextvalue}}{{/progresstext}}
-        </div>
-    </div>
-    <div class="span8">
-        <div class="progress">
-            <div class="bar" style="width: {{$percentage}}{{percentagevalue}}{{/percentage}}%;" role="progressbar" aria-valuenow="{{$percentage}}{{percentagevalue}}{{/percentage}}" aria-valuemin="0" aria-valuemax="100">
-                {{$percentlabel}}{{percentlabelvalue}}{{/percentlabel}}
-            </div>
-        </div>
-    </div>
+<div id="progress-{{uniqid}}">
+    {{$progresstext}}{{progresstextvalue}}{{/progresstext}}
 </div>
+<progress class="progress" aria-describedby="progress-{{uniqid}}"
+    value="{{$percentage}}{{percentagevalue}}{{/percentage}}" max="100"></progress>
index bfa0e8d..aaa36d9 100644 (file)
@@ -7,7 +7,7 @@ Feature: Clear scheduled task fail delay
   Background:
     Given the scheduled task "\core\task\send_new_user_passwords_task" has a fail delay of "60" seconds
     And I log in as "admin"
-    And I navigate to "Server > Scheduled tasks" in site administration
+    And I navigate to "Server > Tasks > Scheduled tasks" in site administration
 
   Scenario: Clear fail delay
     When I click on "Clear" "text" in the "Send new user passwords" "table_row"
index 1f0a9df..4da19e5 100644 (file)
@@ -6,7 +6,7 @@ Feature: Manage scheduled tasks
 
   Background:
     Given I log in as "admin"
-    And I navigate to "Server > Scheduled tasks" in site administration
+    And I navigate to "Server > Tasks > Scheduled tasks" in site administration
 
   Scenario: Disable scheduled task
     When I click on "Edit task schedule: Log table cleanup" "link" in the "Log table cleanup" "table_row"
index 886c250..3771835 100644 (file)
Binary files a/admin/tool/usertours/amd/build/managesteps.min.js and b/admin/tool/usertours/amd/build/managesteps.min.js differ
index 9c9e2e6..75c7d1b 100644 (file)
Binary files a/admin/tool/usertours/amd/build/managetours.min.js and b/admin/tool/usertours/amd/build/managetours.min.js differ
index ba52019..420b89e 100644 (file)
@@ -18,6 +18,7 @@ function($, str, notification) {
          */
         removeStep: function(e) {
             e.preventDefault();
+            var targetUrl = $(e.currentTarget).attr('href');
             str.get_strings([
                 {
                     key:        'confirmstepremovaltitle',
@@ -35,11 +36,15 @@ function($, str, notification) {
                     key:        'no',
                     component:  'moodle'
                 }
-            ]).done(function(s) {
-                notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
-                    window.location = $(this).attr('href');
-                }, e.currentTarget));
-            });
+            ])
+            .then(function(s) {
+                notification.confirm(s[0], s[1], s[2], s[3], function() {
+                    window.location = targetUrl;
+                });
+
+                return;
+            })
+            .catch();
         },
 
         /**
index f5c601a..f6d3237 100644 (file)
@@ -18,7 +18,7 @@ function($, ajax, str, notification) {
          */
         removeTour: function(e) {
             e.preventDefault();
-
+            var targetUrl = $(e.currentTarget).attr('href');
             str.get_strings([
                 {
                     key:        'confirmtourremovaltitle',
@@ -36,11 +36,15 @@ function($, ajax, str, notification) {
                     key:        'no',
                     component:  'moodle'
                 }
-            ]).done(function(s) {
-                notification.confirm(s[0], s[1], s[2], s[3], $.proxy(function() {
-                    window.location = $(this).attr('href');
-                }, e.currentTarget));
-            });
+            ])
+            .then(function(s) {
+                notification.confirm(s[0], s[1], s[2], s[3], function() {
+                    window.location = targetUrl;
+                });
+
+                return;
+            })
+            .catch();
         },
 
         /**
index 14a96e0..1e4cdbb 100644 (file)
@@ -497,6 +497,12 @@ class helper {
             return;
         }
 
+        if (in_array($PAGE->pagelayout, ['maintenance', 'print', 'redirect'])) {
+            // Do not try to show user tours inside iframe, in maintenance mode,
+            // when printing, or during redirects.
+            return;
+        }
+
         if (self::$bootstrapped) {
             return;
         }
index a4fb976..192fec4 100644 (file)
     }
 
 }}
-<div class="modal" data-role="flexitour-step">
-    <div data-role="arrow"></div>
+<div class="modal-dialog" role="document" data-role="flexitour-step">
+  <div class="modal-content">
+    <div class="tooltip-arrow" data-role="arrow"></div>
     <div class="modal-header">
-      <button type="button" class="close" data-dismiss="modal" aria-hidden="true" data-role="end">&times;</button>
-      <h3 data-placeholder="title"></h3>
+      <h5 class="modal-title" data-placeholder="title"></h5>
+      <button type="button" class="close" data-dismiss="modal" aria-label="Close" data-role="end">
+        <span aria-hidden="true">&times;</span>
+      </button>
+
+    </div>
+    <div class="modal-body" data-placeholder="body">
     </div>
-    <div class="modal-body" data-placeholder="body"></div>
     <div class="modal-footer">
-        <div class="btn-group">
-            <button href="#" class="btn" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
-            <button href="#" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
-        </div>
-        <button class="btn" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
+      <button type="button" class="btn btn-secondary" data-role="previous">{{# str }} previous, moodle {{/ str }}</button>
+      <button type="button" class="btn btn-primary" data-role="next">{{# str }} next, moodle {{/ str }}</button>
+      <button class="btn btn-secondary" data-role="end"> {{# str }} endtour, tool_usertours {{/ str }} </button>
     </div>
+  </div>
 </div>
index faa02a4..5d6f205 100644 (file)
@@ -6,7 +6,7 @@ Feature: Add a bookmarks to an admin pages
 
   Background:
     Given I log in as "admin"
-    And I navigate to "Server > Scheduled tasks" in site administration
+    And I navigate to "Server > Tasks > Scheduled tasks" in site administration
     And I click on "Bookmark this page" "link" in the "Admin bookmarks" "block"
     And I log out
 
index 99b59c4..9d6dfd0 100644 (file)
Binary files a/blocks/myoverview/amd/build/view.min.js and b/blocks/myoverview/amd/build/view.min.js differ
index 85c81e1..747d33f 100644 (file)
@@ -217,7 +217,7 @@ function(
 
         setCourseFavouriteState(courseId, true).then(function(success) {
             if (success) {
-                PubSub.publish(CourseEvents.favourited);
+                PubSub.publish(CourseEvents.favourited, courseId);
                 removeAction.removeClass('hidden');
                 addAction.addClass('hidden');
                 showFavouriteIcon(root, courseId);
@@ -240,7 +240,7 @@ function(
 
         setCourseFavouriteState(courseId, false).then(function(success) {
             if (success) {
-                PubSub.publish(CourseEvents.unfavorited);
+                PubSub.publish(CourseEvents.unfavorited, courseId);
                 removeAction.addClass('hidden');
                 addAction.removeClass('hidden');
                 hideFavouriteIcon(root, courseId);
index 594bbde..a5bf1e2 100644 (file)
Binary files a/blocks/recentlyaccessedcourses/amd/build/main.min.js and b/blocks/recentlyaccessedcourses/amd/build/main.min.js differ
index 2fdd54c..203b1a0 100644 (file)
 define(
     [
         'jquery',
-        'core_course/repository',
-        'core/templates',
+        'core/custom_interaction_events',
         'core/notification',
         'core/pubsub',
-        'core_course/events'
+        'core/paged_content_paging_bar',
+        'core/templates',
+        'core_course/events',
+        'core_course/repository',
     ],
     function(
         $,
-        CoursesRepository,
-        Templates,
+        CustomEvents,
         Notification,
         PubSub,
-        CourseEvents
+        PagedContentPagingBar,
+        Templates,
+        CourseEvents,
+        CoursesRepository
     ) {
 
+        // Constants.
+        var NUM_COURSES_TOTAL = 10;
         var SELECTORS = {
-            COURSES_VIEW: '[data-region="recentlyaccessedcourses-view"]',
-            COURSES_VIEW_CONTENT: '[data-region="recentlyaccessedcourses-view-content"]'
+            CARD_CONTAINER: '[data-region="card-deck"]',
+            COURSE_IS_FAVOURITE: '[data-region="is-favourite"]',
+            CONTENT: '[data-region="view-content"]',
+            EMPTY_MESSAGE: '[data-region="empty-message"]',
+            LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]',
+            PAGING_BAR: '[data-region="paging-bar"]',
+            PAGING_BAR_NEXT: '[data-control="next"]',
+            PAGING_BAR_PREVIOUS: '[data-control="previous"]'
         };
+        // Module variables.
+        var contentLoaded = false;
+        var allCourses = [];
+        var visibleCoursesId = null;
+        var cardWidth = null;
+        var viewIndex = 0;
+        var availableVisibleCards = 1;
 
-        var NUM_COURSES_TOTAL = 10;
+        /**
+         * Show the empty message when no course are found.
+         *
+         * @param {object} root The root element for the courses view.
+         */
+        var showEmptyMessage = function(root) {
+            root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');
+            root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');
+            root.find(SELECTORS.CONTENT).addClass('hidden');
+        };
 
         /**
-         * Get enrolled courses from backend.
+         * Show the empty message when no course are found.
          *
-         * @method getRecentCourses
-         * @param {int} userid User from which the courses will be obtained
-         * @param {int} limit Only return this many results
-         * @return {array} Courses user has accessed
+         * @param {object} root The root element for the courses view.
          */
-        var getRecentCourses = function(userid, limit) {
-            return CoursesRepository.getLastAccessedCourses(userid, limit);
+        var showContent = function(root) {
+            root.find(SELECTORS.CONTENT).removeClass('hidden');
+            root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');
+            root.find(SELECTORS.LOADING_PLACEHOLDER).addClass('hidden');
         };
 
         /**
-         * Render the dashboard courses.
+         * Show the paging bar.
          *
-         * @method renderCourses
          * @param {object} root The root element for the courses view.
-         * @param {array} courses containing array of returned courses.
-         * @return {promise} Resolved with HTML and JS strings
          */
-        var renderCourses = function(root, courses) {
-            if (courses.length > 0) {
-                return Templates.render('core_course/view-cards', {
-                    courses: courses
-                });
-            } else {
-                var nocoursesimgurl = root.attr('data-nocoursesimg');
-                return Templates.render('block_recentlyaccessedcourses/no-courses', {
-                    nocoursesimg: nocoursesimgurl
+        var showPagingBar = function(root) {
+            var pagingBar = root.find(SELECTORS.PAGING_BAR);
+            pagingBar.css('opacity', 1);
+            pagingBar.css('visibility', 'visible');
+            pagingBar.attr('aria-hidden', 'false');
+        };
+
+        /**
+         * Hide the paging bar.
+         *
+         * @param {object} root The root element for the courses view.
+         */
+        var hidePagingBar = function(root) {
+            var pagingBar = root.find(SELECTORS.PAGING_BAR);
+            pagingBar.css('opacity', 0);
+            pagingBar.css('visibility', 'hidden');
+            pagingBar.attr('aria-hidden', 'true');
+        };
+
+        /**
+         * Show the favourite indicator for the given course (if it's in the list).
+         *
+         * @param {object} root The root element for the courses view.
+         * @param {number} courseId The id of the course to be favourited.
+         */
+        var favouriteCourse = function(root, courseId) {
+            allCourses.forEach(function(course) {
+                if (course.attr('data-course-id') == courseId) {
+                    course.find(SELECTORS.COURSE_IS_FAVOURITE).removeClass('hidden');
+                }
+            });
+        };
+
+        /**
+         * Hide the favourite indicator for the given course (if it's in the list).
+         *
+         * @param {object} root The root element for the courses view.
+         * @param {number} courseId The id of the course to be unfavourited.
+         */
+        var unfavouriteCourse = function(root, courseId) {
+            allCourses.forEach(function(course) {
+                if (course.attr('data-course-id') == courseId) {
+                    course.find(SELECTORS.COURSE_IS_FAVOURITE).addClass('hidden');
+                }
+            });
+        };
+
+        /**
+         * Render the a list of courses.
+         *
+         * @param {array} courses containing array of courses.
+         * @return {promise} Resolved with list of rendered courses as jQuery objects.
+         */
+        var renderAllCourses = function(courses) {
+            var promises = courses.map(function(course) {
+                return Templates.render('block_recentlyaccessedcourses/course-card', course);
+            });
+
+            return $.when.apply(null, promises).then(function() {
+                var renderedCourses = [];
+
+                promises.forEach(function(promise) {
+                    promise.then(function(html) {
+                        renderedCourses.push($(html));
+                        return;
+                    })
+                    .catch(Notification.exception);
                 });
-            }
+
+                return renderedCourses;
+            });
         };
 
         /**
          * Fetch user's recently accessed courses and reload the content of the block.
          *
          * @param {int} userid User whose courses will be shown
-         * @param {object} root The root element for the recentlyaccessedcourses view.
          * @returns {promise} The updated content for the block.
          */
-        var reloadContent = function(userid, root) {
+        var loadContent = function(userid) {
+            return CoursesRepository.getLastAccessedCourses(userid, NUM_COURSES_TOTAL)
+                .then(function(courses) {
+                    return renderAllCourses(courses);
+                });
+        };
 
-            var recentcoursesViewRoot = root.find(SELECTORS.COURSES_VIEW);
-            var recentcoursesViewContent = root.find(SELECTORS.COURSES_VIEW_CONTENT);
+        /**
+         * Recalculate the number of courses that should be visible.
+         *
+         * @param {object} root The root element for the courses view.
+         */
+        var recalculateVisibleCourses = function(root) {
+            var container = root.find(SELECTORS.CONTENT).find(SELECTORS.CARD_CONTAINER);
+            var availableWidth = parseFloat(root.css('width'));
+            var numberOfCourses = allCourses.length;
+            var start = 0;
+
+            if (!cardWidth) {
+                container.html(allCourses[0]);
+                // Render one card initially to calculate the width of the cards
+                // including the margins.
+                cardWidth = allCourses[0].outerWidth(true);
+            }
+
+            availableVisibleCards = Math.floor(availableWidth / cardWidth);
+
+            if (viewIndex + availableVisibleCards < numberOfCourses) {
+                start = viewIndex;
+            } else {
+                var overflow = (viewIndex + availableVisibleCards) - numberOfCourses;
+                start = viewIndex - overflow;
+                start = start >= 0 ? start : 0;
+            }
+
+            var coursesToShow = allCourses.slice(start, start + availableVisibleCards);
+            // Create an id for the list of courses we expect to be displayed.
+            var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {
+                return carry + course.attr('data-course-id');
+            }, '');
 
-            var coursesPromise = getRecentCourses(userid, NUM_COURSES_TOTAL);
+            // Don't bother updating the DOM unless the visible courses have changed.
+            if (visibleCoursesId != newVisibleCoursesId) {
+                var pagingBar = root.find(PagedContentPagingBar.rootSelector);
+                container.html(coursesToShow);
+                visibleCoursesId = newVisibleCoursesId;
 
-            return coursesPromise.then(function(courses) {
-                var pagedContentPromise = renderCourses(recentcoursesViewRoot, courses);
+                if (availableVisibleCards >= allCourses.length) {
+                    hidePagingBar(root);
+                } else {
+                    showPagingBar(root);
 
-                pagedContentPromise.then(function(html, js) {
-                    return Templates.replaceNodeContents(recentcoursesViewContent, html, js);
-                }).catch(Notification.exception);
-                return coursesPromise;
-            }).catch(Notification.exception);
+                    if (viewIndex === 0) {
+                        PagedContentPagingBar.disablePreviousControlButtons(pagingBar);
+                    } else {
+                        PagedContentPagingBar.enablePreviousControlButtons(pagingBar);
+                    }
+
+                    if (viewIndex + availableVisibleCards >= allCourses.length) {
+                        PagedContentPagingBar.disableNextControlButtons(pagingBar);
+                    } else {
+                        PagedContentPagingBar.enableNextControlButtons(pagingBar);
+                    }
+                }
+            }
         };
 
         /**
          * Register event listeners for the block.
          *
-         * @param {int} userid User whose courses will be shown
          * @param {object} root The root element for the recentlyaccessedcourses block.
          */
-        var registerEventListeners = function(userid, root) {
-            PubSub.subscribe(CourseEvents.favourited, function() {
-                reloadContent(userid, root);
+        var registerEventListeners = function(root) {
+            var resizeTimeout = null;
+            var drawerToggling = false;
+
+            PubSub.subscribe(CourseEvents.favourited, function(courseId) {
+                favouriteCourse(root, courseId);
+            });
+
+            PubSub.subscribe(CourseEvents.unfavorited, function(courseId) {
+                unfavouriteCourse(root, courseId);
+            });
+
+            PubSub.subscribe('nav-drawer-toggle-start', function() {
+                if (!contentLoaded || !allCourses.length || drawerToggling) {
+                    // Nothing to recalculate.
+                    return;
+                }
+
+                drawerToggling = true;
+                var recalculationCount = 0;
+                // This function is going to recalculate the number of courses while
+                // the nav drawer is opening or closes (up to a maximum of 5 recalcs).
+                var doRecalculation = function() {
+                    setTimeout(function() {
+                        recalculateVisibleCourses(root);
+                        recalculationCount++;
+
+                        if (recalculationCount < 5 && drawerToggling) {
+                            // If we haven't done too many recalculations and the drawer
+                            // is still toggling then recurse.
+                            doRecalculation();
+                        }
+                    }, 100);
+                };
+
+                // Start the recalculations.
+                doRecalculation(root);
+            });
+
+            PubSub.subscribe('nav-drawer-toggle-end', function() {
+                drawerToggling = false;
+            });
+
+            $(window).on('resize', function() {
+                if (!contentLoaded || !allCourses.length) {
+                    // Nothing to reclculate.
+                    return;
+                }
+
+                // Resize events fire rapidly so recalculating the visible courses each
+                // time can be expensive. Let's debounce them,
+                if (!resizeTimeout) {
+                    resizeTimeout = setTimeout(function() {
+                        resizeTimeout = null;
+                        recalculateVisibleCourses(root);
+                    // The recalculateVisibleCourses function will execute at a rate of 15fps.
+                    }, 66);
+                }
+            });
+
+            CustomEvents.define(root, [CustomEvents.events.activate]);
+            root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_NEXT, function(e, data) {
+                var button = $(e.target).closest(SELECTORS.PAGING_BAR_NEXT);
+                if (!button.hasClass('disabled')) {
+                    viewIndex = viewIndex + availableVisibleCards;
+                    recalculateVisibleCourses(root);
+                }
+
+                data.originalEvent.preventDefault();
             });
 
-            PubSub.subscribe(CourseEvents.unfavorited, function() {
-                reloadContent(userid, root);
+            root.on(CustomEvents.events.activate, SELECTORS.PAGING_BAR_PREVIOUS, function(e, data) {
+                var button = $(e.target).closest(SELECTORS.PAGING_BAR_PREVIOUS);
+                if (!button.hasClass('disabled')) {
+                    viewIndex = viewIndex - availableVisibleCards;
+                    viewIndex = viewIndex < 0 ? 0 : viewIndex;
+                    recalculateVisibleCourses(root);
+                }
+
+                data.originalEvent.preventDefault();
             });
         };
 
@@ -129,8 +334,22 @@ define(
         var init = function(userid, root) {
             root = $(root);
 
-            registerEventListeners(userid, root);
-            reloadContent(userid, root);
+            registerEventListeners(root);
+            loadContent(userid)
+                .then(function(renderedCourses) {
+                    allCourses = renderedCourses;
+                    contentLoaded = true;
+
+                    if (allCourses.length) {
+                        showContent(root);
+                        recalculateVisibleCourses(root);
+                    } else {
+                        showEmptyMessage(root);
+                    }
+
+                    return;
+                })
+                .catch(Notification.exception);
         };
 
         return {
index 06eb282..631af28 100644 (file)
@@ -45,11 +45,15 @@ class main implements renderable, templatable {
     public function export_for_template(renderer_base $output) {
         global $USER;
 
-        $nocoursesurl = $output->image_url('courses', 'block_recentlyaccessedcourses')->out();
+        $nocoursesurl = $output->image_url('courses', 'block_recentlyaccessedcourses')->out(false);
 
         return [
             'userid' => $USER->id,
-            'nocoursesimg' => $nocoursesurl
+            'nocoursesimgurl' => $nocoursesurl,
+            'pagingbar' => [
+                'next' => true,
+                'previous' => true
+            ]
         ];
     }
 }
diff --git a/blocks/recentlyaccessedcourses/templates/course-card.mustache b/blocks/recentlyaccessedcourses/templates/course-card.mustache
new file mode 100644 (file)
index 0000000..7b98a5e
--- /dev/null
@@ -0,0 +1,44 @@
+{{!
+    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 Licensebllsdsadfasfd
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template block_recentlyaccessedcourses/course-card
+
+    This template renders a course for the recentlyaccessedcourses block.
+
+    Example context (json):
+    {
+        "courses": [
+            {
+                "name": "Assignment due 1",
+                "viewurl": "https://moodlesite/course/view.php?id=2",
+                "courseimageurl": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
+                "fullname": "course 3",
+                "isfavourite": true
+            }
+        ]
+    }
+}}
+
+{{< core_course/coursecard }}
+    {{$coursecategory}}
+        <span class="sr-only">
+            {{#str}}aria:coursecategory, core_course{{/str}}
+        </span>
+        <span class="text-truncate">{{{coursecategory}}}</span>
+    {{/coursecategory}}
+    {{$coursename}} <span class="text-truncate">{{{fullname}}}</span> {{/coursename}}
+{{/ core_course/coursecard }}
diff --git a/blocks/recentlyaccessedcourses/templates/no-courses.mustache b/blocks/recentlyaccessedcourses/templates/no-courses.mustache
deleted file mode 100644 (file)
index cf1b8b0..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-{{!
-    This file is part of Moodle - http://moodle.org/
-
-    Moodle is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    Moodle is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-}}
-{{< core_course/no-courses}}
-    {{$nocoursestring}}
-        {{#str}} nocourses, block_recentlyaccessedcourses {{/str}}
-    {{/nocoursestring}}
-{{/ core_course/no-courses}}
index ec44dae..bb894d7 100644 (file)
 
     Example context (json):
     {
-        "nocoursesimg": "https://moodlesite/theme/image.php/boost/block_recentlyaccessedcourses/1535727318/courses"
     }
 }}
-<div id="recentlyaccessedcourses-view-{{uniqid}}"
-     data-region="recentlyaccessedcourses-view"
-     data-nocoursesimg="{{nocoursesimg}}">
-    <div data-region="recentlyaccessedcourses-view-content">
-        <div data-region="recentlyaccessedcourses-loading-placeholder">
-            <div class="card-deck dashboard-card-deck one-row" style="height: 11.1rem">
-                {{> core_course/placeholder-course }}
-                {{> core_course/placeholder-course }}
-                {{> core_course/placeholder-course }}
-                {{> core_course/placeholder-course }}
-            </div>
+<div id="recentlyaccessedcourses-view-{{uniqid}}" data-region="recentlyaccessedcourses-view">
+    <div data-region="loading-placeholder">
+        <div class="card-deck dashboard-card-deck one-row overflow-hidden" style="height: 13.05rem">
+            {{> core_course/placeholder-course }}
+            {{> core_course/placeholder-course }}
+            {{> core_course/placeholder-course }}
+            {{> core_course/placeholder-course }}
         </div>
     </div>
-</div>
\ No newline at end of file
+    <div class="hidden" data-region="view-content">
+        {{#pagingbar}}
+            <div class="d-flex paging-bar-container mb-3" data-region="paging-bar-container">
+                {{> core/paged_content_paging_bar }}
+            </div>
+        {{/pagingbar}}
+        {{< core_course/coursecards }}
+            {{$classes}}one-row fixed-width-cards justify-content-center overflow-hidden{{/classes}}
+        {{/ core_course/coursecards }}
+    </div>
+    <div class="hidden text-xs-center text-center m-t-3" data-region="empty-message">
+        <img class="empty-placeholder-image-lg m-t-1"
+            src="{{nocoursesimgurl}}"
+            alt="{{#str}} nocourses, block_recentlyaccessedcourses {{/str}}"
+            role="presentation">
+        <p class="text-muted mt-3">{{#str}} nocourses, block_recentlyaccessedcourses {{/str}}</p>
+    </div>
+</div>
index 9f0411f..0dc8b87 100644 (file)
@@ -1,15 +1,18 @@
 <div class="searchform">
-    <form action="{{actionurl}}" style="display: inline;">
-        <fieldset class="invisiblefieldset">
-            <legend class="accesshide">{{#str}}search{{/str}}</legend>
-            <input type="hidden" name="id" value="{{courseid}}">
-            <label class="accesshide" for="searchform_search">{{#str}}search{{/str}}</label>
-            <input id="searchform_search" name="search" type="text" size="16">
-            <button id="searchform_button" type="submit" title={{#quote}}{{#str}}search{{/str}}{{/quote}}>{{#str}}go{{/str}}</button><br>
-            <a href="{{advancedsearchurl}}">{{#str}}advancedsearch, block_search_forums{{/str}}</a>
-            {{#helpicon}}
-                {{>core/help_icon}}
-            {{/helpicon}}
-        </fieldset>
+    <form action="{{actionurl}}" class="form-inline">
+        <input type="hidden" name="id" value="{{courseid}}">
+        <div class="input-group w-100">
+            <label class="sr-only" for="searchform_search">{{#str}}search{{/str}}</label>
+            <input id="searchform_search" name="search" type="text" class="form-control" size="10">
+            <div class="input-group-append">
+                <button class="btn btn-secondary" id="searchform_button" type="submit">{{#str}}go{{/str}}</button>
+            </div>
+        </div>
     </form>
+    <div class="mt-3">
+        <a href="{{advancedsearchurl}}">{{#str}}advancedsearch, block_search_forums{{/str}}</a>
+        {{#helpicon}}
+            {{>core/help_icon}}
+        {{/helpicon}}
+    </div>
 </div>
index 34e99ef..bceefd8 100644 (file)
@@ -143,14 +143,12 @@ class block_settings_renderer extends plugin_renderer_base {
     }
 
     public function search_form(moodle_url $formtarget, $searchvalue) {
-        $content = html_writer::start_tag('form', array('class'=>'adminsearchform', 'method'=>'get', 'action'=>$formtarget, 'role' => 'search'));
-        $content .= html_writer::start_tag('div');
-        $content .= html_writer::tag('label', s(get_string('searchinsettings', 'admin')), array('for'=>'adminsearchquery', 'class'=>'accesshide'));
-        $content .= html_writer::empty_tag('input', array('id'=>'adminsearchquery', 'type'=>'text', 'name'=>'query', 'value'=>s($searchvalue)));
-        $content .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>s(get_string('search'))));
-        $content .= html_writer::end_tag('div');
-        $content .= html_writer::end_tag('form');
-        return $content;
+        $data = [
+                'action' => $formtarget->out(false),
+                'label' => get_string('searchinsettings', 'admin'),
+                'searchvalue' => $searchvalue
+        ];
+        return $this->render_from_template('block_settings/search_form', $data);
     }
 
 }
index b05d84c..5f55ca2 100644 (file)
@@ -46,15 +46,13 @@ class behat_blocks extends behat_base {
      * @param string $blockname
      */
     public function i_add_the_block($blockname) {
-        $this->execute('behat_forms::i_set_the_field_to',
-            array("bui_addblock", $this->escape($blockname))
-        );
+        $addblock = get_string('addblock');
+        $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
 
-        // If we are running without javascript we need to submit the form.
         if (!$this->running_javascript()) {
-            $this->execute('behat_general::i_click_on_in_the',
-                array(get_string('go'), "button", "#add_block", "css_element")
-            );
+            $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
+        } else {
+            $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
         }
     }
 
@@ -108,7 +106,7 @@ class behat_blocks extends behat_base {
         }
 
         $this->execute('behat_general::i_click_on_in_the',
-            array(get_string('actions'), "link", $this->escape($blockname), "block")
+                array("a[data-toggle='dropdown']", "css_element", $this->escape($blockname), "block")
         );
     }
 
@@ -137,7 +135,17 @@ class behat_blocks extends behat_base {
      * @param string $blockname
      */
     public function the_add_block_selector_should_contain_block($blockname) {
-        $this->execute('behat_forms::the_select_box_should_contain', [get_string('addblock'), $blockname]);
+        $addblock = get_string('addblock');
+        $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
+
+        $cancelstr = get_string('cancel');
+        if (!$this->running_javascript()) {
+            $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
+            $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']);
+        } else {
+            $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
+            $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']);
+        }
     }
 
     /**
@@ -147,6 +155,16 @@ class behat_blocks extends behat_base {
      * @param string $blockname
      */
     public function the_add_block_selector_should_not_contain_block($blockname) {
-        $this->execute('behat_forms::the_select_box_should_not_contain', [get_string('addblock'), $blockname]);
+        $addblock = get_string('addblock');
+        $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', $addblock);
+
+        $cancelstr = get_string('cancel');
+        if (!$this->running_javascript()) {
+            $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']);
+            $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']);
+        } else {
+            $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']);
+            $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']);
+        }
     }
 }
index d6c91af..072345d 100644 (file)
         "day": "Today",
         "url": "http://example.com/",
         "title": "Monday 2nd January",
-        "content": "<img class='icon smallicon' alt='icon' src='../../../pix/i/siteevent.svg'>Test site event"
+        "content": "<img class='icon smallicon' src='../../../pix/i/siteevent.svg'>Test site event"
     }
 }}
-{{< core/hover_tooltip }}
-    {{$anchor}}
-        <a href="{{url}}">{{$day}}{{day}}{{/day}}</a>
-    {{/anchor}}
-    {{$tooltip}}
-        <b>{{$title}}{{title}}{{/title}}</b>
-        {{$content}}{{{content}}}{{/content}}
-    {{/tooltip}}
-{{/ core/hover_tooltip }}
+<a {{!
+    }} id="calendar-day-popover-link-{{courseid}}-{{year}}-{{yday}}-{{uniqid}}"{{!
+    }} href="{{$url}}{{url}}{{/url}}"{{!
+    }} data-container="body"{{!
+    }} data-toggle="popover"{{!
+    }} data-html="true"{{!
+    }} data-trigger="hover"{{!
+    }} data-placement="top"{{!
+    }} data-title="{{$title}}{{title}}{{/title}}"{{!
+    }} data-alternate="{{$nocontent}}{{/nocontent}}"{{!
+}}>{{$day}}{{day}}{{/day}}</a>
+<div class="hidden">
+    {{$content}}{{/content}}
+</div>
+{{#js}}
+require(['jquery'], function($) {
+    require(['theme_boost/popover'], function() {
+        var target = $("#calendar-day-popover-link-{{courseid}}-{{year}}-{{yday}}-{{uniqid}}");
+        target.popover({
+            content: function() {
+                var source = target.next().find("> *:not('.hidden')");
+                var content = $('<div>');
+
+                if (source.length) {
+                    content.html(source.clone(false));
+                } else {
+                    content.html(target.data('alternate'));
+                }
+
+                return content.html();
+            }
+        });
+    });
+});
+{{/js}}
index 40bef75..d2e7092 100644 (file)
@@ -139,13 +139,15 @@ class core_course_management_renderer extends plugin_renderer_base {
         $listing = core_course_category::get(0)->get_children();
 
         $attributes = array(
-            'class' => 'ml',
-            'role' => 'tree',
-            'aria-labelledby' => 'category-listing-title'
+                'class' => 'ml-1 list-unstyled',
+                'role' => 'tree',
+                'aria-labelledby' => 'category-listing-title'
         );
 
-        $html  = html_writer::start_div('category-listing');
-        $html .= html_writer::tag('h3', get_string('categories'), array('id' => 'category-listing-title'));
+        $html  = html_writer::start_div('category-listing card w-100');
+        $html .= html_writer::tag('h3', get_string('categories'),
+                array('class' => 'card-header', 'id' => 'category-listing-title'));
+        $html .= html_writer::start_div('card-body');
         $html .= $this->category_listing_actions($category);
         $html .= html_writer::start_tag('ul', $attributes);
         foreach ($listing as $listitem) {
@@ -155,16 +157,17 @@ class core_course_management_renderer extends plugin_renderer_base {
                 $subcategories = $listitem->get_children();
             }
             $html .= $this->category_listitem(
-                $listitem,
-                $subcategories,
-                $listitem->get_children_count(),
-                $selectedcategory,
-                $selectedparents
+                    $listitem,
+                    $subcategories,
+                    $listitem->get_children_count(),
+                    $selectedcategory,
+                    $selectedparents
             );
         }
         $html .= html_writer::end_tag('ul');
         $html .= $this->category_bulk_actions($category);
         $html .= html_writer::end_div();
+        $html .= html_writer::end_div();
         return $html;
     }
 
@@ -181,20 +184,20 @@ class core_course_management_renderer extends plugin_renderer_base {
      * @return string
      */
     public function category_listitem(core_course_category $category, array $subcategories, $totalsubcategories,
-                                      $selectedcategory = null, $selectedcategories = array()) {
+            $selectedcategory = null, $selectedcategories = array()) {
 
         $isexpandable = ($totalsubcategories > 0);
         $isexpanded = (!empty($subcategories));
         $activecategory = ($selectedcategory === $category->id);
         $attributes = array(
-            'class' => 'listitem listitem-category',
-            'data-id' => $category->id,
-            'data-expandable' => $isexpandable ? '1' : '0',
-            'data-expanded' => $isexpanded ? '1' : '0',
-            'data-selected' => $activecategory ? '1' : '0',
-            'data-visible' => $category->visible ? '1' : '0',
-            'role' => 'treeitem',
-            'aria-expanded' => $isexpanded ? 'true' : 'false'
+                'class' => 'listitem listitem-category list-group-item list-group-item-action',
+                'data-id' => $category->id,
+                'data-expandable' => $isexpandable ? '1' : '0',
+                'data-expanded' => $isexpanded ? '1' : '0',
+                'data-selected' => $activecategory ? '1' : '0',
+                'data-visible' => $category->visible ? '1' : '0',
+                'role' => 'treeitem',
+                'aria-expanded' => $isexpanded ? 'true' : 'false'
         );
         $text = $category->get_formatted_name();
         if ($category->parent) {
@@ -205,12 +208,12 @@ class core_course_management_renderer extends plugin_renderer_base {
         }
         $courseicon = $this->output->pix_icon('i/course', get_string('courses'));
         $bcatinput = array(
-            'type' => 'checkbox',
-            'name' => 'bcat[]',
-            'value' => $category->id,
-            'class' => 'bulk-action-checkbox',
-            'aria-label' => get_string('bulkactionselect', 'moodle', $text),
-            'data-action' => 'select'
+                'type' => 'checkbox',
+                'name' => 'bcat[]',
+                'value' => $category->id,
+                'class' => 'bulk-action-checkbox',
+                'aria-label' => get_string('bulkactionselect', 'moodle', $text),
+                'data-action' => 'select'
         );
 
         if (!$category->can_resort_subcategories() && !$category->has_manage_capability()) {
@@ -220,34 +223,36 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         $viewcaturl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
         if ($isexpanded) {
-            $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
+            $icon = $this->output->pix_icon('t/switch_minus', get_string('collapse'),
+                    'moodle', array('class' => 'tree-icon', 'title' => ''));
             $icon = html_writer::link(
-                $viewcaturl,
-                $icon,
-                array(
-                    'class' => 'float-left',
-                    'data-action' => 'collapse',
-                    'title' => get_string('collapsecategory', 'moodle', $text),
-                    'aria-controls' => 'subcategoryof'.$category->id
-                )
+                    $viewcaturl,
+                    $icon,
+                    array(
+                            'class' => 'float-left',
+                            'data-action' => 'collapse',
+                            'title' => get_string('collapsecategory', 'moodle', $text),
+                            'aria-controls' => 'subcategoryof'.$category->id
+                    )
             );
         } else if ($isexpandable) {
-            $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'), 'moodle', array('class' => 'tree-icon', 'title' => ''));
+            $icon = $this->output->pix_icon('t/switch_plus', get_string('expand'),
+                    'moodle', array('class' => 'tree-icon', 'title' => ''));
             $icon = html_writer::link(
-                $viewcaturl,
-                $icon,
-                array(
-                    'class' => 'float-left',
-                    'data-action' => 'expand',
-                    'title' => get_string('expandcategory', 'moodle', $text)
-                )
+                    $viewcaturl,
+                    $icon,
+                    array(
+                            'class' => 'float-left',
+                            'data-action' => 'expand',
+                            'title' => get_string('expandcategory', 'moodle', $text)
+                    )
             );
         } else {
             $icon = $this->output->pix_icon(
-                'i/empty',
-                '',
-                'moodle',
-                array('class' => 'tree-icon'));
+                    'i/empty',
+                    '',
+                    'moodle',
+                    array('class' => 'tree-icon'));
             $icon = html_writer::span($icon, 'float-left');
         }
         $actions = \core_course\management\helper::get_category_listitem_actions($category);
@@ -268,7 +273,7 @@ class core_course_management_renderer extends plugin_renderer_base {
             $textattributes['aria-label'] = $textlabel;
         }
         $html .= html_writer::link($viewcaturl, $text, $textattributes);
-        $html .= html_writer::start_div('float-right');
+        $html .= html_writer::start_div('float-right d-flex');
         if ($category->idnumber) {
             $html .= html_writer::tag('span', s($category->idnumber), array('class' => 'dimmed idnumber'));
         }
@@ -277,28 +282,28 @@ class core_course_management_renderer extends plugin_renderer_base {
         }
         $countid = 'course-count-'.$category->id;
         $html .= html_writer::span(
-            html_writer::span($category->get_courses_count()) .
-            html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
-            $courseicon,
-            'course-count dimmed',
-            array('aria-labelledby' => $countid)
+                html_writer::span($category->get_courses_count()) .
+                html_writer::span(get_string('courses'), 'accesshide', array('id' => $countid)) .
+                $courseicon,
+                'course-count dimmed',
+                array('aria-labelledby' => $countid)
         );
         $html .= html_writer::end_div();
         $html .= html_writer::end_div();
         if ($isexpanded) {
             $html .= html_writer::start_tag('ul',
-                array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
+                    array('class' => 'ml', 'role' => 'group', 'id' => 'subcategoryof'.$category->id));
             $catatlevel = \core_course\management\helper::get_expanded_categories($category->path);
             $catatlevel[] = array_shift($selectedcategories);
             $catatlevel = array_unique($catatlevel);
             foreach ($subcategories as $listitem) {
                 $childcategories = (in_array($listitem->id, $catatlevel)) ? $listitem->get_children() : array();
                 $html .= $this->category_listitem(
-                    $listitem,
-                    $childcategories,
-                    $listitem->get_children_count(),
-                    $selectedcategory,
-                    $selectedcategories
+                        $listitem,
+                        $childcategories,
+                        $listitem->get_children_count(),
+                        $selectedcategory,
+                        $selectedcategories
                 );
             }
             $html .= html_writer::end_tag('ul');
@@ -327,7 +332,7 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         if ($cancreatecategory) {
             $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
-            $actions[] = html_writer::link($url, get_string('createnewcategory'));
+            $actions[] = html_writer::link($url, get_string('createnewcategory'), array('class' => 'btn btn-default'));
         }
         if (core_course_category::can_approve_course_requests()) {
             $actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
@@ -335,7 +340,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         if (count($actions) === 0) {
             return '';
         }
-        return html_writer::div(join(' | ', $actions), 'listing-actions category-listing-actions');
+        return html_writer::div(join(' ', $actions), 'listing-actions category-listing-actions mb-3');
     }
 
     /**
@@ -480,20 +485,19 @@ class core_course_management_renderer extends plugin_renderer_base {
      * Renders a course listing.
      *
      * @param core_course_category $category The currently selected category. This is what the listing is focused on.
-     * @param core_course_list_element  $course The currently selected course.
+     * @param core_course_list_element $course The currently selected course.
      * @param int $page The page being displayed.
      * @param int $perpage The number of courses to display per page.
      * @param string|null $viewmode The view mode the page is in, one out of 'default', 'combined', 'courses' or 'categories'.
      * @return string
      */
     public function course_listing(core_course_category $category = null, core_course_list_element $course = null,
-                                   $page = 0, $perpage = 20,
-        $viewmode = 'default') {
+            $page = 0, $perpage = 20, $viewmode = 'default') {
 
         if ($category === null) {
             $html = html_writer::start_div('select-a-category');
             $html .= html_writer::tag('h3', get_string('courses'),
-                array('id' => 'course-listing-title', 'tabindex' => '0'));
+                    array('id' => 'course-listing-title', 'tabindex' => '0'));
             $html .= $this->output->notification(get_string('selectacategory'), 'notifymessage');
             $html .= html_writer::end_div();
             return $html;
@@ -507,8 +511,8 @@ class core_course_management_renderer extends plugin_renderer_base {
             $page = $totalpages - 1;
         }
         $options = array(
-            'offset' => $page * $perpage,
-            'limit' => $perpage
+                'offset' => $page * $perpage,
+                'limit' => $perpage
         );
         $courseid = isset($course) ? $course->id : null;
         $class = '';
@@ -519,15 +523,16 @@ class core_course_management_renderer extends plugin_renderer_base {
             $class .= ' lastpage';
         }
 
-        $html  = html_writer::start_div('course-listing'.$class, array(
-            'data-category' => $category->id,
-            'data-page' => $page,
-            'data-totalpages' => $totalpages,
-            'data-totalcourses' => $totalcourses,
-            'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
+        $html  = html_writer::start_div('card course-listing w-100'.$class, array(
+                'data-category' => $category->id,
+                'data-page' => $page,
+                'data-totalpages' => $totalpages,
+                'data-totalcourses' => $totalcourses,
+                'data-canmoveoutof' => $category->can_move_courses_out_of() && $category->can_move_courses_into()
         ));
         $html .= html_writer::tag('h3', $category->get_formatted_name(),
-            array('id' => 'course-listing-title', 'tabindex' => '0'));
+                array('id' => 'course-listing-title', 'tabindex' => '0', 'class' => 'card-header'));
+        $html .= html_writer::start_div('card-body');
         $html .= $this->course_listing_actions($category, $course, $perpage);
         $html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
         $html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
@@ -538,6 +543,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         $html .= $this->listing_pagination($category, $page, $perpage, true, $viewmode);
         $html .= $this->course_bulk_actions($category);
         $html .= html_writer::end_div();
+        $html .= html_writer::end_div();
         return $html;
     }
 
@@ -588,7 +594,7 @@ class core_course_management_renderer extends plugin_renderer_base {
      * This function will be called for every course being displayed by course_listing.
      *
      * @param core_course_category $category The currently selected category and the category the course belongs to.
-     * @param core_course_list_element  $course The course to produce HTML for.
+     * @param core_course_list_element $course The course to produce HTML for.
      * @param int $selectedcourse The id of the currently selected course.
      * @return string
      */
@@ -596,19 +602,19 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         $text = $course->get_formatted_name();
         $attributes = array(
-            'class' => 'listitem listitem-course',
-            'data-id' => $course->id,
-            'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
-            'data-visible' => $course->visible ? '1' : '0'
+                'class' => 'listitem listitem-course list-group-item list-group-item-action',
+                'data-id' => $course->id,
+                'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
+                'data-visible' => $course->visible ? '1' : '0'
         );
 
         $bulkcourseinput = array(
-            'type' => 'checkbox',
-            'name' => 'bc[]',
-            'value' => $course->id,
-            'class' => 'bulk-action-checkbox',
-            'aria-label' => get_string('bulkactionselect', 'moodle', $text),
-            'data-action' => 'select'
+                'type' => 'checkbox',
+                'name' => 'bc[]',
+                'value' => $course->id,
+                'class' => 'bulk-action-checkbox',
+                'aria-label' => get_string('bulkactionselect', 'moodle', $text),
+                'data-action' => 'select'
         );
         if (!$category->has_manage_capability()) {
             // Very very hardcoded here.
@@ -654,7 +660,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         $actions = array();
         if ($category->can_create_course()) {
             $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
-            $actions[] = html_writer::link($url, get_string('createnewcourse'));
+            $actions[] = html_writer::link($url, get_string('createnewcourse'), array('class' => 'btn btn-default'));
         }
         if ($category->can_request_course()) {
             // Request a new course.
@@ -675,42 +681,42 @@ class core_course_management_renderer extends plugin_renderer_base {
             $timecreatedurl = new moodle_url($baseurl, array('resort' => 'timecreated'));
             $timecreateddescurl = new moodle_url($baseurl, array('resort' => 'timecreateddesc'));
             $menu = new action_menu(array(
-                new action_menu_link_secondary($fullnameurl,
-                                               null,
-                                               get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
-                new action_menu_link_secondary($fullnameurldesc,
-                                               null,
-                                               get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
-                new action_menu_link_secondary($shortnameurl,
-                                               null,
-                                               get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
-                new action_menu_link_secondary($shortnameurldesc,
-                                               null,
-                                               get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
-                new action_menu_link_secondary($idnumberurl,
-                                               null,
-                                               get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
-                new action_menu_link_secondary($idnumberdescurl,
-                                               null,
-                                               get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
-                new action_menu_link_secondary($timecreatedurl,
-                                               null,
-                                               get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
-                new action_menu_link_secondary($timecreateddescurl,
-                                               null,
-                                               get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
+                    new action_menu_link_secondary($fullnameurl,
+                            null,
+                            get_string('sortbyx', 'moodle', get_string('fullnamecourse'))),
+                    new action_menu_link_secondary($fullnameurldesc,
+                            null,
+                            get_string('sortbyxreverse', 'moodle', get_string('fullnamecourse'))),
+                    new action_menu_link_secondary($shortnameurl,
+                            null,
+                            get_string('sortbyx', 'moodle', get_string('shortnamecourse'))),
+                    new action_menu_link_secondary($shortnameurldesc,
+                            null,
+                            get_string('sortbyxreverse', 'moodle', get_string('shortnamecourse'))),
+                    new action_menu_link_secondary($idnumberurl,
+                            null,
+                            get_string('sortbyx', 'moodle', get_string('idnumbercourse'))),
+                    new action_menu_link_secondary($idnumberdescurl,
+                            null,
+                            get_string('sortbyxreverse', 'moodle', get_string('idnumbercourse'))),
+                    new action_menu_link_secondary($timecreatedurl,
+                            null,
+                            get_string('sortbyx', 'moodle', get_string('timecreatedcourse'))),
+                    new action_menu_link_secondary($timecreateddescurl,
+                            null,
+                            get_string('sortbyxreverse', 'moodle', get_string('timecreatedcourse')))
             ));
             $menu->set_menu_trigger(get_string('resortcourses'));
             $actions[] = $this->render($menu);
         }
         $strall = get_string('all');
         $menu = new action_menu(array(
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
-            new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 5)), null, 5),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 10)), null, 10),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 20)), null, 20),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 50)), null, 50),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 100)), null, 100),
+                new action_menu_link_secondary(new moodle_url($this->page->url, array('perpage' => 999)), null, $strall),
         ));
         if ((int)$perpage === 999) {
             $perpage = $strall;
@@ -718,7 +724,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         $menu->attributes['class'] .= ' courses-per-page';
         $menu->set_menu_trigger(get_string('perpagea', 'moodle', $perpage));
         $actions[] = $this->render($menu);
-        return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
+        return html_writer::div(join(' ', $actions), 'listing-actions course-listing-actions');
     }
 
     /**
@@ -800,20 +806,25 @@ class core_course_management_renderer extends plugin_renderer_base {
     /**
      * Renderers detailed course information.
      *
-     * @param core_course_list_element  $course The course to display details for.
+     * @param core_course_list_element $course The course to display details for.
      * @return string
      */
     public function course_detail(core_course_list_element $course) {
         $details = \core_course\management\helper::get_course_detail_array($course);
         $fullname = $details['fullname']['value'];
 
-        $html  = html_writer::start_div('course-detail');
-        $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title', 'tabindex' => '0'));
+        $html = html_writer::start_div('course-detail card');
+        $html .= html_writer::start_div('card-header');
+        $html .= html_writer::tag('h3', $fullname, array('id' => 'course-detail-title',
+                'class' => 'card-title', 'tabindex' => '0'));
+        $html .= html_writer::end_div();
+        $html .= html_writer::start_div('card-body');
         $html .= $this->course_detail_actions($course);
         foreach ($details as $class => $data) {
             $html .= $this->detail_pair($data['key'], $data['value'], $class);
         }
         $html .= html_writer::end_div();
+        $html .= html_writer::end_div();
         return $html;
     }
 
@@ -827,8 +838,8 @@ class core_course_management_renderer extends plugin_renderer_base {
      */
     protected function detail_pair($key, $value, $class ='') {
         $html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
-        $html .= html_writer::div(html_writer::span($key), 'pair-key span3 col-md-3 yui3-u-1-4');
-        $html .= html_writer::div(html_writer::span($value), 'pair-value span9 col-md-9 m-b-1 yui3-u-3-4 form-inline');
+        $html .= html_writer::div(html_writer::span($key), 'pair-key col-md-3 yui3-u-1-4 font-weight-bold');
+        $html .= html_writer::div(html_writer::span($value), 'pair-value col-md-8 yui3-u-3-4');
         $html .= html_writer::end_div();
         return $html;
     }
@@ -836,7 +847,7 @@ class core_course_management_renderer extends plugin_renderer_base {
     /**
      * A collection of actions for a course.
      *
-     * @param core_course_list_element  $course The course to display actions for.
+     * @param core_course_list_element $course The course to display actions for.
      * @return string
      */
     public function course_detail_actions(core_course_list_element $course) {
@@ -846,9 +857,10 @@ class core_course_management_renderer extends plugin_renderer_base {
         }
         $options = array();
         foreach ($actions as $action) {
-            $options[] = $this->action_link($action['url'], $action['string']);
+            $options[] = $this->action_link($action['url'], $action['string'], null,
+                    array('class' => 'btn btn-sm btn-secondary mr-1 mb-3'));
         }
-        return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
+        return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
     }
 
     /**
@@ -893,7 +905,7 @@ class core_course_management_renderer extends plugin_renderer_base {
      * @return string
      */
     public function grid_start($id = null, $class = null) {
-        $gridclass = 'grid-row-r row-fluid';
+        $gridclass = 'grid-start grid-row-r d-flex flex-wrap row';
         if (is_null($class)) {
             $class = $gridclass;
         } else {
@@ -925,30 +937,15 @@ class core_course_management_renderer extends plugin_renderer_base {
      */
     public function grid_column_start($size, $id = null, $class = null) {
 
-        // Calculate Bootstrap grid sizing.
-        $bootstrapclass = 'span'.$size.' col-md-'.$size;
-
-        // Calculate YUI grid sizing.
-        if ($size === 12) {
-            $maxsize = 1;
-            $size = 1;
-        } else {
-            $maxsize = 12;
-            $divisors = array(8, 6, 5, 4, 3, 2);
-            foreach ($divisors as $divisor) {
-                if (($maxsize % $divisor === 0) && ($size % $divisor === 0)) {
-                    $maxsize = $maxsize / $divisor;
-                    $size = $size / $divisor;
-                    break;
-                }
-            }
-        }
-        if ($maxsize > 1) {
-            $yuigridclass =  "grid-col-{$size}-{$maxsize} grid-col";
+        if ($id == 'course-detail') {
+            $size = 12;
+            $bootstrapclass = 'col-md-'.$size;
         } else {
-            $yuigridclass =  "grid-col-1 grid-col";
+            $bootstrapclass = 'd-flex flex-wrap px-3 mb-3';
         }
 
+        $yuigridclass = "col-sm";
+
         if (is_null($class)) {
             $class = $yuigridclass . ' ' . $bootstrapclass;
         } else {
@@ -958,7 +955,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         if (!is_null($id)) {
             $attributes['id'] = $id;
         }
-        return html_writer::start_div($class, $attributes);
+        return html_writer::start_div($class . " grid_column_start", $attributes);
     }
 
     /**
@@ -1058,14 +1055,14 @@ class core_course_management_renderer extends plugin_renderer_base {
      *
      * @param array $courses The courses to display.
      * @param int $totalcourses The total number of courses to display.
-     * @param core_course_list_element  $course The currently selected course if there is one.
+     * @param core_course_list_element $course The currently selected course if there is one.
      * @param int $page The current page, starting at 0.
      * @param int $perpage The number of courses to display per page.
      * @param string $search The string we are searching for.
      * @return string
      */
     public function search_listing(array $courses, $totalcourses, core_course_list_element $course = null, $page = 0, $perpage = 20,
-        $search = '') {
+            $search = '') {
         $page = max($page, 0);
         $perpage = max($perpage, 2);
         $totalpages = ceil($totalcourses / $perpage);
@@ -1077,11 +1074,11 @@ class core_course_management_renderer extends plugin_renderer_base {
         $last = false;
         $i = $page * $perpage;
 
-        $html  = html_writer::start_div('course-listing', array(
-            'data-category' => 'search',
-            'data-page' => $page,
-            'data-totalpages' => $totalpages,
-            'data-totalcourses' => $totalcourses
+        $html  = html_writer::start_div('course-listing w-100', array(
+                'data-category' => 'search',
+                'data-page' => $page,
+                'data-totalpages' => $totalpages,
+                'data-totalcourses' => $totalcourses
         ));
         $html .= html_writer::tag('h3', get_string('courses'));
         $html .= $this->search_pagination($totalcourses, $page, $perpage);
@@ -1172,7 +1169,7 @@ class core_course_management_renderer extends plugin_renderer_base {
      *
      * This function will be called for every course being displayed by course_listing.
      *
-     * @param core_course_list_element  $course The course to produce HTML for.
+     * @param core_course_list_element $course The course to produce HTML for.
      * @param int $selectedcourse The id of the currently selected course.
      * @return string
      */
@@ -1180,20 +1177,20 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         $text = $course->get_formatted_name();
         $attributes = array(
-            'class' => 'listitem listitem-course',
-            'data-id' => $course->id,
-            'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
-            'data-visible' => $course->visible ? '1' : '0'
+                'class' => 'listitem listitem-course list-group-item list-group-item-action',
+                'data-id' => $course->id,
+                'data-selected' => ($selectedcourse == $course->id) ? '1' : '0',
+                'data-visible' => $course->visible ? '1' : '0'
         );
         $bulkcourseinput = '';
         if (core_course_category::get($course->category)->can_move_courses_out_of()) {
             $bulkcourseinput = array(
-                'type' => 'checkbox',
-                'name' => 'bc[]',
-                'value' => $course->id,
-                'class' => 'bulk-action-checkbox',
-                'aria-label' => get_string('bulkactionselect', 'moodle', $text),
-                'data-action' => 'select'
+                    'type' => 'checkbox',
+                    'name' => 'bc[]',
+                    'value' => $course->id,
+                    'class' => 'bulk-action-checkbox',
+                    'aria-label' => get_string('bulkactionselect', 'moodle', $text),
+                    'data-action' => 'select'
             );
         }
         $viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
@@ -1302,16 +1299,26 @@ class core_course_management_renderer extends plugin_renderer_base {
         $strsearchcourses = get_string("searchcourses");
         $searchurl = new moodle_url('/course/management.php');
 
-        $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get',
-            'class' => 'form-inline'));
-        $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset m-y-1'));
-        $output .= html_writer::tag('label', $strsearchcourses, array('for' => $inputid));
-        $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid, 'size' => $inputsize,
-            'name' => 'search', 'value' => s($value), 'class' => 'form-control m-x-1'));
-        $output .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('go'),
-            'class' => 'btn btn-secondary'));
+        $output = html_writer::start_div('row');
+        $output .= html_writer::start_div('col-md-12');
+        $output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
+                'action' => $searchurl, 'method' => 'get'));
+        $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
+        $output .= html_writer::tag('div', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
+                array('class' => 'card-header'));
+        $output .= html_writer::start_div('card-body');
+        $output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
+        $output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
+                'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
+        $output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
+        $output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
+        $output .= html_writer::end_tag('span');
+        $output .= html_writer::end_div();
+        $output .= html_writer::end_div();
         $output .= html_writer::end_tag('fieldset');
         $output .= html_writer::end_tag('form');
+        $output .= html_writer::end_div();
+        $output .= html_writer::end_div();
 
         return $output;
     }
index 3bba431..b7a01bd 100644 (file)
@@ -344,13 +344,13 @@ class core_course_renderer extends plugin_renderer_base {
     }
 
     /**
-     * Renders html to display a course search form
+     * Renders html to display a course search form.
      *
      * @param string $value default value to populate the search field
      * @param string $format display format - 'plain' (default), 'short' or 'navbar'
      * @return string
      */
-    function course_search_form($value = '', $format = 'plain') {
+    public function course_search_form($value = '', $format = 'plain') {
         static $count = 0;
         $formid = 'coursesearch';
         if ((++$count) > 1) {
@@ -372,23 +372,19 @@ class core_course_renderer extends plugin_renderer_base {
                 $inputsize = 30;
         }
 
-        $strsearchcourses= get_string("searchcourses");
-        $searchurl = new moodle_url('/course/search.php');
-
-        $output = html_writer::start_tag('form', array('id' => $formid, 'action' => $searchurl, 'method' => 'get'));
-        $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
-        $output .= html_writer::tag('label', $strsearchcourses.': ', array('for' => $inputid));
-        $output .= html_writer::empty_tag('input', array('type' => 'text', 'id' => $inputid,
-            'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
-        $output .= html_writer::empty_tag('input', array('type' => 'submit',
-            'value' => get_string('go')));
-        $output .= html_writer::end_tag('fieldset');
+        $data = (object) [
+                'searchurl' => (new moodle_url('/course/search.php'))->out(false),
+                'id' => $formid,
+                'inputid' => $inputid,
+                'inputsize' => $inputsize,
+                'value' => $value
+        ];
         if ($format != 'navbar') {
-            $output .= $this->output->help_icon("coursesearch", "core");
+            $helpicon = new \help_icon('coursesearch', 'core');
+            $data->helpicon = $helpicon->export_for_template($this);
         }
-        $output .= html_writer::end_tag('form');
 
-        return $output;
+        return $this->render_from_template('core_course/course_search_form', $data);
     }
 
     /**
diff --git a/course/templates/coursecard.mustache b/course/templates/coursecard.mustache
new file mode 100644 (file)
index 0000000..0ea7e8f
--- /dev/null
@@ -0,0 +1,69 @@
+{{!
+    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 course_course/coursecard
+
+    This template renders the a card for the course cards.
+
+    Example context (json):
+    {
+        "courses": [
+            {
+                "name": "Assignment due 1",
+                "viewurl": "https://moodlesite/course/view.php?id=2",
+                "courseimage": "https://moodlesite/pluginfile/123/course/overviewfiles/123.jpg",
+                "fullname": "course 3",
+                "hasprogress": true,
+                "progress": 10
+            }
+        ]
+    }
+}}
+<div class="card dashboard-card" role="listitem"
+    data-region="course-content"
+    data-course-id="{{{id}}}">
+    <a href="{{viewurl}}" tabindex="-1">
+        <div class="card-img dashboard-card-img" style='background-image: url("{{{courseimage}}}");'>
+            <span class="sr-only">{{#str}}aria:courseimage, core_course{{/str}}</span>
+        </div>
+    </a>
+    <div class="card-body pr-1 course-info-container" id="course-info-container-{{id}}-{{uniqid}}">
+        <div class="d-flex align-items-start">
+            <a href="{{viewurl}}" class="coursename mr-2 text-truncate">
+                <div class="text-muted muted d-flex w-100 mb-1 text-truncate" style="flex-flow:wrap;">
+                    {{$coursecategory}}{{/coursecategory}}
+                    {{#showshortname}}
+                    {{$divider}}{{/divider}}
+                    <span class="sr-only">
+                        {{#str}}aria:courseshortname, core_course{{/str}}
+                    </span>
+                    <div>
+                        {{{shortname}}}
+                    </div>
+                    {{/showshortname}}
+                </div>
+                {{> core_course/favouriteicon }}
+                <span class="sr-only">
+                    {{#str}}aria:coursename, core_course{{/str}}
+                </span>
+                {{$coursename}}{{/coursename}}
+            </a>
+            {{$menu}}{{/menu}}
+        </div>
+    </div>
+    {{$progress}}{{/progress}}
+</div>
index c2c09a0..88ae6be 100644 (file)
     }
 }}
 
-<div class="card-deck dashboard-card-deck {{$classes}}{{/classes}}" role="list">
+<div class="card-deck dashboard-card-deck {{$classes}}{{/classes}}" data-region="card-deck" role="list">
 {{#courses}}
-    <div class="card dashboard-card" role="listitem"
-        data-region="course-content"
-        data-course-id="{{{id}}}">
-        <a href="{{viewurl}}" tabindex="-1">
-            <div class="card-img dashboard-card-img" style='background-image: url("{{{courseimage}}}");'>
-                <span class="sr-only">{{#str}}aria:courseimage, core_course{{/str}}</span>
-            </div>
-        </a>
-        <div class="card-body pr-1 course-info-container" id="course-info-container-{{id}}-{{uniqid}}">
-            <div class="d-flex align-items-start">
-                <a href="{{viewurl}}" class="coursename mr-2 text-truncate">
-                    <div class="text-muted muted d-flex w-100 mb-1 text-truncate" style="flex-flow:wrap;">
-                        {{$coursecategory}}{{/coursecategory}}
-                        {{#showshortname}}
-                        {{$divider}}{{/divider}}
-                        <span class="sr-only">
-                            {{#str}}aria:courseshortname, core_course{{/str}}
-                        </span>
-                        <div>
-                            {{{shortname}}}
-                        </div>
-                        {{/showshortname}}
-                    </div>
-                    {{> core_course/favouriteicon }}
-                    <span class="sr-only">
-                        {{#str}}aria:coursename, core_course{{/str}}
-                    </span>
-                    {{$coursename}}{{/coursename}}
-                </a>
-                {{$menu}}{{/menu}}
-            </div>
-        </div>
-        {{$progress}}{{/progress}}
-    </div>
+    {{> core_course/coursecard }}
 {{/courses}}
 </div>
index 93b8860..ee06c76 100644 (file)
@@ -186,10 +186,11 @@ class behat_course extends behat_base {
             // We are on the frontpage.
             if ($section) {
                 // Section 1 represents the contents on the frontpage.
-                $sectionxpath = "//body[@id='page-site-index']/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
+                $sectionxpath = "//body[@id='page-site-index']" .
+                        "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]";
             } else {
                 // Section 0 represents "Site main menu" block.
-                $sectionxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
+                $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]";
             }
         } else {
             // We are inside the course.
@@ -201,15 +202,16 @@ class behat_course extends behat_base {
         if ($this->running_javascript()) {
 
             // Clicks add activity or resource section link.
-            $sectionxpath = $sectionxpath . "/descendant::div[@class='section-modchooser']/span/a";
+            $sectionxpath = $sectionxpath . "/descendant::div" .
+                    "[contains(concat(' ', normalize-space(@class) , ' '), ' section-modchooser ')]/span/a";
             $sectionnode = $this->find('xpath', $sectionxpath);
             $sectionnode->click();
 
             // Clicks the selected activity if it exists.
             $activityxpath = "//div[@id='chooseform']/descendant::label" .
-                "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
-                "[normalize-space(.)=$activityliteral]" .
-                "/parent::label/child::input";
+                    "/descendant::span[contains(concat(' ', normalize-space(@class), ' '), ' typename ')]" .
+                    "[normalize-space(.)=$activityliteral]" .
+                    "/parent::label/child::input";
             $activitynode = $this->find('xpath', $activityxpath);
             $activitynode->doubleClick();
 
@@ -217,8 +219,9 @@ class behat_course extends behat_base {
             // Without Javascript.
 
             // Selecting the option from the select box which contains the option.
-            $selectxpath = $sectionxpath . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
-                "/descendant::select[option[normalize-space(.)=$activityliteral]]";
+            $selectxpath = $sectionxpath . "/descendant::div" .
+                    "[contains(concat(' ', normalize-space(@class), ' '), ' section_add_menus ')]" .
+                    "/descendant::select[option[normalize-space(.)=$activityliteral]]";
             $selectnode = $this->find('xpath', $selectxpath);
             $selectnode->selectOption($activity);
 
@@ -230,7 +233,6 @@ class behat_course extends behat_base {
 
     }
 
-
     /**
      * Opens a section edit menu if it is not already opened.
      *
@@ -248,7 +250,7 @@ class behat_course extends behat_base {
 
         // If it is already opened we do nothing.
         $xpath = $this->section_exists($sectionnumber);
-        $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@class, 'textmenu')]";
+        $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]";
 
         $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
         $menu = $this->find('xpath', $xpath, $exception);
@@ -550,8 +552,8 @@ class behat_course extends behat_base {
         // Edit menu should be visible.
         if ($this->is_course_editor()) {
             $xpath = $sectionxpath .
-                     "/descendant::div[contains(@class, 'section-actions')]" .
-                     "/descendant::a[contains(@class, 'textmenu')]";
+                    "/descendant::div[contains(@class, 'section-actions')]" .
+                    "/descendant::a[contains(@data-toggle, 'dropdown')]";
             if (!$this->getSession()->getPage()->find('xpath', $xpath)) {
                 throw new ExpectationException('The section edit menu is not available', $this->getSession());
             }
@@ -843,15 +845,23 @@ class behat_course extends behat_base {
 
         // If it is already opened we do nothing.
         $activitynode = $this->get_activity_node($activityname);
-        $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
-        if (!empty($classes['action-menu-shown'])) {
+
+        // Find the menu.
+        $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
+        if (!$menunode) {
+            throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
+                    $this->getSession());
+        }
+        $expanded = $menunode->getAttribute('aria-expanded');
+        if ($expanded == 'true') {
             return;
         }
 
         $this->execute('behat_course::i_click_on_in_the_activity',
-            array("a[role='menuitem']", "css_element", $this->escape($activityname))
+                array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
         );
 
+        $this->actions_menu_should_be_open($activityname);
     }
 
     /**
@@ -869,13 +879,19 @@ class behat_course extends behat_base {
 
         // If it is already closed we do nothing.
         $activitynode = $this->get_activity_node($activityname);
-        $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
-        if (empty($classes['action-menu-shown'])) {
+        // Find the menu.
+        $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
+        if (!$menunode) {
+            throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
+                    $this->getSession());
+        }
+        $expanded = $menunode->getAttribute('aria-expanded');
+        if ($expanded != 'true') {
             return;
         }
 
         $this->execute('behat_course::i_click_on_in_the_activity',
-            array("a[role='menuitem']", "css_element", $this->escape($activityname))
+                array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname))
         );
     }
 
@@ -892,10 +908,15 @@ class behat_course extends behat_base {
             throw new DriverException('Activities actions menu not available when Javascript is disabled');
         }
 
-        // If it is already closed we do nothing.
         $activitynode = $this->get_activity_node($activityname);
-        $classes = array_flip(explode(' ', $activitynode->getAttribute('class')));
-        if (empty($classes['action-menu-shown'])) {
+        // Find the menu.
+        $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]');
+        if (!$menunode) {
+            throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname),
+                    $this->getSession());
+        }
+        $expanded = $menunode->getAttribute('aria-expanded');
+        if ($expanded != 'true') {
             throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession());
         }
     }
@@ -1039,18 +1060,18 @@ class behat_course extends behat_base {
 
         // Determine the future new activity xpath from the former one.
         $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
-            "[contains(., $activityliteral)]/following-sibling::li";
-        $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@role='menuitem']";
+                "[contains(., $activityliteral)]/following-sibling::li";
+        $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']";
 
         if ($this->running_javascript()) {
             // We wait until the AJAX request finishes and the section is visible again.
             $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" .
-                "[contains(., $activityliteral)]" .
-                "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
-                "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
+                    "[contains(., $activityliteral)]" .
+                    "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" .
+                    "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]";
 
             $this->execute("behat_general::wait_until_exists",
-                array($this->escape($hiddenlightboxxpath), "xpath_element")
+                    array($this->escape($hiddenlightboxxpath), "xpath_element")
             );
 
             // Close the original activity actions menu.
@@ -1059,13 +1080,13 @@ class behat_course extends behat_base {
             // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at
             // this point, it don't even exists in the DOM (the steps are executed when we return them).
             $this->execute('behat_general::i_click_on',
-                array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
+                    array($this->escape($duplicatedactionsmenuxpath), "xpath_element")
             );
         }
 
         // We force the xpath as otherwise mink tries to interact with the former one.
         $this->execute('behat_general::i_click_on_in_the',
-            array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
+                array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element")
         );
 
         $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
@@ -1281,8 +1302,8 @@ class behat_course extends behat_base {
     protected function is_course_editor() {
 
         // We don't need to behat_base::spin() here as all is already loaded.
-        if (!$this->getSession()->getPage()->findButton(get_string('turneditingoff')) &&
-                !$this->getSession()->getPage()->findButton(get_string('turneditingon'))) {
+        if (!$this->getSession()->getPage()->findLink(get_string('turneditingoff')) &&
+                !$this->getSession()->getPage()->findLink(get_string('turneditingon'))) {
             return false;
         }
 
@@ -1842,7 +1863,8 @@ class behat_course extends behat_base {
      * @throws Behat\Mink\Exception\ExpectationException
      */
     protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) {
-        $actionsnode = $listingnode->find('xpath', "//*[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
+        $actionsnode = $listingnode->find('xpath', "//*" .
+                "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]");
         if (!$actionsnode) {
             throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession());
         }
@@ -1851,7 +1873,7 @@ class behat_course extends behat_base {
             throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession());
         }
         if ($this->running_javascript() && !$actionnode->isVisible()) {
-            $actionsnode->find('css', 'a.toggle-display')->click();
+            $actionsnode->find('css', 'a[data-toggle=dropdown]')->click();
             $actionnode = $actionsnode->find('css', '.action-'.$action);
         }
         $actionnode->click();
@@ -1874,10 +1896,7 @@ class behat_course extends behat_base {
      * @Given /^I navigate to course participants$/
      */
     public function i_navigate_to_course_participants() {
-        $coursestr = behat_context_helper::escape(get_string('courses'));
-        $mycoursestr = behat_context_helper::escape(get_string('mycourses'));
-        $xpath = "//div[contains(@class,'block')]//li[p/*[string(.)=$coursestr or string(.)=$mycoursestr]]";
-        $this->execute('behat_general::i_click_on_in_the', [get_string('participants'), 'link', $xpath, 'xpath_element']);
+        $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', get_string('participants'));
     }
 
     /**
index 1cf5509..0a6fcff 100644 (file)
@@ -602,8 +602,8 @@ class enrol_self_testcase extends advanced_testcase {
         $selfplugin->enrol_user($instance1, $user2->id, $editingteacherrole->id);
 
         $this->setUser($guest);
-        $noaccesshtml = get_string('noguestaccess', 'enrol') . $OUTPUT->continue_button(get_login_url());
-        $this->assertSame($noaccesshtml, $selfplugin->can_self_enrol($instance1, true));
+        $this->assertContains(get_string('noguestaccess', 'enrol'),
+                $selfplugin->can_self_enrol($instance1, true));
 
         $this->setUser($user1);
         $this->assertTrue($selfplugin->can_self_enrol($instance1, true));
index 1cf7e30..5484ff5 100644 (file)
@@ -134,124 +134,16 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * Returns html for displaying one file manager
      *
-     * The main element in HTML must have id="filemanager-{$client_id}" and
-     * class="filemanager fm-loading";
-     * After all necessary code on the page (both html and javascript) is loaded,
-     * the class fm-loading will be removed and added class fm-loaded;
-     * The main element (class=filemanager) will be assigned the following classes:
-     * 'fm-maxfiles' - when filemanager has maximum allowed number of files;
-     * 'fm-nofiles' - when filemanager has no files at all (although there might be folders);
-     * 'fm-noitems' - when current view (folder) has no items - neither files nor folders;
-     * 'fm-updating' - when current view is being updated (usually means that loading icon is to be displayed);
-     * 'fm-nomkdir' - when 'Make folder' action is unavailable (empty($fm->options->subdirs) == true)
-     *
-     * Element with class 'filemanager-container' will be holding evens for dnd upload (dragover, etc.).
-     * It will have class:
-     * 'dndupload-ready' - when a file is being dragged over the browser
-     * 'dndupload-over' - when file is being dragged over this filepicker (additional to 'dndupload-ready')
-     * 'dndupload-uploading' - during the upload process (note that after dnd upload process is
-     * over, the file manager will refresh the files list and therefore will have for a while class
-     * fm-updating. Both waiting processes should look similar so the images don't jump for user)
-     *
-     * If browser supports Drag-and-drop, the body element will have class 'dndsupported',
-     * otherwise - 'dndnotsupported';
-     *
-     * Element with class 'fp-content' will be populated with files list;
-     * Element with class 'fp-btn-add' will hold onclick event for adding a file (opening filepicker);
-     * Element with class 'fp-btn-mkdir' will hold onclick event for adding new folder;
-     * Element with class 'fp-btn-download' will hold onclick event for download action;
-     *
-     * Element with class 'fp-path-folder' is a template for one folder in path toolbar.
-     * It will hold mouse click event and will be assigned classes first/last/even/odd respectfully.
-     * Parent element will receive class 'empty' when there are no folders to be displayed;
-     * The content of subelement with class 'fp-path-folder-name' will be substituted with folder name;
-     *
-     * Element with class 'fp-viewbar' will have the class 'enabled' or 'disabled' when view mode
-     * can be changed or not;
-     * Inside element with class 'fp-viewbar' there are expected elements with classes
-     * 'fp-vb-icons', 'fp-vb-tree' and 'fp-vb-details'. They will handle onclick events to switch
-     * between the view modes, the last clicked element will have the class 'checked';
-     *
      * @param form_filemanager $fm
      * @return string
      */
     protected function fm_print_generallayout($fm) {
-        global $OUTPUT;
-        $options = $fm->options;
-        $client_id = $options->client_id;
-        $straddfile  = get_string('addfile', 'repository');
-        $strmakedir  = get_string('makeafolder', 'moodle');
-        $strdownload = get_string('downloadfolder', 'repository');
-        $strloading  = get_string('loading', 'repository');
-        $strdroptoupload = get_string('droptoupload', 'moodle');
-        $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
-        $restrictions = $this->fm_print_restrictions($fm);
-        $strdndnotsupported = get_string('dndnotsupported_insentence', 'moodle').$OUTPUT->help_icon('dndnotsupported');
-        $strdndenabledinbox = get_string('dndenabled_inbox', 'moodle');
-        $loading = get_string('loading', 'repository');
-        $straddfiletext = get_string('addfiletext', 'repository');
-        $strcreatefolder = get_string('createfolder', 'repository');
-        $strdownloadallfiles = get_string('downloadallfiles', 'repository');
-
-        $html = '
-<div id="filemanager-'.$client_id.'" class="filemanager fm-loading">
-    <div class="fp-restrictions">
-        '.$restrictions.'
-        <span class="dnduploadnotsupported-message"> - '.$strdndnotsupported.' </span>
-    </div>
-    <div class="fp-navbar">
-        <div class="filemanager-toolbar">
-            <div class="fp-toolbar">
-                <div class="fp-btn-add">
-                    <a role="button" title="' . $straddfile . '" href="#">
-                        ' . $this->pix_icon('a/add_file', $straddfiletext) . '
-                    </a>
-                </div>
-                <div class="fp-btn-mkdir">
-                    <a role="button" title="' . $strmakedir . '" href="#">
-                        ' . $this->pix_icon('a/create_folder', $strcreatefolder) . '
-                    </a>
-                </div>
-                <div class="fp-btn-download">
-                    <a role="button" title="' . $strdownload . '" href="#">
-                        ' . $this->pix_icon('a/download_all', $strdownloadallfiles) . '
-                    </a>
-                </div>
-                <span class="fp-img-downloading">
-                    ' . $this->pix_icon('i/loading_small', '') . '
-                </span>
-            </div>
-            <div class="fp-viewbar">
-                <a title="'. get_string('displayicons', 'repository') .'" class="fp-vb-icons" href="#">
-                    ' . $this->pix_icon('fp/view_icon_active', get_string('displayasicons', 'repository'), 'theme') . '
-                </a>
-                <a title="'. get_string('displaydetails', 'repository') .'" class="fp-vb-details" href="#">
-                    ' . $this->pix_icon('fp/view_list_active', get_string('displayasdetails', 'repository'), 'theme') . '
-                </a>
-                <a title="'. get_string('displaytree', 'repository') .'" class="fp-vb-tree" href="#">
-                    ' . $this->pix_icon('fp/view_tree_active', get_string('displayastree', 'repository'), 'theme') . '
-                </a>
-            </div>
-        </div>
-        <div class="fp-pathbar">
-            <span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
-        </div>
-    </div>
-    <div class="filemanager-loading mdl-align">'.$icon_progress.'</div>
-    <div class="filemanager-container" >
-        <div class="fm-content-wrapper">
-            <div class="fp-content"></div>
-            <div class="fm-empty-container">
-                <div class="dndupload-message">'.$strdndenabledinbox.'<br/><div class="dndupload-arrow"></div></div>
-            </div>
-            <div class="dndupload-target">'.$strdroptoupload.'<br/><div class="dndupload-arrow"></div></div>
-            <div class="dndupload-progressbars"></div>
-            <div class="dndupload-uploadinprogress">'.$icon_progress.'</div>
-        </div>
-        <div class="filemanager-updating">'.$icon_progress.'</div>
-    </div>
-</div>';
-        return $html;
+        $context = [
+                'client_id' => $fm->options->client_id,
+                'helpicon' => $this->help_icon('setmainfile', 'repository'),
+                'restrictions' => $this->fm_print_restrictions($fm)
+        ];
+        return $this->render_from_template('core/filemanager_page_generallayout', $context);
     }
 
     /**
@@ -350,140 +242,21 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * FileManager JS template for window with file information/actions.
      *
-     * All content must be enclosed in one element, CSS for this class must define width and
-     * height of the window;
-     *
-     * Thumbnail image will be added as content to the element with class 'fp-thumbnail';
-     *
-     * Inside the window the elements with the following classnames must be present:
-     * 'fp-saveas', 'fp-author', 'fp-license', 'fp-path'. Inside each of them must be
-     * one input element (or select in case of fp-license and fp-path). They may also have labels.
-     * The elements will be assign with class 'uneditable' and input/select element will become
-     * disabled if they are not applicable for the particular file;
-     *
-     * There may be present elements with classes 'fp-original', 'fp-datemodified', 'fp-datecreated',
-     * 'fp-size', 'fp-dimensions', 'fp-reflist'. They will receive additional class 'fp-unknown' if
-     * information is unavailable. If there is information available, the content of embedded
-     * element with class 'fp-value' will be substituted with the value;
-     *
-     * The value of Original ('fp-original') is loaded in separate request. When it is applicable
-     * but not yet loaded the 'fp-original' element receives additional class 'fp-loading';
-     *
-     * The value of 'Aliases/Shortcuts' ('fp-reflist') is also loaded in separate request. When it
-     * is applicable but not yet loaded the 'fp-original' element receives additional class
-     * 'fp-loading'. The string explaining that XX references exist will replace content of element
-     * 'fp-refcount'. Inside '.fp-reflist .fp-value' each reference will be enclosed in <li>;
-     *
-     * Elements with classes 'fp-file-update', 'fp-file-download', 'fp-file-delete', 'fp-file-zip',
-     * 'fp-file-unzip', 'fp-file-setmain' and 'fp-file-cancel' will hold corresponding onclick
-     * events (there may be several elements with class 'fp-file-cancel');
-     *
-     * When confirm button is pressed and file is being selected, the top element receives
-     * additional class 'loading'. It is removed when response from server is received.
-     *
-     * When any of the input fields is changed, the top element receives class 'fp-changed';
-     * When current file can be set as main - top element receives class 'fp-cansetmain';
-     * When current file is folder/zip/file - top element receives respectfully class
-     * 'fp-folder'/'fp-zip'/'fp-file';
-     *
-     * @return string
      */
     protected function fm_js_template_fileselectlayout() {
-        global $OUTPUT;
-        $strloading  = get_string('loading', 'repository');
-        $iconprogress = $this->pix_icon('i/loading_small', $strloading).'';
-        $rv = '
-<div class="filemanager fp-select">
-    <div class="fp-select-loading">
-        ' . $this->pix_icon('i/loading_small', '') . '
-    </div>
-    <form class="form-horizontal">
-        <button class="fp-file-download">'.get_string('download').'</button>
-        <button class="fp-file-delete">'.get_string('delete').'</button>
-        <button class="fp-file-setmain">'.get_string('setmainfile', 'repository').'</button>
-        <span class="fp-file-setmain-help">'.$OUTPUT->help_icon('setmainfile', 'repository').'</span>
-        <button class="fp-file-zip">'.get_string('zip', 'editor').'</button>
-        <button class="fp-file-unzip">'.get_string('unzip').'</button>
-        <div class="fp-hr"></div>
-
-        <div class="fp-forminset">
-                <div class="fp-saveas control-group clearfix">
-                    <label class="control-label">'.get_string('name', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-author control-group clearfix">
-                    <label class="control-label">'.get_string('author', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-license control-group clearfix">
-                    <label class="control-label">'.get_string('chooselicense', 'repository').'</label>
-                    <div class="controls">
-                        <select></select>
-                    </div>
-                </div>
-                <div class="fp-path control-group clearfix">
-                    <label class="control-label">'.get_string('path', 'repository').'</label>
-                    <div class="controls">
-                        <select></select>
-                    </div>
-                </div>
-                <div class="fp-original control-group clearfix">
-                    <label class="control-label">'.get_string('original', 'repository').'</label>
-                    <div class="controls">
-                        <span class="fp-originloading">'.$iconprogress.' '.$strloading.'</span><span class="fp-value"></span>
-                    </div>
-                </div>
-                <div class="fp-reflist control-group clearfix">
-                    <label class="control-label">'.get_string('referenceslist', 'repository').'</label>
-                    <div class="controls">
-                        <p class="fp-refcount"></p>
-                        <span class="fp-reflistloading">'.$iconprogress.' '.$strloading.'</span>
-                        <ul class="fp-value"></ul>
-                    </div>
-                </div>
-        </div>
-        <div class="fp-select-buttons">
-            <button class="fp-file-update btn-primary btn">'.get_string('update', 'moodle').'</button>
-            <button class="fp-file-cancel btn-cancel btn">'.get_string('cancel').'</button>
-        </div>
-    </form>
-    <div class="fp-info clearfix">
-        <div class="fp-hr"></div>
-        <p class="fp-thumbnail"></p>
-        <div class="fp-fileinfo">
-            <div class="fp-datemodified">'.get_string('lastmodified', 'repository').' <span class="fp-value"></span></div>
-            <div class="fp-datecreated">'.get_string('datecreated', 'repository').' <span class="fp-value"></span></div>
-            <div class="fp-size">'.get_string('size', 'repository').' <span class="fp-value"></span></div>
-            <div class="fp-dimensions">'.get_string('dimensions', 'repository').' <span class="fp-value"></span></div>
-        </div>
-    </div>
-</div>';
-        return $rv;
+        $context = [
+                'helpicon' => $this->help_icon('setmainfile', 'repository')
+        ];
+        return $this->render_from_template('core/filemanager_fileselect', $context);
     }
 
     /**
      * FileManager JS template for popup confirm dialogue window.
      *
-     * Must have one top element, CSS for this element must define width and height of the window;
-     *
-     * content of element with class 'fp-dlg-text' will be replaced with dialog text;
-     * elements with classes 'fp-dlg-butconfirm' and 'fp-dlg-butcancel' will
-     * hold onclick events;
-     *
      * @return string
      */
     protected function fm_js_template_confirmdialog() {
-        $rv = '
-<div class="filemanager fp-dlg">
-    <div class="fp-dlg-text"></div>
-    <button class="fp-dlg-butconfirm btn-primary btn">'.get_string('ok').'</button>
-    <button class="fp-dlg-butcancel btn-cancel btn">'.get_string('cancel').'</button>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_confirmdialog', []);
     }
 
     /**
@@ -529,112 +302,11 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * Template for FilePicker with general layout (not QuickUpload).
      *
-     * Must have one top element containing everything else (recommended <div class="file-picker">),
-     * CSS for this element must define width and height of the filepicker window. Or CSS must
-     * define min-width, max-width, min-height and max-height and in this case the filepicker
-     * window will be resizeable;
-     *
-     * Element with class 'fp-viewbar' will have the class 'enabled' or 'disabled' when view mode
-     * can be changed or not;
-     * Inside element with class 'fp-viewbar' there are expected elements with classes
-     * 'fp-vb-icons', 'fp-vb-tree' and 'fp-vb-details'. They will handle onclick events to switch
-     * between the view modes, the last clicked element will have the class 'checked';
-     *
-     * Element with class 'fp-repo' is a template for displaying one repository. Other repositories
-     * will be attached as siblings (classes first/last/even/odd will be added respectfully).
-     * The currently selected repostory will have class 'active'. Contents of element with class
-     * 'fp-repo-name' will be replaced with repository name, source of image with class
-     * 'fp-repo-icon' will be replaced with repository icon;
-     *
-     * Element with class 'fp-content' is obligatory and will hold the current contents;
-     *
-     * Element with class 'fp-paging' will contain page navigation (will be deprecated soon);
-     *
-     * Element with class 'fp-path-folder' is a template for one folder in path toolbar.
-     * It will hold mouse click event and will be assigned classes first/last/even/odd respectfully.
-     * Parent element will receive class 'empty' when there are no folders to be displayed;
-     * The content of subelement with class 'fp-path-folder-name' will be substituted with folder name;
-     *
-     * Element with class 'fp-toolbar' will have class 'empty' if all 'Back', 'Search', 'Refresh',
-     * 'Logout', 'Manage' and 'Help' are unavailable for this repo;
-     *
-     * Inside fp-toolbar there are expected elements with classes fp-tb-back, fp-tb-search,
-     * fp-tb-refresh, fp-tb-logout, fp-tb-manage and fp-tb-help. Each of them will have
-     * class 'enabled' or 'disabled' if particular repository has this functionality.
-     * Element with class 'fp-tb-search' must contain empty form inside, it's contents will
-     * be substituted with the search form returned by repository (in the most cases it
-     * is generated with template core_repository_renderer::repository_default_searchform);
-     * Other elements must have either <a> or <button> element inside, it will hold onclick
-     * event for corresponding action; labels for fp-tb-back and fp-tb-logout may be
-     * replaced with those specified by repository;
      *
      * @return string
      */
     protected function fp_js_template_generallayout() {
-        $rv = '
-<div tabindex="0" class="file-picker fp-generallayout" role="dialog" aria-live="assertive">
-    <div class="fp-repo-area">
-        <ul class="fp-list" role="tablist">
-            <li class="fp-repo" role="tab" aria-selected="false" tabindex="-1">
-                <a href="#" tabindex="-1"><img class="fp-repo-icon" alt=" " width="16" height="16" />&nbsp;
-                    <span class="fp-repo-name"></span>
-                </a>
-            </li>
-        </ul>
-    </div>
-    <div class="fp-repo-items" tabindex="0">
-        <div class="fp-navbar">
-            <div>
-                <div class="fp-toolbar">
-                    <div class="fp-tb-back">
-                        <a href="#">'.get_string('back', 'repository').'</a>
-                    </div>
-                    <div class="fp-tb-search">
-                        <form></form>
-                    </div>
-                    <div class="fp-tb-refresh">
-                        <a title="'. get_string('refresh', 'repository') .'" href="#">
-                            ' . $this->pix_icon('a/refresh', '') . '
-                        </a>
-                    </div>
-                    <div class="fp-tb-logout">
-                        <a title="'. get_string('logout', 'repository') .'" href="#">
-                            ' . $this->pix_icon('a/logout', '') . '
-                        </a>
-                    </div>
-                    <div class="fp-tb-manage">
-                        <a title="'. get_string('manageurl', 'repository') .'" href="#">
-                            ' . $this->pix_icon('a/setting', '') . '
-                        </a>
-                    </div>
-                    <div class="fp-tb-help">
-                        <a title="'. get_string('help', 'repository') .'" href="#">
-                            ' . $this->pix_icon('a/help', '') . '
-                        </a>
-                    </div>
-                    <div class="fp-tb-message"></div>
-                </div>
-                <div class="fp-viewbar">
-                    <a role="button" title="'. get_string('displayicons', 'repository') .'" class="fp-vb-icons" href="#">
-                        ' . $this->pix_icon('fp/view_icon_active', '', 'theme') . '
-                    </a>
-                    <a role="button" title="'. get_string('displaydetails', 'repository') .'" class="fp-vb-details" href="#">
-                        ' . $this->pix_icon('fp/view_list_active', '', 'theme') . '
-                    </a>
-                    <a role="button" title="'. get_string('displaytree', 'repository') .'" class="fp-vb-tree" href="#">
-                        ' . $this->pix_icon('fp/view_tree_active', '', 'theme') . '
-                    </a>
-                </div>
-                <div class="fp-clear-left"></div>
-            </div>
-            <div class="fp-pathbar">
-                 <span class="fp-path-folder"><a class="fp-path-folder-name" href="#"></a></span>
-            </div>
-        </div>
-        <div class="fp-content"></div>
-    </div>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_modal_generallayout', []);
     }
 
     /**
@@ -723,159 +395,19 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * FilePicker JS template for window appearing to select a file.
      *
-     * All content must be enclosed in one element, CSS for this class must define width and
-     * height of the window;
-     *
-     * Thumbnail image will be added as content to the element with class 'fp-thumbnail';
-     *
-     * Inside the window the elements with the following classnames must be present:
-     * 'fp-saveas', 'fp-linktype-2', 'fp-linktype-1', 'fp-linktype-4', 'fp-setauthor',
-     * 'fp-setlicense'. Inside each of them must have one input element (or select in case of
-     * fp-setlicense). They may also have labels.
-     * The elements will be assign with class 'uneditable' and input/select element will become
-     * disabled if they are not applicable for the particular file;
-     *
-     * There may be present elements with classes 'fp-datemodified', 'fp-datecreated', 'fp-size',
-     * 'fp-license', 'fp-author', 'fp-dimensions'. They will receive additional class 'fp-unknown'
-     * if information is unavailable. If there is information available, the content of embedded
-     * element with class 'fp-value' will be substituted with the value;
-     *
-     * Elements with classes 'fp-select-confirm' and 'fp-select-cancel' will hold corresponding
-     * onclick events;
-     *
-     * When confirm button is pressed and file is being selected, the top element receives
-     * additional class 'loading'. It is removed when response from server is received.
-     *
      * @return string
      */
     protected function fp_js_template_selectlayout() {
-        $rv = '
-<div class="file-picker fp-select">
-    <div class="fp-select-loading">
-        ' . $this->pix_icon('i/loading_small', '') . '
-    </div>
-    <form class="form-horizontal">
-        <div class="fp-forminset">
-                <div class="fp-linktype-2 control-group control-radio clearfix">
-                    <label class="control-label control-radio">'.get_string('makefileinternal', 'repository').'</label>
-                    <div class="controls control-radio">
-                        <input type="radio"/>
-                    </div>
-                </div>
-                <div class="fp-linktype-1 control-group control-radio clearfix">
-                    <label class="control-label control-radio">'.get_string('makefilelink', 'repository').'</label>
-                    <div class="controls control-radio">
-                        <input type="radio"/>
-                    </div>
-                </div>
-                <div class="fp-linktype-4 control-group control-radio clearfix">
-                    <label class="control-label control-radio">'.get_string('makefilereference', 'repository').'</label>
-                    <div class="controls control-radio">
-                        <input type="radio"/>
-                    </div>
-                </div>
-                <div class="fp-linktype-8 control-group control-radio clearfix">
-                    <label class="control-label control-radio">'.get_string('makefilecontrolledlink', 'repository').'</label>
-                    <div class="controls control-radio">
-                        <input type="radio"/>
-                    </div>
-                </div>
-                <div class="fp-saveas control-group clearfix">
-                    <label class="control-label">'.get_string('saveas', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-setauthor control-group clearfix">
-                    <label class="control-label">'.get_string('author', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-setlicense control-group clearfix">
-                    <label class="control-label">'.get_string('chooselicense', 'repository').'</label>
-                    <div class="controls">
-                        <select></select>
-                    </div>
-                </div>
-        </div>
-       <div class="fp-select-buttons">
-            <button class="fp-select-confirm btn-primary btn">'.get_string('getfile', 'repository').'</button>
-            <button class="fp-select-cancel btn-cancel btn">'.get_string('cancel').'</button>
-        </div>
-    </form>
-    <div class="fp-info clearfix">
-        <div class="fp-hr"></div>
-        <p class="fp-thumbnail"></p>
-        <div class="fp-fileinfo">
-            <div class="fp-datemodified">'.get_string('lastmodified', 'repository').'<span class="fp-value"></span></div>
-            <div class="fp-datecreated">'.get_string('datecreated', 'repository').'<span class="fp-value"></span></div>
-            <div class="fp-size">'.get_string('size', 'repository').'<span class="fp-value"></span></div>
-            <div class="fp-license">'.get_string('license', 'repository').'<span class="fp-value"></span></div>
-            <div class="fp-author">'.get_string('author', 'repository').'<span class="fp-value"></span></div>
-            <div class="fp-dimensions">'.get_string('dimensions', 'repository').'<span class="fp-value"></span></div>
-        </div>
-    </div>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_selectlayout', []);
     }
 
     /**
      * FilePicker JS template for 'Upload file' repository
      *
-     * Content to display when user chooses 'Upload file' repository (will be nested inside
-     * element with class 'fp-content').
-     *
-     * Must contain form (enctype="multipart/form-data" method="POST")
-     *
-     * The elements with the following classnames must be present:
-     * 'fp-file', 'fp-saveas', 'fp-setauthor', 'fp-setlicense'. Inside each of them must have
-     * one input element (or select in case of fp-setlicense). They may also have labels.
-     *
-     * Element with class 'fp-upload-btn' will hold onclick event for uploading the file;
-     *
-     * Please note that some fields may be hidden using CSS if this is part of quickupload form
-     *
      * @return string
      */
     protected function fp_js_template_uploadform() {
-        $rv = '
-<div class="fp-upload-form">
-    <div class="fp-content-center">
-        <form enctype="multipart/form-data" method="POST" class="form-horizontal">
-            <div class="fp-formset">
-                <div class="fp-file control-group clearfix">
-                    <label class="control-label">'.get_string('attachment', 'repository').'</label>
-                    <div class="controls">
-                        <input type="file"/>
-                    </div>
-                </div>
-                <div class="fp-saveas control-group clearfix">
-                    <label class="control-label">'.get_string('saveas', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-setauthor control-group clearfix">
-                    <label class="control-label">'.get_string('author', 'repository').'</label>
-                    <div class="controls">
-                        <input type="text"/>
-                    </div>
-                </div>
-                <div class="fp-setlicense control-group clearfix">
-                    <label class="control-label">'.get_string('chooselicense', 'repository').'</label>
-                    <div class="controls">
-                        <select ></select>
-                    </div>
-                </div>
-            </div>
-        </form>
-        <div class="mdl-align">
-            <button class="fp-upload-btn btn-primary btn">'.get_string('upload', 'repository').'</button>
-        </div>
-    </div>
-</div> ';
-        return $rv;
+        return $this->render_from_template('core/filemanager_uploadform', []);
     }
 
     /**
@@ -932,115 +464,29 @@ class core_files_renderer extends plugin_renderer_base {
     /**
      * FilePicker JS template for popup dialogue window asking for action when file with the same name already exists.
      *
-     * Must have one top element, CSS for this element must define width and height of the window;
-     *
-     * content of element with class 'fp-dlg-text' will be replaced with dialog text;
-     * elements with classes 'fp-dlg-butoverwrite', 'fp-dlg-butrename',
-     * 'fp-dlg-butoverwriteall', 'fp-dlg-butrenameall' and 'fp-dlg-butcancel' will
-     * hold onclick events;
-     *
-     * content of element with class 'fp-dlg-butrename' will be substituted with appropriate string
-     * (Note that it may have long text)
-     *
      * @return string
      */
     protected function fp_js_template_processexistingfile() {
-        $rv = '
-<div class="file-picker fp-dlg">
-    <p class="fp-dlg-text"></p>
-    <div class="fp-dlg-buttons">
-        <button class="fp-dlg-butoverwrite btn">'.get_string('overwrite', 'repository').'</button>
-        <button class="fp-dlg-butrename btn"></button>
-        <button class="fp-dlg-butcancel btn btn-cancel">'.get_string('cancel').'</button>
-    </div>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_processexistingfile', []);
     }
 
     /**
-     * FilePicker JS template for popup dialogue window asking for action when file with the same name already exists (multiple-file version).
-     *
-     * Must have one top element, CSS for this element must define width and height of the window;
-     *
-     * content of element with class 'fp-dlg-text' will be replaced with dialog text;
-     * elements with classes 'fp-dlg-butoverwrite', 'fp-dlg-butrename' and 'fp-dlg-butcancel' will
-     * hold onclick events;
-     *
-     * content of element with class 'fp-dlg-butrename' will be substituted with appropriate string
-     * (Note that it may have long text)
+     * FilePicker JS template for popup dialogue window asking for action when file with the same name already exists
+     * (multiple-file version).
      *
      * @return string
      */
     protected function fp_js_template_processexistingfilemultiple() {
-        $rv = '
-<div class="file-picker fp-dlg">
-    <p class="fp-dlg-text"></p>
-    <a class="fp-dlg-butoverwrite fp-panel-button" href="#">'.get_string('overwrite', 'repository').'</a>
-    <a class="fp-dlg-butcancel fp-panel-button" href="#">'.get_string('cancel').'</a>
-    <a class="fp-dlg-butrename fp-panel-button" href="#"></a>
-    <br/>
-    <a class="fp-dlg-butoverwriteall fp-panel-button" href="#">'.get_string('overwriteall', 'repository').'</a>
-    <a class="fp-dlg-butrenameall fp-panel-button" href="#">'.get_string('renameall', 'repository').'</a>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_processexistingfilemultiple', []);
     }
 
     /**
      * FilePicker JS template for repository login form including templates for each element type
      *
-     * Must contain one <form> element with templates for different input types inside:
-     * Elements with classes 'fp-login-popup', 'fp-login-textarea', 'fp-login-select' and
-     * 'fp-login-input' are templates for displaying respective login form elements. Inside
-     * there must be exactly one element with type <button>, <textarea>, <select> or <input>
-     * (i.e. fp-login-popup should have <button>, fp-login-textarea should have <textarea>, etc.);
-     * They may also contain the <label> element and it's content will be substituted with
-     * label;
-     *
-     * You can also define elements with classes 'fp-login-checkbox', 'fp-login-text'
-     * but if they are not found, 'fp-login-input' will be used;
-     *
-     * Element with class 'fp-login-radiogroup' will be used for group of radio inputs. Inside
-     * it should hava a template for one radio input (with class 'fp-login-radio');
-     *
-     * Element with class 'fp-login-submit' will hold on click mouse event (form submission). It
-     * will be removed if at least one popup element is present;
-     *
      * @return string
      */
     protected function fp_js_template_loginform() {
-        $rv = '
-<div class="fp-login-form">
-    <div class="fp-content-center">
-        <form class="form-horizontal">
-            <div class="fp-formset">
-                <div class="fp-login-popup control-group clearfix">
-                    <div class="controls fp-popup">
-                        <button class="fp-login-popup-but btn-primary btn">'.get_string('login', 'repository').'</button>
-                    </div>
-                </div>
-                <div class="fp-login-textarea control-group clearfix">
-                    <div class="controls"><textarea></textarea></div>
-                </div>
-                <div class="fp-login-select control-group clearfix">
-                    <label class="control-label"></label>
-
-                    <div class="controls"><select></select></div>
-                </div>';
-        $rv .= '
-                <div class="fp-login-input control-group clearfix">
-                    <label class="control-label"></label>
-                    <div class="controls"><input/></div>
-                </div>
-                <div class="fp-login-radiogroup control-group clearfix">
-                    <label class="control-label"></label>
-                    <div class="controls fp-login-radio"><input /> <label></label></div>
-                </div>
-            </div>
-            <p><button class="fp-login-submit btn-primary btn">'.get_string('submit', 'repository').'</button></p>
-        </form>
-    </div>
-</div>';
-        return $rv;
+        return $this->render_from_template('core/filemanager_loginform', []);
     }
 
     /**
@@ -1066,13 +512,7 @@ class core_files_renderer extends plugin_renderer_base {
      * Default contents is one text input field with name="s"
      */
     public function repository_default_searchform() {
-        $searchinput = html_writer::label(get_string('searchrepo', 'repository'),
-            'reposearch', false, array('class' => 'accesshide'));
-        $searchinput .= html_writer::empty_tag('input', array('type' => 'text',
-            'id' => 'reposearch', 'name' => 's', 'value' => get_string('search', 'repository')));
-        $str = html_writer::tag('div', $searchinput, array('class' => "fp-def-search"));
-
-        return $str;
+        return $this->render_from_template('core/filemanager_default_searchform', []);
     }
 }
 
index 883121c..1199a1e 100644 (file)
     }
 }}
 <div class="gradingform_guide_comment_chooser" id="comment_chooser">
-    <ul role="list">
+    <div class="list-group">
         {{#comments}}
-            <li role="listitem">
-                <button id="comment-option-{{criterionId}}-{{id}}" class="btn btn-link" tabindex="0">
-                    {{description}}
-                </button>
-            </li>
+            <button class="list-group-item list-group-item-action" id="comment-option-{{criterionId}}-{{id}}" tabindex="0">
+                {{description}}
+            </button>
         {{/comments}}
-    </ul>
+    </div>
 </div>
index 384822a..f7ca951 100644 (file)
@@ -46,50 +46,8 @@ class renderer extends \plugin_renderer_base {
      * @return string HTML to display
      */
     protected function render_user_button(user_button $button) {
-        $attributes = array('type'     => 'button',
-                            'class'    => 'selectortrigger',
-                            'value'    => $button->label,
-                            'disabled' => $button->disabled ? 'disabled' : null,
-                            'title'    => $button->tooltip);
-
-        if ($button->actions) {
-            $id = \html_writer::random_id('single_button');
-            $attributes['id'] = $id;
-            foreach ($button->actions as $action) {
-                $this->add_action_handler($action, $id);
-            }
-        }
-        // First the input element.
-        $output = \html_writer::empty_tag('input', $attributes);
-
-        // Then hidden fields.
-        $params = $button->url->params();
-        if ($button->method === 'post') {
-            $params['sesskey'] = sesskey();
-        }
-        foreach ($params as $var => $val) {
-            $output .= \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
-        }
-
-        // Then div wrapper for xhtml strictness.
-        $output = \html_writer::tag('div', $output);
-
-        // Now the form itself around it.
-        if ($button->method === 'get') {
-            $url = $button->url->out_omit_querystring(true); // Url without params, the anchor part allowed.
-        } else {
-            $url = $button->url->out_omit_querystring();     // Url without params, the anchor part not allowed.
-        }
-        if ($url === '') {
-            $url = '#'; // There has to be always some action.
-        }
-        $attributes = array('method' => $button->method,
-                            'action' => $url,
-                            'id'     => $button->formid);
-        $output = \html_writer::tag('div', $output, $attributes);
-
-        // Finally one more wrapper with class.
-        return \html_writer::tag('div', $output, array('class' => $button->class));
+        $data = $button->export_for_template($this);
+        return $this->render_from_template('gradereport_history/user_button', $data);
     }
 
     /**
index ad4b657..abe592a 100644 (file)
     <input type="checkbox" name="{{applyname}}" value="1" id="{{applyname}}">
     <label for="{{applyname}}">{{applylabel}}</label>
 </div>
-<fieldset>
+<fieldset class="form-inline">
     <legend class="accesshide">{{label}}</legend>
     <label for="{{menuname}}">{{menulabel}}</label>
-    <select name="{{menuname}}" id="{{menuname}}">
+    <select name="{{menuname}}" id="{{menuname}}" class="form-control">
         {{#menuoptions}}
             <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
         {{/menuoptions}}
index a922e89..43fdbcf 100644 (file)
@@ -17,4 +17,4 @@
 {{!
     Button.
 }}
-<input type="{{type}}" value={{#quote}}{{value}}{{/quote}}>
+<input type="{{type}}" value={{#quote}}{{value}}{{/quote}} class="btn btn-secondary">
index e6388ca..9f3be1a 100644 (file)
@@ -17,7 +17,7 @@
 {{!
     Dropdown attribute.
 }}
-<select id="{{name}}" name="{{name}}" tabindex="1" {{#disabled}}disabled{{/disabled}}>
+<select id="{{name}}" name="{{name}}" class="custom-select" tabindex="1" {{#disabled}}disabled{{/disabled}}>
     {{#options}}
         <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
     {{/options}}
index 3b93780..ffd06e9 100644 (file)
@@ -18,5 +18,5 @@
     Text attribute.
 }}
 <label for="{{name}}" class="accesshide">{{label}}</label>
-<input id="{{name}}" name="{{name}}" type="text" value="{{value}}" {{#tabindex}}tabindex="{{.}}"{{/tabindex}} {{#disabled}}disabled{{/disabled}}>
+<input id="{{name}}" name="{{name}}" type="text" value="{{value}}" class="form-control" {{#tabindex}}tabindex="{{.}}"{{/tabindex}} {{#disabled}}disabled{{/disabled}}>
 <input type="hidden" name="old{{name}}" value="{{value}}">
index 12e6789..b17c4cd 100644 (file)
@@ -76,15 +76,18 @@ class behat_grade extends behat_base {
         $gradeitem = behat_context_helper::escape($gradeitem);
 
         if ($this->running_javascript()) {
-            $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
+            $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
             if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
-                $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
+                $this->execute("behat_action_menu::i_open_the_action_menu_in",
+                        array("//tr[contains(.,$gradeitem)]",
+                                "xpath_element"));
             }
         }
 
         $savechanges = get_string('savechanges', 'grades');
         $edit = behat_context_helper::escape(get_string('edit') . '  ');
-        $linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
+        $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
+                "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
 
         $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
         $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data);
@@ -128,16 +131,19 @@ class behat_grade extends behat_base {
         $gradeitem = behat_context_helper::escape($gradeitem);
 
         if ($this->running_javascript()) {
-            $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
+            $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]";
             if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
-                $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
+                $this->execute("behat_action_menu::i_open_the_action_menu_in",
+                        array("//tr[contains(.,$gradeitem)]",
+                                "xpath_element"));
             }
         }
 
         // Going to edit calculation.
         $savechanges = get_string('savechanges', 'grades');
         $edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
-        $linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
+        $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
+                "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
         $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
 
         // Mapping names to idnumbers.
@@ -145,11 +151,12 @@ class behat_grade extends behat_base {
         foreach ($datahash as $gradeitem => $idnumber) {
             // This xpath looks for course, categories and items with the provided name.
             // Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
-            $inputxpath ="//input[@class='idnumber'][" .
-                "parent::li[@class='item'][text()='" . $gradeitem . "']" .
-                " or " .
-                "parent::li[@class='categoryitem' or @class='courseitem']/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
-            "]";
+            $inputxpath = "//input[@class='idnumber'][" .
+                    "parent::li[@class='item'][text()='" . $gradeitem . "']" .
+                    " or " .
+                    "parent::li[@class='categoryitem' or @class='courseitem']" .
+                    "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
+                    "]";
             $this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
         }
 
@@ -174,17 +181,18 @@ class behat_grade extends behat_base {
         $gradeitem = behat_context_helper::escape($gradeitem);
 
         if ($this->running_javascript()) {
-            $xpath = "//tr[contains(.,$gradecategorytotal)]//*[contains(@class,'moodle-actionmenu')]" .
-                "//a[contains(@class,'toggle-display')]";
+            $xpath = "//tr[contains(.,$gradecategorytotal)]//*[contains(@class,'moodle-actionmenu')]";
             if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
-                $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
+                $xpath = "//tr[contains(.,$gradecategorytotal)]";
+                $this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
             }
         }
 
         // Going to edit calculation.
         $savechanges = get_string('savechanges', 'grades');
         $edit = behat_context_helper::escape(get_string('editcalculation', 'grades'));
-        $linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
+        $linkxpath = "//a[./*[contains(concat(' ', normalize-space(@class), ' '), ' icon ') " .
+                "and starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
         $this->execute("behat_general::i_click_on", array($this->escape($linkxpath), "xpath_element"));
 
         // Mapping names to idnumbers.
@@ -193,11 +201,11 @@ class behat_grade extends behat_base {
             // This xpath looks for course, categories and items with the provided name.
             // Grrr, we can't equal in categoryitem and courseitem because there is a line jump...
             $inputxpath = "//input[@class='idnumber'][" .
-                "parent::li[@class='item'][text()='" . $gradeitem . "']" .
-                " | " .
-                "parent::li[@class='categoryitem' | @class='courseitem']" .
-                "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
-            "]";
+                    "parent::li[@class='item'][text()='" . $gradeitem . "']" .
+                    " | " .
+                    "parent::li[@class='categoryitem' | @class='courseitem']" .
+                    "/parent::ul/parent::li[starts-with(text(),'" . $gradeitem . "')]" .
+                    "]";
             $this->execute('behat_forms::i_set_the_field_with_xpath_to', array($inputxpath, $idnumber));
         }
 
@@ -221,9 +229,10 @@ class behat_grade extends behat_base {
 
         if ($this->running_javascript()) {
             $gradeitemliteral = behat_context_helper::escape($gradeitem);
-            $xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
+            $xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]";
             if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
-                $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
+                $xpath = "//tr[contains(.,$gradeitemliteral)]";
+                $this->execute("behat_action_menu::i_open_the_action_menu_in", array($xpath, "xpath_element"));
             }
         }
 
@@ -290,11 +299,10 @@ class behat_grade extends behat_base {
      * @param string $gradepath
      */
     public function i