Merge branch 'MDL-56250-32-formvalidation' of https://github.com/roperto/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 25 Oct 2016 18:23:12 +0000 (20:23 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 25 Oct 2016 18:23:12 +0000 (20:23 +0200)
422 files changed:
admin/admin_settings_search_form.php [new file with mode: 0644]
admin/auth_config.php
admin/search.php
admin/settings/appearance.php
admin/settings/top.php
admin/templates/setting_configpasswordunmask.mustache
admin/templates/settings.mustache
admin/templates/settings_search_results.mustache
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/behat/tests/behat/edit_permissions.feature
admin/tool/behat/tests/behat/get_and_set_fields.feature
admin/tool/behat/tests/behat/manipulate_forms.feature
admin/tool/behat/tests/behat/nasty_strings.feature
admin/tool/behat/tests/fixtures/theme/defaulttheme/behat_theme_defaulttheme_test_context_1.php [new file with mode: 0644]
admin/tool/behat/tests/manager_util_test.php
admin/tool/log/store/standard/tests/store_test.php
admin/tool/lp/lib.php
admin/tool/lp/tests/behat/user_evidence_comp_link.feature
admin/tool/mobile/classes/api.php
admin/tool/mobile/classes/external.php
admin/tool/mobile/tests/api_test.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/monitor/tests/behat/subscription.feature
admin/tool/recyclebin/tests/behat/backup_user_data.feature
admin/tool/recyclebin/tests/behat/basic_functionality.feature
admin/tool/usertours/amd/build/tour.min.js
admin/tool/usertours/amd/src/tour.js
admin/tool/usertours/classes/cache.php [new file with mode: 0644]
admin/tool/usertours/classes/configuration.php
admin/tool/usertours/classes/helper.php
admin/tool/usertours/classes/local/forms/editstep.php
admin/tool/usertours/classes/local/forms/edittour.php
admin/tool/usertours/classes/local/table/step_list.php
admin/tool/usertours/classes/local/target/block.php
admin/tool/usertours/classes/manager.php
admin/tool/usertours/classes/output/step.php
admin/tool/usertours/classes/step.php
admin/tool/usertours/classes/tour.php
admin/tool/usertours/db/caches.php [new file with mode: 0644]
admin/tool/usertours/lang/en/tool_usertours.php
admin/tool/usertours/tests/cache_test.php [new file with mode: 0644]
admin/tool/usertours/tests/helper_trait.php [new file with mode: 0644]
admin/tool/usertours/tests/manager_test.php
admin/tool/usertours/tests/step_output_test.php [deleted file]
admin/tool/usertours/tests/step_test.php
admin/tool/usertours/thirdpartylibs.xml
admin/tool/usertours/version.php
admin/upgradesettings.php
availability/condition/group/tests/condition_test.php
availability/condition/grouping/tests/condition_test.php
backup/util/ui/tests/behat/restore_moodle2_courses.feature
backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-debug.js
backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js
backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall.js
backup/util/ui/yui/src/backupselectall/js/backupselectall.js
badges/tests/behat/award_badge.feature
blocks/activity_results/tests/behat/addblockinactivity.feature
blocks/activity_results/tests/behat/highscoreswithoutgroups.feature
blocks/activity_results/tests/behat/highscoreswithscales.feature
blocks/activity_results/tests/behat/highscoreswithscalesandgroups.feature
blocks/activity_results/tests/behat/highscoreswithseperategroups.feature
blocks/activity_results/tests/behat/highscoreswithvisiblegroups.feature
blocks/activity_results/tests/behat/lowscoreswithoutgroups.feature
blocks/activity_results/tests/behat/lowscoreswithscales.feature
blocks/activity_results/tests/behat/lowscoreswithscalesandgroups.feature
blocks/activity_results/tests/behat/lowscoreswithseperategroups.feature
blocks/activity_results/tests/behat/lowscoreswithvisiblegroups.feature
blocks/blog_menu/tests/behat/block_blog_menu_activity.feature
blocks/blog_tags/tests/behat/blogtag.feature
blocks/calendar_month/tests/behat/block_calendar_month_frontpage.feature
blocks/course_list/tests/behat/block_course_list_category.feature
blocks/course_summary/tests/behat/block_course_summary_frontpage.feature
blocks/navigation/tests/behat/expand_my_courses_setting.feature [deleted file]
blocks/news_items/tests/behat/display_news.feature
blocks/recent_activity/tests/behat/structural_changes.feature
blocks/search_forums/tests/behat/block_search_forums_course.feature
blocks/site_main_menu/tests/behat/add_url.feature
blocks/site_main_menu/tests/behat/edit_activities.feature
blocks/social_activities/tests/behat/edit_activities.feature
blocks/tags/tests/behat/tagcloud.feature
blocks/tests/behat/configure_block_throughout_site.feature
blocks/tests/behat/hidden_block_region.feature
blocks/tests/behat/hide_blocks.feature
blocks/tests/behat/return_block_original_state.feature
cache/classes/loaders.php
cache/stores/static/lib.php
cache/tests/fixtures/lib.php
cache/tests/fixtures/stores.php
calendar/tests/behat/calendar_lookahead.feature
cohort/tests/behat/add_cohort.feature
comment/locallib.php
completion/tests/externallib_test.php
course/admin.php [new file with mode: 0644]
course/externallib.php
course/format/lib.php
course/format/social/lib.php
course/format/topics/lib.php
course/format/topics/tests/behat/edit_delete_sections.feature
course/format/weeks/lib.php
course/format/weeks/tests/behat/edit_delete_sections.feature
course/lib.php
course/switchrole.php
course/switchrole_form.php [new file with mode: 0644]
course/tests/behat/behat_course.php
course/tests/behat/category_change_visibility.feature
course/tests/behat/category_resort.feature
course/tests/behat/course_change_visibility.feature
course/tests/behat/course_controls.feature
course/tests/behat/course_creation.feature
course/tests/behat/move_activities.feature
course/tests/behat/navigate_course_list.feature
course/tests/courselib_test.php
course/tests/externallib_test.php
course/user.php
enrol/lti/tests/helper_test.php
enrol/lti/tests/tool_provider_test.php
enrol/renderer.php
enrol/tests/behat/manage_enrolments_from_participants.feature
files/renderer.php
files/tests/behat/course_files.feature
filter/emoticon/tests/filter_test.php
filter/mathjaxloader/db/upgrade.php
filter/mathjaxloader/settings.php
filter/mathjaxloader/version.php
grade/export/txt/tests/behat/export.feature
grade/export/xml/tests/behat/export.feature
grade/lib.php
grade/report/grader/lib.php
grade/report/grader/styles.css
grade/report/grader/tests/behat/ajax_grader.feature
grade/report/grader/tests/behat/switch_views.feature
grade/report/history/tests/behat/basic_functionality.feature
grade/report/singleview/tests/behat/singleview.feature
grade/report/user/tests/behat/user_view.feature
grade/report/user/tests/behat/view_usereport.feature
grade/tests/behat/grade_UI_settings.feature
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_aggregation_changes.feature
grade/tests/behat/grade_average.feature
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_calculated_grade_items_20150627.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature
grade/tests/behat/grade_grade_minmax_change.feature
grade/tests/behat/grade_hidden_items.feature
grade/tests/behat/grade_letter_boundary.feature
grade/tests/behat/grade_letter_boundary_20160518.feature
grade/tests/behat/grade_mingrade.feature
grade/tests/behat/grade_minmax.feature
grade/tests/behat/grade_natural_exclude_empty.feature
grade/tests/behat/grade_natural_exclude_empty_20150619.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_natural_normalisation_20150619.feature
grade/tests/behat/grade_override_letter.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_scales_aggregation.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_to_pass.feature
grade/tests/behat/grade_view.feature
group/tests/behat/update_groups.feature
lang/en/admin.php
lang/en/block.php
lang/en/deprecated.txt
lang/en/form.php
lang/en/grades.php
lib/adminlib.php
lib/amd/build/templates.min.js
lib/amd/src/templates.js
lib/antivirus/clamav/db/upgrade.php
lib/antivirus/clamav/lang/en/antivirus_clamav.php
lib/antivirus/clamav/settings.php
lib/antivirus/clamav/version.php
lib/behat/behat_field_manager.php
lib/behat/classes/behat_config_util.php
lib/behat/classes/behat_context_helper.php
lib/behat/classes/partial_named_selector.php
lib/behat/classes/util.php
lib/behat/form_field/behat_form_passwordunmask.php [new file with mode: 0644]
lib/blocklib.php
lib/classes/event/dashboard_reset.php
lib/classes/event/dashboards_reset.php
lib/classes/session/manager.php
lib/db/access.php
lib/db/services.php
lib/deprecatedlib.php
lib/editor/atto/plugins/accessibilitychecker/tests/behat/accessibilitychecker.feature
lib/editor/atto/plugins/accessibilityhelper/tests/behat/accessibilityhelper.feature
lib/editor/atto/plugins/align/tests/behat/align.feature
lib/editor/atto/plugins/bold/tests/behat/bold.feature
lib/editor/atto/plugins/charmap/tests/behat/charmap.feature
lib/editor/atto/plugins/clear/tests/behat/clear.feature
lib/editor/atto/plugins/collapse/tests/behat/collapse.feature
lib/editor/atto/plugins/equation/tests/behat/equation.feature
lib/editor/atto/plugins/html/tests/behat/html.feature
lib/editor/atto/plugins/image/tests/behat/image.feature
lib/editor/atto/plugins/indent/tests/behat/indent.feature
lib/editor/atto/plugins/italic/tests/behat/italic.feature
lib/editor/atto/plugins/link/tests/behat/link.feature
lib/editor/atto/plugins/media/tests/behat/media.feature
lib/editor/atto/plugins/orderedlist/tests/behat/orderedlist.feature
lib/editor/atto/plugins/strike/tests/behat/strike.feature
lib/editor/atto/plugins/subscript/tests/behat/subscript.feature
lib/editor/atto/plugins/superscript/tests/behat/superscript.feature
lib/editor/atto/plugins/title/tests/behat/title.feature
lib/editor/atto/plugins/underline/tests/behat/underline.feature
lib/editor/atto/plugins/unorderedlist/tests/behat/unorderedlist.feature
lib/editor/atto/tests/behat/autosave.feature
lib/filestorage/file_storage.php
lib/form/advcheckbox.php
lib/form/amd/build/passwordunmask.min.js [new file with mode: 0644]
lib/form/amd/src/passwordunmask.js [new file with mode: 0644]
lib/form/checkbox.php
lib/form/passwordunmask.php
lib/form/templatable_form_element.php
lib/form/templates/element-passwordunmask-fill.mustache [new file with mode: 0644]
lib/form/templates/element-passwordunmask.mustache [new file with mode: 0644]
lib/form/templates/element-template.mustache [new file with mode: 0644]
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js
lib/form/yui/src/passwordunmask/js/passwordunmask.js
lib/form/yui/src/passwordunmask/meta/passwordunmask.json
lib/formslib.php
lib/gradelib.php
lib/javascript-static.js
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/outputlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/setup.php
lib/templates/settings_link_page.mustache [new file with mode: 0644]
lib/templates/settings_link_page_single.mustache [moved from lib/templates/prevent_form_autofill_password.mustache with 57% similarity]
lib/templates/signup_form_layout.mustache [new file with mode: 0644]
lib/testing/classes/util.php
lib/tests/accesslib_test.php
lib/tests/admintree_test.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/tests/formslib_test.php
lib/tests/outputcomponents_test.php
lib/tests/user_menu_test.php
lib/upgrade.txt
lib/weblib.php
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer-debug.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer-min.js
lib/yui/build/moodle-core-maintenancemodetimer/moodle-core-maintenancemodetimer.js
lib/yui/src/maintenancemodetimer/js/maintenancemodetimer.js
login/signup.php
login/signup_form.php
login/tests/behat/change_password.feature
message/amd/build/message_area_contacts.min.js
message/amd/build/message_area_profile.min.js
message/amd/src/message_area_contacts.js
message/amd/src/message_area_profile.js
message/classes/api.php
message/classes/helper.php
message/output/popup/amd/build/message_popover_controller.min.js
message/output/popup/amd/build/notification_area_control_area.min.js
message/output/popup/amd/build/notification_popover_controller.min.js
message/output/popup/amd/src/message_popover_controller.js
message/output/popup/amd/src/notification_area_control_area.js
message/output/popup/amd/src/notification_popover_controller.js
message/tests/api_test.php
message/tests/events_test.php
message/tests/messagelib_test.php
mod/assign/amd/build/grading_panel.min.js
mod/assign/amd/src/grading_panel.js
mod/assign/externallib.php
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/feedback/file/tests/behat/feedback_file.feature
mod/assign/gradingtable.php
mod/assign/lib.php
mod/assign/styles.css
mod/assign/tests/behat/edit_student_submission.feature
mod/assign/tests/behat/outcome_grading.feature
mod/assign/tests/behat/quickgrading.feature
mod/assign/tests/behat/steps_blind_marking.feature
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/book/lib.php
mod/choice/lib.php
mod/data/lib.php
mod/data/tests/behat/view_entries.feature
mod/feedback/tests/behat/anonymous.feature
mod/feedback/tests/behat/coursemapping.feature
mod/feedback/tests/behat/export_import.feature
mod/feedback/tests/behat/groups.feature
mod/feedback/tests/behat/multichoice.feature
mod/feedback/tests/behat/non_anonymous.feature
mod/feedback/tests/behat/question_types.feature
mod/feedback/tests/behat/question_types_non_anon.feature
mod/feedback/tests/behat/show_nonrespondents.feature
mod/feedback/tests/behat/templates.feature
mod/folder/lib.php
mod/forum/lib.php
mod/forum/tests/behat/advanced_search.feature
mod/forum/tests/behat/edit_post_student.feature
mod/forum/tests/behat/separate_group_single_group_discussions.feature
mod/glossary/lib.php
mod/imscp/lib.php
mod/label/lib.php
mod/lesson/tests/behat/duplicate_lesson_page.feature
mod/lesson/tests/behat/lesson_group_override.feature
mod/lesson/tests/behat/lesson_practice.feature
mod/lesson/tests/behat/lesson_user_override.feature
mod/lesson/tests/behat/questions_images.feature
mod/lesson/tests/behat/teacher_grade_essays.feature
mod/lti/lib.php
mod/page/lib.php
mod/quiz/lib.php
mod/quiz/tests/behat/editing_repaginate.feature
mod/quiz/tests/behat/settings_form_fields_disableif.feature
mod/resource/lib.php
mod/scorm/lib.php
mod/survey/lib.php
mod/upgrade.txt
mod/url/lib.php
mod/wiki/lib.php
mod/wiki/tests/behat/page_history.feature
mod/workshop/tests/behat/embedded_images.feature
my/lib.php
my/tests/events_test.php
notes/index.php
pix/t/passwordunmask-edit.png [new file with mode: 0644]
pix/t/passwordunmask-edit.svg [new file with mode: 0644]
pix/t/passwordunmask-reveal.png [new file with mode: 0644]
pix/t/passwordunmask-reveal.svg [new file with mode: 0644]
rating/lib.php
report/log/tests/behat/filter_log.feature
report/log/tests/behat/user_log.feature
report/outline/tests/behat/user.feature
repository/filepicker.php
tag/tests/behat/collections.feature
tag/tests/behat/standard_tags.feature
theme/boost/amd/build/drawer.min.js [new file with mode: 0644]
theme/boost/amd/src/drawer.js [new file with mode: 0644]
theme/boost/classes/output/core/files_renderer.php
theme/boost/classes/output/core_renderer.php
theme/boost/config.php
theme/boost/lang/en/theme_boost.php
theme/boost/layout/columns1.php
theme/boost/layout/columns2.php
theme/boost/layout/login.php [new file with mode: 0644]
theme/boost/layout/secure.php
theme/boost/pix/glyphicons-halflings-white.png [deleted file]
theme/boost/pix/glyphicons-halflings.png [deleted file]
theme/boost/pix/header.jpg [deleted file]
theme/boost/pix/horizontal-menu-submenu-indicator.png [deleted file]
theme/boost/pix/screenshot.jpg
theme/boost/pix/sprite.png [deleted file]
theme/boost/pix/vertical-menu-submenu-indicator.png [deleted file]
theme/boost/scss/moodle.scss
theme/boost/scss/moodle/backup-restore.scss
theme/boost/scss/moodle/blocks.scss
theme/boost/scss/moodle/bootswatch.scss [new file with mode: 0644]
theme/boost/scss/moodle/bs2-compat.scss [new file with mode: 0644]
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/debug.scss
theme/boost/scss/moodle/drawer.scss [new file with mode: 0644]
theme/boost/scss/moodle/forms.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/icons.scss
theme/boost/scss/moodle/login.scss [new file with mode: 0644]
theme/boost/scss/moodle/message.scss
theme/boost/scss/moodle/modules.scss
theme/boost/scss/moodle/sticky-footer.scss
theme/boost/scss/moodle/user.scss
theme/boost/scss/preset-default.scss
theme/boost/templates/blocks-drawer.mustache [new file with mode: 0644]
theme/boost/templates/columns1.mustache
theme/boost/templates/columns2.mustache
theme/boost/templates/core/block.mustache
theme/boost/templates/core/filemanager_loginform.mustache
theme/boost/templates/core/login.mustache
theme/boost/templates/core/modal.mustache
theme/boost/templates/core/navbar.mustache
theme/boost/templates/core/signup_form_layout.mustache [new file with mode: 0644]
theme/boost/templates/core_admin/setting_configpasswordunmask.mustache
theme/boost/templates/core_admin/settings.mustache
theme/boost/templates/core_admin/settings_search_results.mustache
theme/boost/templates/core_form/element-advcheckbox-inline.mustache
theme/boost/templates/core_form/element-advcheckbox.mustache
theme/boost/templates/core_form/element-autocomplete-inline.mustache
theme/boost/templates/core_form/element-autocomplete.mustache
theme/boost/templates/core_form/element-checkbox-inline.mustache
theme/boost/templates/core_form/element-checkbox.mustache
theme/boost/templates/core_form/element-passwordunmask.mustache
theme/boost/templates/core_form/element-select-inline.mustache
theme/boost/templates/core_form/element-select.mustache
theme/boost/templates/core_form/element-text-inline.mustache
theme/boost/templates/core_form/element-text.mustache
theme/boost/templates/core_form/element-url.mustache
theme/boost/templates/custom_menu_footer.mustache [new file with mode: 0644]
theme/boost/templates/flat_navigation.mustache [new file with mode: 0644]
theme/boost/templates/header.mustache
theme/boost/templates/login.mustache [new file with mode: 0644]
theme/boost/templates/maintenance.mustache
theme/boost/templates/nav-drawer.mustache [new file with mode: 0644]
theme/boost/templates/secure.mustache
theme/boost/tests/behat/behat_theme_boost_behat_course.php
theme/boost/tests/behat/behat_theme_boost_behat_navigation.php
theme/boost/tests/behat/blacklist.json
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/bs4-compat.less [new file with mode: 0644]
theme/bootstrapbase/less/moodle/debug.less
theme/bootstrapbase/less/moodle/dock.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
user/profile.php
user/tests/behat/edituserpassword.feature
user/tests/behat/name_fields.feature
user/tests/behat/user_grade_navigation.feature
user/tests/behat/view_full_profile.feature
user/tests/behat/view_preferences_page.feature
version.php

diff --git a/admin/admin_settings_search_form.php b/admin/admin_settings_search_form.php
new file mode 100644 (file)
index 0000000..ad42300
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Admin settings search form
+ *
+ * @package    admin
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once $CFG->libdir.'/formslib.php';
+
+/**
+ * Admin settings search form
+ *
+ * @package    admin
+ * @copyright  2016 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_settings_search_form extends moodleform {
+    function definition () {
+        $mform = $this->_form;
+
+        //$mform->addElement('header', 'settingsheader', get_string('search', 'admin'));
+        $elements = [];
+        $elements[] = $mform->createElement('text', 'query', get_string('query', 'admin'));
+        $elements[] = $mform->createElement('submit', 'search', get_string('search'));
+        $mform->addGroup($elements);
+        $mform->setType('query', PARAM_RAW);
+        $mform->setDefault('query', optional_param('query', '', PARAM_RAW));
+    }
+}
index 1fe945b..b307cd7 100644 (file)
@@ -64,8 +64,6 @@ echo "<form id=\"authmenu\" method=\"post\" action=\"auth_config.php\">\n";
 echo "<div>\n";
 echo "<input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n";
 echo "<input type=\"hidden\" name=\"auth\" value=\"".$auth."\" />\n";
-// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-echo prevent_form_autofill_password();
 
 // auth plugin description
 echo $OUTPUT->box_start();
index 6c255c6..0e9a531 100644 (file)
@@ -36,6 +36,8 @@ if ($data = data_submitted() and confirm_sesskey()) {
 // to modify them
 echo $OUTPUT->header($focus);
 
+echo $OUTPUT->heading(get_string('administrationsite'));
+
 if ($errormsg !== '') {
     echo $OUTPUT->notification($errormsg);
 
@@ -43,6 +45,18 @@ if ($errormsg !== '') {
     echo $OUTPUT->notification($statusmsg, 'notifysuccess');
 }
 
-echo admin_search_settings_html($query);
+require_once("admin_settings_search_form.php");
+$form = new admin_settings_search_form();
+$form->display();
+echo '<hr>';
+
+if ($query) {
+    echo admin_search_settings_html($query);
+} else {
+    $node = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
+    if ($node) {
+        echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node]);
+    }
+}
 
 echo $OUTPUT->footer();
index f2047d8..7ee3f70 100644 (file)
@@ -172,7 +172,6 @@ preferences,moodle|/user/preferences.php|preferences',
     $temp->add(new admin_setting_configcheckbox('navshowcategories', new lang_string('navshowcategories', 'admin'), new lang_string('confignavshowcategories', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navshowmycoursecategories', new lang_string('navshowmycoursecategories', 'admin'), new lang_string('navshowmycoursecategories_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('navshowallcourses', new lang_string('navshowallcourses', 'admin'), new lang_string('confignavshowallcourses', 'admin'), 0));
-    $temp->add(new admin_setting_configcheckbox('navexpandmycourses', new lang_string('navexpandmycourses', 'admin'), new lang_string('navexpandmycourses_desc', 'admin'), 1));
     $sortoptions = array(
         'sortorder' => new lang_string('sort_sortorder', 'admin'),
         'fullname' => new lang_string('sort_fullname', 'admin'),
index 223af0c..59a6102 100644 (file)
@@ -47,4 +47,4 @@ $ADMIN->add('root', new admin_category('development', new lang_string('developme
 $ADMIN->add('root', new admin_category('unsupported', new lang_string('unsupported', 'admin'), true));
 
 // hidden search script
-$ADMIN->add('root', new admin_externalpage('search', new lang_string('searchresults'), "$CFG->wwwroot/$CFG->admin/search.php", 'moodle/site:config', true));
+$ADMIN->add('root', new admin_externalpage('search', new lang_string('search', 'admin'), "$CFG->wwwroot/$CFG->admin/search.php", 'moodle/site:config', true));
index b9da812..bf00ea7 100644 (file)
     }
 }}
 <div class="form-password">
-    <input type="password" name="{{name}}" size="{{size}}" id="{{id}}" value="{{value}}">
-    <div class="unmask" id="{{id}}unmaskdiv"></div>
+    <span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
+        <noscript>
+            <!-- Backwards compatability for Behat -->
+            <input  type="password"
+                    name="{{ name }}"
+                    id="{{ id }}"
+                    value="{{ value }}"
+                    size="{{ size }}"
+                    >
+        </noscript>
+        <span class="visibleifjs">
+            <span data-passwordunmask="editor">
+                <!-- The input in the noscript will be moved here as part of the page load -->
+            </span>
+            <a href="#" data-passwordunmask="edit" title="{{ edithint }}">
+                <span data-passwordunmask="displayvalue">{{> core_form/element-passwordunmask-fill }}</span>
+                {{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
+            </a>
+            <a href="#" data-passwordunmask="unmask" title="{{ unmaskhint }}">
+                {{# pix }} t/passwordunmask-reveal, core, {{# str }} passwordunmaskrevealhint, form {{/ str }}{{/ pix }}
+            </a>
+            <span data-passwordunmask="instructions" class="form-text text-muted" style="display: none;">
+                {{# str }} passwordunmaskinstructions, form {{/ str }}
+            </span>
+        </span>
+    </span>
 </div>
 {{#js}}
-(function() {
-    var id = '{{id}}';
-    var unmaskid = id + 'unmask';
-    var unmaskdivid = id + 'unmaskdiv';
-    var unmaskstr = {{#quote}}{{#str}}unmaskpassword, form{{/str}}{{/quote}};
-    var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
-
-    document.getElementById(id).setAttribute("autocomplete", "off");
-
-    var unmaskdiv = document.getElementById(unmaskdivid);
-
-    var unmaskchb = document.createElement("input");
-    unmaskchb.setAttribute("type", "checkbox");
-    unmaskchb.setAttribute("id", unmaskid);
-    unmaskchb.onchange = function() {unmaskPassword(id);};
-    unmaskdiv.appendChild(unmaskchb);
-
-    var unmasklbl = document.createElement("label");
-    unmasklbl.innerHTML = unmaskstr;
-    if (is_ie) {
-      unmasklbl.setAttribute("htmlFor", unmaskid);
-    } else {
-      unmasklbl.setAttribute("for", unmaskid);
-    }
-    unmaskdiv.appendChild(unmasklbl);
-
-    if (is_ie) {
-      // Ugly hack to work around the famous onchange IE bug.
-      unmaskchb.onclick = function() {this.blur();};
-      unmaskdiv.onclick = function() {this.blur();};
-    }
-})()
+require(['core_form/passwordunmask'], function(PasswordUnmask) {
+    new PasswordUnmask("{{ id }}");
+});
 {{/js}}
index aba6601..4f351b8 100644 (file)
@@ -43,7 +43,6 @@
         {{/params}}
         <input type="hidden" name="sesskey" value="{{sesskey}}">
         <input type="hidden" name="return" value="{{return}}">
-        {{>core/prevent_form_autofill_password}}
         {{#title}}
             <h2>{{title}}</h2>
         {{/title}}
index dd392f7..c6f7bbb 100644 (file)
@@ -37,7 +37,6 @@
 <form action="{{actionurl}}" method="post" id="adminsettings">
     <div>
         <input type="hidden" name="sesskey" value="{{sesskey}}">
-        {{>core/prevent_form_autofill_password}}
     </div>
     <fieldset>
         <div class="clearer"></div>
index cac5f63..6b6d5ed 100644 (file)
@@ -325,7 +325,7 @@ Feature: Set up contextual data for tests
     And I am on site homepage
     And I follow "Courses"
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     Then I should see "Grade category 1"
     And I should see "Grade sub category 2"
 
@@ -349,7 +349,7 @@ Feature: Set up contextual data for tests
     When I log in as "admin"
     And I am on site homepage
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I expand "Setup" node
     And I follow "Gradebook setup"
     Then I should see "Test Grade Item 1"
@@ -380,7 +380,7 @@ Feature: Set up contextual data for tests
     When I log in as "admin"
     And I am on site homepage
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I follow "Scales"
     Then I should see "Test Scale 1"
     And I should see "Disappointing,  Good,  Very good,  Excellent"
@@ -432,7 +432,7 @@ Feature: Set up contextual data for tests
     When I log in as "admin"
     And I am on site homepage
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I expand "Setup" node
     And I follow "Gradebook setup"
     Then I should see "Test Outcome Grade Item 1"
index 29f7981..b07c121 100644 (file)
@@ -52,13 +52,13 @@ Feature: Edit capabilities
       | Forum name | I'm the name |
       | Description | I'm the introduction |
     And I follow "I'm the name"
-    And I follow "Permissions"
+    And I navigate to "Permissions" node in "Forum administration"
     And I override the system permissions of "Student" role with:
       | mod/forum:deleteanypost | Prohibit |
       | mod/forum:editanypost | Prevent |
       | mod/forum:addquestion | Allow |
     When I set the field "Advanced role override" to "Student (3)"
-    And I press "Go"
+    And I click on "Go" "button" in the "region-main" "region"
     Then "mod/forum:deleteanypost" capability has "Prohibit" permission
     And "mod/forum:editanypost" capability has "Prevent" permission
     And "mod/forum:addquestion" capability has "Allow" permission
index ca4f2a1..9edf445 100644 (file)
@@ -37,7 +37,7 @@ Feature: Verify that all form fields values can be get and set
     And I expand "Appearance" node
     And I am on site homepage
     And I follow "Course 1"
-    And I follow "Reset"
+    And I navigate to "Reset" node in "Course administration"
     # Select (multi-select) - Checking "the select box should contain".
     And I expand all fieldsets
     And the "Unenrol users" select box should contain "No roles"
index 126a800..ed97899 100644 (file)
@@ -8,15 +8,13 @@ Feature: Forms manipulation
   Scenario: Basic forms manipulation
     Given I log in as "admin"
     And I follow "Preferences" in the user menu
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     When I set the field "First name" to "Field value"
     And I set the field "Select a country" to "Japan"
-    And I set the field "Unmask" to "1"
+    And I set the field "New password" to "TestPass"
     Then the field "First name" matches value "Field value"
     And the "Select a country" select box should contain "Japan"
-    And the field "Unmask" matches value "1"
-    And I set the field "Unmask" to ""
-    And the field "Unmask" matches value ""
+    And the field "New password" matches value "TestPass"
     And I press "Update profile"
 
   @javascript
index f8d847d..1c54765 100644 (file)
@@ -11,14 +11,14 @@ Feature: Transform steps arguments
       | Course 1 | C1 | 0 |
     And I log in as "admin"
     And I follow "Preferences" in the user menu
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
 
   Scenario: Use nasty strings on steps arguments
     When I set the field "Surname" to "$NASTYSTRING1"
     And I set the field "Description" to "$NASTYSTRING2"
     And I set the field "City/town" to "$NASTYSTRING3"
     And I press "Update profile"
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "NASTYSTRING"
     And the field "Surname" matches value "$NASTYSTRING1"
     And the field "City/town" matches value "$NASTYSTRING3"
@@ -29,7 +29,7 @@ Feature: Transform steps arguments
       | Description | $NASTYSTRING2 |
       | City/town | $NASTYSTRING3 |
     And I press "Update profile"
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "NASTYSTRING"
     # BEHAT Transformation regression - See MDL-56397
     #And the field "Surname" matches value "$NASTYSTRING1"
@@ -41,7 +41,7 @@ Feature: Transform steps arguments
       | Description | va\"lue2 |
     And I set the field "City/town" to "va\"lue3"
     And I press "Update profile"
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "NASTYSTRING"
     And the field "First name" matches value "va\"lue1"
     And the field "Description" matches value "va\\"lue2"
@@ -52,7 +52,7 @@ Feature: Transform steps arguments
     And I set the following fields to these values:
       | Surname | My Surname $NASTYSTRING2 |
     And I press "Update profile"
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "NASTYSTRING"
     And I should see "My Firstname"
     And I should see "My Surname"
diff --git a/admin/tool/behat/tests/fixtures/theme/defaulttheme/behat_theme_defaulttheme_test_context_1.php b/admin/tool/behat/tests/fixtures/theme/defaulttheme/behat_theme_defaulttheme_test_context_1.php
new file mode 100644 (file)
index 0000000..ff164cd
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Theme test context 1
+ *
+ * @package    tool_behat
+ * @copyright  2016 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// 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');
+
+/**
+ * Default Theme test context 1
+ *
+ * @package    tool_behat
+ * @copyright  2016 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_theme_defaulttheme_test_context_1 extends behat_base {
+
+}
\ No newline at end of file
index 6ae0a6c..2d10776 100644 (file)
@@ -61,7 +61,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     private $contextspath = array(
         'default' => array(
             'behat_test_context_1',
-            'behat_test_context_2'
+            'behat_test_context_2',
+            'behat_theme_defaulttheme_test_context_1'
         ),
         'withfeatures' => array(
             'behat_test_context_2',
@@ -76,7 +77,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     );
 
     /** @var array List of core features. */
-    private $corefatures = array('test_1_core_fixtures_tests_behat_tool' => __DIR__.'/fixtures/core/test_1.feature',
+    private $corefeatures = array('test_1_core_fixtures_tests_behat_tool' => __DIR__.'/fixtures/core/test_1.feature',
                                  'test_2_core_fixtures_tests_behat_tool' => __DIR__.'/fixtures/core/test_2.feature');
 
     /** @var array List of core contexts. */
@@ -104,14 +105,15 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         // Create a map of arguments to return values.
         $map = array(
             array('withfeatures', __DIR__.'/fixtures/theme/withfeatures'),
-            array('nofeatures', __DIR__.'/fixtures/theme/nofeatures')
+            array('nofeatures', __DIR__.'/fixtures/theme/nofeatures'),
+            array('defaulttheme', __DIR__.'/fixtures/theme/defaulttheme'),
         );
 
         // List of themes is const for test.
         if ($notheme) {
-            $themelist = array();
+            $themelist = array('defaulttheme');
         } else {
-            $themelist = array('withfeatures', 'nofeatures');
+            $themelist = array('withfeatures', 'nofeatures', 'defaulttheme');
         }
 
         $behatconfigutil->expects($this->any())
@@ -123,6 +125,10 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             ->method('get_theme_test_directory')
             ->will($this->returnValueMap($map));
 
+        $behatconfigutil->expects($this->any())
+            ->method('get_default_theme')
+            ->will($this->returnValue('defaulttheme'));
+
         return $behatconfigutil;
     }
 
@@ -132,12 +138,12 @@ 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'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
         $behatconfigutil = $this->get_behat_config_util($behatconfigutil);
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
 
         // Two suites should be present.
         $suites = $config['default']['suites'];
@@ -161,8 +167,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             }
         }
 
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -171,12 +177,12 @@ 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'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
         $behatconfigutil = $this->get_behat_config_util($behatconfigutil, true);
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
 
         // Two suites should be present.
         $suites = $config['default']['suites'];
@@ -192,7 +198,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $contextspath = array(
             'default' => array(
                 'behat_test_context_1',
-                'behat_test_context_2'
+                'behat_test_context_2',
+                'behat_theme_defaulttheme_test_context_1',
             )
         );
 
@@ -214,8 +221,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             }
         }
 
-        // There are 6 step definitions.
-        $this->assertCount(2, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 3 step definitions.
+        $this->assertCount(3, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -224,14 +231,14 @@ 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'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
         $behatconfigutil = $this->get_behat_config_util($behatconfigutil);
 
         // Test first run out of 3.
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts, '', 3, 1);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts, '', 3, 1);
         // Three suites should be present.
         $suites = $config['default']['suites'];
         $this->assertCount(3, $suites);
@@ -249,6 +256,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertContains($feature, $suites[$themename]['paths'][$key]);
             }
         }
+
         // Check contexts.
         foreach ($this->contextspath as $themename => $paths) {
             $this->assertCount(count($paths), $suites[$themename]['contexts']);
@@ -257,8 +265,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
 
         // Test second run out of 3.
         $config = $behatconfigutil->get_config_file_contents('', '', '', 3, 2);
@@ -287,8 +295,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
 
         // Test third run out of 3.
         $config = $behatconfigutil->get_config_file_contents('', '', '', 3, 3);
@@ -316,8 +324,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -326,14 +334,14 @@ 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'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
         $behatconfigutil = $this->get_behat_config_util($behatconfigutil);
 
         // Test first run out of 3.
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts, '@commontag', 3, 1);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts, '@commontag', 3, 1);
 
         // Three suites should be present.
         $suites = $config['default']['suites'];
@@ -360,8 +368,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
 
         // Test second run out of 3.
         $config = $behatconfigutil->get_config_file_contents('', '', '@commontag', 3, 2);
@@ -391,8 +399,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
 
         // Test third run out of 3.
         $config = $behatconfigutil->get_config_file_contents('', '', '', 3, 3);
@@ -420,8 +428,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -470,7 +478,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_blacklisted_tags() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
+            'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -480,10 +489,13 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $map = array(
             array('withfeatures', 'tags', array('@test1')),
             array('nofeatures', 'tags', array('@test2')),
+            array('defaulttheme', 'tags', array()),
             array('withfeatures', 'features', array()),
             array('nofeatures', 'features', array()),
+            array('defaulttheme', 'features', array()),
             array('withfeatures', 'contexts', array()),
-            array('nofeatures', 'contexts', array())
+            array('nofeatures', 'contexts', array()),
+            array('defaulttheme', 'contexts', array())
         );
 
         $behatconfigutil->expects($this->any())
@@ -491,7 +503,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             ->will($this->returnValueMap($map));
 
         $behatconfigutil->set_theme_suite_to_include_core_features(true);
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
 
         // Three suites should be present.
         $suites = $config['default']['suites'];
@@ -520,8 +532,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -530,7 +542,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
     public function test_get_config_file_contents_with_blacklisted_features_contexts() {
 
         $mockbuilder = $this->getMockBuilder('behat_config_util');
-        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_blacklisted_tests_for_theme',
+            'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -540,10 +553,13 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $map = array(
             array('withfeatures', 'tags', array()),
             array('nofeatures', 'tags', array()),
+            array('defaulttheme', 'tags', array()),
             array('withfeatures', 'features', array('admin/tool/behat/tests/fixtures/core/test_1.feature')),
             array('nofeatures', 'features', array('admin/tool/behat/tests/fixtures/core/test_2.feature')),
+            array('defaulttheme', 'features', array()),
             array('withfeatures', 'contexts', array('admin/tool/behat/tests/fixtures/core/behat_test_context_2.php')),
-            array('nofeatures', 'contexts', array('admin/tool/behat/tests/fixtures/core/behat_test_context_1.php'))
+            array('nofeatures', 'contexts', array('admin/tool/behat/tests/fixtures/core/behat_test_context_1.php')),
+            array('defaulttheme', 'contexts', array()),
         );
 
         $behatconfigutil->expects($this->any())
@@ -551,7 +567,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
             ->will($this->returnValueMap($map));
 
         $behatconfigutil->set_theme_suite_to_include_core_features(true);
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
 
         // Three suites should be present.
         $suites = $config['default']['suites'];
@@ -566,7 +582,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $contextspath = array(
             'default' => array(
                 'behat_test_context_1',
-                'behat_test_context_2'
+                'behat_test_context_2',
+                'behat_theme_defaulttheme_test_context_1',
             ),
             'withfeatures' => array(
                 'behat_theme_withfeatures_test_context_2',
@@ -594,8 +611,8 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
                 $this->assertTrue(in_array($context, $suites[$themename]['contexts']));
             }
         }
-        // There are 6 step definitions.
-        $this->assertCount(6, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
+        // There are 7 step definitions.
+        $this->assertCount(7, $config['default']['extensions']['Moodle\BehatExtension']['steps_definitions']);
     }
 
     /**
@@ -604,7 +621,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'));
+        $mockbuilder->setMethods(array('get_theme_test_directory', 'get_list_of_themes', 'get_default_theme'));
 
         $behatconfigutil = $mockbuilder->getMock();
 
@@ -612,7 +629,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
 
         // Check features when, no theme is specified.
         $behatconfigutil->set_theme_suite_to_include_core_features('');
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
         $suites = $config['default']['suites'];
         foreach ($this->featurepaths as $themename => $paths) {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
@@ -628,7 +645,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $featurepaths['nofeatures'] = array_merge ($featurepaths['default'], $featurepaths['nofeatures']);
 
         $behatconfigutil->set_theme_suite_to_include_core_features('ALL');
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
         $suites = $config['default']['suites'];
         foreach ($featurepaths as $themename => $paths) {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
@@ -644,7 +661,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $featurepaths['nofeatures'] = array_merge ($featurepaths['default'], $featurepaths['nofeatures']);
 
         $behatconfigutil->set_theme_suite_to_include_core_features('withfeatures, nofeatures');
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
         $suites = $config['default']['suites'];
         foreach ($featurepaths as $themename => $paths) {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
@@ -659,7 +676,7 @@ class tool_behat_manager_util_testcase extends advanced_testcase {
         $featurepaths['nofeatures'] = array_merge ($featurepaths['default'], $featurepaths['nofeatures']);
 
         $behatconfigutil->set_theme_suite_to_include_core_features('nofeatures');
-        $config = $behatconfigutil->get_config_file_contents($this->corefatures, $this->corecontexts);
+        $config = $behatconfigutil->get_config_file_contents($this->corefeatures, $this->corecontexts);
         $suites = $config['default']['suites'];
         foreach ($featurepaths as $themename => $paths) {
             $this->assertCount(count($paths), $suites[$themename]['paths']);
index d28af98..def6684 100644 (file)
@@ -28,6 +28,11 @@ require_once(__DIR__ . '/fixtures/event.php');
 require_once(__DIR__ . '/fixtures/restore_hack.php');
 
 class logstore_standard_store_testcase extends advanced_testcase {
+    /**
+     * @var bool Determine if we disabled the GC, so it can be re-enabled in tearDown.
+     */
+    private $wedisabledgc = false;
+
     public function test_log_writing() {
         global $DB;
         $this->resetAfterTest();
@@ -219,6 +224,19 @@ class logstore_standard_store_testcase extends advanced_testcase {
         }
     }
 
+    /**
+     * Verify that gc disabling works
+     */
+    public function test_gc_enabled_as_expected() {
+        if (!gc_enabled()) {
+            $this->markTestSkipped('Garbage collector (gc) is globally disabled.');
+        }
+
+        $this->disable_gc();
+        $this->assertTrue($this->wedisabledgc);
+        $this->assertFalse(gc_enabled());
+    }
+
     /**
      * Test sql_reader::get_events_select_iterator.
      * @return void
@@ -226,6 +244,8 @@ class logstore_standard_store_testcase extends advanced_testcase {
     public function test_events_traversable() {
         global $DB;
 
+        $this->disable_gc();
+
         $this->resetAfterTest();
         $this->preventResetByRollback();
         $this->setAdminUser();
@@ -271,7 +291,6 @@ class logstore_standard_store_testcase extends advanced_testcase {
         $this->assertInstanceOf('\Traversable', $eventsit);
 
         $this->assertLessThan($arraymemusage / 10, $itmemusage);
-
         set_config('enabled_stores', '', 'tool_log');
         get_log_manager(true);
     }
@@ -312,4 +331,24 @@ class logstore_standard_store_testcase extends advanced_testcase {
 
         $this->assertEquals(1, $DB->count_records('logstore_standard_log'));
     }
+
+    /**
+     * Disable the garbage collector if it's enabled to ensure we don't adjust memory statistics.
+     */
+    private function disable_gc() {
+        if (gc_enabled()) {
+            $this->wedisabledgc = true;
+            gc_disable();
+        }
+    }
+
+    /**
+     * Reset any garbage collector changes to the previous state at the end of the test.
+     */
+    public function tearDown() {
+        if ($this->wedisabledgc) {
+            gc_enable();
+        }
+        $this->wedisabledgc = false;
+    }
 }
index 62b6892..7f1ba9c 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-/**
- * This function extends the course navigation
- *
- * @param navigation_node $navigation The navigation node to extend
- * @param stdClass $course The course to object for the tool
- * @param context $coursecontext The context of the course
- */
-function tool_lp_extend_navigation_course($navigation, $course, $coursecontext) {
-    if (!get_config('core_competency', 'enabled')) {
-        return;
-    }
-
-    // Check access to the course and competencies page.
-    $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage');
-    $context = context_course::instance($course->id);
-    if (!has_any_capability($capabilities, $context) || !can_access_course($course)) {
-        return;
-    }
-
-    // Just a link to course competency.
-    $title = get_string('competencies', 'core_competency');
-    $path = new moodle_url("/admin/tool/lp/coursecompetencies.php", array('courseid' => $course->id));
-    $settingsnode = navigation_node::create($title,
-                                            $path,
-                                            navigation_node::TYPE_SETTING,
-                                            null,
-                                            null,
-                                            new pix_icon('i/competencies', ''));
-    if (isset($settingsnode)) {
-        $navigation->add_node($settingsnode);
-    }
-}
-
-
 /**
  * This function extends the user navigation.
  *
index 4ebd5c9..779ece3 100644 (file)
@@ -41,6 +41,7 @@ Feature: Manage competencies linked to evidence of prior learning
 
   Scenario: Link competency to evidence of prior learning from list
     Given I follow "Evidence of prior learning"
+    And I change window size to "large"
     And I should see "List of evidence"
     And I should see "Test-Evidence"
     And I click on "Link" of edit menu in the "Test-Evidence" row
index 657a0e2..e25a57b 100644 (file)
@@ -100,7 +100,7 @@ class api {
      * @return array with the settings and warnings
      */
     public static function get_public_config() {
-        global $CFG, $SITE, $PAGE;
+        global $CFG, $SITE, $PAGE, $OUTPUT;
 
         $context = context_system::instance();
         // We need this to make work the format text functions.
@@ -135,6 +135,14 @@ class api {
             $url = new moodle_url("/$CFG->admin/tool/mobile/launch.php");
             $settings['launchurl'] = $url->out(false);
         }
+
+        if ($logourl = $OUTPUT->get_logo_url()) {
+            $settings['logourl'] = $logourl->out(false);
+        }
+        if ($compactlogourl = $OUTPUT->get_compact_logo_url()) {
+            $settings['compactlogourl'] = $compactlogourl->out(false);
+        }
+
         return $settings;
     }
 
index e06fe73..3094ebd 100644 (file)
@@ -144,6 +144,8 @@ class external extends external_api {
                 'enablemobilewebservice' => new external_value(PARAM_INT, 'Whether the Mobile service is enabled.'),
                 'maintenanceenabled' => new external_value(PARAM_INT, 'Whether site maintenance is enabled.'),
                 'maintenancemessage' => new external_value(PARAM_RAW, 'Maintenance message.'),
+                'logourl' => new external_value(PARAM_URL, 'The site logo URL', VALUE_OPTIONAL),
+                'compactlogourl' => new external_value(PARAM_URL, 'The site compact logo URL', VALUE_OPTIONAL),
                 'typeoflogin' => new external_value(PARAM_INT, 'The type of login. 1 for app, 2 for browser, 3 for embedded.'),
                 'launchurl' => new external_value(PARAM_URL, 'SSO login launch URL. Empty if it won\'t be used.', VALUE_OPTIONAL),
                 'warnings' => new external_warnings(),
index 1cd4e21..ece75c4 100644 (file)
@@ -56,11 +56,11 @@ class tool_mobile_api_testcase extends externallib_advanced_testcase {
         // SEt user to GMT+5.
         $USER->timezone = 5;
 
-        $timenow = time();
+        $timenow = $this->setCurrentTimeStart();
         $key = api::get_autologin_key();
 
         $key = $DB->get_record('user_private_key', array('value' => $key), '*', MUST_EXIST);
-        $this->assertEquals($timenow + api::LOGIN_KEY_TTL, $key->validuntil);
+        $this->assertTimeCurrent($key->validuntil - api::LOGIN_KEY_TTL);
         $this->assertEquals('0.0.0.0', $key->iprestriction);
     }
 }
index 7300cc7..eacb24e 100644 (file)
@@ -56,7 +56,7 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
     }
 
     public function test_get_public_config() {
-        global $CFG, $SITE;
+        global $CFG, $SITE, $OUTPUT;
 
         $this->resetAfterTest(true);
         $result = external::get_public_config();
@@ -89,11 +89,20 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         $authinstructions = 'Something with <b>html tags</b>';
         set_config('auth_instructions', $authinstructions);
         set_config('typeoflogin', api::LOGIN_VIA_BROWSER, 'tool_mobile');
+        set_config('logo', 'mock.png', 'core_admin');
+        set_config('logocompact', 'mock.png', 'core_admin');
 
         $expected['registerauth'] = 'email';
         $expected['authinstructions'] = format_text($authinstructions);
         $expected['typeoflogin'] = api::LOGIN_VIA_BROWSER;
-        $expected['launchurl'] = "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php";;
+        $expected['launchurl'] = "$CFG->wwwroot/$CFG->admin/tool/mobile/launch.php";
+
+        if ($logourl = $OUTPUT->get_logo_url()) {
+            $expected['logourl'] = $logourl->out(false);
+        }
+        if ($compactlogourl = $OUTPUT->get_compact_logo_url()) {
+            $expected['compactlogourl'] = $compactlogourl->out(false);
+        }
 
         $result = external::get_public_config();
         $result = external_api::clean_returnvalue(external::get_public_config_returns(), $result);
index bcf2ff6..4b29277 100644 (file)
@@ -147,7 +147,7 @@ Feature: tool_monitor_subscriptions
     When I follow "Event monitoring"
     And I set the field "Select a course" to "Course 1"
     Then I should see "You can manage rules from the Event monitoring rules page."
-    And I follow "Event monitoring rules"
+    And I click on "Event monitoring rules" "link" in the "region-main" "region"
     And I should see "You can subscribe to rules from the Event monitoring page."
     And I log out
     And I log in as "teacher1"
@@ -155,7 +155,7 @@ Feature: tool_monitor_subscriptions
     And I follow "Event monitoring"
     And I set the field "Select a course" to "Course 1"
     And I should see "You can manage rules from the Event monitoring rules page."
-    And I follow "Event monitoring rules"
+    And I click on "Event monitoring rules" "link" in the "region-main" "region"
     And I should see "You can subscribe to rules from the Event monitoring page."
     And I click on "//a[text()='Event monitoring']" "xpath_element"
     And the field "courseid" matches value "Course 1"
index 7c20c3e..500a273 100644 (file)
@@ -58,12 +58,12 @@ Feature: Backup user data
     And I follow "Course 1"
     And I turn editing mode on
     And I delete "Quiz 1" activity
-    And I follow "Recycle bin"
+    And I navigate to "Recycle bin" node in "Course administration"
     And I should see "Quiz 1"
-    And I follow "Restore"
+    And I click on "Restore" "link" in the "region-main" "region"
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
-    When I navigate to "Grades" node in "Course administration"
+    When I click on "Grades" "link" in the "Navigation" "block"
     Then "Quiz 1" row "Grade" column of "user-grade" table should contain "5"
     And "Quiz 1" row "Percentage" column of "user-grade" table should contain "50"
index 85e5d57..ded5e99 100644 (file)
@@ -31,10 +31,10 @@ Feature: Basic recycle bin functionality
       | Assignment name | Test assign |
       | Description | Test |
     And I delete "Test assign" activity
-    When I follow "Recycle bin"
+    When I navigate to "Recycle bin" node in "Course administration"
     Then I should see "Test assign"
     And I should see "Contents will be permanently deleted after 7 days"
-    And I follow "Restore"
+    And I click on "Restore" "link" in the "region-main" "region"
     And I should see "'Test assign' has been restored"
     And I wait to be redirected
     And I am on homepage
@@ -51,10 +51,10 @@ Feature: Basic recycle bin functionality
     And I press "Continue"
     And I go to the courses management page
     And I should not see "Course 2" in the "#course-listing" "css_element"
-    When I follow "Recycle bin"
+    When I navigate to "Recycle bin" node in "Category: Miscellaneous"
     Then I should see "Course 2"
     And I should see "Contents will be permanently deleted after 14 days"
-    And I follow "Restore"
+    And I click on "Restore" "link" in the "region-main" "region"
     And I should see "'Course 2' has been restored"
     And I wait to be redirected
     And I go to the courses management page
@@ -69,7 +69,7 @@ Feature: Basic recycle bin functionality
       | Assignment name | Test assign |
       | Description | Test |
     And I delete "Test assign" activity
-    And I follow "Recycle bin"
+    And I navigate to "Recycle bin" node in "Course administration"
     When I click on "Delete" "link"
     Then I should see "Are you sure you want to delete the selected item from the recycle bin?"
     And I press "Cancel"
@@ -92,7 +92,7 @@ Feature: Basic recycle bin functionality
       | Description | Test 2 |
     And I delete "Test assign 1" activity
     And I delete "Test assign 2" activity
-    And I follow "Recycle bin"
+    And I navigate to "Recycle bin" node in "Course administration"
     And I should see "Test assign 1"
     And I should see "Test assign 2"
     When I click on "Delete all" "link"
index 2a58fee..2f37014 100644 (file)
Binary files a/admin/tool/usertours/amd/build/tour.min.js and b/admin/tool/usertours/amd/build/tour.min.js differ
index 46a0e19..1f7d0c6 100644 (file)
@@ -722,7 +722,11 @@ Tour.prototype.addStepToPage = function (stepConfig) {
     var animationTarget = $('body, html').stop(true, true);
 
     if (this.isStepActuallyVisible(stepConfig)) {
-        var zIndex = this.calculateZIndex(this.getStepTarget(stepConfig));
+        var targetNode = this.getStepTarget(stepConfig);
+
+        targetNode.data('flexitour', 'target');
+
+        var zIndex = this.calculateZIndex(targetNode);
         if (zIndex) {
             stepConfig.zIndex = zIndex + 1;
         }
@@ -774,6 +778,7 @@ Tour.prototype.addStepToPage = function (stepConfig) {
         this.currentStepNode.offset(this.calculateStepPositionInPage());
 
         this.currentStepPopper = new Popper($('body'), this.currentStepNode[0], {
+            removeOnDestroy: true,
             placement: stepConfig.placement + '-start',
             arrowElement: '[data-role="arrow"]',
             // Empty the modifiers. We've already placed the step and don't want it moved.
@@ -847,6 +852,8 @@ Tour.prototype.announceStep = function (stepConfig) {
         target.data('original-describedby', target.attr('aria-describedby')).attr('aria-describedby', stepId + '-body');
     }
 
+    this.accessibilityShow(stepConfig);
+
     return this;
 };
 
@@ -1036,6 +1043,8 @@ Tour.prototype.hide = function (transition) {
     // Reset the listeners.
     this.resetStepListeners();
 
+    this.accessibilityHide();
+
     this.fireEventHandlers('afterHide');
 
     this.currentStepNode = null;
@@ -1372,6 +1381,50 @@ Tour.prototype.centerPopper = function (data) {
     return data;
 };
 
+Tour.prototype.accessibilityShow = function (stepConfig) {
+    var stateHolder = 'data-has-hidden';
+    var attrName = 'aria-hidden';
+    var hideFunction = function hideFunction(child) {
+        var flexitourRole = child.data('flexitour');
+        if (flexitourRole) {
+            switch (flexitourRole) {
+                case 'container':
+                case 'target':
+                    return;
+            }
+        }
+
+        var hidden = child.attr(attrName);
+        if (!hidden) {
+            child.attr(stateHolder, true);
+            child.attr(attrName, true);
+        }
+    };
+
+    this.currentStepNode.siblings().each(function (index, node) {
+        hideFunction($(node));
+    });
+    this.currentStepNode.parentsUntil('body').siblings().each(function (index, node) {
+        hideFunction($(node));
+    });
+};
+
+Tour.prototype.accessibilityHide = function () {
+    var stateHolder = 'data-has-hidden';
+    var attrName = 'aria-hidden';
+    var showFunction = function showFunction(child) {
+        var hidden = child.attr(stateHolder);
+        if (typeof hidden !== 'undefined') {
+            child.removeAttr(stateHolder);
+            child.removeAttr(attrName);
+        }
+    };
+
+    $('[' + stateHolder + ']').each(function (index, node) {
+        showFunction($(node));
+    });
+};
+
 if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
     module.exports = Tour;
 }
diff --git a/admin/tool/usertours/classes/cache.php b/admin/tool/usertours/classes/cache.php
new file mode 100644 (file)
index 0000000..cefc779
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Cache manager.
+ *
+ * @package    tool_usertours
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_usertours;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Cache manager.
+ *
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache {
+    /**
+     * @var CACHENAME_TOUR      The name of the cache used for storing tours.
+     */
+    const CACHENAME_TOUR = 'tourdata';
+
+    /**
+     * @var CACHEKEY_TOUR       The name of the key used for storing tours.
+     */
+    const CACHEKEY_TOUR = 'tours';
+
+    /**
+     * @var CACHENAME_STEP      The name of the cache used for storing steps.
+     */
+    const CACHENAME_STEP = 'stepdata';
+
+    /**
+     * Fetch all enabled tours.
+     */
+    public static function get_enabled_tourdata() {
+        global $DB;
+
+        $cache = \cache::make('tool_usertours', self::CACHENAME_TOUR);
+
+        $data = $cache->get(self::CACHEKEY_TOUR);
+        if ($data === false) {
+            $sql = <<<EOF
+                SELECT t.*
+                  FROM {tool_usertours_tours} t
+                 WHERE t.enabled = 1
+                   AND t.id IN (SELECT s.tourid FROM {tool_usertours_steps} s GROUP BY s.tourid)
+              ORDER BY t.sortorder ASC
+EOF;
+
+            $data = $DB->get_records_sql($sql);
+            $cache->set('tours', $data);
+        }
+
+        return $data;
+    }
+
+    /**
+     * Fetch all enabled tours matching the specified target.
+     *
+     * @param   string      $targetmatch    The URL to match.
+     */
+    public static function get_matching_tourdata($targetmatch) {
+        $tours = self::get_enabled_tourdata();
+
+        return array_filter($tours, function($tour) use ($targetmatch) {
+            $pattern = preg_quote($tour->pathmatch, '@');
+            $pattern = str_replace('%', '.*', $pattern);
+            return !!preg_match("@{$pattern}@", $targetmatch);
+        });
+    }
+
+    /**
+     * Notify of changes to any tour to clear the tour cache.
+     */
+    public static function notify_tour_change() {
+        $cache = \cache::make('tool_usertours', self::CACHENAME_TOUR);
+        $cache->delete(self::CACHEKEY_TOUR);
+    }
+
+    /**
+     * Fetch the tour data for the specified tour.
+     *
+     * @param   int         $tourid         The ID of the tour to fetch steps for
+     */
+    public static function get_stepdata($tourid) {
+        global $DB;
+
+        $cache = \cache::make('tool_usertours', self::CACHENAME_STEP);
+
+        $data = $cache->get($tourid);
+        if ($data === false) {
+            $sql = <<<EOF
+                SELECT s.*
+                  FROM {tool_usertours_steps} s
+                 WHERE s.tourid = :tourid
+              ORDER BY s.sortorder ASC
+EOF;
+
+            $data = $DB->get_records_sql($sql, ['tourid' => $tourid]);
+            $cache->set($tourid, $data);
+        }
+
+        return $data;
+    }
+    /**
+     * Notify of changes to any step to clear the step cache for that tour.
+     *
+     * @param   int         $tourid         The ID of the tour to clear the step cache for
+     */
+    public static function notify_step_change($tourid) {
+        $cache = \cache::make('tool_usertours', self::CACHENAME_STEP);
+        $cache->delete($tourid);
+    }
+}
index 3caac6f..ae63554 100644 (file)
@@ -94,8 +94,8 @@ class configuration {
      */
     public static function get_placement_options($default = null) {
         $values = [
-            'top'    => get_string('top',     'tool_usertours'),
-            'bottom' => get_string('bottom',  'tool_usertours'),
+            'top'    => get_string('above',   'tool_usertours'),
+            'bottom' => get_string('below',   'tool_usertours'),
             'left'   => get_string('left',    'tool_usertours'),
             'right'  => get_string('right',   'tool_usertours'),
         ];
index d27db08..ee0eb52 100644 (file)
@@ -355,6 +355,8 @@ class helper {
      * @return  string
      */
     public static function render_stepname_inplace_editable(step $step) {
+        $title = format_text(step::get_string_from_input($step->get_title()), FORMAT_HTML);
+
         return new \core\output\inplace_editable(
                 'tool_usertours',
                 'stepname',
@@ -362,9 +364,9 @@ class helper {
                 true,
                 \html_writer::link(
                     $step->get_edit_link(),
-                    $step->get_title()
+                    $title
                 ),
-                $step->get_title(false)
+                $step->get_title()
             );
     }
 
@@ -432,6 +434,11 @@ class helper {
             }
             $index++;
         }
+
+        // Notify the cache that a tour has changed.
+        // Tours are only stored in the cache if there are steps.
+        // If there step count has changed for some reason, this will change the potential cache results.
+        cache::notify_tour_change();
     }
 
 
@@ -442,11 +449,8 @@ class helper {
      * @return  stdClass[]
      */
     public static function get_steps($tourid) {
-        global $DB;
-
-        $order = 'sortorder ASC';
+        $steps = cache::get_stepdata($tourid);
 
-        $steps = $DB->get_records('tool_usertours_steps', array('tourid' => $tourid), $order);
         $return = [];
         foreach ($steps as $step) {
             $return[$step->id] = step::load_from_record($step);
index 5610efc..83d5665 100644 (file)
@@ -58,11 +58,13 @@ class editstep extends \moodleform {
     public function definition() {
         $mform = $this->_form;
 
+        $mform->addElement('header', 'heading_target', get_string('target_heading', 'tool_usertours'));
         $types = [];
         foreach (\tool_usertours\target::get_target_types() as $value => $type) {
             $types[$value] = get_string('target_' . $type, 'tool_usertours');
         }
         $mform->addElement('select', 'targettype', get_string('targettype', 'tool_usertours'), $types);
+        $mform->addHelpButton('targettype', 'targettype', 'tool_usertours');
 
         // The target configuration.
         foreach (\tool_usertours\target::get_target_types() as $value => $type) {
@@ -70,7 +72,8 @@ class editstep extends \moodleform {
             $targetclass::add_config_to_form($mform);
         }
 
-        // Title of the step.
+        // Content of the step.
+        $mform->addElement('header', 'heading_content', get_string('content_heading', 'tool_usertours'));
         $mform->addElement('textarea', 'title', get_string('title', 'tool_usertours'));
         $mform->addRule('title', get_string('required'), 'required', null, 'client');
         $mform->setType('title', PARAM_TEXT);
@@ -82,6 +85,7 @@ class editstep extends \moodleform {
         $mform->addHelpButton('content', 'content', 'tool_usertours');
 
         // Add the step configuration.
+        $mform->addElement('header', 'heading_options', get_string('options_heading', 'tool_usertours'));
         // All step configuration is defined in the step.
         $this->step->add_config_to_form($mform);
 
index 3bb71ae..272267d 100644 (file)
@@ -77,7 +77,7 @@ class edittour extends \moodleform {
         $mform->setType('pathmatch', PARAM_RAW);
         $mform->addHelpButton('pathmatch', 'pathmatch', 'tool_usertours');
 
-        $mform->addElement('checkbox', 'enabled', get_string('enabled', 'tool_usertours'));
+        $mform->addElement('checkbox', 'enabled', get_string('tourisenabled', 'tool_usertours'));
 
         // Configuration.
         $this->tour->add_config_to_form($mform);
index 2afafb8..a37127f 100644 (file)
@@ -96,7 +96,7 @@ class step_list extends \flexible_table {
      * @return  string
      */
     protected function col_content(step $step) {
-        return $step->get_content(false);
+        return format_text(step::get_string_from_input($step->get_content()), FORMAT_HTML);
     }
 
     /**
index adcecbd..ffbd787 100644 (file)
@@ -90,6 +90,8 @@ class block extends base {
             $blocks[$block->name] = get_string('pluginname', 'block_' . $block->name);
         }
 
+        \core_collator::asort($blocks);
+
         $mform->addElement('select', 'targetvalue_block', get_string('block', 'tool_usertours'), $blocks);
     }
 
index 2362ac2..47e4633 100644 (file)
@@ -559,22 +559,13 @@ class manager {
      * @return  tour
      */
     public static function get_matching_tours(\moodle_url $pageurl) {
-        global $DB, $PAGE;
-
-        $sql = <<<EOF
-            SELECT * FROM {tool_usertours_tours}
-             WHERE enabled = 1
-               AND ? LIKE pathmatch
-          ORDER BY sortorder ASC
-EOF;
+        global $PAGE;
 
-        $tours = $DB->get_records_sql($sql, array(
-            $pageurl->out_as_local_url(),
-        ));
+        $tours = cache::get_matching_tourdata($pageurl->out_as_local_url());
 
         foreach ($tours as $record) {
             $tour = tour::load_from_record($record);
-            if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context) && $tour->count_steps() > 0) {
+            if ($tour->is_enabled() && $tour->matches_all_filters($PAGE->context)) {
                 return $tour;
             }
         }
index 3da0d09..772edd0 100644 (file)
@@ -63,26 +63,16 @@ class step implements \renderable {
         $result = (object) [
             'stepid'    => $step->get_id(),
             'title'     => external_format_text(
-                    static::get_string_from_input($step->get_title()),
+                    stepsource::get_string_from_input($step->get_title()),
                     FORMAT_HTML,
                     $PAGE->context->id,
-                    'tool_usertours',
-                    null,
-                    null,
-                    [
-                        'filter' => true,
-                    ]
+                    'tool_usertours'
                 )[0],
             'content'   => external_format_text(
-                    static::get_string_from_input($step->get_content()),
+                    stepsource::get_string_from_input($step->get_content()),
                     FORMAT_HTML,
                     $PAGE->context->id,
-                    'tool_usertours',
-                    null,
-                    null,
-                    [
-                        'filter' => true,
-                    ]
+                    'tool_usertours'
                 )[0],
             'element'   => $step->get_target()->convert_to_css(),
         ];
@@ -95,27 +85,4 @@ class step implements \renderable {
 
         return $result;
     }
-
-    /**
-     * Attempt to fetch any matching langstring if the string is in the
-     * format identifier,component.
-     *
-     * @param   string  $string
-     * @return  string
-     */
-    protected static function get_string_from_input($string) {
-        $string = trim($string);
-
-        if (preg_match('|^([a-zA-Z][a-zA-Z0-9\.:/_-]*),([a-zA-Z][a-zA-Z0-9\.:/_-]*)$|', $string, $matches)) {
-            if ($matches[2] === 'moodle') {
-                $matches[2] = 'core';
-            }
-
-            if (get_string_manager()->string_exists($matches[1], $matches[2])) {
-                $string = get_string($matches[1], $matches[2]);
-            }
-        }
-
-        return $string;
-    }
 }
index b30fccd..6049e8f 100644 (file)
@@ -216,7 +216,7 @@ class step {
      * @return  $this
      */
     public function set_title($value) {
-        $this->title = clean_param($value, PARAM_TEXT);
+        $this->title = clean_text($value);
         $this->dirty = true;
 
         return $this;
@@ -485,8 +485,19 @@ class step {
             $this->id = $DB->insert_record('tool_usertours_steps', $record);
         }
 
+        $this->get_tour()->reset_step_sortorder();
+
         $this->reload();
 
+        // Notify of a change to the step configuration.
+        // This must be done separately to tour change notifications.
+        cache::notify_step_change($this->get_tourid());
+
+        // Notify the cache that a tour has changed.
+        // Tours are only stored in the cache if there are steps.
+        // If there step count has changed for some reason, this will change the potential cache results.
+        cache::notify_tour_change();
+
         return $this;
     }
 
@@ -502,6 +513,15 @@ class step {
 
         $DB->delete_records('tool_usertours_steps', array('id' => $this->id));
         $this->get_tour()->reset_step_sortorder();
+
+        // Notify of a change to the step configuration.
+        // This must be done separately to tour change notifications.
+        cache::notify_step_change($this->get_id());
+
+        // Notify the cache that a tour has changed.
+        // Tours are only stored in the cache if there are steps.
+        // If there step count has changed for some reason, this will change the potential cache results.
+        cache::notify_tour_change();
     }
 
     /**
@@ -630,4 +650,27 @@ class step {
 
         return $this;
     }
+
+    /**
+     * Attempt to fetch any matching langstring if the string is in the
+     * format identifier,component.
+     *
+     * @param   string  $string
+     * @return  string
+     */
+    public static function get_string_from_input($string) {
+        $string = trim($string);
+
+        if (preg_match('|^([a-zA-Z][a-zA-Z0-9\.:/_-]*),([a-zA-Z][a-zA-Z0-9\.:/_-]*)$|', $string, $matches)) {
+            if ($matches[2] === 'moodle') {
+                $matches[2] = 'core';
+            }
+
+            if (get_string_manager()->string_exists($matches[1], $matches[2])) {
+                $string = get_string($matches[1], $matches[2]);
+            }
+        }
+
+        return $string;
+    }
 }
index b628744..473a077 100644 (file)
@@ -523,6 +523,9 @@ class tour {
 
         $this->reload();
 
+        // Notify the cache that a tour has changed.
+        cache::notify_tour_change();
+
         return $this;
     }
 
@@ -566,6 +569,10 @@ class tour {
             $index++;
         }
 
+        // Notify of a change to the step configuration.
+        // Note: Do not notify of a tour change here. This is only a step change for a tour.
+        cache::notify_step_change($this->get_id());
+
         return $this;
     }
 
diff --git a/admin/tool/usertours/db/caches.php b/admin/tool/usertours/db/caches.php
new file mode 100644 (file)
index 0000000..3ae3c8f
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Plugin cache definitions.
+ *
+ * @package   tool_usertours
+ * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$definitions = array(
+    'tourdata' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true,
+        'simpledata' => true,
+        'staticacceleration' => true,
+        'staticaccelerationsize' => 1,
+    ),
+    'stepdata' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'simplekeys' => true,
+        'simpledata' => true,
+        'staticacceleration' => true,
+        'staticaccelerationsize' => 1,
+    ),
+);
index 9d3fae6..90e72ee 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['above'] = 'Above';
 $string['actions'] = 'Actions';
 $string['appliesto'] = 'Applies to';
+$string['below'] = 'Below';
 $string['block'] = 'Block';
 $string['block_named'] = 'Block named \'{$a}\'';
-$string['bottom'] = 'Bottom';
+$string['cachedef_stepdata'] = 'List of User Tour steps';
+$string['cachedef_tourdata'] = 'List of enabled User Tours information which are fetched on every page';
 $string['description'] = 'Description';
 $string['confirmstepremovalquestion'] = 'Are you sure that you wish to remove this step?';
 $string['confirmstepremovaltitle'] = 'Confirm step removal';
 $string['confirmtourremovalquestion'] = 'Are you sure that you wish to remove this tour?';
 $string['confirmtourremovaltitle'] = 'Confirm tour removal';
 $string['content'] = 'Content';
+$string['content_heading'] = 'Content';
 $string['content_help'] = 'This is the content of the step.
 You can enter a content in the following formats:
 <dl>
@@ -48,6 +52,7 @@ $string['defaultvalue'] = 'Default ({$a})';
 $string['delay'] = 'Delay before showing the step';
 $string['done'] = 'Done';
 $string['editstep'] = 'Editing "{$a}"';
+$string['tourisenabled'] = 'Tour is enabled';
 $string['enabled'] = 'Enabled';
 $string['event_tour_started'] = 'Tour started';
 $string['event_tour_reset'] = 'Tour reset';
@@ -72,6 +77,7 @@ $string['newstep'] = 'Create step';
 $string['newstep'] = 'New step';
 $string['newtour'] = 'Create a new tour';
 $string['next'] = 'Next';
+$string['options_heading'] = 'Options';
 $string['pathmatch'] = 'Apply to URL match';
 $string['pathmatch_help'] = 'Tours will be displayed on any page whose URL matches this value.
 
@@ -87,10 +93,10 @@ $string['pluginname'] = 'User Tours';
 $string['resettouronpage'] = 'Reset user tour on this page';
 $string['right'] = 'Right';
 $string['select_block'] = 'Select a block';
-$string['select_targettype'] = 'Every step is associated with a part of the page which you must choose. To make this easier there are several types of target for different types of page content.
+$string['targettype_help'] = 'Every step is associated with a part of the page which you must choose. To make this easier there are several types of target for different types of page content.
 <dl>
     <dt>Block</dt>
-    <dd>Display the step next to the first matching block of the type on the page</dd>
+    <dd>Display the step next to the first matching block of the type on the page.</dd>
     <dt>Selector</dt>
     <dd>CSS Selectors are a powerful way which allow you to select different parts of the page based on metadata built into the page.</dd>
     <dt>Display in middle of the page</dt>
@@ -100,6 +106,7 @@ $string['selector_defaulttitle'] = 'Enter a descriptive title';
 $string['selectordisplayname'] = 'A CSS selector matching \'{$a}\'';
 $string['skip'] = 'Skip';
 $string['target'] = 'Target';
+$string['target_heading'] = 'Step Target';
 $string['target_block'] = 'Block';
 $string['target_selector'] = 'Selector';
 $string['target_unattached'] = 'Display in middle of page';
@@ -115,7 +122,6 @@ You can enter a title in the following formats:
     <dt>Moodle Translated string</dt>
     <dd>A value found in a standard Moodle language file in the format identifier,component</dd>
 </dl>';
-$string['top'] = 'Top';
 $string['tourconfig'] = 'Tour configuration file to import';
 $string['tourlist_explanation'] = 'You can create as many tours as you like and enable them for different parts of Moodle. Only one tour can be created per page.';
 $string['tours'] = 'Tours';
@@ -129,11 +135,13 @@ $string['backdrop_help'] = 'You can use a backdrop to highlight the part of the
 
 Note: Backdrops are not compatible with some parts of the page such as the navigation bar.
 ';
-$string['reflex'] = 'Move on click';
-$string['reflex_help'] = 'Move on to the next step when the target is clicked on.';
+$string['reflex'] = 'Proceed on click';
+$string['reflex_help'] = 'Proceed to the next step when the target is clicked on.';
 $string['placement_help'] = 'You can place a step either above, below, to the left of, or to the right of the target.
 
-The best options are top, or bottom as these adjust better for mobile display.';
+The best options are above, or below as these adjust better for mobile display.
+
+If the step does not fit into the page at at the placement you choose, it will be automatically be moved to give the best viewing experience. ';
 $string['delay_help'] = 'You can optionally choose to add a delay before the step is displayed.
 
 This delay is in milliseconds.';
diff --git a/admin/tool/usertours/tests/cache_test.php b/admin/tool/usertours/tests/cache_test.php
new file mode 100644 (file)
index 0000000..fdbdde0
--- /dev/null
@@ -0,0 +1,340 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests for cache.
+ *
+ * @package    tool_usertours
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(__DIR__ . '/helper_trait.php');
+
+/**
+ * Tests for cache.
+ *
+ * @package    tool_usertours
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class cache_testcase extends advanced_testcase {
+    // There are shared helpers for these tests in the helper trait.
+    use tool_usertours_helper_trait;
+
+    /**
+     * Test that get_enabled_tourdata does not return disabled tours.
+     */
+    public function test_get_enabled_tourdata_disabled() {
+        $this->resetAfterTest();
+
+        $tour = $this->helper_create_tour((object)['enabled' => false]);
+        $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEmpty($matches);
+    }
+
+    /**
+     * Test that get_enabled_tourdata does not return an enabled but empty tour.
+     */
+    public function test_get_enabled_tourdata_enabled_no_steps() {
+        $this->resetAfterTest();
+
+        $this->helper_create_tour();
+
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEmpty($matches);
+    }
+
+    /**
+     * Test that get_enabled_tourdata returns a tour with steps.
+     */
+    public function test_get_enabled_tourdata_enabled() {
+        $this->resetAfterTest();
+
+        // Create two tours. Only the second has steps.
+        $this->helper_create_tour();
+        $tour2 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
+
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertNotEmpty($matches);
+        $this->assertCount(1, $matches);
+
+        $match = array_shift($matches);
+        $this->assertEquals($tour2->get_id(), $match->id);
+    }
+
+    /**
+     * Test that get_enabled_tourdata returns tours in the correct sortorder
+     */
+    public function test_get_enabled_tourdata_enabled_sortorder() {
+        $this->resetAfterTest();
+
+        $tour1 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $tour2 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
+
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertNotEmpty($matches);
+        $this->assertCount(2, $matches);
+
+        $match = array_shift($matches);
+        $this->assertEquals($tour1->get_id(), $match->id);
+        $match = array_shift($matches);
+        $this->assertEquals($tour2->get_id(), $match->id);
+    }
+
+    /**
+     * Test that caching prevents additional DB reads.
+     */
+    public function test_get_enabled_tourdata_single_fetch() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $tour1 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $tour2 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
+
+        // Only one read for the first call.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // No subsequent reads for any further calls.
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+    }
+
+    /**
+     * Data provider for get_matching_tourdata.
+     *
+     * @return  array
+     */
+    public function get_matching_tourdata_provider() {
+        $tourconfigs = [
+            (object) [
+                'name' => 'my_exact_1',
+                'pathmatch' => '/my/view.php'
+            ],
+            (object) [
+                'name' => 'my_failed_regex',
+                'pathmatch' => '/my/*.php'
+            ],
+            (object) [
+                'name' => 'my_glob_1',
+                'pathmatch' => '/my/%'
+            ],
+            (object) [
+                'name' => 'my_glob_2',
+                'pathmatch' => '/my/%'
+            ],
+        ];
+
+        return [
+            'Matches expected glob' => [
+                $tourconfigs,
+                '/my/index.php',
+                ['my_glob_1', 'my_glob_2'],
+            ],
+            'Matches expected glob and exact' => [
+                $tourconfigs,
+                '/my/view.php',
+                ['my_exact_1', 'my_glob_1', 'my_glob_2'],
+            ],
+        ];
+    }
+
+    /**
+     * Tests for the get_matching_tourdata function.
+     *
+     * @dataProvider    get_matching_tourdata_provider
+     * @param   array   $tourconfigs    The configuration for the tours to create
+     * @param   string  $targetmatch    The match to be tested
+     * @param   array   $expected       An array containing the ordered names of the expected tours
+     */
+    public function test_get_matching_tourdata($tourconfigs, $targetmatch, $expected) {
+        $this->resetAfterTest();
+        foreach ($tourconfigs as $tourconfig) {
+            $tour = $this->helper_create_tour($tourconfig);
+            $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+        }
+
+        $matches = \tool_usertours\cache::get_matching_tourdata((new moodle_url($targetmatch))->out_as_local_url());
+        $this->assertCount(count($expected), $matches);
+
+        for ($i = 0; $i < count($matches); $i++) {
+            $match = array_shift($matches);
+            $this->assertEquals($expected[$i], $match->name);
+        }
+    }
+
+    /**
+     * Test that notify_tour_change clears the cache.
+     */
+    public function test_notify_tour_change() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $tour1 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $tour2 = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour2->get_id()]);
+
+        // Only one read for the first call.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // No subsequent reads for any further calls.
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // Reset.
+        \tool_usertours\cache::notify_tour_change();
+
+        // An additional DB read now.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_enabled_tourdata();
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+    }
+
+    /**
+     * Test that get_stepdata returns an empty array when no steps were found.
+     */
+    public function test_get_stepdata_no_steps() {
+        $this->resetAfterTest();
+
+        $tour = $this->helper_create_tour((object)['enabled' => false]);
+
+        $data = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertInternalType('array', $data);
+        $this->assertEmpty($data);
+    }
+
+    /**
+     * Test that get_stepdata returns an empty array when no steps were found.
+     */
+    public function test_get_stepdata_correct_tour() {
+        $this->resetAfterTest();
+
+        $tour1 = $this->helper_create_tour((object)['enabled' => false]);
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $this->helper_create_step((object) ['tourid' => $tour1->get_id()]);
+        $tour2 = $this->helper_create_tour((object)['enabled' => false]);
+
+        $data = \tool_usertours\cache::get_stepdata($tour1->get_id());
+        $this->assertInternalType('array', $data);
+        $this->assertCount(3, $data);
+
+        $data = \tool_usertours\cache::get_stepdata($tour2->get_id());
+        $this->assertInternalType('array', $data);
+        $this->assertEmpty($data);
+    }
+
+    /**
+     * Test that get_stepdata returns an array containing multiple steps in
+     * the same order.
+     *
+     * This is very difficult to determine because the act of changing the
+     * order will likely change the DB natural sorting.
+     */
+    public function test_get_stepdata_ordered_steps() {
+        $this->resetAfterTest();
+
+        $tour = $this->helper_create_tour((object)['enabled' => false]);
+        $steps = [];
+        $steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+        $steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+        $steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+        $steps[] = $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+        $steps[0]->set_sortorder(10)->persist();
+
+        $data = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertInternalType('array', $data);
+        $this->assertCount(4, $data);
+
+        // Re-order the steps.
+        usort($steps, function($a, $b) {
+            return ($a->get_sortorder() < $b->get_sortorder()) ? -1 : 1;
+        });
+
+        for ($i = 0; $i < count($data); $i++) {
+            $step = array_shift($data);
+            $this->assertEquals($steps[$i]->get_id(), $step->id);
+        }
+    }
+
+    /**
+     * Test that caching prevents additional DB reads.
+     */
+    public function test_get_stepdata_single_fetch() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $tour = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+
+        // Only one read for the first call.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // No subsequent reads for any further calls.
+        $matches = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+    }
+
+    /**
+     * Test that notify_step_change clears the cache.
+     */
+    public function test_notify_step_change() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $tour = $this->helper_create_tour();
+        $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
+
+        // Only one read for the first call.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // No subsequent reads for any further calls.
+        $matches = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+
+        // Reset.
+        \tool_usertours\cache::notify_step_change($tour->get_id());
+
+        // An additional DB read now.
+        $startreads = $DB->perf_get_reads();
+        $matches = \tool_usertours\cache::get_stepdata($tour->get_id());
+        $this->assertEquals(1, $DB->perf_get_reads() - $startreads);
+    }
+}
diff --git a/admin/tool/usertours/tests/helper_trait.php b/admin/tool/usertours/tests/helper_trait.php
new file mode 100644 (file)
index 0000000..aab15de
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Helpers for unit tests.
+ *
+ * @package    tool_usertours
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Helpers for unit tests.
+ *
+ * @package    tool_usertours
+ * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+trait tool_usertours_helper_trait {
+    /**
+     * A helper to create an empty tour.
+     *
+     * @param   stdClass    $tourconfig     The configuration for the new tour
+     * @param   bool        $persist        Whether to persist the data
+     * @return  \tool_usertours\tour
+     */
+    public function helper_create_tour(\stdClass $tourconfig = null, $persist = true) {
+        $minvalues = [
+            'id' => null,
+            'pathmatch' => '/my/%',
+            'enabled' => true,
+            'name' => '',
+            'description' => '',
+            'configdata' => '',
+        ];
+
+        if ($tourconfig === null) {
+            $tourconfig = new \stdClass();
+        }
+
+        foreach ($minvalues as $key => $value) {
+            if (!isset($tourconfig->$key)) {
+                $tourconfig->$key = $value;
+            }
+        }
+
+        $tour = \tool_usertours\tour::load_from_record($tourconfig, true);
+        if ($persist) {
+            $tour->persist(true);
+        }
+
+        return $tour;
+    }
+
+    /**
+     * A helper to create an empty step for the specified tour.
+     *
+     * @param   stdClass    $stepconfig     The configuration for the new step
+     * @param   bool        $persist        Whether to persist the data
+     * @return  \tool_usertours\step
+     */
+    public function helper_create_step(\stdClass $stepconfig = null, $persist = true) {
+        $minvalues = [
+            'id' => null,
+            'title' => '',
+            'content' => '',
+            'targettype' => \tool_usertours\target::TARGET_UNATTACHED,
+            'targetvalue' => '',
+            'sortorder' => 0,
+            'configdata' => '',
+        ];
+
+        if ($stepconfig === null) {
+            $stepconfig = new \stdClass();
+        }
+
+        foreach ($minvalues as $key => $value) {
+            if (!isset($stepconfig->$key)) {
+                $stepconfig->$key = $value;
+            }
+        }
+
+        $step = \tool_usertours\step::load_from_record($stepconfig, true);
+        if ($persist) {
+            $step->persist(true);
+        }
+
+        return $step;
+    }
+}
index aa7a6bf..321ff06 100644 (file)
@@ -26,6 +26,7 @@ defined('MOODLE_INTERNAL') || die();
 
 global $CFG;
 require_once($CFG->libdir . '/formslib.php');
+require_once(__DIR__ . '/helper_trait.php');
 
 /**
  * Tests for step.
@@ -34,7 +35,9 @@ require_once($CFG->libdir . '/formslib.php');
  * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class manager_testcase extends advanced_testcase {
+class tool_usertours_manager_testcase extends advanced_testcase {
+    // There are shared helpers for these tests in the helper trait.
+    use tool_usertours_helper_trait;
 
     /**
      * @var moodle_database
@@ -239,55 +242,16 @@ class manager_testcase extends advanced_testcase {
         $this->resetAfterTest();
 
         foreach ($alltours as $tourconfig) {
-            $tourconfig = (object) $tourconfig;
-            $tourconfig->id = null;
-            $tour = \tool_usertours\tour::load_from_record($tourconfig, true);
-            $tour->persist(true);
-
-            $stepconfig = (object) [
-                'id' => null,
-                'tourid' => $tour->get_id(),
-                'title' => '',
-                'content' => '',
-                'targettype' => \tool_usertours\target::TARGET_UNATTACHED,
-                'targetvalue' => '',
-                'sortorder' => 0,
-                'configdata' => '',
-            ];
-            $step = \tool_usertours\step::load_from_record($stepconfig, true);
-            $step->persist(true);
+            $tour = $this->helper_create_tour((object) $tourconfig);
+            $this->helper_create_step((object) ['tourid' => $tour->get_id()]);
         }
 
         $match = \tool_usertours\manager::get_matching_tours(new moodle_url($url));
         if ($expected === null) {
             $this->assertNull($match);
         } else {
+            $this->assertNotNull($match);
             $this->assertEquals($expected, $match->get_name());
         }
     }
-
-    /**
-     * Tests for the get_matching_tours function when requiring an upgrade
-     *
-     * @dataProvider get_matching_tours_provider
-     * @param   array   $alltours   The list of tours to insert
-     * @param   string  $url        The URL to test
-     * @param   string  $expected   The name of the expected matching tour
-     */
-    public function test_get_matching_tours_requires_upgrade($alltours, $url, $expected) {
-        $this->resetAfterTest();
-
-        global $CFG;
-        unset($CFG->version);
-
-        foreach ($alltours as $tourconfig) {
-            $tourconfig = (object) $tourconfig;
-            $tourconfig->id = null;
-            $tour = \tool_usertours\tour::load_from_record($tourconfig, true);
-            $tour->persist(true);
-        }
-
-        $this->assertNull(\tool_usertours\manager::get_matching_tours(new moodle_url($url)));
-    }
-
 }
diff --git a/admin/tool/usertours/tests/step_output_test.php b/admin/tool/usertours/tests/step_output_test.php
deleted file mode 100644 (file)
index b14af63..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Tests for step.
- *
- * @package    tool_usertours
- * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Tests for step.
- *
- * @package    tool_usertours
- * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class step_output_testcase extends advanced_testcase {
-
-    /**
-     * Data Provider for get_string_from_inpu.
-     *
-     * @return array
-     */
-    public function get_string_from_input_provider() {
-        return [
-            'Text'  => [
-                'example',
-                'example',
-            ],
-            'Text which looks like a langstring' => [
-                'example,fakecomponent',
-                'example,fakecomponent',
-            ],
-            'Text which is a langstring' => [
-                'administration,core',
-                'Administration',
-            ],
-            'Text which is a langstring but uses "moodle" instead of "core"' => [
-                'administration,moodle',
-                'Administration',
-            ],
-            'Text which is a langstring, but with extra whitespace' => [
-                '  administration,moodle  ',
-                'Administration',
-            ],
-            'Looks like a langstring, but has incorrect space around comma' => [
-                'administration , moodle',
-                'administration , moodle',
-            ],
-        ];
-    }
-
-    /**
-     * Ensure that the get_string_from_input function returns translated
-     * strings correctly.
-     *
-     * @dataProvider get_string_from_input_provider
-     * @param   string  $string     The string to test
-     * @param   string  $expected   The expected result
-     */
-    public function test_get_string_from_input($string, $expected) {
-        $rc = new ReflectionClass('\\tool_usertours\\output\\step');
-        $rcm = $rc->getMethod('get_string_from_input');
-        $rcm->setAccessible(true);
-        $this->assertEquals($expected, $rcm->invoke(null, $string));
-    }
-}
index 444d2da..005272e 100644 (file)
@@ -551,6 +551,11 @@ class step_testcase extends advanced_testcase {
         $rcp->setAccessible(true);
         $rcp->setValue($step, true);
 
+        $tour = $this->createMock(\tool_usertours\tour::class);
+        $rcp = $rc->getProperty('tour');
+        $rcp->setAccessible(true);
+        $rcp->setValue($step, $tour);
+
         $this->assertSame($step, $step->persist());
     }
 
@@ -590,6 +595,12 @@ class step_testcase extends advanced_testcase {
             ->method('reload')
             ;
 
+        $tour = $this->createMock(\tool_usertours\tour::class);
+        $rc = new \ReflectionClass(\tool_usertours\step::class);
+        $rcp = $rc->getProperty('tour');
+        $rcp->setAccessible(true);
+        $rcp->setValue($step, $tour);
+
         $this->assertSame($step, $step->persist(true));
     }
 
@@ -635,6 +646,11 @@ class step_testcase extends advanced_testcase {
         $rcp->setAccessible(true);
         $rcp->setValue($step, true);
 
+        $tour = $this->createMock(\tool_usertours\tour::class);
+        $rcp = $rc->getProperty('tour');
+        $rcp->setAccessible(true);
+        $rcp->setValue($step, $tour);
+
         $this->assertSame($step, $step->persist());
     }
 
@@ -678,6 +694,11 @@ class step_testcase extends advanced_testcase {
         $rcp->setAccessible(true);
         $rcp->setValue($step, 42);
 
+        $tour = $this->createMock(\tool_usertours\tour::class);
+        $rcp = $rc->getProperty('tour');
+        $rcp->setAccessible(true);
+        $rcp->setValue($step, $tour);
+
         $this->assertSame($step, $step->persist(true));
     }
 
@@ -803,4 +824,48 @@ class step_testcase extends advanced_testcase {
         $this->assertEquals($value, $step->$getter());
     }
 
+    /**
+     * Data Provider for get_string_from_input.
+     *
+     * @return array
+     */
+    public function get_string_from_input_provider() {
+        return [
+            'Text'  => [
+                'example',
+                'example',
+            ],
+            'Text which looks like a langstring' => [
+                'example,fakecomponent',
+                'example,fakecomponent',
+            ],
+            'Text which is a langstring' => [
+                'administration,core',
+                'Administration',
+            ],
+            'Text which is a langstring but uses "moodle" instead of "core"' => [
+                'administration,moodle',
+                'Administration',
+            ],
+            'Text which is a langstring, but with extra whitespace' => [
+                '  administration,moodle  ',
+                'Administration',
+            ],
+            'Looks like a langstring, but has incorrect space around comma' => [
+                'administration , moodle',
+                'administration , moodle',
+            ],
+        ];
+    }
+
+    /**
+     * Ensure that the get_string_from_input function returns langstring strings correctly.
+     *
+     * @dataProvider get_string_from_input_provider
+     * @param   string  $string     The string to test
+     * @param   string  $expected   The expected result
+     */
+    public function test_get_string_from_input($string, $expected) {
+        $this->assertEquals($expected, \tool_usertours\step::get_string_from_input($string));
+    }
 }
index 99e6120..45bad95 100644 (file)
@@ -4,7 +4,7 @@
     <location>amd/src/tour.js</location>
     <name>Flexitour</name>
     <license>GPLv3</license>
-    <version>0.9.6</version>
+    <version>0.9.7</version>
     <licenseversion>3</licenseversion>
   </library>
   <library>
index 827b68e..6711935 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2016101800;            // The current module version (Date: YYYYMMDDXX).
+$plugin->version   = 2016102001;            // The current module version (Date: YYYYMMDDXX).
 $plugin->requires  = 2016052300;            // Requires this Moodle version.
 $plugin->component = 'tool_usertours';      // Full name of the plugin (used for diagnostics).
index f4aa617..38a8f06 100644 (file)
@@ -63,8 +63,6 @@ echo '<form action="upgradesettings.php" method="post" id="adminsettings">';
 echo '<div>';
 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
 echo '<input type="hidden" name="return" value="'.$return.'" />';
-// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-echo prevent_form_autofill_password();
 echo '<fieldset>';
 echo '<div class="clearer"><!-- --></div>';
 echo $newsettingshtml;
index da24294..1c94323 100644 (file)
@@ -179,7 +179,7 @@ class availability_group_condition_testcase extends advanced_testcase {
         $course = $generator->create_course();
         $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
         $teacher = $generator->create_user();
-        $generator->enrol_user($teacher->id, $course->id, $roleids['teacher']);
+        $generator->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
         $allusers = array($teacher->id => $teacher);
         $students = array();
         for ($i = 0; $i < 3; $i++) {
index 4ffd76f..94cd4ec 100644 (file)
@@ -222,7 +222,7 @@ class availability_grouping_condition_testcase extends advanced_testcase {
         $course = $generator->create_course();
         $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
         $teacher = $generator->create_user();
-        $generator->enrol_user($teacher->id, $course->id, $roleids['teacher']);
+        $generator->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
         $allusers = array($teacher->id => $teacher);
         $students = array();
         for ($i = 0; $i < 3; $i++) {
index 5aad4a3..1fa38f1 100644 (file)
@@ -67,7 +67,7 @@ Feature: Restore Moodle 2 course backups
     And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum post backup name |
       | Description | Test forum post backup description |
-    And I follow "Restore"
+    And I navigate to "Restore" node in "Course administration"
     And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
       | Schema | Section 3 | 0 |
     Then I should see "Course 1"
@@ -91,6 +91,7 @@ Feature: Restore Moodle 2 course backups
       | id_startdate_month | January |
       | id_startdate_year | 2020 |
       | id_format | Weekly format |
+      | id_enddate_enabled | 0 |
     And I press "Save and display"
     And I should see "1 January - 7 January"
     And I should see "Test forum name"
index e005a81..c3d3787 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-debug.js and b/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-debug.js differ
index b001213..129195e 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js and b/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall-min.js differ
index e005a81..c3d3787 100644 (file)
Binary files a/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall.js and b/backup/util/ui/yui/build/moodle-backup-backupselectall/moodle-backup-backupselectall.js differ
index faf369b..4d152bc 100644 (file)
@@ -63,7 +63,7 @@ M.core_backup.backupselectall = function(modnames) {
         // This is not a relevant page.
         return;
     }
-    if (!firstsection.one('.felement.fcheckbox')) {
+    if (!firstsection.one('input[type="checkbox"]')) {
         // No checkboxes.
         return;
     }
@@ -166,7 +166,8 @@ M.core_backup.backupselectall = function(modnames) {
         }
 
     };
-    Y.one('#backup-bytype').on('click', function() {
+    Y.one('#backup-bytype').on('click', function(e) {
+        e.preventDefault();
         toggletypes();
     });
 
index bd0a070..92cc5ad 100644 (file)
@@ -32,7 +32,7 @@ Feature: Award badges
     And I press "Continue"
     And I click on "Admin User" "link"
     And I choose "Profile" in the open action menu
-    And I follow "Edit profile"
+    And I click on "Edit profile" "link" in the "region-main" "region"
     And I expand all fieldsets
     And I set the field "Phone" to "123456789"
     And I press "Update profile"
@@ -113,7 +113,7 @@ Feature: Award badges
     And I log out
     And I log in as "student1"
     And I follow "Profile" in the user menu
-    And I follow "Course 1"
+    And I click on "Course 1" "link" in the "region-main" "region"
     And I should see "Course Badge"
 
   @javascript
@@ -131,7 +131,7 @@ Feature: Award badges
       | student1 | C1 | student |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I follow "Edit settings"
+    And I navigate to "Edit settings" node in "Course administration"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
     And I press "Save and display"
@@ -156,13 +156,13 @@ Feature: Award badges
     And I log out
     And I log in as "student1"
     And I follow "Profile" in the user menu
-    And I follow "Course 1"
+    And I click on "Course 1" "link" in the "region-main" "region"
     Then I should not see "badges"
     And I am on homepage
     And I follow "Course 1"
     And I press "Mark as complete: Test assignment name"
     And I follow "Profile" in the user menu
-    And I follow "Course 1"
+    And I click on "Course 1" "link" in the "region-main" "region"
     Then I should see "Course Badge"
 
   @javascript
@@ -180,7 +180,7 @@ Feature: Award badges
       | student1 | C1 | student |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I follow "Edit settings"
+    And I navigate to "Edit settings" node in "Course administration"
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
     And I press "Save and display"
@@ -189,7 +189,7 @@ Feature: Award badges
       | Assignment name | Test assignment name |
       | Description | Submit your online text |
       | assignsubmission_onlinetext_enabled | 1 |
-    And I follow "Course completion"
+    And I navigate to "Course completion" node in "Course administration"
     And I set the field "id_overall_aggregation" to "2"
     And I click on "Condition: Activity completion" "link"
     And I set the field "Assignment - Test assignment name" to "1"
@@ -211,7 +211,7 @@ Feature: Award badges
     And I log out
     And I log in as "student1"
     And I follow "Profile" in the user menu
-    And I follow "Course 1"
+    And I click on "Course 1" "link" in the "region-main" "region"
     Then I should not see "badges"
     And I am on homepage
     And I follow "Course 1"
@@ -300,14 +300,14 @@ Feature: Award badges
     # Student 1 should have just course badge 1.
     And I log in as "student1"
     And I follow "Profile" in the user menu
-    When I follow "Course 1"
+    When I click on "Course 1" "link" in the "region-main" "region"
     Then I should see "Course Badge 1"
     And I should not see "Course Badge 2"
     And I log out
     # Student 2 should have just course badge 2.
     And I log in as "student2"
     And I follow "Profile" in the user menu
-    When I follow "Course 1"
+    When I click on "Course 1" "link" in the "region-main" "region"
     Then I should see "Course Badge 2"
     Then I should not see "Course Badge 1"
 
index 1f444c8..2c62fb7 100644 (file)
@@ -50,7 +50,7 @@ Feature: The activity results block displays student scores
     And I press "Save and return to course"
     And I follow "Course 1"
     And I should see "Test page name"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment 1"
     And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment 1"
index b744851..6797129 100644 (file)
@@ -32,7 +32,7 @@ Feature: The activity results block displays student scores
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment"
@@ -167,4 +167,4 @@ Feature: The activity results block displays student scores
     Then I should see "User" in the "Activity results" "block"
     And I should see "90.00%" in the "Activity results" "block"
     And I should see "80.00%" in the "Activity results" "block"
-    And I should see "70.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "70.00%" in the "Activity results" "block"
index b5e52f8..44ea70b 100644 (file)
@@ -26,7 +26,7 @@ Feature: The activity results block displays student scores as scales
       | student5 | C1 | student |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I navigate to "Scales" node in "Grade administration"
     And I press "Add a new scale"
     And I set the following fields to these values:
@@ -42,7 +42,7 @@ Feature: The activity results block displays student scores as scales
       | id_grade_modgrade_type | Scale |
       | id_grade_modgrade_scale | My Scale |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment"
index d546fda..1f23958 100644 (file)
@@ -43,7 +43,7 @@ Feature: The activity results block displays student scores as scales
       | student6 | G3 |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I navigate to "Scales" node in "Grade administration"
     And I press "Add a new scale"
     And I set the following fields to these values:
@@ -60,7 +60,7 @@ Feature: The activity results block displays student scores as scales
       | id_grade_modgrade_scale | My Scale |
       | Group mode | Separate groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment"
index 27807f8..5e76326 100644 (file)
@@ -50,7 +50,7 @@ Feature: The activity results block displays student scores
       | assignsubmission_file_enabled | 0 |
       | Group mode | Separate groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "100.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "90.00" to the user "Student 2" for the grade item "Test assignment"
@@ -225,4 +225,4 @@ Feature: The activity results block displays student scores
     And I follow "Course 1"
     And I should see "User" in the "Activity results" "block"
     And I should see "100.00%" in the "Activity results" "block"
-    And I should see "90.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "90.00%" in the "Activity results" "block"
index f5fd1bd..1d4d8d4 100644 (file)
@@ -50,7 +50,7 @@ Feature: The activity results block displays student scores
       | assignsubmission_file_enabled | 0 |
       | Group mode | Visible groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "100.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "90.00" to the user "Student 2" for the grade item "Test assignment"
@@ -202,4 +202,4 @@ Feature: The activity results block displays student scores
     And I should see "Group" in the "Activity results" "block"
     And I should see "95.00%" in the "Activity results" "block"
     And I should see "85.00%" in the "Activity results" "block"
-    And I should see "75.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "75.00%" in the "Activity results" "block"
index c20061f..9290095 100644 (file)
@@ -32,7 +32,7 @@ Feature: The activity results block displays student scores
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "80.00" to the user "Student 2" for the grade item "Test assignment"
@@ -156,4 +156,4 @@ Feature: The activity results block displays student scores
     Then I should see "User" in the "Activity results" "block"
     And I should see "50.00%" in the "Activity results" "block"
     And I should see "60.00%" in the "Activity results" "block"
-    And I should see "70.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "70.00%" in the "Activity results" "block"
index 00f31f6..52c97bd 100644 (file)
@@ -26,7 +26,7 @@ Feature: The activity results block displays student scores as scales
       | student5 | C1 | student |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I navigate to "Scales" node in "Grade administration"
     And I press "Add a new scale"
     And I set the following fields to these values:
@@ -42,7 +42,7 @@ Feature: The activity results block displays student scores as scales
       | id_grade_modgrade_type | Scale |
       | id_grade_modgrade_scale | My Scale |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment"
index be5ae33..b16a339 100644 (file)
@@ -43,7 +43,7 @@ Feature: The activity results block displays student scores as scales
       | student6 | G3 |
     And I log in as "teacher1"
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I navigate to "Scales" node in "Grade administration"
     And I press "Add a new scale"
     And I set the following fields to these values:
@@ -60,7 +60,7 @@ Feature: The activity results block displays student scores as scales
       | id_grade_modgrade_scale | My Scale |
       | Group mode | Separate groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "Excellent!" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "Very good" to the user "Student 2" for the grade item "Test assignment"
index b5e0546..47c5f56 100644 (file)
@@ -50,7 +50,7 @@ Feature: The activity results block displays student scores
       | assignsubmission_file_enabled | 0 |
       | Group mode | Separate groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "100.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "90.00" to the user "Student 2" for the grade item "Test assignment"
@@ -217,4 +217,4 @@ Feature: The activity results block displays student scores
     And I follow "Course 1"
     And I should see "User" in the "Activity results" "block"
     And I should see "100.00%" in the "Activity results" "block"
-    And I should see "90.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "90.00%" in the "Activity results" "block"
index 7049958..0d0f224 100644 (file)
@@ -50,7 +50,7 @@ Feature: The activity results block displays student scores
       | assignsubmission_file_enabled | 0 |
       | Group mode | Visible groups |
     And I follow "Course 1"
-    And I navigate to "Grades" node in "Course administration"
+    And I click on "Grades" "link" in the "Navigation" "block"
     And I turn editing mode on
     And I give the grade "100.00" to the user "Student 1" for the grade item "Test assignment"
     And I give the grade "90.00" to the user "Student 2" for the grade item "Test assignment"
@@ -198,4 +198,4 @@ Feature: The activity results block displays student scores
     And I follow "Course 1"
     And I should see "Group" in the "Activity results" "block"
     And I should see "85.00%" in the "Activity results" "block"
-    And I should see "75.00%" in the "Activity results" "block"
\ No newline at end of file
+    And I should see "75.00%" in the "Activity results" "block"
index 7bfb063..6b8001f 100644 (file)
@@ -219,4 +219,4 @@ Feature: Enable Block blog menu in an activity
     And I press "Search"
     Then I should see "S1 First Blog"
     And I should see "S2 First Blog"
-    And I should not see "S2 Second Blog"
\ No newline at end of file
+    And I should not see "S2 Second Blog"
index d840c38..597cbe7 100644 (file)
@@ -25,7 +25,7 @@ Feature: Adding blog tag block
     And I turn editing mode on
     And I add the "Blog tags" block
 
-    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I navigate to "Course blogs" node in "My courses > c1 > Participants"
     And I follow "Blog about this Course"
     And I set the following fields to these values:
       | Entry title                                 | Blog post from teacher    |
@@ -35,7 +35,7 @@ Feature: Adding blog tag block
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
-    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I navigate to "Course blogs" node in "My courses > c1 > Participants"
     And I follow "Blog about this Course"
     And I set the following fields to these values:
       | Entry title                                 | Blog post from student    |
index ae09aa8..b7ef68e 100644 (file)
@@ -11,6 +11,8 @@ Feature: Enable the calendar block on the site front page
       | student1 | Student | 1 | student1@example.com | S1 |
     And I log in as "admin"
     And I am on site homepage
+    And I turn editing mode on
+    And I add the "Calendar" block
     And I create a calendar event with form data:
       | id_eventtype | Site |
       | id_name | Site Event |
index 4f3fa50..c427300 100644 (file)
@@ -28,13 +28,13 @@ Feature: Enable the course_list block on a category page and view it's contents
   Scenario: Add the course list block on category page and navigate to the course listing
     Given I log in as "admin"
     And I am on site homepage
-    And I navigate to "Turn editing on" node in "Front page settings"
-    And I follow "Course 1"
+    And I turn editing mode on
+    And I am on course index
     And I follow "Miscellaneous"
     And I add the "Courses" block
     And I log out
     When I log in as "teacher1"
-    And I follow "Course 1"
+    And I am on course index
     And I follow "Miscellaneous"
     Then I should see "Course 1" in the "My courses" "block"
     And I should see "Course 2" in the "My courses" "block"
@@ -46,13 +46,13 @@ Feature: Enable the course_list block on a category page and view it's contents
   Scenario: Add the course list block on category page and navigate to another course
     Given I log in as "admin"
     And I am on site homepage
-    And I navigate to "Turn editing on" node in "Front page settings"
-    And I follow "Course 1"
+    And I turn editing mode on
+    And I am on course index
     And I follow "Miscellaneous"
     And I add the "Courses" block
     And I log out
     When I log in as "teacher1"
-    And I follow "Course 1"
+    And I am on course index
     And I follow "Miscellaneous"
     Then I should see "Course 1" in the "My courses" "block"
     And I should see "Course 2" in the "My courses" "block"
@@ -64,8 +64,8 @@ Feature: Enable the course_list block on a category page and view it's contents
   Scenario: Add the course list block on category page and view as an admin
     Given I log in as "admin"
     And I am on site homepage
-    And I navigate to "Turn editing on" node in "Front page settings"
-    And I follow "Course 1"
+    And I turn editing mode on
+    And I am on course index
     And I follow "Miscellaneous"
     When I add the "Courses" block
     Then I should see "Miscellaneous" in the "Course categories" "block"
index e8980b0..6299f59 100644 (file)
@@ -7,6 +7,8 @@ Feature: Course summary block used on the frontpage
   Background:
     Given I log in as "admin"
     And I am on site homepage
+    And I turn editing mode on
+    And I add the "Course/site summary" block
     And I navigate to "Edit settings" node in "Front page settings"
     And I set the following fields to these values:
       | summary | Proved the summary block works! |
diff --git a/blocks/navigation/tests/behat/expand_my_courses_setting.feature b/blocks/navigation/tests/behat/expand_my_courses_setting.feature
deleted file mode 100644 (file)
index bfba988..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-@block @block_navigation
-Feature: Test expand my courses navigation setting
-  As a student
-  I visit my My Moodle page and observe the the My Courses branch
-
-  Background:
-    Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | student1 | Student | 1 | student1@example.com |
-    And the following "categories" exist:
-      | name  | category | idnumber |
-      | cat1  | 0        | cat1     |
-    And the following "courses" exist:
-      | fullname | shortname | category |
-      | Course1  | c1        | cat1     |
-      | Course2  | c2        | cat1     |
-      | Course3  | c3        | cat1     |
-    And the following "course enrolments" exist:
-      | user     | course | role    |
-      | student1 | c1     | student |
-      | student1 | c2    | student |
-
-  Scenario: The My Courses branch is expanded on the My Moodle page by default
-    When I log in as "student1"
-    And I click on "Dashboard" "link" in the "Navigation" "block"
-    Then I should see "c1" in the "Navigation" "block"
-    And I should see "c2" in the "Navigation" "block"
-    And I should not see "c3" in the "Navigation" "block"
-
-  @javascript
-  Scenario: The My Courses branch is collapsed when expand my courses is off
-    Given I log in as "admin"
-    And I set the following administration settings values:
-      | Show My courses expanded on Dashboard | 0 |
-    And I log out
-    When I log in as "student1"
-    And I click on "Dashboard" "link" in the "Navigation" "block"
-    Then I should not see "c1" in the "Navigation" "block"
-    And I should not see "c2" in the "Navigation" "block"
-    And I should not see "c3" in the "Navigation" "block"
-
-  @javascript
-  Scenario: My Courses can be expanded on the My Moodle page when expand my courses is off
-    Given I log in as "admin"
-    And I set the following administration settings values:
-      | Show My courses expanded on Dashboard | 0 |
-    And I log out
-    When I log in as "student1"
-    And I click on "Dashboard" "link" in the "Navigation" "block"
-    And I should not see "c1" in the "Navigation" "block"
-    And I should not see "c2" in the "Navigation" "block"
-    And I should not see "c3" in the "Navigation" "block"
-    And I expand "My courses" node
-    Then I should see "c1" in the "Navigation" "block"
-    And I should see "c2" in the "Navigation" "block"
-    And I should not see "c3" in the "Navigation" "block"
index ecac0bd..a899073 100644 (file)
@@ -18,6 +18,9 @@ Feature: Latest announcements block displays the course latest news
     And I log out
     And I log in as "teacher1"
     And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Latest announcements" block
+    And I turn editing mode off
     When I add a new topic to "Announcements" forum with:
       | Subject | Discussion One |
       | Message | Not important |
@@ -31,14 +34,14 @@ Feature: Latest announcements block displays the course latest news
     Then I should see "Discussion One" in the "Latest announcements" "block"
     And I should see "Discussion Two" in the "Latest announcements" "block"
     And I should see "Discussion Three" in the "Latest announcements" "block"
-    And I follow "Edit settings"
+    And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | News items to show | 2 |
     And I press "Save and display"
     And I should not see "Discussion One" in the "Latest announcements" "block"
     And I should see "Discussion Two" in the "Latest announcements" "block"
     And I should see "Discussion Three" in the "Latest announcements" "block"
-    And I follow "Edit settings"
+    And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
       | News items to show | 0 |
     And I press "Save and display"
index 380ea1e..d66978a 100644 (file)
@@ -49,6 +49,7 @@ Feature: View structural changes in recent activity block
     Given I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
+    And I add the "Recent activity" block
     When I add a "Forum" to section "1" and I fill the form with:
       | name        | ForumVisibleGroups |
       | Description | No description     |
@@ -145,6 +146,7 @@ Feature: View structural changes in recent activity block
     When I log in as "teacher1"
     And I follow "Course 1"
     And I turn editing mode on
+    And I add the "Recent activity" block
     And I add a "Forum" to section "1" and I fill the form with:
       | name        | ForumNew       |
       | Description | No description |
index 48d164d..0f54b4b 100644 (file)
@@ -21,6 +21,9 @@ Feature: The search forums block allows users to search for forum posts
     And I navigate to "Edit settings" node in "Course administration"
     And I set the field "id_newsitems" to "1"
     And I press "Save and display"
+    And I turn editing mode on
+    And I add the "Latest announcements" block
+    And I add the "Search forums" block
     And I log out
 
   Scenario: Use the search forum block in a course without any forum posts
index f391d7b..62054a0 100644 (file)
@@ -9,6 +9,7 @@ Feature: Add URL to main menu block
     Given I log in as "admin"
     And I am on site homepage
     And I navigate to "Turn editing on" node in "Front page settings"
+    And I add the "Main menu" block
     When I add a "URL" to section "0" and I fill the form with:
       | Name | google |
       | Description | gooooooooogle |
index dc73b9d..4799495 100644 (file)
@@ -9,6 +9,7 @@ Feature: Edit activities in main menu block
     Given I log in as "admin"
     And I am on site homepage
     And I navigate to "Turn editing on" node in "Front page settings"
+    And I add the "Main menu" block
     When I add a "Forum" to section "0" and I fill the form with:
       | Forum name | My forum name |
     And I click on "Edit title" "link" in the "//*[contains(@class,'block_site_main_menu')]//li[contains(.,'My forum name')]" "xpath_element"
index 07b58e3..ca391d3 100644 (file)
@@ -18,6 +18,7 @@ Feature: Edit activities in social activities block
     Given I log in as "user1"
     And I follow "Course 1"
     And I turn editing mode on
+    And I add the "Social activities" block
     And I set the field "Add an activity to section 'section 0'" to "Forum"
     And I set the field "Forum name" to "My forum name"
     And I press "Save and return to course"
index 36e1bc0..9c7ee6b 100644 (file)
@@ -31,7 +31,7 @@ Feature: Block tags displaying tag cloud
     And I should see "Cats" in the "Tags" "block"
     And I should not see "Neverusedtag" in the "Tags" "block"
     And I click on "Dogs" "link" in the "Tags" "block"
-    And I should see "Log in to the site" in the ".breadcrumb" "css_element"
+    And I should see "You are not logged in"
 
   Scenario: Add Tags block in a course
     When I log in as "teacher1"
index 9498021..db03ce2 100644 (file)
@@ -58,6 +58,7 @@ Feature: Add and configure blocks throughout the site
     Given I log in as "teacher1"
     And I follow "Course 1"
     And I follow "Turn editing on"
+    And I add the "Search forums" block
     Then I should see "Assign roles in Search forums block"
 
   @javascript
index bd3a6c4..6abadca 100644 (file)
@@ -14,6 +14,10 @@ Feature: Show hidden blocks in a docked block region when editing
     And I log in as "admin"
     And I follow "Course 1"
     And I turn editing mode on
+    And I add the "Search forums" block
+    And I add the "Latest announcements" block
+    And I add the "Upcoming events" block
+    And I add the "Recent activity" block
     # Hide all the blocks in the non-default region
     And I configure the "Search forums" block
     And I set the following fields to these values:
index 43ae149..c83bd02 100644 (file)
@@ -11,18 +11,19 @@ Feature: Block visibility
     And I log in as "admin"
     And I am on site homepage
     And I follow "Course 1"
-    And I follow "Turn editing on"
+    And I turn editing mode on
 
   @javascript
   Scenario: Hiding all blocks on the page should remove the column they're in
-    Given I open the "Search forums" blocks action menu
+    Given I add the "Search forums" block
+    And I open the "Search forums" blocks action menu
+    And I click on "Configure Search forums block" "link" in the "Search forums" "block"
+    And I set the field "Region" to "Right"
+    And I press "Save changes"
+    And I turn editing mode off
+    And ".empty-region-side-post" "css_element" should not exist in the "body" "css_element"
+    And I turn editing mode on
+    And I open the "Search forums" blocks action menu
     And I click on "Hide Search forums block" "link" in the "Search forums" "block"
-    And I open the "Latest announcements" blocks action menu
-    And I click on "Hide Latest announcements block" "link" in the "Latest announcements" "block"
-    And I open the "Upcoming events" blocks action menu
-    And I click on "Hide Upcoming events block" "link" in the "Upcoming events" "block"
-    And I open the "Recent activity" blocks action menu
-    When I click on "Hide Recent activity block" "link" in the "Recent activity" "block"
-    Then ".empty-region-side-post" "css_element" should not exist in the "body" "css_element"
     And I follow "Turn editing off"
     And ".empty-region-side-post" "css_element" should exist in the "body" "css_element"
index 8b43a40..931eb3f 100644 (file)
@@ -14,7 +14,7 @@ Feature: The context of a block can always be returned to it's original state.
     And I follow "Turn editing on"
     And I add the "Tags" block
     Then I should see "Tags" in the "Tags" "block"
-    And I click on "Participants" "link" in the "//li[p/span[contains(normalize-space(string(.)), 'Current course')]]" "xpath_element"
+    And I click on "Participants" "link" in the "//li[p/a[contains(normalize-space(string(.)), 'Courses')]]" "xpath_element"
     And I configure the "Tags" block
     And I set the following fields to these values:
       | Display on page types | Any page |
@@ -31,7 +31,7 @@ Feature: The context of a block can always be returned to it's original state.
     And I should see "Tags" in the "Tags" "block"
     And I follow "Course 1"
     And "Tags" "block" should not exist
-    And I click on "Participants" "link" in the "//li[p/span[contains(normalize-space(string(.)), 'Current course')]]" "xpath_element"
+    And I navigate to "Participants" node in "Courses > C1"
     And "Tags" "block" should not exist
     And I follow "Course 1"
     And I add a "Assignment" to section "1" and I fill the form with:
@@ -45,5 +45,5 @@ Feature: The context of a block can always be returned to it's original state.
     And I press "Save changes"
     And I follow "Course 1"
     And I should see "Tags" in the "Tags" "block"
-    And I click on "Participants" "link" in the "//li[p/span[contains(normalize-space(string(.)), 'Current course')]]" "xpath_element"
+    And I navigate to "Participants" node in "Courses > C1"
     And I should see "Tags" in the "Tags" "block"
index 2612fea..7a0adc6 100644 (file)
@@ -386,6 +386,9 @@ class cache implements cache_loader {
         $isusingpersist = $this->use_static_acceleration();
         foreach ($keys as $key) {
             $pkey = $this->parse_key($key);
+            if (is_array($pkey)) {
+                $pkey = $pkey['key'];
+            }
             $keysparsed[$key] = $pkey;
             $parsedkeys[$pkey] = $key;
             $keystofind[$pkey] = $key;
index 5f7de85..d1f6f71 100644 (file)
@@ -106,18 +106,17 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      */
     protected $store;
 
-    /**
-     * The ttl if there is one. Hopefully not.
-     * @var int
-     */
-    protected $ttl = 0;
-
     /**
      * The maximum size for the store, or false if there isn't one.
      * @var bool
      */
     protected $maxsize = false;
 
+    /**
+     * Where this cache uses simpledata and we don't need to serialize it.
+     * @var bool
+     */
+    protected $simpledata = false;
     /**
      * The number of items currently being stored.
      * @var int
@@ -146,18 +145,20 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
     public static function get_supported_features(array $configuration = array()) {
         return self::SUPPORTS_DATA_GUARANTEE +
                self::SUPPORTS_NATIVE_TTL +
-               self::IS_SEARCHABLE;
+               self::IS_SEARCHABLE +
+               self::SUPPORTS_MULTIPLE_IDENTIFIERS +
+               self::DEREFERENCES_OBJECTS;
     }
 
     /**
-     * Returns false as this store does not support multiple identifiers.
+     * Returns true as this store does support multiple identifiers.
      * (This optional function is a performance optimisation; it must be
      * consistent with the value from get_supported_features.)
      *
-     * @return bool False
+     * @return bool true
      */
     public function supports_multiple_identifiers() {
-        return false;
+        return true;
     }
 
     /**
@@ -197,10 +198,11 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @param cache_definition $definition
      */
     public function initialise(cache_definition $definition) {
-        $this->storeid = $definition->generate_definition_hash();
+        $keyarray = $definition->generate_multi_key_parts();
+        $this->storeid = $keyarray['mode'].'/'.$keyarray['component'].'/'.$keyarray['area'].'/'.$keyarray['siteidentifier'];
         $this->store = &self::register_store_id($this->storeid);
-        $this->ttl = $definition->get_ttl();
         $maxsize = $definition->get_maxsize();
+        $this->simpledata = $definition->uses_simple_data();
         if ($maxsize !== null) {
             // Must be a positive int.
             $this->maxsize = abs((int)$maxsize);
@@ -224,11 +226,16 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return mixed The data that was associated with the key, or false if the key did not exist.
      */
     public function get($key) {
+        if (!is_array($key)) {
+            $key = array('key' => $key);
+        }
+
+        $key = $key['key'];
         if (isset($this->store[$key])) {
-            if ($this->ttl == 0) {
-                return $this->store[$key][0];
-            } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
-                return $this->store[$key][0];
+            if ($this->store[$key]['serialized']) {
+                return unserialize($this->store[$key]['data']);
+            } else {
+                return $this->store[$key]['data'];
             }
         }
         return false;
@@ -245,17 +252,18 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      */
     public function get_many($keys) {
         $return = array();
-        if ($this->ttl != 0) {
-            $maxtime = cache::now() - $this->ttl;
-        }
 
         foreach ($keys as $key) {
+            if (!is_array($key)) {
+                $key = array('key' => $key);
+            }
+            $key = $key['key'];
             $return[$key] = false;
             if (isset($this->store[$key])) {
-                if ($this->ttl == 0) {
-                    $return[$key] = $this->store[$key][0];
-                } else if ($this->store[$key][1] >= $maxtime) {
-                    $return[$key] = $this->store[$key][0];
+                if ($this->store[$key]['serialized']) {
+                    $return[$key] = unserialize($this->store[$key]['data']);
+                } else {
+                    $return[$key] = $this->store[$key]['data'];
                 }
             }
         }
@@ -271,15 +279,23 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return bool True if the operation was a success false otherwise.
      */
     public function set($key, $data, $testmaxsize = true) {
+        if (!is_array($key)) {
+            $key = array('key' => $key);
+        }
+        $key = $key['key'];
         $testmaxsize = ($testmaxsize && $this->maxsize !== false);
         if ($testmaxsize) {
             $increment = (!isset($this->store[$key]));
         }
-        if ($this->ttl == 0) {
-            $this->store[$key][0] = $data;
+
+        if ($this->simpledata || is_scalar($data)) {
+            $this->store[$key]['data'] = $data;
+            $this->store[$key]['serialized'] = false;
         } else {
-            $this->store[$key] = array($data, cache::now());
+            $this->store[$key]['data'] = serialize($data);
+            $this->store[$key]['serialized'] = true;
         }
+
         if ($testmaxsize && $increment) {
             $this->storecount++;
             if ($this->storecount > $this->maxsize) {
@@ -300,8 +316,11 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
     public function set_many(array $keyvaluearray) {
         $count = 0;
         foreach ($keyvaluearray as $pair) {
+            if (!is_array($pair['key'])) {
+                $pair['key'] = array('key' => $pair['key']);
+            }
             // Don't test the maxsize here. We'll do it once when we are done.
-            $this->set($pair['key'], $pair['value'], false);
+            $this->set($pair['key']['key'], $pair['value'], false);
             $count++;
         }
         if ($this->maxsize !== false) {
@@ -320,14 +339,10 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return bool
      */
     public function has($key) {
-        if (isset($this->store[$key])) {
-            if ($this->ttl == 0) {
-                return true;
-            } else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
-                return true;
-            }
+        if (is_array($key)) {
+            $key = $key['key'];
         }
-        return false;
+        return isset($this->store[$key]);
     }
 
     /**
@@ -337,15 +352,12 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return bool
      */
     public function has_all(array $keys) {
-        if ($this->ttl != 0) {
-            $maxtime = cache::now() - $this->ttl;
-        }
-
         foreach ($keys as $key) {
-            if (!isset($this->store[$key])) {
-                return false;
+            if (!is_array($key)) {
+                $key = array('key' => $key);
             }
-            if ($this->ttl != 0 && $this->store[$key][1] < $maxtime) {
+            $key = $key['key'];
+            if (!isset($this->store[$key])) {
                 return false;
             }
         }
@@ -359,12 +371,13 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return bool
      */
     public function has_any(array $keys) {
-        if ($this->ttl != 0) {
-            $maxtime = cache::now() - $this->ttl;
-        }
-
         foreach ($keys as $key) {
-            if (isset($this->store[$key]) && ($this->ttl == 0 || $this->store[$key][1] >= $maxtime)) {
+            if (!is_array($key)) {
+                $key = array('key' => $key);
+            }
+            $key = $key['key'];
+
+            if (isset($this->store[$key])) {
                 return true;
             }
         }
@@ -378,6 +391,10 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @return bool Returns true if the operation was a success, false otherwise.
      */
     public function delete($key) {
+        if (!is_array($key)) {
+            $key = array('key' => $key);
+        }
+        $key = $key['key'];
         $result = isset($this->store[$key]);
         unset($this->store[$key]);
         if ($this->maxsize !== false) {
@@ -395,6 +412,10 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
     public function delete_many(array $keys) {
         $count = 0;
         foreach ($keys as $key) {
+            if (!is_array($key)) {
+                $key = array('key' => $key);
+            }
+            $key = $key['key'];
             if (isset($this->store[$key])) {
                 $count++;
             }
index 7b05b2e..1bdf7d7 100644 (file)
@@ -536,6 +536,11 @@ class cache_phpunit_factory extends cache_factory {
         parent::disable();
     }
 
+    /**
+     * @var bool Whether the warning notice about alternative cache store used has been displayed.
+     */
+    protected $altcachestorenotice = false;
+
     /**
      * Creates a store instance given its name and configuration.
      *
@@ -559,7 +564,15 @@ class cache_phpunit_factory extends cache_factory {
                     return $instance;
                 }
             }
+
+            // Notify user that alternative store is being used, so action can be taken.
+            if (!$this->altcachestorenotice) {
+                echo PHP_EOL . "++ WARNING: " . 'Failed to use "' . $details['plugin'] . '" cache store, alt "' .
+                    $details['alt']['plugin'] . '" cache store is used.' . PHP_EOL . PHP_EOL;
+                $this->altcachestorenotice = true;
+            }
             $details = $details['alt'];
+            $details['class'] = 'cachestore_'.$details['plugin'];
             $name = $details['name'];
         }
 
index 8d4f11c..1dbe75a 100644 (file)
@@ -85,6 +85,8 @@ abstract class cachestore_tests extends advanced_testcase {
      * Test the store for basic functionality.
      */
     public function run_tests(cache_store $instance) {
+        $object = new stdClass;
+        $object->data = 1;
 
         // Test set with a string.
         $this->assertTrue($instance->set('test1', 'test1'));
@@ -113,6 +115,13 @@ abstract class cachestore_tests extends advanced_testcase {
         $this->assertSame(true, $instance->get('test1'));
         $this->assertInternalType('boolean', $instance->get('test1'));
 
+        // Test with an object.
+        $this->assertTrue($instance->set('obj', $object));
+        if ($instance::get_supported_features() & cache_store::DEREFERENCES_OBJECTS) {
+            $this->assertNotSame($object, $instance->get('obj'), 'Objects must be dereferenced when returned.');
+        }
+        $this->assertEquals($object, $instance->get('obj'));
+
         // Test delete.
         $this->assertTrue($instance->delete('test1'));
         $this->assertTrue($instance->delete('test3'));
index 449574f..9c6c046 100644 (file)
@@ -20,6 +20,7 @@ Feature: Limit displayed upcoming events
     Given I follow "C1"
     And I turn editing mode on
     And I add the "Calendar" block
+    And I add the "Upcoming events" block
     And I follow "This month"
     And I click on "a.next" "css_element"
     And I click on "a.next" "css_element"
index 7d8ccdb..3d89faf 100644 (file)
@@ -32,7 +32,7 @@ Feature: Add cohorts of users
     When I add "First User (first@example.com)" user to "333" cohort members
     And I add "Second User (second@example.com)" user to "333" cohort members
     Then I should see "2" in the "#cohorts" "css_element"
-    And I follow "Assign"
+    And I click on "Assign" "link" in the "Test cohort name" "table_row"
     And the "Current users" select box should contain "First User (first@example.com)"
     And the "Current users" select box should contain "Second User (second@example.com)"
     And the "Current users" select box should not contain "Forth User (forth@example.com)"
@@ -49,7 +49,7 @@ Feature: Add cohorts of users
     And I press "Add to cohort"
     And I follow "Cohorts"
     Then I should see "2" in the "#cohorts" "css_element"
-    And I follow "Assign"
+    And I click on "Assign" "link" in the "Test cohort name" "table_row"
     And the "Current users" select box should contain "Third User (third@example.com)"
     And the "Current users" select box should contain "Forth User (forth@example.com)"
     And the "Current users" select box should not contain "First User (first@example.com)"
index 14a4965..a42d2b0 100644 (file)
@@ -217,4 +217,50 @@ class comment_manager {
         }
         return true;
     }
+
+    /**
+     * Get comments created since a given time.
+     *
+     * @param  stdClass $course    course object
+     * @param  stdClass $context   context object
+     * @param  string $component   component name
+     * @param  int $since          the time to check
+     * @param  stdClass $cm        course module object
+     * @return array list of comments db records since the given timelimit
+     * @since Moodle 3.2
+     */
+    public function get_component_comments_since($course, $context, $component, $since, $cm = null) {
+        global $DB;
+
+        $commentssince = array();
+        $where = 'contextid = ? AND component = ? AND timecreated > ?';
+        $comments = $DB->get_records_select('comments', $where, array($context->id, $component, $since));
+        // Check item by item if we have permissions.
+        $managersviewstatus = array();
+        foreach ($comments as $comment) {
+            // Check if the manager for the item is cached.
+            if (!isset($managersviewstatus[$comment->commentarea]) or
+                    !isset($managersviewstatus[$comment->commentarea][$comment->itemid])) {
+
+                $args = new stdClass;
+                $args->area      = $comment->commentarea;
+                $args->itemid    = $comment->itemid;
+                $args->context   = $context;
+                $args->course    = $course;
+                $args->client_id = 0;
+                $args->component = $component;
+                if (!empty($cm)) {
+                    $args->cm = $cm;
+                }
+
+                $manager = new comment($args);
+                $managersviewstatus[$comment->commentarea][$comment->itemid] = $manager->can_view();
+            }
+
+            if ($managersviewstatus[$comment->commentarea][$comment->itemid]) {
+                $commentssince[$comment->id] = $comment;
+            }
+        }
+        return $commentssince;
+    }
 }
index ebcedc0..61ef537 100644 (file)
@@ -116,7 +116,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
         $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 
         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
-        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 
@@ -220,7 +220,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
         $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 
         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
-        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 
diff --git a/course/admin.php b/course/admin.php
new file mode 100644 (file)
index 0000000..959101b
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Listing of the course administration pages for this course.
+ *
+ * @copyright 2016 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once("../config.php");
+
+$courseid = required_param('courseid', PARAM_INT);
+
+$PAGE->set_url('/course/admin.php', array('courseid'=>$courseid));
+
+$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+
+require_login($course);
+$context = context_course::instance($course->id);
+
+$PAGE->set_pagelayout('incourse');
+
+if ($courseid == $SITE->id) {
+    $title = get_string('frontpagesettings');
+    $node = $PAGE->settingsnav->find('frontpage', navigation_node::TYPE_SETTING);
+} else {
+    $title = get_string('courseadministration');
+    $node = $PAGE->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
+}
+$PAGE->set_title($title);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add($title);
+echo $OUTPUT->header();
+echo $OUTPUT->heading($title);
+
+if ($node) {
+    echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node]);
+}
+
+echo $OUTPUT->footer();
index 7d3a96f..999f71a 100644 (file)
@@ -2492,10 +2492,12 @@ class core_course_external extends external_api {
                         $info->outcomes = array();
                     }
                     $id = str_replace('outcome_', '', $key);
-                    $outcome = $outcome = grade_outcome::fetch(array('id' => $id));
+                    $outcome = grade_outcome::fetch(array('id' => $id));
+                    $scaleitems = $outcome->load_scale();
                     $info->outcomes[] = array(
                         'id' => $id,
-                        'name' => external_format_string($outcome->get_name(), $context->id)
+                        'name' => external_format_string($outcome->get_name(), $context->id),
+                        'scale' => $scaleitems->scale
                     );
                 }
             }
@@ -2572,6 +2574,7 @@ class core_course_external extends external_api {
                                 array(
                                     'id' => new external_value(PARAM_ALPHANUMEXT, 'Outcome id'),
                                     'name'  => new external_value(PARAM_TEXT, 'Outcome full name'),
+                                    'scale' => new external_value(PARAM_TEXT, 'Scale items')
                                 )
                             ),
                             'Outcomes information', VALUE_OPTIONAL