From fe285d54964160c659e2ee257137b55afb9fafac Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Thu, 8 Apr 2021 12:37:16 +0800 Subject: [PATCH 01/50] MDL-71148 core_navigation: Provide a menu header in a system context. - Part of: MDL-69588 --- lang/en/moodle.php | 1 + lib/classes/navigation/views/secondary.php | 1 + lib/tests/navigation/views/secondary_test.php | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 318803f28d5..22297f8ae56 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1007,6 +1007,7 @@ $string['hits'] = 'Hits'; $string['hitsoncourse'] = 'Hits on {$a->coursename} by {$a->username}'; $string['hitsoncoursetoday'] = 'Today\'s hits on {$a->coursename} by {$a->username}'; $string['home'] = 'Home'; +$string['homeheader'] = 'Home menu'; $string['hour'] = 'hour'; $string['hours'] = 'hours'; $string['howtomakethemes'] = 'How to make new themes'; diff --git a/lib/classes/navigation/views/secondary.php b/lib/classes/navigation/views/secondary.php index 918bac3f655..266680b2084 100644 --- a/lib/classes/navigation/views/secondary.php +++ b/lib/classes/navigation/views/secondary.php @@ -132,6 +132,7 @@ class secondary extends view { $this->load_module_navigation(); break; case CONTEXT_SYSTEM: + $this->headertitle = get_string('homeheader'); $this->load_admin_navigation(); break; } diff --git a/lib/tests/navigation/views/secondary_test.php b/lib/tests/navigation/views/secondary_test.php index fa5ef8d85a8..55a25a414de 100644 --- a/lib/tests/navigation/views/secondary_test.php +++ b/lib/tests/navigation/views/secondary_test.php @@ -135,7 +135,7 @@ class secondary_test extends \advanced_testcase { return [ 'Testing in a course context' => ['course', 'coursehome', 'courseheader', 'Course page'], 'Testing in a module context' => ['module', 'modulepage', 'activityheader', 'Activity'], - 'Testing in a site admin' => ['system', 'siteadminnode', 'menu', 'Site administration'], + 'Testing in a site admin' => ['system', 'siteadminnode', 'homeheader', 'Site administration'], ]; } } From 25e178aa5998dca25c6f8a15a5dd7c0d0f20a83f Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Wed, 31 Mar 2021 12:41:23 +0800 Subject: [PATCH 02/50] MDL-71148 core_navigation: Move unauthenticated user checks to the lib - Part of: MDL-69588 Added conditional information in user_get_user_navigation_info for when a user is unauthenticated. --- lib/outputrenderers.php | 47 +++++++++++++------------------------ user/lib.php | 10 ++++++++ user/tests/userlib_test.php | 1 + 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 69677c274f6..09c0fb748a2 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3321,41 +3321,26 @@ EOD; $loginpage = $this->is_login_page(); $loginurl = get_login_url(); - // If not logged in, show the typical not-logged-in string. - if (!isloggedin()) { - $returnstr = get_string('loggedinnot', 'moodle'); - if (!$loginpage) { - $returnstr .= " (" . get_string('login') . ')'; - } - return html_writer::div( - html_writer::span( - $returnstr, - 'login nav-link' - ), - $usermenuclasses - ); - - } - - // If logged in as a guest user, show a string to that effect. - if (isguestuser()) { - $returnstr = get_string('loggedinasguest'); - if (!$loginpage && $withlinks) { - $returnstr .= " (".get_string('login').')'; - } - - return html_writer::div( - html_writer::span( - $returnstr, - 'login nav-link' - ), - $usermenuclasses - ); - } // Get some navigation opts. $opts = user_get_user_navigation_info($user, $this->page); + if (!empty($opts->unauthenticateduser)) { + $returnstr = get_string($opts->unauthenticateduser['content'], 'moodle'); + // If not logged in, show the typical not-logged-in string. + if (!$loginpage && (!$opts->unauthenticateduser['guest'] || $withlinks)) { + $returnstr .= " (" . get_string('login') . ')'; + } + + return html_writer::div( + html_writer::span( + $returnstr, + 'login nav-link' + ), + $usermenuclasses + ); + } + $avatarclasses = "avatars"; $avatarcontents = html_writer::span($opts->metadata['useravatar'], 'avatar current'); $usertextcontents = $opts->metadata['userfullname']; diff --git a/user/lib.php b/user/lib.php index 6332648c790..85e5cf3a679 100644 --- a/user/lib.php +++ b/user/lib.php @@ -823,6 +823,16 @@ function user_get_user_navigation_info($user, $page, $options = array()) { $returnobject->navitems = array(); $returnobject->metadata = array(); + $guest = isguestuser(); + if (!isloggedin() || $guest) { + $returnobject->unauthenticateduser = [ + 'guest' => $guest, + 'content' => $guest ? 'loggedinasguest' : 'loggedinnot', + ]; + + return $returnobject; + } + $course = $page->course; // Query the environment. diff --git a/user/tests/userlib_test.php b/user/tests/userlib_test.php index aaca2547d21..68a383fb4a2 100644 --- a/user/tests/userlib_test.php +++ b/user/tests/userlib_test.php @@ -583,6 +583,7 @@ class core_userliblib_testcase extends advanced_testcase { $PAGE->set_url('/'); $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); $opts = user_get_user_navigation_info($user, $PAGE, array('avatarsize' => $testsize)); $avatarhtml = $opts->metadata['useravatar']; From 6462e651e4c1dd26c953a063910a84840732f6ad Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Wed, 24 Mar 2021 10:07:47 +0800 Subject: [PATCH 03/50] MDL-71148 core_navigation: New renderer to combine nav components - Part of: MDL-69588 --- lib/classes/navigation/output/primary.php | 153 ++++++++++++++++++++++ lib/pagelib.php | 19 +++ 2 files changed, 172 insertions(+) create mode 100644 lib/classes/navigation/output/primary.php diff --git a/lib/classes/navigation/output/primary.php b/lib/classes/navigation/output/primary.php new file mode 100644 index 00000000000..fd1d92f4806 --- /dev/null +++ b/lib/classes/navigation/output/primary.php @@ -0,0 +1,153 @@ +. + +namespace core\navigation\output; + +use renderable; +use renderer_base; +use templatable; +use custom_menu; + +/** + * Primary navigation renderable + * + * This file combines primary nav, custom menu, lang menu and + * usermenu into a standardized format for the frontend + * + * @package core + * @category navigation + * @copyright 2021 onwards Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class primary implements renderable, templatable { + /** @var moodle_page $page the moodle page that the navigation belongs to */ + private $page = null; + + /** + * primary constructor. + * @param \moodle_page $page + */ + public function __construct($page) { + $this->page = $page; + } + + /** + * Combine the various menus into a standardized output. + * + * @param renderer_base|null $output + * @return array + */ + public function export_for_template(?renderer_base $output = null): array { + if (!$output) { + $output = $this->page->get_renderer('core'); + } + + return [ + 'primary' => $this->get_primary_nav(), + 'custom' => $this->get_custom_menu($output), + 'lang' => $this->get_lang_menu($output), + 'user' => $this->get_user_menu(), + ]; + } + + /** + * Get the primary nav object and standardize the output + * + * @return array + */ + protected function get_primary_nav(): array { + $nodes = []; + foreach ($this->page->primarynav->children as $node) { + $nodes[] = [ + 'title' => $node->get_title(), + 'url' => $node->action(), + 'text' => $node->text, + 'icon' => $node->icon, + 'isactive' => $node->isactive, + ]; + } + + return $nodes; + } + + /** + * Custom menu items reside on the same level as the original nodes. + * Fetch and convert the nodes to a standardised array. + * + * @param renderer_base $output + * @return array + */ + protected function get_custom_menu(renderer_base $output): array { + global $CFG; + + // Early return if a custom menu does not exists. + if (empty($CFG->custommenuitems)) { + return []; + } + + $custommenuitems = $CFG->custommenuitems; + $currentlang = current_language(); + $custommenunodes = custom_menu::convert_text_to_menu_nodes($custommenuitems, $currentlang); + $nodes = []; + foreach ($custommenunodes as $node) { + $nodes[] = $node->export_for_template($output); + } + + return $nodes; + } + + /** + * Get a list of options for the lang picker. + * + * @param renderer_base $output + * @return array + */ + protected function get_lang_menu(renderer_base $output): array { + // Early return if a lang menu does not exists. + if (empty($output->lang_menu())) { + return []; + } + + $currentlang = current_language(); + $langs = get_string_manager()->get_list_of_translations(); + $nodes = []; + + // Add the lang picker if needed. + foreach ($langs as $langtype => $langname) { + $isactive = $langtype == $currentlang; + $node = [ + 'title' => $langname, + 'text' => $langname, + 'isactive' => $isactive, + 'url' => $isactive ? new \moodle_url('#') : new \moodle_url($this->page->url, ['lang' => $langtype]), + ]; + + $nodes[] = $node; + } + + return $nodes; + } + + /** + * Get/Generate the user menu + * + * @return array + */ + public function get_user_menu(): array { + // Empty stub to add to. + return []; + } +} diff --git a/lib/pagelib.php b/lib/pagelib.php index 2f08c914f6d..b12b668cc90 100644 --- a/lib/pagelib.php +++ b/lib/pagelib.php @@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die(); use core\navigation\views\primary; use core\navigation\views\secondary; +use core\navigation\output\primary as primaryoutput; /** * $PAGE is a central store of information about the current page we are @@ -84,6 +85,7 @@ use core\navigation\views\secondary; * @property-read secondary $secondarynav The secondary navigation object * used to display the secondarynav in boost * @property-read primary $primarynav The primary navigation object used to display the primary nav in boost + * @property-read primaryoutput $primarynavcombined The primary navigation object used to display the primary nav in boost * @property-read global_navigation $navigation The navigation structure for this page. * @property-read xhtml_container_stack $opencontainers Tracks XHTML tags on this page that have been opened but not closed. * mainly for internal use by the rendering code. @@ -311,6 +313,12 @@ class moodle_page { */ protected $_primarynav = null; + /** + * @var primaryoutput Contains the combined nav nodes that will appear + * in the primary navigation. Includes - primarynav, langmenu, usermenu + */ + protected $_primarynavcombined = null; + /** * @var navbar Contains the navbar structure. */ @@ -824,6 +832,17 @@ class moodle_page { return $this->_primarynav; } + /** + * Returns the primary navigation object + * @return primary + */ + protected function magic_get_primarynavcombined() { + if ($this->_primarynavcombined === null) { + $this->_primarynavcombined = new primaryoutput($this); + } + return $this->_primarynavcombined; + } + /** * Returns request IP address. * From 8782702d34e7f618ad1ba2ad224e6218684eb56f Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Wed, 7 Apr 2021 13:54:27 +0800 Subject: [PATCH 04/50] MDL-71148 core_navigation: Provide user menu as nodes - Part of: MDL-69588 --- lib/classes/navigation/output/primary.php | 77 ++++++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/lib/classes/navigation/output/primary.php b/lib/classes/navigation/output/primary.php index fd1d92f4806..b03e528f102 100644 --- a/lib/classes/navigation/output/primary.php +++ b/lib/classes/navigation/output/primary.php @@ -142,12 +142,83 @@ class primary implements renderable, templatable { } /** - * Get/Generate the user menu + * Get/Generate the user menu. + * This is leveraging the data from user_get_user_navigation_info and the logic in $OUTPUT->user_menu() * * @return array */ public function get_user_menu(): array { - // Empty stub to add to. - return []; + global $CFG, $USER, $PAGE; + require_once($CFG->dirroot . '/user/lib.php'); + + $usermenudata = []; + $info = user_get_user_navigation_info($USER, $PAGE); + if (isset($info->unauthenticateduser)) { + $info->unauthenticateduser['content'] = get_string($info->unauthenticateduser['content']); + $info->unauthenticateduser['url'] = get_login_url(); + return (array) $info; + } + // Gather all the avatar data to be displayed in the user menu. + $usermenudata['avatardata'][] = [ + 'content' => $info->metadata['useravatar'], + 'classes' => 'current' + ]; + $usermenudata['userfullname'] = $info->metadata['realuserfullname'] ?? $info->metadata['userfullname']; + + // Logged in as someone else. + if ($info->metadata['asotheruser']) { + $usermenudata['avatardata'][] = [ + 'content' => $info->metadata['realuseravatar'], + 'classes' => 'realuser' + ]; + $usermenudata['metadata'][] = [ + 'content' => get_string('loggedinas', 'moodle', $info->metadata['userfullname']), + 'classes' => 'viewingas' + ]; + } + + // Gather all the meta data to be displayed in the user menu. + $metadata = [ + 'asotherrole' => [ + 'value' => 'rolename', + 'class' => 'role role-##GENERATEDCLASS##', + ], + 'userloginfail' => [ + 'value' => 'userloginfail', + 'class' => 'loginfailures', + ], + 'asmnetuser' => [ + 'value' => 'mnetidprovidername', + 'class' => 'mnet mnet-##GENERATEDCLASS##', + ], + ]; + foreach ($metadata as $key => $value) { + if (!empty($info->metadata[$key])) { + $content = $info->metadata[$value['value']] ?? ''; + $generatedclass = strtolower(preg_replace('#[ ]+#', '-', trim($content))); + $customclass = str_replace('##GENERATEDCLASS##', $generatedclass, ($value['class'] ?? '')); + $usermenudata['metadata'][] = [ + 'content' => $content, + 'classes' => $customclass + ]; + } + } + + $modifiedarray = array_map(function($value) { + $value->divider = $value->itemtype == 'divider'; + $value->link = $value->itemtype == 'link'; + if (isset($value->pix) && !empty($value->pix)) { + $value->pixicon = $value->pix; + unset($value->pix); + } + return $value; + }, $info->navitems); + + // Add dividers after the first item and before the last item. + $modifiedarray[0]->divider = true; + $modifiedarray[count($info->navitems) - 2]->divider = true; + $usermenudata['items'] = $modifiedarray; + + return $usermenudata; } } From 0c76a848ea30de143c0d571e21f48334cb125200 Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Wed, 7 Apr 2021 13:56:00 +0800 Subject: [PATCH 05/50] MDL-71148 core_navigation: Templates to generate user menu - Part of: MDL-69588 --- lib/templates/user_action_menu_items.mustache | 59 ++++++++++++++++ lib/templates/user_menu.mustache | 67 +++++++++++++++++++ lib/templates/user_menu_metadata.mustache | 63 +++++++++++++++++ version.php | 2 +- 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 lib/templates/user_action_menu_items.mustache create mode 100644 lib/templates/user_menu.mustache create mode 100644 lib/templates/user_menu_metadata.mustache diff --git a/lib/templates/user_action_menu_items.mustache b/lib/templates/user_action_menu_items.mustache new file mode 100644 index 00000000000..0a89d1da451 --- /dev/null +++ b/lib/templates/user_action_menu_items.mustache @@ -0,0 +1,59 @@ +{{! + 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 . +}} +{{! + @template core/user_action_menu_items + + Template for user action menu items. + + Context variables required for this template: + * items - The different items to be rendered + * link - If a link is provided render it. + * title - The text to be shown for the link. + * url - The href for the link. + * pixicon - (Optional) The Moodle icon to use + * imgsrc - (Optional) If provided, uses this as source for an image tag. Note: pixicon is preferred. + * divider - Whether a divider is to be displayed or not + + Example context (json): + { + "items": [ + { + "link": { + "title": "Github user", + "url": "https://raw.githubusercontent.com/", + "pixicon": "t/dashboard", + "imgsrc": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png" + }, + "divider": 1 + }, + ] + } +}} +{{#items}} + {{#link}} + + {{#pixicon}} + {{#pix}}{{pixicon}}{{/pix}} + {{/pixicon}} + {{^pixicon}} + {{#imgsrc}}{{/imgsrc}} + {{/pixicon}} + {{title}} + + {{/link}} + {{#divider}}{{/divider}} +{{/items}} diff --git a/lib/templates/user_menu.mustache b/lib/templates/user_menu.mustache new file mode 100644 index 00000000000..b8197515ca6 --- /dev/null +++ b/lib/templates/user_menu.mustache @@ -0,0 +1,67 @@ +{{! + 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 . +}} +{{! + @template core/user_menu + + Action link template. + + Context variables required for this template: + * unauthenticateduseruser - (Optional) Items to be displayed if an an unautheticated user is accessing the site + * content - The content to be displayed in the header. + * url - The login url + * items - Array of user menu items used in user_action_menu_items. Required if the above not provided. + * metadata - Array of additional metadata to be displayed in the dropdown button. + * avatardata - Array of avatars to be displayed. Usually only the current user's avatar. If viewing as another user, + includes that user's avatar. + * userfullname - The name of the logged in user + + Example context (json): + { + "unauthenticateduser": { + "content": "You are not logged in", + "url": "https://yourmoodlesite/login/index.php" + }, + "items": [], + "metadata": [], + "avatardata": [], + "userfullname": "Admin User" + } +}} +
+ {{#unauthenticateduser}} + + {{/unauthenticateduser}} + {{^unauthenticateduser}} + + {{/unauthenticateduser}} +
diff --git a/lib/templates/user_menu_metadata.mustache b/lib/templates/user_menu_metadata.mustache new file mode 100644 index 00000000000..b88a0cc8720 --- /dev/null +++ b/lib/templates/user_menu_metadata.mustache @@ -0,0 +1,63 @@ +{{! + 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 . +}} +{{! + @template core/user_menu_metadata + + User menu metadata template. + + Context variables required for this template: + * metadata - Array of additional metadata to be displayed in the dropdown button. + * classes - Additional classes to be applied to the content + * content - The content to be displayed. May/may not have html within it. + * avatardata - Array of avatars to be displayed. Usually only the current user's avatar. If viewing as another user, + includes that user's avatar. + * classes - Additional classes to be applied to the content + * content - The content to be displayed. May/may not have html within it. + * userfullname - The name of the logged in user + + Example context (json): + { + "metadata": [ + { + "classes": "loginfailures", + "content": "1 failed login attempts" + } + ], + "avatardata": [ + { + "classes": "current", + "content": "" + } + ], + "userfullname": "Admin User" + } +}} + + {{userfullname}} + {{#metadata}} + + {{{content}}} + + {{/metadata}} + + +{{#avatardata}} + + {{{content}}} + +{{/avatardata}} + diff --git a/version.php b/version.php index 403b2c4aee5..1af92ab3073 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021082000.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021082000.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev (Build: 20210820)'; // Human-friendly version name From 5446cadef81e1607e6b61dd724e606d449eb9fc1 Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Wed, 31 Mar 2021 08:54:45 +0800 Subject: [PATCH 06/50] MDL-71148 core_navigation: Unit test for the primary output - Part of: MDL-69588 --- lib/tests/navigation/output/primary_test.php | 315 +++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 lib/tests/navigation/output/primary_test.php diff --git a/lib/tests/navigation/output/primary_test.php b/lib/tests/navigation/output/primary_test.php new file mode 100644 index 00000000000..ab289d9de9e --- /dev/null +++ b/lib/tests/navigation/output/primary_test.php @@ -0,0 +1,315 @@ +. + +namespace core\navigation\output; + +use ReflectionMethod; + +/** + * Primary navigation renderable test + * + * @package core + * @category navigation + * @copyright 2021 onwards Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class primary_test extends \advanced_testcase { + /** + * Basic setup to make sure the nav objects gets generated without any issues. + */ + public function setUp(): void { + global $PAGE; + $this->resetAfterTest(); + $pagecourse = $this->getDataGenerator()->create_course(); + $assign = $this->getDataGenerator()->create_module('assign', ['course' => $pagecourse->id]); + $cm = get_coursemodule_from_id('assign', $assign->cmid); + $contextrecord = \context_module::instance($cm->id); + $pageurl = new \moodle_url('/mod/assign/view.php', ['id' => $cm->instance]); + $PAGE->set_cm($cm); + $PAGE->set_url($pageurl); + $PAGE->set_course($pagecourse); + $PAGE->set_context($contextrecord); + } + + /** + * Test the primary export to confirm we are getting the nodes + * + * @dataProvider test_primary_export_provider + * @param bool $withcustom Setup with custom menu + * @param bool $withlang Setup with langs + * @param array $expecteditems An array of nodes expected with content in them. + */ + public function test_primary_export(bool $withcustom, bool $withlang, array $expecteditems) { + global $PAGE, $CFG; + if ($withcustom) { + $CFG->custommenuitems = "Course search|/course/search.php + Google|https://google.com.au/ + Netflix|https://netflix.com/au"; + } + $this->setAdminUser(); + + // Mimic multiple langs installed. To trigger responses 'get_list_of_translations'. + // Note: The text/title of the nodes generated will be 'English(fr), English(de)' but we don't care about this. + // We are testing whether the nodes gets generated when the lang menu is available. + if ($withlang) { + mkdir("$CFG->dataroot/lang/de", 0777, true); + mkdir("$CFG->dataroot/lang/fr", 0777, true); + } + + $primary = new primary($PAGE); + $renderer = $PAGE->get_renderer('core'); + $data = $primary->export_for_template($renderer); + foreach ($data as $menutype => $value) { + if ($value) { + $this->assertTrue(in_array($menutype, $expecteditems)); + } + } + } + + /** + * Provider for the test_primary_export function. + * + * @return array + */ + public function test_primary_export_provider(): array { + return [ + "Export the menu data with custom and lang menu" => [ + true, true, ['primary', 'custom', 'lang', 'user'] + ], + "Export the menu data with custom menu" => [ + true, false, ['primary', 'custom', 'user'] + ], + "Export the menu data with lang menu" => [ + false, true, ['primary', 'lang', 'user'] + ], + "Export the menu data without the custom and lang menu" => [ + false, false, ['primary', 'user'] + ], + ]; + } + + /** + * Test the get_lang_menu + * + * @dataProvider get_lang_menu_provider + * @param bool $withadditionallangs + * @param string $language + * @param array $expected + */ + public function test_get_lang_menu(bool $withadditionallangs, string $language, array $expected) { + global $CFG, $PAGE; + + force_current_language($language); + + // Mimic multiple langs installed. To trigger responses 'get_list_of_translations'. + // Note: The text/title of the nodes generated will be 'English(fr), English(de)' but we don't care about this. + // We are testing whether the nodes gets generated when the lang menu is available. + if ($withadditionallangs) { + mkdir("$CFG->dataroot/lang/de", 0777, true); + mkdir("$CFG->dataroot/lang/fr", 0777, true); + } + + $output = new primary($PAGE); + $method = new ReflectionMethod('core\navigation\output\primary', 'get_lang_menu'); + $method->setAccessible(true); + $renderer = $PAGE->get_renderer('core'); + + $response = $method->invoke($output, $renderer); + if (!$withadditionallangs) { + $this->assertEquals($expected, $response); + } + + $expectedurls = array_map(function ($value) use ($PAGE) { + $url = new \moodle_url($PAGE->url, ['lang' => $value]); + return $url->out(); + }, array_keys($expected)); + $expectedurls[] = "#"; + + // Make sure the urls match up. + foreach ($response as $lang) { + $this->assertTrue(in_array($lang['url']->out(), $expectedurls)); + } + } + + /** + * Provider for test_get_lang_menu + * + * @return array + */ + public function get_lang_menu_provider(): array { + return [ + 'Lang menu with only the current language' => [ + false, 'en', [] + ], + 'Lang menu with only multiple languages installed' => [ + true, 'en', [ + 'de' => 'English ‎(de)‎', + 'fr' => 'English ‎(fr)‎', + ] + ], + 'Lang menu with only multiple languages installed and other than EN set active.' => [ + true, 'de', [ + 'en' => 'English ‎(en)‎', + 'fr' => 'English ‎(fr)‎', + ] + ], + ]; + } + + /** + * Test the custom menu getter to confirm the nodes gets generated and are returned correctly. + * + * @dataProvider custom_menu_provider + * @param string $config + * @param array $expected + */ + public function test_get_custom_menu(string $config, array $expected) { + global $CFG, $PAGE; + $CFG->custommenuitems = $config; + $output = new primary($PAGE); + $method = new ReflectionMethod('core\navigation\output\primary', 'get_custom_menu'); + $method->setAccessible(true); + $renderer = $PAGE->get_renderer('core'); + $this->assertEquals($expected, $method->invoke($output, $renderer)); + } + + /** + * Provider for test_get_custom_menu + * + * @return array + */ + public function custom_menu_provider(): array { + return [ + 'Simple custom menu' => [ + "Course search|/course/search.php + Google|https://google.com.au/ + Netflix|https://netflix.com/au", [ + (object) [ + 'text' => 'Course search', + 'url' => 'https://www.example.com/moodle/course/search.php', + 'title' => '', + 'sort' => 1, + 'children' => [], + 'haschildren' => false, + ], + (object) [ + 'text' => 'Google', + 'url' => 'https://google.com.au/', + 'title' => '', + 'sort' => 2, + 'children' => [], + 'haschildren' => false, + ], + (object) [ + 'text' => 'Netflix', + 'url' => 'https://netflix.com/au', + 'title' => '', + 'sort' => 3, + 'children' => [], + 'haschildren' => false, + ], + ] + ], + 'Complex, nested custom menu' => [ + "Moodle community|http://moodle.org + -Moodle free support|http://moodle.org/support + -Moodle development|http://moodle.org/development + --Moodle Tracker|http://tracker.moodle.org + --Moodle Docs|https://docs.moodle.org + -Moodle News|http://moodle.org/news + Moodle company + -Moodle commercial hosting|http://moodle.com/hosting + -Moodle commercial support|http://moodle.com/support", [ + (object) [ + 'text' => 'Moodle community', + 'url' => 'http://moodle.org', + 'title' => '', + 'sort' => 1, + 'children' => [ + (object) [ + 'text' => 'Moodle free support', + 'url' => 'http://moodle.org/support', + 'title' => '', + 'sort' => 2, + 'children' => [], + 'haschildren' => false, + ], + (object) [ + 'text' => 'Moodle development', + 'url' => 'http://moodle.org/development', + 'title' => '', + 'sort' => 3, + 'children' => [ + (object) [ + 'text' => 'Moodle Tracker', + 'url' => 'http://tracker.moodle.org', + 'title' => '', + 'sort' => 4, + 'children' => [], + 'haschildren' => false, + ], + (object) [ + 'text' => 'Moodle Docs', + 'url' => 'https://docs.moodle.org', + 'title' => '', + 'sort' => 5, + 'children' => [], + 'haschildren' => false, + ], + ], + 'haschildren' => true, + ], + (object) [ + 'text' => 'Moodle News', + 'url' => 'http://moodle.org/news', + 'title' => '', + 'sort' => 6, + 'children' => [], + 'haschildren' => false, + ], + ], + 'haschildren' => true, + ], + (object) [ + 'text' => 'Moodle company', + 'url' => null, + 'title' => '', + 'sort' => 7, + 'children' => [ + (object) [ + 'text' => 'Moodle commercial hosting', + 'url' => 'http://moodle.com/hosting', + 'title' => '', + 'sort' => 8, + 'children' => [], + 'haschildren' => false, + ], + (object) [ + 'text' => 'Moodle commercial support', + 'url' => 'http://moodle.com/support', + 'title' => '', + 'sort' => 9, + 'children' => [], + 'haschildren' => false, + ], + ], + 'haschildren' => true, + ], + ] + ] + ]; + } +} From dbc014b2a6798349983a2da9523488244b44f279 Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Fri, 19 Feb 2021 10:19:04 +0800 Subject: [PATCH 07/50] MDL-70196 theme_boost: New navbar renderer for navbar alterations - Part of: MDL-69588 --- theme/boost/classes/boostnavbar.php | 179 +++++++++++++++++++ theme/boost/classes/output/core_renderer.php | 9 + theme/boost/version.php | 2 +- 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 theme/boost/classes/boostnavbar.php diff --git a/theme/boost/classes/boostnavbar.php b/theme/boost/classes/boostnavbar.php new file mode 100644 index 00000000000..19507ba8eb8 --- /dev/null +++ b/theme/boost/classes/boostnavbar.php @@ -0,0 +1,179 @@ +. + +namespace theme_boost; + +/** + * Creates a navbar for boost that allows easy control of the navbar items. + * + * @package theme_boost + * @copyright 2021 Adrian Greeve + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class boostnavbar implements \renderable { + + /** @var array The individual items of the navbar. */ + protected $items = []; + + /** + * Takes a navbar object and picks the necessary parts for display. + * + * @param \navbar $navbar The navigation bar. + */ + public function __construct(\navbar $navbar) { + foreach ($navbar->get_items() as $item) { + $this->items[] = $item; + } + $this->prepare_nodes_for_boost(); + } + + /** + * Prepares the navigation nodes for use with boost. + */ + protected function prepare_nodes_for_boost(): void { + // Don't display the navbar if we are in the site navigation. + if (!is_null($this->get_item('root'))) { + $this->clear_items(); + return; + } + + $this->remove('myhome'); // Dashboard. + $this->remove('home'); + + // Set the designated one path for courses. + $mycoursesnode = $this->get_item('mycourses'); + if (!is_null($mycoursesnode)) { + $url = new \moodle_url('/mycourses/'); + $mycoursesnode->action = $url; + $mycoursesnode->text = get_string('courses'); + } + + $this->remove_no_link_items(); + + // Don't display the navbar if there is only one item. Apparently this is bad UX design. + if ($this->item_count() <= 1) { + $this->clear_items(); + return; + } + + // Make sure that the last item is not a link. Not sure if this is always a good idea. + $this->remove_last_item_action(); + } + + /** + * Get all the boostnavbaritem elements. + * + * @return boostnavbaritem[] Boost navbar items. + */ + public function get_items(): array { + return $this->items; + } + + /** + * Removes all navigation items out of this boost navbar + */ + protected function clear_items(): void { + $this->items = []; + } + + /** + * Retrieve a single navbar item. + * + * @param string|int $key The identifier of the navbar item to return. + * @return \breadcrumb_navigation_node|null The navbar item. + */ + protected function get_item($key): ?\breadcrumb_navigation_node { + foreach ($this->items as $item) { + if ($key === $item->key) { + return $item; + } + } + return null; + } + + /** + * Counts all of the navbar items. + * + * @return int How many navbar items there are. + */ + protected function item_count(): int { + return count($this->items); + } + + /** + * Remove a boostnavbaritem from the boost navbar. + * + * @param string|int $itemkey An identifier for the boostnavbaritem + */ + protected function remove($itemkey): void { + + $itemfound = false; + foreach ($this->items as $key => $item) { + if ($item->key === $itemkey) { + unset($this->items[$key]); + $itemfound = true; + break; + } + } + if (!$itemfound) { + return; + } + + $itemcount = $this->item_count(); + if ($itemcount <= 0) { + return; + } + + $this->items = array_values($this->items); + // Set the last item to last item if it is not. + $lastitem = $this->items[$itemcount - 1]; + if (!$lastitem->is_last()) { + $lastitem->set_last(true); + } + } + + /** + * Removes the action from the last item of the boostnavbaritem. + */ + protected function remove_last_item_action(): void { + $item = end($this->items); + $item->action = null; + reset($this->items); + } + + /** + * Returns the second last navbar item. This is for use in the mobile view where we are showing just the second + * last item in the breadcrumb navbar. + * + * @return breakcrumb_navigation_node|null The second last navigation node. + */ + public function get_penultimate_item(): ?\breadcrumb_navigation_node { + $number = $this->item_count() - 2; + return ($number >= 0) ? $this->items[$number] : null; + } + + /** + * Remove items that are categories or have no actions associated with them. + */ + protected function remove_no_link_items(): void { + foreach ($this->items as $key => $value) { + if (!$value->has_action() || $value->type == \navigation_node::TYPE_SECTION) { + unset($this->items[$key]); + } + } + $this->items = array_values($this->items); + } +} diff --git a/theme/boost/classes/output/core_renderer.php b/theme/boost/classes/output/core_renderer.php index 05c2ef8c68e..480aadabcd8 100644 --- a/theme/boost/classes/output/core_renderer.php +++ b/theme/boost/classes/output/core_renderer.php @@ -42,4 +42,13 @@ class core_renderer extends \core_renderer { return $this->render_single_button($button); } + /** + * Renders the "breadcrumb" for all pages in boost. + * + * @return string the HTML for the navbar. + */ + public function navbar(): string { + $newnav = new \theme_boost\boostnavbar($this->page->navbar); + return $this->render_from_template('core/navbar', $newnav); + } } diff --git a/theme/boost/version.php b/theme/boost/version.php index 4284349dd4c..d3acb13b9b1 100644 --- a/theme/boost/version.php +++ b/theme/boost/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2021052500; +$plugin->version = 2021052503; $plugin->requires = 2021052500; $plugin->component = 'theme_boost'; From 7d435fb5f24a56c4f94e034243f372da4548acd5 Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Tue, 30 Mar 2021 10:16:57 +0800 Subject: [PATCH 08/50] MDL-70196 navigation: Update to feature files. - Part of: MDL-69588 A lot of tests rely on the last element of the breadcrumb being active. This updates feature files to not rely on this anymore. --- .../tests/behat/customisation_create.feature | 12 +++---- .../tests/behat/import_files.feature | 8 ++--- .../tests/behat/import_mode.feature | 26 +++++++-------- admin/tool/lp/tests/behat/plan_crud.feature | 2 +- .../tool/lp/tests/behat/plan_workflow.feature | 3 -- .../oauth2/tests/behat/basic_settings.feature | 32 +++++++++---------- analytics/tests/behat/manage_models.feature | 2 +- badges/tests/behat/add_badge.feature | 6 ++-- badges/tests/behat/award_badge.feature | 15 ++++----- badges/tests/behat/award_badge_groups.feature | 7 +--- badges/tests/behat/criteria_activity.feature | 1 - badges/tests/behat/criteria_cohort.feature | 4 +++ .../tests/behat/criteria_competency.feature | 1 - badges/tests/behat/role_visibility.feature | 2 -- cohort/tests/behat/add_cohort.feature | 14 ++++---- course/tests/behat/course_browsing.feature | 2 +- course/tests/behat/course_controls.feature | 16 +++++----- course/tests/behat/edit_settings.feature | 1 - .../tests/behat/submission_comments.feature | 2 +- .../tests/behat/reorganize_chapters.feature | 26 +++++++-------- .../behat/discussion_subscriptions.feature | 1 - .../tests/behat/edit_post_teacher.feature | 3 +- mod/forum/tests/behat/private_replies.feature | 3 +- .../tests/behat/lesson_student_resume.feature | 7 ++++ mod/lti/tests/behat/backup_restore.feature | 3 +- tag/tests/behat/collections.feature | 2 +- tag/tests/behat/delete_tag.feature | 2 ++ tag/tests/behat/edit_tag.feature | 6 ++-- tag/tests/behat/flag_tags.feature | 1 + tag/tests/behat/standard_tags.feature | 1 + 30 files changed, 106 insertions(+), 105 deletions(-) diff --git a/admin/tool/customlang/tests/behat/customisation_create.feature b/admin/tool/customlang/tests/behat/customisation_create.feature index 1d139473593..a473c0c58fb 100644 --- a/admin/tool/customlang/tests/behat/customisation_create.feature +++ b/admin/tool/customlang/tests/behat/customisation_create.feature @@ -14,20 +14,20 @@ Feature: Within a moodle instance, an administrator should be able to modify lan And I press "Open language pack for editing" And I press "Continue" And I set the field "Show strings of these components" to "moodle.php" - And I set the field "String identifier" to "administrationsite" + And I set the field "String identifier" to "moodledocslink" And I press "Show strings" - And I set the field "core/administrationsite" to "Custom string example" + And I set the field "core/moodledocslink" to "moodle documents" @javascript Scenario: Edit an string but don't save it to lang pack. When I press "Apply changes and continue editing" - Then I should see "Site administration" in the "page-header" "region" - And I should not see "Custom string example" in the "page-header" "region" + Then I should see "Help and documentation" in the ".helplink" "css_element" + And I should not see "moodle documents" in the ".helplink" "css_element" @javascript Scenario: Customize an string as admin and save it to lang pack. Given I press "Save changes to the language pack" And I should see "There are 1 modified strings." When I click on "Continue" "button" - Then I should see "Custom string example" in the "page-header" "region" - And I should not see "Site administration" in the "page-header" "region" + Then I should see "moodle documents" in the ".helplink" "css_element" + And I should not see "Help and documentation" in the ".helplink" "css_element" diff --git a/admin/tool/customlang/tests/behat/import_files.feature b/admin/tool/customlang/tests/behat/import_files.feature index 3d93dfec90f..68265a47d62 100644 --- a/admin/tool/customlang/tests/behat/import_files.feature +++ b/admin/tool/customlang/tests/behat/import_files.feature @@ -24,7 +24,7 @@ Feature: Within a moodle instance, an administrator should be able to import mod And I should see "There are 1 modified strings." And I click on "Save strings to language pack" "button" And I click on "Continue" "button" - And I should see "An amazing import feature" in the "page-header" "region" + And I should see "An amazing import feature" @javascript Scenario: Try to import a PHP file from a non existent component @@ -46,6 +46,6 @@ Feature: Within a moodle instance, an administrator should be able to import mod And I should see "There are 3 modified strings." And I click on "Save strings to language pack" "button" And I click on "Continue" "button" - And I should see "Uploaded custom string" in the "page-header" "region" - And I should see "Another Uploaded string" in the "page-header" "region" - And I should see "An amazing import feature" in the "page-header" "region" + And I should see "Uploaded custom string" + And I should see "Another Uploaded string" + And I should see "An amazing import feature" diff --git a/admin/tool/customlang/tests/behat/import_mode.feature b/admin/tool/customlang/tests/behat/import_mode.feature index e977237a5e8..2b0186acdbb 100644 --- a/admin/tool/customlang/tests/behat/import_mode.feature +++ b/admin/tool/customlang/tests/behat/import_mode.feature @@ -21,7 +21,7 @@ Feature: Within a moodle instance, an administrator should be able to import lan And I press "Save changes to the language pack" And I should see "There are 1 modified strings." And I click on "Continue" "button" - And I should see "Custom string example" in the "page-header" "region" + And I should see "Custom string example" @javascript Scenario: Update only customized strings @@ -36,12 +36,12 @@ Feature: Within a moodle instance, an administrator should be able to import lan And I should see "String core/nonexistentinvetedstring not found." And I click on "Continue" "button" And I should see "There are 1 modified strings." - And I should not see "Uploaded custom string" in the "page-header" "region" + And I should not see "Uploaded custom string" And I click on "Save strings to language pack" "button" And I click on "Continue" "button" - And I should not see "Custom string example" in the "page-header" "region" - And I should see "Uploaded custom string" in the "page-header" "region" - And I should not see "Another Uploaded string" in the "page-header" "region" + And I should not see "Custom string example" + And I should see "Uploaded custom string" + And I should not see "Another Uploaded string" @javascript Scenario: Create only new strings @@ -56,12 +56,12 @@ Feature: Within a moodle instance, an administrator should be able to import lan And I should see "String core/nonexistentinvetedstring not found." And I click on "Continue" "button" And I should see "There are 1 modified strings." - And I should not see "Uploaded custom string" in the "page-header" "region" + And I should not see "Uploaded custom string" And I click on "Save strings to language pack" "button" And I click on "Continue" "button" - And I should see "Custom string example" in the "page-header" "region" - And I should not see "Uploaded custom string" in the "page-header" "region" - And I should see "Another Uploaded string" in the "page-header" "region" + And I should see "Custom string example" + And I should not see "Uploaded custom string" + And I should see "Another Uploaded string" @javascript Scenario: Import all strings @@ -76,9 +76,9 @@ Feature: Within a moodle instance, an administrator should be able to import lan And I should see "String core/nonexistentinvetedstring not found." And I click on "Continue" "button" And I should see "There are 2 modified strings." - And I should not see "Uploaded custom string" in the "page-header" "region" + And I should not see "Uploaded custom string" And I click on "Save strings to language pack" "button" And I click on "Continue" "button" - And I should not see "Custom string example" in the "page-header" "region" - And I should see "Uploaded custom string" in the "page-header" "region" - And I should see "Another Uploaded string" in the "page-header" "region" + And I should not see "Custom string example" + And I should see "Uploaded custom string" + And I should see "Another Uploaded string" diff --git a/admin/tool/lp/tests/behat/plan_crud.feature b/admin/tool/lp/tests/behat/plan_crud.feature index 9f3dc075302..761a569fc87 100644 --- a/admin/tool/lp/tests/behat/plan_crud.feature +++ b/admin/tool/lp/tests/behat/plan_crud.feature @@ -56,7 +56,7 @@ Feature: Manage plearning plan And I set the field "Select cohorts to sync" to "cohort plan" When I click on "Add cohorts" "button" Then I should see "2 learning plans were created." - And I follow "Learning plan templates" + And I navigate to "Competencies > Learning plan templates" in site administration And I click on ".template-userplans" "css_element" in the "Science template cohort" "table_row" And I should see "Student 1" And I should see "Student 2" diff --git a/admin/tool/lp/tests/behat/plan_workflow.feature b/admin/tool/lp/tests/behat/plan_workflow.feature index 5410f2ab2e9..9d8f2eb39c1 100644 --- a/admin/tool/lp/tests/behat/plan_workflow.feature +++ b/admin/tool/lp/tests/behat/plan_workflow.feature @@ -149,7 +149,6 @@ Feature: Manage plan workflow And I follow "User 1" And I follow "Learning plans" And I should see "List of learning plans" - And I follow "Learning plans" When I click on "Send back to draft" of edit menu in the "Test-Plan3" row And I follow "Test-Plan4" And I follow "Send back to draft" @@ -168,7 +167,6 @@ Feature: Manage plan workflow And I follow "User 1" And I follow "Learning plans" And I should see "List of learning plans" - And I follow "Learning plans" When I click on "Complete this learning plan" of edit menu in the "Test-Plan3" row And I click on "Complete this learning plan" "button" in the "Confirm" "dialogue" And I wait until the page is ready @@ -190,7 +188,6 @@ Feature: Manage plan workflow And I follow "User 1" And I follow "Learning plans" And I should see "List of learning plans" - And I follow "Learning plans" When I click on "Reopen this learning plan" of edit menu in the "Test-Plan3" row And I click on "Reopen this learning plan" "button" in the "Confirm" "dialogue" And I follow "Test-Plan4" diff --git a/admin/tool/oauth2/tests/behat/basic_settings.feature b/admin/tool/oauth2/tests/behat/basic_settings.feature index beb669eab94..d2c402c436c 100644 --- a/admin/tool/oauth2/tests/behat/basic_settings.feature +++ b/admin/tool/oauth2/tests/behat/basic_settings.feature @@ -24,11 +24,11 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Testing service" "table_row" And I should see "https://accounts.google.com/.well-known/openid-configuration" in the "discovery_endpoint" "table_row" And I should see "authorization_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Testing service" "table_row" And I should see "firstname" in the "given_name" "table_row" And I should see "middlename" in the "middle_name" "table_row" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Testing service" "table_row" And I set the following fields to these values: | Name | Testing service modified | @@ -57,10 +57,10 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Testing service" "table_row" And I should see "authorization_endpoint" And I should not see "discovery_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Testing service" "table_row" And I should see "firstname" in the "givenName" "table_row" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Testing service" "table_row" And I set the following fields to these values: | Name | Testing service modified | @@ -89,10 +89,10 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Testing service" "table_row" And I should see "authorization_endpoint" And I should not see "discovery_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Testing service" "table_row" And I should see "firstname" in the "first_name" "table_row" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Testing service" "table_row" And I set the following fields to these values: | Name | Testing service modified | @@ -126,10 +126,10 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Testing service" "table_row" And I should see "authorization_endpoint" And I should not see "discovery_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Testing service" "table_row" And I should see "username" in the "ocs-data-id" "table_row" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Testing service" "table_row" And I set the following fields to these values: | Name | Testing service modified | @@ -159,11 +159,11 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Open Badges" "table_row" And I should see "https://dc.imsglobal.org/.well-known/badgeconnect.json" in the "discovery_endpoint" "table_row" And I should see "authorization_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Open Badges" "table_row" And I should not see "given_name" And I should not see "middle_name" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Open Badges" "table_row" And I set the following fields to these values: | Name | IMS Global | @@ -194,11 +194,11 @@ Feature: Basic OAuth2 functionality And I click on "Configure endpoints" "link" in the "Google custom" "table_row" And I should see "https://accounts.google.com/.well-known/openid-configuration" in the "discovery_endpoint" "table_row" And I should see "authorization_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Google custom" "table_row" And I should see "firstname" in the "given_name" "table_row" And I should see "middlename" in the "middle_name" "table_row" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Google custom" "table_row" And I set the following fields to these values: | Name | Google custom modified | @@ -227,11 +227,11 @@ Feature: Basic OAuth2 functionality And I should see "-" in the "Invalid custom service" "table_row" And I click on "Configure endpoints" "link" in the "Invalid custom service" "table_row" And I should not see "discovery_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Invalid custom service" "table_row" And I should not see "given_name" And I should not see "middle_name" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Invalid custom service" "table_row" And I set the following fields to these values: | Name | Valid custom service | @@ -268,11 +268,11 @@ Feature: Basic OAuth2 functionality And I should see "-" in the "Empty custom service" "table_row" And I click on "Configure endpoints" "link" in the "Empty custom service" "table_row" And I should not see "discovery_endpoint" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Configure user field mappings" "link" in the "Empty custom service" "table_row" And I should not see "given_name" And I should not see "middle_name" - And I follow "OAuth 2 services" + And I navigate to "Server > OAuth 2 services" in site administration And I click on "Edit" "link" in the "Empty custom service" "table_row" # Check it works as expected too without slash at the end of the service base URL. And I set the following fields to these values: diff --git a/analytics/tests/behat/manage_models.feature b/analytics/tests/behat/manage_models.feature index d68ed78e11b..d6b56f56695 100644 --- a/analytics/tests/behat/manage_models.feature +++ b/analytics/tests/behat/manage_models.feature @@ -93,7 +93,7 @@ Feature: Manage analytics models And I click on "View" "link" And I should see "Log extra info" And I click on "Close" "button" - And I click on "Analytics models" "link" + And I navigate to "Analytics > Analytics models" in site administration # Execute scheduled analysis And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row" And I choose "Execute scheduled analysis" in the open action menu diff --git a/badges/tests/behat/add_badge.feature b/badges/tests/behat/add_badge.feature index bc7695df440..82d5c3b1ff9 100644 --- a/badges/tests/behat/add_badge.feature +++ b/badges/tests/behat/add_badge.feature @@ -44,7 +44,7 @@ Feature: Add badges to the system And I should see "Issuer details" And I should see "Test Badge Site" And I should see "testuser@example.com" - And I follow "Manage badges" + And I navigate to "Badges > Manage badges" in site administration And I should see "Number of badges available: 1" And I should not see "There are no badges available." @@ -61,7 +61,7 @@ Feature: Add badges to the system And I upload "badges/tests/behat/badge.png" file to "Image" filemanager And I press "Create badge" And I wait until the page is ready - And I follow "Manage badges" + And I navigate to "Badges > Manage badges" in site administration And I should see "Number of badges available: 1" And I press "Add a new badge" And I set the following fields to these values: @@ -157,7 +157,7 @@ Feature: Add badges to the system And I should see "Related badges (0)" And I should see "Alignments (0)" And I should not see "Create badge" - And I follow "Manage badges" + And I navigate to "Badges > Manage badges" in site administration And I should see "Number of badges available: 1" And I should not see "There are no badges available." # See buttons from the "Site badges" page. diff --git a/badges/tests/behat/award_badge.feature b/badges/tests/behat/award_badge.feature index 7324e75c31f..b25de058730 100644 --- a/badges/tests/behat/award_badge.feature +++ b/badges/tests/behat/award_badge.feature @@ -21,7 +21,6 @@ Feature: Award badges And I am on "Course 1" course homepage # Create course badge 1. And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge 1 | | Description | Course badge 1 description | @@ -38,7 +37,6 @@ Feature: Award badges # Badge #2 And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge 2 | | Description | Course badge 2 description | @@ -57,6 +55,7 @@ Feature: Award badges # Award course badge 1 to student 1. And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)" When I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge 1" And I follow "Recipients (1)" Then I should see "Recipients (1)" @@ -149,6 +148,7 @@ Feature: Award badges And I press "Award badge" And I set the field "potentialrecipients[]" to "student 1 (student1@example.com)" And I press "Award badge" + And I navigate to "Badges > Manage badges" in site administration When I follow "Site Badge" Then I should see "Recipients (2)" And I log out @@ -174,7 +174,6 @@ Feature: Award badges And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -191,6 +190,7 @@ Feature: Award badges And I press "Award badge" And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)" When I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge" Then I should see "Recipients (2)" And I log out @@ -225,7 +225,6 @@ Feature: Award badges | id_completion | 1 | And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -279,7 +278,6 @@ Feature: Award badges And I press "Save changes" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -328,7 +326,6 @@ Feature: Award badges And I am on "Course 1" course homepage # Create course badge 1. And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge 1 | | Description | Course badge description | @@ -347,13 +344,13 @@ Feature: Award badges # Award course badge 1 to student 1. And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)" When I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge 1" And I follow "Recipients (1)" Then I should see "Recipients (1)" # Add course badge 2. And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge 2 | | Description | Course badge description | @@ -372,6 +369,7 @@ Feature: Award badges # Award course badge 2 to student 2. And I set the field "potentialrecipients[]" to "Student 2 (student2@example.com)" When I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge 2" And I follow "Recipients (1)" Then I should see "Recipients (1)" @@ -409,7 +407,6 @@ Feature: Award badges And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -426,6 +423,7 @@ Feature: Award badges And I press "Award badge" And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)" When I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge" Then I should see "Recipients (2)" And I follow "Recipients (2)" @@ -434,5 +432,6 @@ Feature: Award badges And I press "Revoke badge" And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)" When I press "Revoke badge" + And I follow "Manage badges" And I follow "Course Badge" Then I should see "Recipients (0)" diff --git a/badges/tests/behat/award_badge_groups.feature b/badges/tests/behat/award_badge_groups.feature index 76b29b689e3..2596b26dd4c 100644 --- a/badges/tests/behat/award_badge_groups.feature +++ b/badges/tests/behat/award_badge_groups.feature @@ -37,7 +37,6 @@ Feature: Award badges with separate groups And I set the field "Group mode" to "Separate groups" And I press "Save and display" And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -59,7 +58,6 @@ Feature: Award badges with separate groups When I log in as "teacher2" And I am on "Course 1" course homepage And I navigate to "Badges > Manage badges" in current page administration - And I follow "Manage badges" And I follow "Course Badge" And I press "Award badge" And I set the field "role" to "Non-editing teacher" @@ -71,6 +69,7 @@ Feature: Award badges with separate groups # Non-editing teacher can award the badge And I set the field "potentialrecipients[]" to "Student 2 (student2@example.com)" And I press "Award badge" + And I follow "Manage badges" And I follow "Course Badge" And I should see "Recipients (1)" And I log out @@ -85,7 +84,6 @@ Feature: Award badges with separate groups Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Users > Groups" in current page administration - And I follow "Groups" And I set the field "groups" to "Class B (2)" And I press "Add/remove users" And I set the field "addselect" to "Teacher 2 (teacher2@example.com)" @@ -94,7 +92,6 @@ Feature: Award badges with separate groups When I log in as "teacher2" And I am on "Course 1" course homepage And I navigate to "Badges > Manage badges" in current page administration - And I follow "Manage badges" And I follow "Course Badge" And I press "Award badge" And I set the field "role" to "Non-editing teacher" @@ -112,7 +109,6 @@ Feature: Award badges with separate groups Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Users > Groups" in current page administration - And I follow "Groups" And I set the field "groups" to "Class A (2)" And I press "Add/remove users" And I set the field "removeselect" to "Teacher 2 (teacher2@example.com)" @@ -122,7 +118,6 @@ Feature: Award badges with separate groups When I log in as "teacher2" And I am on "Course 1" course homepage And I navigate to "Badges > Manage badges" in current page administration - And I follow "Manage badges" And I follow "Course Badge" And I press "Award badge" # Teacher 2 shouldn't be able to go further diff --git a/badges/tests/behat/criteria_activity.feature b/badges/tests/behat/criteria_activity.feature index 42b16f33c1c..93911ff5715 100644 --- a/badges/tests/behat/criteria_activity.feature +++ b/badges/tests/behat/criteria_activity.feature @@ -36,7 +36,6 @@ Feature: Award badges based on activity completion And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | diff --git a/badges/tests/behat/criteria_cohort.feature b/badges/tests/behat/criteria_cohort.feature index e0578debdf7..32d2625e0af 100644 --- a/badges/tests/behat/criteria_cohort.feature +++ b/badges/tests/behat/criteria_cohort.feature @@ -152,6 +152,7 @@ Feature: Award badges based on cohort And I press "Award badge" And I set the field "potentialrecipients[]" to "Second User (second@example.com)" And I press "Award badge" + And I navigate to "Badges > Manage badges" in site administration And I follow "Site Badge" Then I should see "Recipients (1)" And I log out @@ -203,6 +204,7 @@ Feature: Award badges based on cohort And I press "Award badge" And I set the field "potentialrecipients[]" to "Second User (second@example.com)" And I press "Award badge" + And I navigate to "Badges > Manage badges" in site administration And I follow "Site Badge" Then I should see "Recipients (2)" And I log out @@ -259,6 +261,7 @@ Feature: Award badges based on cohort And I press "Award badge" And I set the field "potentialrecipients[]" to "Second User (second@example.com)" And I press "Award badge" + And I navigate to "Badges > Manage badges" in site administration And I follow "Site Badge" Then I should see "Recipients (2)" And I log out @@ -317,6 +320,7 @@ Feature: Award badges based on cohort And I press "Award badge" And I set the field "potentialrecipients[]" to "Second User (second@example.com)" And I press "Award badge" + And I navigate to "Badges > Manage badges" in site administration And I follow "Site Badge" Then I should see "Recipients (1)" And I log out diff --git a/badges/tests/behat/criteria_competency.feature b/badges/tests/behat/criteria_competency.feature index 0945c09ae58..77bac3fe102 100644 --- a/badges/tests/behat/criteria_competency.feature +++ b/badges/tests/behat/criteria_competency.feature @@ -40,7 +40,6 @@ Feature: Award badges based on competency completion # Add a badge to the course And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | diff --git a/badges/tests/behat/role_visibility.feature b/badges/tests/behat/role_visibility.feature index 036ff5d060b..4d46e8f7f10 100644 --- a/badges/tests/behat/role_visibility.feature +++ b/badges/tests/behat/role_visibility.feature @@ -22,7 +22,6 @@ Feature: Test role visibility for the badge administration page Given I log in as "manager1" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | @@ -37,7 +36,6 @@ Feature: Test role visibility for the badge administration page Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Badges > Add a new badge" in current page administration - And I follow "Add a new badge" And I set the following fields to these values: | Name | Course Badge | | Description | Course badge description | diff --git a/cohort/tests/behat/add_cohort.feature b/cohort/tests/behat/add_cohort.feature index 0d28abfa8a8..7ba866d8588 100644 --- a/cohort/tests/behat/add_cohort.feature +++ b/cohort/tests/behat/add_cohort.feature @@ -33,6 +33,10 @@ Feature: Add cohorts of users Then the following should exist in the "generaltable" table: | Name | Cohort ID | Description | | My new cohort | mynewcohort | My new cohort is cool | + And I should see "Test cohort name" + And I should see "333" + And I should see "Test cohort description" + And I should see "Created manually" @javascript Scenario: Add users to a cohort selecting them from the system users list @@ -76,8 +80,7 @@ Feature: Add cohorts of users @javascript Scenario: Add users to a cohort using a bulk user action - When I follow "Accounts" - And I follow "Bulk user actions" + When I navigate to "Users > Accounts > Bulk user actions" in site administration And I set the field "Available" to "Third User" And I press "Add to selection" And I set the field "Available" to "Forth User" @@ -86,8 +89,7 @@ Feature: Add cohorts of users And I press "Go" And I set the field "Cohort" to "Test cohort name [333]" And I press "Add to cohort" - And I follow "Accounts" - And I follow "Cohorts" + And I navigate to "Users > Accounts > Cohorts" in site administration Then I should see "2" in the "#cohorts" "css_element" 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)" @@ -96,9 +98,9 @@ Feature: Add cohorts of users @javascript Scenario: Edit cohort name in-place - When I follow "Cohorts" + When I navigate to "Users > Accounts > Cohorts" in site administration And I set the field "Edit cohort name" to "Students cohort" Then I should not see "Test cohort name" And I should see "Students cohort" - And I follow "Cohorts" + And I navigate to "Users > Accounts > Cohorts" in site administration And I should see "Students cohort" diff --git a/course/tests/behat/course_browsing.feature b/course/tests/behat/course_browsing.feature index 4993d076e20..586859d2a3c 100644 --- a/course/tests/behat/course_browsing.feature +++ b/course/tests/behat/course_browsing.feature @@ -91,7 +91,7 @@ Feature: Restricting access to course lists And I should see "Biology" And I should not see "Humanities" And I click on "Courses" "link" in the "Navigation" "block" - And "category" "text" should not exist in the ".breadcrumb" "css_element" + # And "category" "text" should not exist in the ".breadcrumb" "css_element" And I should see "Science category" And I should see "English category" And I should not see "Other category" diff --git a/course/tests/behat/course_controls.feature b/course/tests/behat/course_controls.feature index e51d1807df9..a571bf6ad3d 100644 --- a/course/tests/behat/course_controls.feature +++ b/course/tests/behat/course_controls.feature @@ -81,12 +81,12 @@ Feature: Course activity controls works as expected Examples: | courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage | - | topics | 0 | "Course 1" | should | should | "Topic 2" | + | topics | 0 | "C1" | should | should | "Topic 2" | | topics | 1 | "Topic 1" | should not | should not | "Topic 2" | - | topics | 1 | "Course 1" | should | should not | "Topic 2" | - | weeks | 0 | "Course 1" | should | should | "8 January - 14 January" | + | topics | 1 | "C1" | should | should not | "Topic 2" | + | weeks | 0 | "C1" | should | should | "8 January - 14 January" | | weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" | - | weeks | 1 | "Course 1" | should | should not | "8 January - 14 January" | + | weeks | 1 | "C1" | should | should not | "8 January - 14 January" | Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected Given the following "users" exist: @@ -146,9 +146,9 @@ Feature: Course activity controls works as expected Examples: | courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage | - | topics | 0 | "Course 1" | should | should | "Topic 2" | + | topics | 0 | "C1" | should | should | "Topic 2" | | topics | 1 | "Topic 1" | should not | should not | "Topic 2" | - | topics | 1 | "Course 1" | should | should not | "Topic 2" | - | weeks | 0 | "Course 1" | should | should | "8 January - 14 January" | + | topics | 1 | "C1" | should | should not | "Topic 2" | + | weeks | 0 | "C1" | should | should | "8 January - 14 January" | | weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" | - | weeks | 1 | "Course 1" | should | should not | "8 January - 14 January" | + | weeks | 1 | "C1" | should | should not | "8 January - 14 January" | diff --git a/course/tests/behat/edit_settings.feature b/course/tests/behat/edit_settings.feature index 4d46d50d61f..59c801af74e 100644 --- a/course/tests/behat/edit_settings.feature +++ b/course/tests/behat/edit_settings.feature @@ -23,7 +23,6 @@ Feature: Edit course settings | Course short name | Edited course shortname | | Course summary | Edited course summary | And I press "Save and display" - And I follow "Edited course fullname" Then I should not see "Course 1" And I should not see "C1" And I should see "Edited course fullname" diff --git a/mod/assign/tests/behat/submission_comments.feature b/mod/assign/tests/behat/submission_comments.feature index 09f1da1ff65..261521c0e61 100644 --- a/mod/assign/tests/behat/submission_comments.feature +++ b/mod/assign/tests/behat/submission_comments.feature @@ -40,7 +40,7 @@ Feature: In an assignment, students can comment in their submissions And I follow "Save comment" And I should see "Second student comment" And I should not see "First student comment" - And I follow "Test assignment name" + And I reload the page And I click on ".comment-link" "css_element" And I should see "Second student comment" And I should not see "First student comment" diff --git a/mod/book/tests/behat/reorganize_chapters.feature b/mod/book/tests/behat/reorganize_chapters.feature index be0c0883248..4a4601b4921 100644 --- a/mod/book/tests/behat/reorganize_chapters.feature +++ b/mod/book/tests/behat/reorganize_chapters.feature @@ -51,7 +51,8 @@ Feature: In a book, chapters and subchapters can be rearranged Scenario: Moving chapters down rearranges them properly Given I click on "Move chapter down \"1. Originally first chapter\"" "link" - When I follow "Test book" + When I am on "Course 1" course homepage + And I follow "Test book" Then I should see "1. A great second chapter" And I should see "#2 chapter content" And I should see "1.1. Second chapter, subchapter 1" @@ -61,7 +62,8 @@ Feature: In a book, chapters and subchapters can be rearranged Scenario: Moving chapters up rearranges them properly Given I click on "Move chapter up \"3. There aren't 2 without 3\"" "link" - When I follow "Test book" + When I am on "Course 1" course homepage + And I follow "Test book" Then I should see "1. Originally first chapter" And I should see "#1 chapter content" And I should see "2. There aren't 2 without 3" @@ -71,30 +73,26 @@ Feature: In a book, chapters and subchapters can be rearranged Scenario: Moving subchapters down within chapter rearranges them properly Given I click on "Move chapter down \"2.1. Second chapter, subchapter 1\"" "link" - When I follow "Test book" - Then I should see "2.1. Second chapter, subchapter 2" - And I should see "2.2. Second chapter, subchapter 1" + When I should see "2.1. Second chapter, subchapter 2" + Then I should see "2.2. Second chapter, subchapter 1" Scenario: Moving subchapters down out of chapter rearranges them properly Given I click on "Move chapter down \"2.2. Second chapter, subchapter 2\"" "link" - When I follow "Test book" - Then I should see "3.1. Second chapter, subchapter 2" - And I click on "Move chapter down \"3. There aren't 2 without 3\"" "link" + When I should see "3.1. Second chapter, subchapter 2" + Then I click on "Move chapter down \"3. There aren't 2 without 3\"" "link" And I should not see "4. There aren't 2 without 3" And I should see "3. There aren't 2 without 3" And I should see "3.1. Second chapter, subchapter 2" Scenario: Moving subchapters up within chapter rearranges them properly Given I click on "Move chapter up \"2.2. Second chapter, subchapter 2\"" "link" - When I follow "Test book" - Then I should see "2.1. Second chapter, subchapter 2" - And I should see "2.2. Second chapter, subchapter 1" + When I should see "2.1. Second chapter, subchapter 2" + Then I should see "2.2. Second chapter, subchapter 1" Scenario: Moving subchapters up out of chapter rearranges them properly Given I click on "Move chapter up \"2.1. Second chapter, subchapter 1\"" "link" - When I follow "Test book" - Then I should see "1.1. Second chapter, subchapter 1" - And I click on "Move chapter up \"1.1. Second chapter, subchapter 1\"" "link" + When I should see "1.1. Second chapter, subchapter 1" + Then I click on "Move chapter up \"1.1. Second chapter, subchapter 1\"" "link" And I should not see "1.1. Second chapter, subchapter 1" And I should see "1. Second chapter, subchapter 1" And I should see "2. Originally first chapter" diff --git a/mod/forum/tests/behat/discussion_subscriptions.feature b/mod/forum/tests/behat/discussion_subscriptions.feature index 8bf50661194..fc8e8ba3a67 100644 --- a/mod/forum/tests/behat/discussion_subscriptions.feature +++ b/mod/forum/tests/behat/discussion_subscriptions.feature @@ -341,7 +341,6 @@ Feature: A user can control their own subscription preferences for a discussion And I follow "You are not subscribed to this discussion. Click to subscribe" And I should see "Student One will be notified of new posts in 'Test post subject one' of 'Test forum name'" And "Unsubscribe from this discussion" "checkbox" should exist in the "Test post subject one" "table_row" - And I follow "Test forum name" And I navigate to "Subscribe to this forum" in current page administration And I should see "Student One will be notified of new posts in 'Test forum name'" And "Unsubscribe from this forum" "link" should exist in current page administration diff --git a/mod/forum/tests/behat/edit_post_teacher.feature b/mod/forum/tests/behat/edit_post_teacher.feature index 4875a8d648c..6a9ec280d7e 100644 --- a/mod/forum/tests/behat/edit_post_teacher.feature +++ b/mod/forum/tests/behat/edit_post_teacher.feature @@ -57,6 +57,7 @@ Feature: Teachers can edit or delete any forum post And I should see "Edited by Teacher 1 - original submission" Scenario: A student can't edit or delete another user's posts - When I follow "Teacher post subject" + When I follow "Test forum name" + And I follow "Teacher post subject" Then I should not see "Edit" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Teacher post subject')]" "xpath_element" And I should not see "Delete" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Teacher post subject')]" "xpath_element" diff --git a/mod/forum/tests/behat/private_replies.feature b/mod/forum/tests/behat/private_replies.feature index eb2d8eda5ab..364dc3d7fd6 100644 --- a/mod/forum/tests/behat/private_replies.feature +++ b/mod/forum/tests/behat/private_replies.feature @@ -36,7 +36,8 @@ Feature: Forum posts can be replied to in private | Reply privately | 1 | Scenario: As a teacher I can see my own response - Given I follow "Answers to the homework" + Given I follow "Study discussions" + And I follow "Answers to the homework" Then I should see "How about you and I have a meeting after class about plagiarism?" Scenario: As a fellow teacher I can see the other teacher's response diff --git a/mod/lesson/tests/behat/lesson_student_resume.feature b/mod/lesson/tests/behat/lesson_student_resume.feature index e3d8c4185e0..8e207dfedcb 100644 --- a/mod/lesson/tests/behat/lesson_student_resume.feature +++ b/mod/lesson/tests/behat/lesson_student_resume.feature @@ -89,6 +89,7 @@ Feature: In a lesson activity a student should And I should see "Second page contents" And I press "Next page" And I should see "Third page contents" + And I am on "Course 1" course homepage And I follow "Test lesson name" And I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" @@ -98,6 +99,7 @@ Feature: In a lesson activity a student should And I wait "1" seconds And I press "Next page" And I should see "Paper is made from trees." + And I am on "Course 1" course homepage And I follow "Test lesson name" And I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" @@ -108,6 +110,7 @@ Feature: In a lesson activity a student should And I press "Submit" And I press "Continue" And I should see "Kermit is a frog" + And I am on "Course 1" course homepage And I follow "Test lesson name" And I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" @@ -165,6 +168,7 @@ Feature: In a lesson activity a student should And I wait "1" seconds And I press "Next page" And I should see "Third page contents" + And I am on "Course 1" course homepage And I follow "Test lesson name" Then I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" @@ -286,6 +290,7 @@ Feature: In a lesson activity a student should And I press "Submit" And I press "Continue" And I should see "2+2=4" + And I am on "Course 1" course homepage And I follow "Test lesson name" And I should see "You have seen more than one page of this lesson already." Then I should see "Do you want to start at the last page you saw?" @@ -298,6 +303,7 @@ Feature: In a lesson activity a student should And I press "Submit" And I press "Continue" And I should see "Second content page" + And I am on "Course 1" course homepage And I follow "Test lesson name" And I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" @@ -399,6 +405,7 @@ Feature: In a lesson activity a student should And I press "Submit" And I press "Continue" And I should see "2+2=4" + And I am on "Course 1" course homepage And I follow "Test lesson name" Then I should see "You have seen more than one page of this lesson already." And I should see "Do you want to start at the last page you saw?" diff --git a/mod/lti/tests/behat/backup_restore.feature b/mod/lti/tests/behat/backup_restore.feature index 7ae20306771..9da97b66c9d 100644 --- a/mod/lti/tests/behat/backup_restore.feature +++ b/mod/lti/tests/behat/backup_restore.feature @@ -27,13 +27,12 @@ Feature: Restoring Moodle 2 backup restores LTI configuration And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration And "This tool has not yet been used" "text" should exist in the "//div[contains(@id,'tool-card-container') and contains(., 'My site tool')]" "xpath_element" And I am on site homepage - And I follow "Course 1" + And I am on "Course 1" course homepage And I turn editing mode on And I add a "External tool" to section "1" and I fill the form with: | Activity name | My LTI module | | Preconfigured tool | My site tool | | Launch container | Embed | - And I follow "Course 1" And I should see "My LTI module" And I backup "Course 1" course using this options: | Confirmation | Filename | test_backup.mbz | diff --git a/tag/tests/behat/collections.feature b/tag/tests/behat/collections.feature index 50bdd8bf9cb..a1deb030b3e 100644 --- a/tag/tests/behat/collections.feature +++ b/tag/tests/behat/collections.feature @@ -86,7 +86,7 @@ Feature: Managers can create and manage tag collections And I should see "Tag3" And I should not see "Tag1" And I should not see "Tag2" - And I follow "Manage tags" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" # Tag "Swimming" was not standard and was moved completely. And I should not see "Swimming" diff --git a/tag/tests/behat/delete_tag.feature b/tag/tests/behat/delete_tag.feature index 18eb048f16a..b29b1469ac8 100644 --- a/tag/tests/behat/delete_tag.feature +++ b/tag/tests/behat/delete_tag.feature @@ -61,6 +61,7 @@ Feature: Manager is able to delete tags And I press "Yes" And I should see "Tag(s) deleted" And I should not see "Dog" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And I should not see "Dog" And I follow "Cat" @@ -94,6 +95,7 @@ Feature: Manager is able to delete tags And I should see "Tag(s) deleted" And I should not see "Dog" And I should not see "Neverusedtag" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And I should not see "Dog" And I should not see "Neverusedtag" diff --git a/tag/tests/behat/edit_tag.feature b/tag/tests/behat/edit_tag.feature index c3bc35e51ec..21fd143a93e 100644 --- a/tag/tests/behat/edit_tag.feature +++ b/tag/tests/behat/edit_tag.feature @@ -123,7 +123,6 @@ Feature: Users can edit tags to add description or rename | Related tags | Dog, Turtle,Fish | | Standard | 0 | And I press "Update" - Then "Default collection" "link" should exist in the ".breadcrumb" "css_element" And I follow "Kitten" And "Description of tag 1" "text" should exist in the ".tag-description" "css_element" And I should see "Related tags:" in the ".tag_list" "css_element" @@ -143,12 +142,10 @@ Feature: Users can edit tags to add description or rename And I set the following fields to these values: | Tag name | Kitten | And I press "Update" - Then "Default collection" "text" should exist in the ".breadcrumb" "css_element" And I click on "Edit this tag" "link" in the "Kitten" "table_row" And I set the following fields to these values: | Tag name | KITTEN | And I press "Update" - And "Default collection" "text" should exist in the ".breadcrumb" "css_element" And I should see "KITTEN" And I should not see "Kitten" @@ -161,6 +158,7 @@ Feature: Users can edit tags to add description or rename And I set the field "Edit tag name" in the "Cat" "table_row" to "Kitten" Then I should not see "Cat" And "New name for tag" "field" should not exist + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And I should see "Kitten" And I should not see "Cat" @@ -172,6 +170,7 @@ Feature: Users can edit tags to add description or rename And I should see "Turtle" And I should see "Dog" And I should not see "DOG" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And I should see "Turtle" And I should see "Dog" @@ -183,6 +182,7 @@ Feature: Users can edit tags to add description or rename And "New name for tag" "field" should not exist And I should see "Turtle" And I should not see "Penguin" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And I should see "Turtle" And I should not see "Penguin" diff --git a/tag/tests/behat/flag_tags.feature b/tag/tests/behat/flag_tags.feature index 7eda63891eb..b9fade17cf9 100644 --- a/tag/tests/behat/flag_tags.feature +++ b/tag/tests/behat/flag_tags.feature @@ -78,6 +78,7 @@ Feature: Users can flag tags and manager can reset flags And "(1)" "text" should exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element" And "(" "text" should not exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element" And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And "Nicetag" "link" should appear before "Sweartag" "link" And "Sweartag" "link" should appear before "Badtag" "link" diff --git a/tag/tests/behat/standard_tags.feature b/tag/tests/behat/standard_tags.feature index ef06919ff1a..dbc7e98bb0f 100644 --- a/tag/tests/behat/standard_tags.feature +++ b/tag/tests/behat/standard_tags.feature @@ -63,6 +63,7 @@ Feature: Manager can add standard tags and change the tag type of existing tags And "Remove from standard tags" "link" should exist in the "Tag1" "table_row" And "Make standard" "link" should exist in the "Tag2" "table_row" And "Make standard" "link" should exist in the "Tag3" "table_row" + And I navigate to "Appearance > Manage tags" in site administration And I follow "Default collection" And "Make standard" "link" should exist in the "Tag0" "table_row" And "Remove from standard tags" "link" should exist in the "Tag1" "table_row" From c3871a91746f2686671bc0a8aa5b8c3b3ea34ae5 Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Tue, 30 Mar 2021 10:28:56 +0800 Subject: [PATCH 09/50] MDL-70196 navigation: Update to pages to add a URL. - Part of: MDL-69588 Some pages have added an item to the end of the navbar without a link for it. This adds the current page as a url to this navigation node. Doing this brings it in line with all of the other pages around Moodle and also helps with the new navigation changes in theme boost. --- badges/criteria.php | 4 ++-- mod/assign/renderer.php | 2 +- mod/forum/search.php | 6 ++++-- mod/forum/subscribers.php | 2 +- mod/workshop/allocation.php | 2 +- tag/index.php | 5 +++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/badges/criteria.php b/badges/criteria.php index 0a9f290967b..58f9c0bdaf3 100644 --- a/badges/criteria.php +++ b/badges/criteria.php @@ -61,7 +61,7 @@ $PAGE->set_context($context); $PAGE->set_url($currenturl); $PAGE->set_heading($badge->name); $PAGE->set_title($badge->name); -$PAGE->navbar->add($badge->name); +$PAGE->navbar->add($badge->name, $currenturl); $output = $PAGE->get_renderer('core', 'badges'); $msg = optional_param('msg', '', PARAM_TEXT); @@ -105,4 +105,4 @@ if ($badge->has_criteria()) { echo $OUTPUT->box(get_string('addcriteriatext', 'badges')); } -echo $OUTPUT->footer(); \ No newline at end of file +echo $OUTPUT->footer(); diff --git a/mod/assign/renderer.php b/mod/assign/renderer.php index 19476ed5a6c..f32e978ff64 100644 --- a/mod/assign/renderer.php +++ b/mod/assign/renderer.php @@ -233,7 +233,7 @@ class mod_assign_renderer extends plugin_renderer_base { $o = ''; if ($header->subpage) { - $this->page->navbar->add($header->subpage); + $this->page->navbar->add($header->subpage, $header->subpageurl); $args = ['contextname' => $header->context->get_context_name(false, true), 'subpage' => $header->subpage]; $title = get_string('subpagetitle', 'assign', $args); } else { diff --git a/mod/forum/search.php b/mod/forum/search.php index 8bd1bb3f7a3..f72e33b56ac 100644 --- a/mod/forum/search.php +++ b/mod/forum/search.php @@ -139,8 +139,10 @@ $strpage = get_string("page"); if (!$search || $showform) { - $PAGE->navbar->add($strforums, new moodle_url('/mod/forum/index.php', array('id'=>$course->id))); - $PAGE->navbar->add(get_string('advancedsearch', 'forum')); + $url = new moodle_url('/mod/forum/index.php', array('id' => $course->id)); + $PAGE->navbar->add($strforums, $url); + $url = new moodle_url('/mod/forum/search.php', array('id' => $course->id)); + $PAGE->navbar->add(get_string('advancedsearch', 'forum'), $url); $PAGE->set_title($strsearch); $PAGE->set_heading($course->fullname); diff --git a/mod/forum/subscribers.php b/mod/forum/subscribers.php index 55665799ae9..2e62c44df5a 100644 --- a/mod/forum/subscribers.php +++ b/mod/forum/subscribers.php @@ -97,7 +97,7 @@ if (data_submitted()) { } $strsubscribers = get_string("subscribers", "forum"); -$PAGE->navbar->add($strsubscribers); +$PAGE->navbar->add($strsubscribers, $url); $PAGE->set_title($strsubscribers); $PAGE->set_heading($COURSE->fullname); if (has_capability('mod/forum:managesubscriptions', $context) && \mod_forum\subscriptions::is_forcesubscribed($forum) === false) { diff --git a/mod/workshop/allocation.php b/mod/workshop/allocation.php index 5901bcff703..ebc5abaff02 100644 --- a/mod/workshop/allocation.php +++ b/mod/workshop/allocation.php @@ -46,7 +46,7 @@ require_capability('mod/workshop:allocate', $context); $PAGE->set_title($workshop->name); $PAGE->set_heading($course->fullname); -$PAGE->navbar->add(get_string('allocation', 'workshop')); +$PAGE->navbar->add(get_string('allocation', 'workshop'), $workshop->allocation_url($method)); $allocator = $workshop->allocator_instance($method); $initresult = $allocator->init(); diff --git a/tag/index.php b/tag/index.php index b0373168bd6..a5857b19b00 100644 --- a/tag/index.php +++ b/tag/index.php @@ -78,7 +78,8 @@ if ($ctx && ($context = context::instance_by_id($ctx, IGNORE_MISSING)) && $conte $tagcollid = $tag->tagcollid; -$PAGE->set_url($tag->get_view_url($exclusivemode, $fromctx, $ctx, $rec)); +$pageurl = $tag->get_view_url($exclusivemode, $fromctx, $ctx, $rec); +$PAGE->set_url($pageurl); $PAGE->set_subpage($tag->id); $tagnode = $PAGE->navigation->find('tags', null); $tagnode->make_active(); @@ -97,7 +98,7 @@ if ($PAGE->user_allowed_editing()) { $buttons .= $OUTPUT->edit_button(clone($PAGE->url)); } -$PAGE->navbar->add($tagname); +$PAGE->navbar->add($tagname, $pageurl); $PAGE->set_title(get_string('tag', 'tag') .' - '. $tag->get_display_name()); $PAGE->set_heading($COURSE->fullname); $PAGE->set_button($buttons); From c8422f4576a8eef511e6f1b78226ff011c902a2c Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Tue, 30 Mar 2021 10:39:10 +0800 Subject: [PATCH 10/50] MDL-70196 mod_assign: Allow the last navbar item to have a link. - Part of: MDL-69588 This updates a method to allow the last item in the breadcrumb navbar to have a URL. --- mod/assign/locallib.php | 11 ++++++----- mod/assign/renderable.php | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php index d35131f5091..05c71bcbe5a 100644 --- a/mod/assign/locallib.php +++ b/mod/assign/locallib.php @@ -4564,18 +4564,19 @@ class assign { $gradingoptionsform->set_data($gradingoptionsdata); $actionformtext = $this->get_renderer()->render($gradingactions); + + $currenturl = new moodle_url('/mod/assign/view.php', ['id' => $this->get_course_module()->id, 'action' => 'grading']); + $header = new assign_header($this->get_instance(), $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), - $actionformtext); + $actionformtext, + '', + $currenturl); $o .= $this->get_renderer()->render($header); - $currenturl = $CFG->wwwroot . - '/mod/assign/view.php?id=' . - $this->get_course_module()->id . - '&action=grading'; $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true); diff --git a/mod/assign/renderable.php b/mod/assign/renderable.php index 936be1649af..295fc3c93f0 100644 --- a/mod/assign/renderable.php +++ b/mod/assign/renderable.php @@ -671,6 +671,8 @@ class assign_header implements renderable { public $preface = ''; /** @var string $postfix optional postfix (text to show after the intro) */ public $postfix = ''; + /** @var moodle_url $subpageurl link for the subpage */ + public $subpageurl = null; /** * Constructor @@ -688,7 +690,8 @@ class assign_header implements renderable { $coursemoduleid, $subpage='', $preface='', - $postfix='') { + $postfix='', + moodle_url $subpageurl = null) { $this->assign = $assign; $this->context = $context; $this->showintro = $showintro; @@ -696,6 +699,7 @@ class assign_header implements renderable { $this->subpage = $subpage; $this->preface = $preface; $this->postfix = $postfix; + $this->subpageurl = $subpageurl; } } From f28535082ca9d9ed16ca4a44a9660970ebcb8dcb Mon Sep 17 00:00:00 2001 From: Adrian Greeve Date: Tue, 30 Mar 2021 10:39:51 +0800 Subject: [PATCH 11/50] MDL-70196 behat: Add a new step for navigating with the navbar. - Part of: MDL-69588 A lot of tests work on the basis that you can follow the last item of the breadcrumb nav bar. This is not the case. This step will first check to see if the page is already the one requested. If it is then nothing further needs to happen, otherwise we follow the link in the navbar. --- grade/grading/tests/behat/behat_grading.php | 2 +- lib/tests/behat/behat_navigation.php | 16 ++++++++++++++++ mod/choice/tests/behat/behat_mod_choice.php | 4 ++-- mod/data/tests/behat/behat_mod_data.php | 2 +- mod/forum/tests/behat/behat_mod_forum.php | 8 ++++---- mod/glossary/tests/behat/behat_mod_glossary.php | 2 +- .../behat/behat_workshopallocation_manual.php | 2 +- mod/workshop/tests/behat/behat_mod_workshop.php | 8 ++++---- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/grade/grading/tests/behat/behat_grading.php b/grade/grading/tests/behat/behat_grading.php index 1847d07c1fc..3841dbe150f 100644 --- a/grade/grading/tests/behat/behat_grading.php +++ b/grade/grading/tests/behat/behat_grading.php @@ -85,7 +85,7 @@ class behat_grading extends behat_base { // Step to access the user grade page from the grading page. $gradetext = get_string('gradeverb'); - $this->execute('behat_general::click_link', $this->escape($activityname)); + $this->execute('behat_navigation::go_to_breadcrumb_location', $this->escape($activityname)); $this->execute('behat_navigation::i_navigate_to_in_current_page_administration', get_string('viewgrading', 'mod_assign')); diff --git a/lib/tests/behat/behat_navigation.php b/lib/tests/behat/behat_navigation.php index 053010c6b0e..6279c1b9275 100644 --- a/lib/tests/behat/behat_navigation.php +++ b/lib/tests/behat/behat_navigation.php @@ -1065,4 +1065,20 @@ class behat_navigation extends behat_base { } $this->execute('behat_general::i_visit', [$url]); } + + + /** + * First checks to see if we are on this page via the breadcrumb. If not we then attempt to follow the link name given. + * + * @param string $pagename Name of the breadcrumb item to check and follow. + */ + public function go_to_breadcrumb_location(string $pagename): void { + $link = $this->getSession()->getPage()->find( + 'xpath', + "//nav[@aria-label='Navigation bar']/ol/li[last()][contains(normalize-space(.), '" . $pagename . "')]" + ); + if (!$link) { + $this->execute("behat_general::click_link", $pagename); + } + } } diff --git a/mod/choice/tests/behat/behat_mod_choice.php b/mod/choice/tests/behat/behat_mod_choice.php index 2ba915656b7..f35d02e52f3 100644 --- a/mod/choice/tests/behat/behat_mod_choice.php +++ b/mod/choice/tests/behat/behat_mod_choice.php @@ -46,7 +46,7 @@ class behat_mod_choice extends behat_base { * @return array */ public function I_choose_option_from_activity($option, $choiceactivity) { - $this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($choiceactivity), 'choice activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($choiceactivity)); $this->execute('behat_forms::i_set_the_field_to', array( $this->escape($option), 1)); @@ -68,7 +68,7 @@ class behat_mod_choice extends behat_base { $behatforms = behat_context_helper::get('behat_forms'); // Go to choice activity. - $this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($choiceactivity), 'choice activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($choiceactivity)); // Wait for page to be loaded. $this->wait_for_pending_js(); diff --git a/mod/data/tests/behat/behat_mod_data.php b/mod/data/tests/behat/behat_mod_data.php index 1f59bc6cc5d..ce9748ffbd2 100644 --- a/mod/data/tests/behat/behat_mod_data.php +++ b/mod/data/tests/behat/behat_mod_data.php @@ -48,7 +48,7 @@ class behat_mod_data extends behat_base { * @param TableNode $fielddata */ public function i_add_a_field_to_database_and_i_fill_the_form_with($fieldtype, $activityname, TableNode $fielddata) { - $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($activityname), 'data activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($activityname)); // Open "Fields" tab if it is not already open. $fieldsstr = get_string('fields', 'mod_data'); diff --git a/mod/forum/tests/behat/behat_mod_forum.php b/mod/forum/tests/behat/behat_mod_forum.php index f6025b6ec62..28150162b98 100644 --- a/mod/forum/tests/behat/behat_mod_forum.php +++ b/mod/forum/tests/behat/behat_mod_forum.php @@ -113,7 +113,7 @@ class behat_mod_forum extends behat_base { */ public function i_reply_post_from_forum_using_an_inpage_reply_with($postsubject, $forumname, TableNode $table) { // Navigate to forum. - $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname)); $this->execute('behat_general::click_link', $this->escape($postsubject)); $this->execute('behat_general::click_link', get_string('reply', 'forum')); @@ -132,7 +132,7 @@ class behat_mod_forum extends behat_base { */ public function i_navigate_to_post_in_forum($postsubject, $forumname) { // Navigate to forum discussion. - $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname)); $this->execute('behat_general::click_link', $this->escape($postsubject)); } @@ -474,7 +474,7 @@ class behat_mod_forum extends behat_base { */ protected function add_new_discussion($forumname, TableNode $table, $buttonstr) { // Navigate to forum. - $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname)); $this->execute('behat_general::click_link', $buttonstr); $this->execute('behat_forms::press_button', get_string('showadvancededitor')); @@ -493,7 +493,7 @@ class behat_mod_forum extends behat_base { */ protected function add_new_discussion_inline($forumname, TableNode $table, $buttonstr) { // Navigate to forum. - $this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname)); $this->execute('behat_general::click_link', $buttonstr); $this->fill_new_discussion_form($table); } diff --git a/mod/glossary/tests/behat/behat_mod_glossary.php b/mod/glossary/tests/behat/behat_mod_glossary.php index bf84100a699..9ff7c2afb12 100644 --- a/mod/glossary/tests/behat/behat_mod_glossary.php +++ b/mod/glossary/tests/behat/behat_mod_glossary.php @@ -61,7 +61,7 @@ class behat_mod_glossary extends behat_base { */ public function i_add_a_glossary_entries_category_named($categoryname) { - $this->execute("behat_general::click_link", get_string('categoryview', 'mod_glossary')); + $this->execute("behat_navigation::go_to_breadcrumb_location", get_string('categoryview', 'mod_glossary')); $this->execute("behat_forms::press_button", get_string('editcategories', 'mod_glossary')); diff --git a/mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php b/mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php index 4ad7fd94bf1..bc33974f6b4 100644 --- a/mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php +++ b/mod/workshop/allocation/manual/tests/behat/behat_workshopallocation_manual.php @@ -87,7 +87,7 @@ class behat_workshopallocation_manual extends behat_base { * @param TableNode $table should have one column with title 'Reviewer' and another with title 'Participant' (or 'Reviewee') */ public function i_allocate_submissions_in_workshop_as($workshopname, TableNode $table) { - $this->execute("behat_general::i_click_on_in_the", [$this->escape($workshopname), 'link', 'page', 'region']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname); $this->execute('behat_navigation::i_navigate_to_in_current_page_administration', get_string('allocate', 'workshop')); $rows = $table->getRows(); $reviewer = $participant = null; diff --git a/mod/workshop/tests/behat/behat_mod_workshop.php b/mod/workshop/tests/behat/behat_mod_workshop.php index 734bb8c49f3..51c1633d2b9 100644 --- a/mod/workshop/tests/behat/behat_mod_workshop.php +++ b/mod/workshop/tests/behat/behat_mod_workshop.php @@ -52,7 +52,7 @@ class behat_mod_workshop extends behat_base { $xpath = "//*[@class='userplan']/descendant::div[./span[contains(.,$phaseliteral)]]"; $continue = $this->escape(get_string('continue')); - $this->execute("behat_general::i_click_on_in_the", [$this->escape($workshopname), 'link', 'page', 'region']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname); $this->execute('behat_general::i_click_on_in_the', array('a.action-icon', "css_element", $this->escape($xpath), "xpath_element") @@ -73,7 +73,7 @@ class behat_mod_workshop extends behat_base { $savechanges = $this->escape(get_string('savechanges')); $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ownsubmission ')]/descendant::*[@type='submit']"; - $this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($workshopname), 'workshop activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname); $this->execute("behat_general::i_click_on", array($xpath, "xpath_element")); @@ -90,7 +90,7 @@ class behat_mod_workshop extends behat_base { * @param TableNode $table data to fill the submission form with, must contain 'Title' */ public function i_edit_assessment_form_in_workshop_as($workshopname, $table) { - $this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($workshopname), 'workshop activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname); $this->execute('behat_navigation::i_navigate_to_in_current_page_administration', get_string('editassessmentform', 'workshop')); @@ -116,7 +116,7 @@ class behat_mod_workshop extends behat_base { $assess = $this->escape(get_string('assess', 'workshop')); $saveandclose = $this->escape(get_string('saveandclose', 'workshop')); - $this->execute("behat_navigation::i_am_on_page_instance", [$workshopname, 'workshop activity']); + $this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname); $this->execute('behat_general::i_click_on_in_the', array($assess, "button", $xpath, "xpath_element") From 19fd786270013807525a53eda5e041be974681fe Mon Sep 17 00:00:00 2001 From: Mihail Geshoski Date: Thu, 3 Jun 2021 15:34:34 +0800 Subject: [PATCH 12/50] MDL-71680 navigation: Enable forcing navigation_node into "more" menu - Part of: MDL-69588 Adds new property 'forceintomoremenu' and new setter method in the navigation_node class to enable forcing a navigation note into a "more" menu whenever possible. --- lib/navigationlib.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/navigationlib.php b/lib/navigationlib.php index de3f0bd75a5..da5dc7cb816 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -149,6 +149,8 @@ class navigation_node implements renderable { public $requiresajaxloading = false; /** @var bool If set to true this node will be added to the "flat" navigation */ public $showinflatnavigation = false; + /** @var bool If set to true this node will be forced into a "more" menu whenever possible */ + public $forceintomoremenu = false; /** * Constructs a new navigation_node @@ -840,6 +842,18 @@ class navigation_node implements renderable { return $this->action; } + /** + * Sets whether the node and its children should be added into a "more" menu whenever possible. + * + * @param bool $forceintomoremenu + */ + public function set_force_into_more_menu(bool $forceintomoremenu = false) { + $this->forceintomoremenu = $forceintomoremenu; + foreach ($this->children as $child) { + $child->forceintomoremenu = $forceintomoremenu; + } + } + /** * Add the menu item to handle locking and unlocking of a conext. * From da67b468fadc7ecccfaaceedbd5229dd20030da0 Mon Sep 17 00:00:00 2001 From: Bas Brands Date: Mon, 31 May 2021 17:42:44 +0200 Subject: [PATCH 13/50] MDL-70208 theme_boost: frontend for primary navigation - Part of: MDL-69588 --- lang/en/moodle.php | 1 + lib/amd/build/moremenu.min.js | 2 + lib/amd/build/moremenu.min.js.map | 1 + lib/amd/src/moremenu.js | 174 ++++++++++++++++++ lib/outputrenderers.php | 14 ++ lib/templates/moremenu.mustache | 66 +++++++ lib/templates/moremenu_children.mustache | 56 ++++++ theme/boost/layout/columns2.php | 10 +- theme/boost/scss/moodle.scss | 2 + theme/boost/scss/moodle/core.scss | 2 + theme/boost/scss/moodle/moremenu.scss | 36 ++++ .../boost/scss/moodle/primarynavigation.scss | 8 + theme/boost/scss/moodle/variables.scss | 2 + theme/boost/style/moodle.css | 36 +++- theme/boost/templates/navbar.mustache | 4 +- theme/classic/style/moodle.css | 36 +++- 16 files changed, 441 insertions(+), 9 deletions(-) create mode 100644 lib/amd/build/moremenu.min.js create mode 100644 lib/amd/build/moremenu.min.js.map create mode 100644 lib/amd/src/moremenu.js create mode 100644 lib/templates/moremenu.mustache create mode 100644 lib/templates/moremenu_children.mustache create mode 100644 theme/boost/scss/moodle/moremenu.scss create mode 100644 theme/boost/scss/moodle/primarynavigation.scss diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 22297f8ae56..90266b87ea7 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1352,6 +1352,7 @@ $string['movefilestohere'] = 'Move files to here'; $string['movefull'] = 'Move {$a} to this location'; $string['movehere'] = 'Move to here'; $string['moveleft'] = 'Move left'; +$string['moremenu'] = 'More'; $string['moveright'] = 'Move right'; $string['movesection'] = 'Move section {$a}'; $string['moveselectedcategoriesto'] = 'Move selected categories to'; diff --git a/lib/amd/build/moremenu.min.js b/lib/amd/build/moremenu.min.js new file mode 100644 index 00000000000..e327b536683 --- /dev/null +++ b/lib/amd/build/moremenu.min.js @@ -0,0 +1,2 @@ +define ("core/moremenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},d=6,e=function(a){var b=a.parentNode.offsetHeight+1,f=a.querySelector(c.regions.moredropdown),g=a.querySelector(c.regions.morebutton),h=a.querySelector("."+c.classes.dropdowntoggle);if(a.offsetHeight>b||a.children.length>d){g.classList.remove(c.classes.hidden);var i=Array.from(a.children).reverse();i.forEach(function(e){if(!e.classList.contains(c.classes.dropdownmoremenu)){if(a.offsetHeight>b||a.children.length>d){var g=a.removeChild(e),i=g.querySelector("."+c.classes.navlink);if(i&&!i.hasAttribute("role")){i.setAttribute("role","menuitem")}if(i.classList.contains(c.classes.active)){h.classList.add(c.classes.active)}i.classList.remove(c.classes.navlink);i.classList.add(c.classes.dropdownitem);f.prepend(g)}}})}else{if("children"in f){var j=Array.from(f.children);j.forEach(function(e){if(a.offsetHeightb){e(a)}}a.parentNode.classList.add(c.classes.observed)},f=function(a){e(a);window.addEventListener("resize",function(){e(a)});var d=function(a){var b=a.target.parentNode.querySelector(c.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+c.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(c.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",d,!0);a.addEventListener("click",d,!0)})})};a.default=f;return a.default}); +//# sourceMappingURL=moremenu.min.js.map diff --git a/lib/amd/build/moremenu.min.js.map b/lib/amd/build/moremenu.min.js.map new file mode 100644 index 00000000000..3ce9876691e --- /dev/null +++ b/lib/amd/build/moremenu.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/moremenu.js"],"names":["Selectors","regions","moredropdown","morebutton","classes","dropdownitem","dropdownmoremenu","dropdowntoggle","hidden","active","nav","navlink","observed","attributes","menu","maxMenuItems","autoCollapse","maxHeight","parentNode","offsetHeight","moreDropdown","querySelector","moreButton","dropdownToggle","children","length","classList","remove","menuNodes","Array","from","reverse","forEach","item","contains","lastNode","removeChild","navLink","hasAttribute","setAttribute","add","prepend","currentAttribute","getAttribute","removeAttribute","insertBefore","window","addEventListener","toggledropdown","e","innerMenu","target","toggle","stopPropagation","on","querySelectorAll","dropdown","removeEventListener"],"mappings":"0IAyBA,uD,GAIMA,CAAAA,CAAS,CAAG,CACdC,OAAO,CAAE,CACLC,YAAY,CAAE,gCADT,CAELC,UAAU,CAAE,8BAFP,CADK,CAKdC,OAAO,CAAE,CACLC,YAAY,CAAE,eADT,CAELC,gBAAgB,CAAE,kBAFb,CAGLC,cAAc,CAAE,iBAHX,CAILC,MAAM,CAAE,QAJH,CAKLC,MAAM,CAAE,QALH,CAMLC,GAAG,CAAE,KANA,CAOLC,OAAO,CAAE,UAPJ,CAQLC,QAAQ,CAAE,UARL,CALK,CAedC,UAAU,CAAE,CACRC,IAAI,CAAE,iBADE,CAfE,C,CAoBZC,CAAY,CAAG,C,CAMfC,CAAY,CAAG,SAAAF,CAAI,CAAI,IAEnBG,CAAAA,CAAS,CAAGH,CAAI,CAACI,UAAL,CAAgBC,YAAhB,CAA+B,CAFxB,CAInBC,CAAY,CAAGN,CAAI,CAACO,aAAL,CAAmBrB,CAAS,CAACC,OAAV,CAAkBC,YAArC,CAJI,CAKnBoB,CAAU,CAAGR,CAAI,CAACO,aAAL,CAAmBrB,CAAS,CAACC,OAAV,CAAkBE,UAArC,CALM,CAOnBoB,CAAc,CAAGT,CAAI,CAACO,aAAL,CAAmB,IAAMrB,CAAS,CAACI,OAAV,CAAkBG,cAA3C,CAPE,CAYzB,GAAIO,CAAI,CAACK,YAAL,CAAoBF,CAApB,EAAiCH,CAAI,CAACU,QAAL,CAAcC,MAAd,CAAuBV,CAA5D,CAA0E,CAEtEO,CAAU,CAACI,SAAX,CAAqBC,MAArB,CAA4B3B,CAAS,CAACI,OAAV,CAAkBI,MAA9C,EAEA,GAAMoB,CAAAA,CAAS,CAAGC,KAAK,CAACC,IAAN,CAAWhB,CAAI,CAACU,QAAhB,EAA0BO,OAA1B,EAAlB,CACAH,CAAS,CAACI,OAAV,CAAkB,SAAAC,CAAI,CAAI,CACtB,GAAI,CAACA,CAAI,CAACP,SAAL,CAAeQ,QAAf,CAAwBlC,CAAS,CAACI,OAAV,CAAkBE,gBAA1C,CAAL,CAAkE,CAI9D,GAAIQ,CAAI,CAACK,YAAL,CAAoBF,CAApB,EAAiCH,CAAI,CAACU,QAAL,CAAcC,MAAd,CAAuBV,CAA5D,CAA0E,IAChEoB,CAAAA,CAAQ,CAAGrB,CAAI,CAACsB,WAAL,CAAiBH,CAAjB,CADqD,CAEhEI,CAAO,CAAGF,CAAQ,CAACd,aAAT,CAAuB,IAAMrB,CAAS,CAACI,OAAV,CAAkBO,OAA/C,CAFsD,CAGtE,GAAI0B,CAAO,EAAI,CAACA,CAAO,CAACC,YAAR,CAAqB,MAArB,CAAhB,CAA8C,CAG1CD,CAAO,CAACE,YAAR,CAAqB,MAArB,CAA6B,UAA7B,CACH,CAID,GAAIF,CAAO,CAACX,SAAR,CAAkBQ,QAAlB,CAA2BlC,CAAS,CAACI,OAAV,CAAkBK,MAA7C,CAAJ,CAA0D,CACtDc,CAAc,CAACG,SAAf,CAAyBc,GAAzB,CAA6BxC,CAAS,CAACI,OAAV,CAAkBK,MAA/C,CACH,CAID4B,CAAO,CAACX,SAAR,CAAkBC,MAAlB,CAAyB3B,CAAS,CAACI,OAAV,CAAkBO,OAA3C,EACA0B,CAAO,CAACX,SAAR,CAAkBc,GAAlB,CAAsBxC,CAAS,CAACI,OAAV,CAAkBC,YAAxC,EACAe,CAAY,CAACqB,OAAb,CAAqBN,CAArB,CACH,CACJ,CACJ,CA3BD,CA4BH,CAjCD,IAiCO,CAIH,GAAI,YAAcf,CAAAA,CAAlB,CAAgC,CAC5B,GAAMQ,CAAAA,CAAS,CAAGC,KAAK,CAACC,IAAN,CAAWV,CAAY,CAACI,QAAxB,CAAlB,CACAI,CAAS,CAACI,OAAV,CAAkB,SAAAC,CAAI,CAAI,CAEtB,GAAInB,CAAI,CAACK,YAAL,CAAoBF,CAApB,EAAiCH,CAAI,CAACU,QAAL,CAAcC,MAAd,CAAuBV,CAA5D,CAA0E,IAChEoB,CAAAA,CAAQ,CAAGf,CAAY,CAACgB,WAAb,CAAyBH,CAAzB,CADqD,CAEhEI,CAAO,CAAGF,CAAQ,CAACd,aAAT,CAAuB,IAAMrB,CAAS,CAACI,OAAV,CAAkBC,YAA/C,CAFsD,CAGtE,GAAIgC,CAAJ,CAAa,CACT,GAAMK,CAAAA,CAAgB,CAAGL,CAAO,CAACM,YAAR,CAAqB,MAArB,CAAzB,CACA,GAAyB,UAArB,GAAAD,CAAJ,CAAqC,CACjCL,CAAO,CAACO,eAAR,CAAwB,MAAxB,CACH,CACJ,CAID,GAAIP,CAAO,CAACX,SAAR,CAAkBQ,QAAlB,CAA2BlC,CAAS,CAACI,OAAV,CAAkBK,MAA7C,CAAJ,CAA0D,CACtDc,CAAc,CAACG,SAAf,CAAyBC,MAAzB,CAAgC3B,CAAS,CAACI,OAAV,CAAkBK,MAAlD,CACH,CACD4B,CAAO,CAACX,SAAR,CAAkBC,MAAlB,CAAyB3B,CAAS,CAACI,OAAV,CAAkBC,YAA3C,EACAgC,CAAO,CAACX,SAAR,CAAkBc,GAAlB,CAAsBxC,CAAS,CAACI,OAAV,CAAkBO,OAAxC,EACAG,CAAI,CAAC+B,YAAL,CAAkBV,CAAlB,CAA4Bb,CAA5B,CACH,CACJ,CArBD,EAwBA,GAAyB,CAArB,GAAAM,CAAS,CAACH,MAAd,CAA4B,CACxBH,CAAU,CAACI,SAAX,CAAqBc,GAArB,CAAyBxC,CAAS,CAACI,OAAV,CAAkBI,MAA3C,CACH,CACJ,CAED,GAAIM,CAAI,CAACK,YAAL,CAAoBF,CAAxB,CAAmC,CAC/BD,CAAY,CAACF,CAAD,CACf,CACJ,CACDA,CAAI,CAACI,UAAL,CAAgBQ,SAAhB,CAA0Bc,GAA1B,CAA8BxC,CAAS,CAACI,OAAV,CAAkBQ,QAAhD,CACH,C,GAOc,SAAAE,CAAI,CAAI,CACnBE,CAAY,CAACF,CAAD,CAAZ,CAGAgC,MAAM,CAACC,gBAAP,CAAwB,QAAxB,CAAkC,UAAM,CACpC/B,CAAY,CAACF,CAAD,CACf,CAFD,EAIA,GAAMkC,CAAAA,CAAc,CAAG,SAAAC,CAAC,CAAI,CACxB,GAAMC,CAAAA,CAAS,CAAGD,CAAC,CAACE,MAAF,CAASjC,UAAT,CAAoBG,aAApB,CAAkCrB,CAAS,CAACa,UAAV,CAAqBC,IAAvD,CAAlB,CACA,GAAIoC,CAAJ,CAAe,CACXA,CAAS,CAACxB,SAAV,CAAoB0B,MAApB,CAA2B,MAA3B,CACH,CACDH,CAAC,CAACI,eAAF,EACH,CAND,CAWA,cAAE,IAAMrD,CAAS,CAACI,OAAV,CAAkBE,gBAA1B,EAA4CgD,EAA5C,CAA+C,kBAA/C,CAAmE,UAAW,CAC1E,GAAMlC,CAAAA,CAAY,CAAGN,CAAI,CAACO,aAAL,CAAmBrB,CAAS,CAACC,OAAV,CAAkBC,YAArC,CAArB,CACAkB,CAAY,CAACmC,gBAAb,CAA8B,WAA9B,EAA2CvB,OAA3C,CAAmD,SAACwB,CAAD,CAAc,CAC7DA,CAAQ,CAACC,mBAAT,CAA6B,OAA7B,CAAsCT,CAAtC,KACAQ,CAAQ,CAACT,gBAAT,CAA0B,OAA1B,CAAmCC,CAAnC,IACH,CAHD,CAIH,CAND,CAOH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Moves wrapping navigation items into a more menu.\n *\n * @module core/moremenu\n * @package core\n * @copyright 2021 Moodle\n * @author Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\n/**\n * Moremenu selectors.\n */\nconst Selectors = {\n regions: {\n moredropdown: '[data-region=\"moredropdown\"]',\n morebutton: '[data-region=\"morebutton\"]'\n },\n classes: {\n dropdownitem: 'dropdown-item',\n dropdownmoremenu: 'dropdownmoremenu',\n dropdowntoggle: 'dropdown-toggle',\n hidden: 'd-none',\n active: 'active',\n nav: 'nav',\n navlink: 'nav-link',\n observed: 'observed',\n },\n attributes: {\n menu: '[role=\"menu\"]'\n }\n};\n\nconst maxMenuItems = 6;\n/**\n * Auto Collapse navigation items that wrap into a dropdown menu.\n *\n * @param {HTMLElement} menu The navbar container.\n */\nconst autoCollapse = menu => {\n\n const maxHeight = menu.parentNode.offsetHeight + 1;\n\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n\n // If the menuitems wrap and the menu height is larger than the height of the\n // parent. Or if the number if menuitems is larger than the maximum menu items\n // allowed then start pushing navlinks into the moreDropdown.\n if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) {\n\n moreButton.classList.remove(Selectors.classes.hidden);\n\n const menuNodes = Array.from(menu.children).reverse();\n menuNodes.forEach(item => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) {\n // After moving the menuitems into the moreDropdown check again\n // if the menuheight is still larger then the height of the parent or if the\n // menu still has more items than maxMenuItems.\n if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) {\n const lastNode = menu.removeChild(item);\n const navLink = lastNode.querySelector('.' + Selectors.classes.navlink);\n if (navLink && !navLink.hasAttribute('role')) {\n // Adding the menuitem role so the dropdown includes the\n // Accessibility improvements from theme/boost/amd/src/aria.js\n navLink.setAttribute('role', 'menuitem');\n }\n\n // If there are navLinks that contain an active link in the moreDropdown\n // make the dropdownToggle in the moreButton active.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.add(Selectors.classes.active);\n }\n\n // Change the styling of the navLink to a dropdownitem and push it into\n // the moreDropdown.\n navLink.classList.remove(Selectors.classes.navlink);\n navLink.classList.add(Selectors.classes.dropdownitem);\n moreDropdown.prepend(lastNode);\n }\n }\n });\n } else {\n // If the the menu height is smaller than the height of the parent and there are\n // less than the maximum items in the menu, then try returning navlinks to the menu.\n\n if ('children' in moreDropdown) {\n const menuNodes = Array.from(moreDropdown.children);\n menuNodes.forEach(item => {\n\n if (menu.offsetHeight < maxHeight && menu.children.length < maxMenuItems) {\n const lastNode = moreDropdown.removeChild(item);\n const navLink = lastNode.querySelector('.' + Selectors.classes.dropdownitem);\n if (navLink) {\n const currentAttribute = navLink.getAttribute('role');\n if (currentAttribute === 'menuitem') {\n navLink.removeAttribute('role');\n }\n }\n\n // Stop displaying the active state on the dropdownToggle if\n // the active navlink is removed.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.remove(Selectors.classes.active);\n }\n navLink.classList.remove(Selectors.classes.dropdownitem);\n navLink.classList.add(Selectors.classes.navlink);\n menu.insertBefore(lastNode, moreButton);\n }\n });\n\n // If there are no more menuNodes in the dropdown we can hide the moreButton.\n if (menuNodes.length === 0) {\n moreButton.classList.add(Selectors.classes.hidden);\n }\n }\n\n if (menu.offsetHeight > maxHeight) {\n autoCollapse(menu);\n }\n }\n menu.parentNode.classList.add(Selectors.classes.observed);\n};\n\n/**\n * Initialise the more menus.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n */\nexport default menu => {\n autoCollapse(menu);\n\n // When the screen size changes make sure the menu still fits.\n window.addEventListener('resize', () => {\n autoCollapse(menu);\n });\n\n const toggledropdown = e => {\n const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu);\n if (innerMenu) {\n innerMenu.classList.toggle('show');\n }\n e.stopPropagation();\n };\n\n // If there are dropdowns in the MoreMenu, add a new\n // eventlistener to show the contents on click and prevent the\n // moreMenu from closing.\n $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => {\n dropdown.removeEventListener('click', toggledropdown, true);\n dropdown.addEventListener('click', toggledropdown, true);\n });\n });\n};\n"],"file":"moremenu.min.js"} \ No newline at end of file diff --git a/lib/amd/src/moremenu.js b/lib/amd/src/moremenu.js new file mode 100644 index 00000000000..57b283fb254 --- /dev/null +++ b/lib/amd/src/moremenu.js @@ -0,0 +1,174 @@ +// 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 . + +/** + * Moves wrapping navigation items into a more menu. + * + * @module core/moremenu + * @package core + * @copyright 2021 Moodle + * @author Bas Brands + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import $ from 'jquery'; +/** + * Moremenu selectors. + */ +const Selectors = { + regions: { + moredropdown: '[data-region="moredropdown"]', + morebutton: '[data-region="morebutton"]' + }, + classes: { + dropdownitem: 'dropdown-item', + dropdownmoremenu: 'dropdownmoremenu', + dropdowntoggle: 'dropdown-toggle', + hidden: 'd-none', + active: 'active', + nav: 'nav', + navlink: 'nav-link', + observed: 'observed', + }, + attributes: { + menu: '[role="menu"]' + } +}; + +const maxMenuItems = 6; +/** + * Auto Collapse navigation items that wrap into a dropdown menu. + * + * @param {HTMLElement} menu The navbar container. + */ +const autoCollapse = menu => { + + const maxHeight = menu.parentNode.offsetHeight + 1; + + const moreDropdown = menu.querySelector(Selectors.regions.moredropdown); + const moreButton = menu.querySelector(Selectors.regions.morebutton); + + const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle); + + // If the menuitems wrap and the menu height is larger than the height of the + // parent. Or if the number if menuitems is larger than the maximum menu items + // allowed then start pushing navlinks into the moreDropdown. + if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) { + + moreButton.classList.remove(Selectors.classes.hidden); + + const menuNodes = Array.from(menu.children).reverse(); + menuNodes.forEach(item => { + if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) { + // After moving the menuitems into the moreDropdown check again + // if the menuheight is still larger then the height of the parent or if the + // menu still has more items than maxMenuItems. + if (menu.offsetHeight > maxHeight || menu.children.length > maxMenuItems) { + const lastNode = menu.removeChild(item); + const navLink = lastNode.querySelector('.' + Selectors.classes.navlink); + if (navLink && !navLink.hasAttribute('role')) { + // Adding the menuitem role so the dropdown includes the + // Accessibility improvements from theme/boost/amd/src/aria.js + navLink.setAttribute('role', 'menuitem'); + } + + // If there are navLinks that contain an active link in the moreDropdown + // make the dropdownToggle in the moreButton active. + if (navLink.classList.contains(Selectors.classes.active)) { + dropdownToggle.classList.add(Selectors.classes.active); + } + + // Change the styling of the navLink to a dropdownitem and push it into + // the moreDropdown. + navLink.classList.remove(Selectors.classes.navlink); + navLink.classList.add(Selectors.classes.dropdownitem); + moreDropdown.prepend(lastNode); + } + } + }); + } else { + // If the the menu height is smaller than the height of the parent and there are + // less than the maximum items in the menu, then try returning navlinks to the menu. + + if ('children' in moreDropdown) { + const menuNodes = Array.from(moreDropdown.children); + menuNodes.forEach(item => { + + if (menu.offsetHeight < maxHeight && menu.children.length < maxMenuItems) { + const lastNode = moreDropdown.removeChild(item); + const navLink = lastNode.querySelector('.' + Selectors.classes.dropdownitem); + if (navLink) { + const currentAttribute = navLink.getAttribute('role'); + if (currentAttribute === 'menuitem') { + navLink.removeAttribute('role'); + } + } + + // Stop displaying the active state on the dropdownToggle if + // the active navlink is removed. + if (navLink.classList.contains(Selectors.classes.active)) { + dropdownToggle.classList.remove(Selectors.classes.active); + } + navLink.classList.remove(Selectors.classes.dropdownitem); + navLink.classList.add(Selectors.classes.navlink); + menu.insertBefore(lastNode, moreButton); + } + }); + + // If there are no more menuNodes in the dropdown we can hide the moreButton. + if (menuNodes.length === 0) { + moreButton.classList.add(Selectors.classes.hidden); + } + } + + if (menu.offsetHeight > maxHeight) { + autoCollapse(menu); + } + } + menu.parentNode.classList.add(Selectors.classes.observed); +}; + +/** + * Initialise the more menus. + * + * @param {HTMLElement} menu The navbar moremenu. + */ +export default menu => { + autoCollapse(menu); + + // When the screen size changes make sure the menu still fits. + window.addEventListener('resize', () => { + autoCollapse(menu); + }); + + const toggledropdown = e => { + const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu); + if (innerMenu) { + innerMenu.classList.toggle('show'); + } + e.stopPropagation(); + }; + + // If there are dropdowns in the MoreMenu, add a new + // eventlistener to show the contents on click and prevent the + // moreMenu from closing. + $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() { + const moreDropdown = menu.querySelector(Selectors.regions.moredropdown); + moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => { + dropdown.removeEventListener('click', toggledropdown, true); + dropdown.addEventListener('click', toggledropdown, true); + }); + }); +}; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 09c0fb748a2..9c5d98c9a26 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3808,6 +3808,20 @@ EOD; return $content; } + /** + * Renders a navigation bar into a "more menu" navigation bar + * + * @param array $content + * @param string $navbarstyle navbar-nav or nav-tabs + * @return string + */ + public function more_menu($content, $navbarstyle) { + return $this->render_from_template('core/moremenu', (object) [ + 'nodearray' => $content, + 'navbarstyle' => $navbarstyle + ]); + } + /** * Renders theme links for switching between default and other themes. * diff --git a/lib/templates/moremenu.mustache b/lib/templates/moremenu.mustache new file mode 100644 index 00000000000..0b63ff6c355 --- /dev/null +++ b/lib/templates/moremenu.mustache @@ -0,0 +1,66 @@ +{{! + 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 . +}} +{{! + @template core/moremenu + + The More menu. + + Example context (json): + { + "nodecollection": { + "children": [ + { + "text": "Home", + "action": "/index.php?redirect=0", + "active": "true" + }, + { + "text": "Dashboard", + "action": "/my" + }, + { + "text": "Courses", + "action": "/course" + }, + { + "text": "Site Administration", + "action": "/admin/search.php" + } + ] + } + } +}} + +{{#js}} +require(['core/moremenu'], function(moremenu) { + var moreMenu = document.querySelector('#moremenu-{{ uniqid }}'); + moremenu(moreMenu); +}); +{{/js}} diff --git a/lib/templates/moremenu_children.mustache b/lib/templates/moremenu_children.mustache new file mode 100644 index 00000000000..4e10461f972 --- /dev/null +++ b/lib/templates/moremenu_children.mustache @@ -0,0 +1,56 @@ +{{! + 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 . +}} +{{! + @template core/moremenu_children + + The More menu children + + Example context (json): + { + "divider": "", + "haschildren": "", + "uniqid": "Unique string", + "text": "Moodle community", + "children": "", + "title": "Moodle community", + "url": "https://moodle.org" + } +}} +{{#haschildren}} + +{{/haschildren}} +{{^haschildren}} + +{{/haschildren}} diff --git a/theme/boost/layout/columns2.php b/theme/boost/layout/columns2.php index 384158d94e5..3498b762ca8 100644 --- a/theme/boost/layout/columns2.php +++ b/theme/boost/layout/columns2.php @@ -42,6 +42,11 @@ $hasblocks = strpos($blockshtml, 'data-block=') !== false; $buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_actions(); // If the settings menu will be included in the header then don't add it here. $regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false; + +$primary = new core\navigation\output\primary($PAGE); +$renderer = $PAGE->get_renderer('core'); +$primarymenu = $primary->export_for_template($renderer); + $templatecontext = [ 'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]), 'output' => $OUTPUT, @@ -50,11 +55,10 @@ $templatecontext = [ 'bodyattributes' => $bodyattributes, 'navdraweropen' => $navdraweropen, 'regionmainsettingsmenu' => $regionmainsettingsmenu, - 'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu) + 'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu), + 'primarymoremenu' => $OUTPUT->more_menu(array_merge($primarymenu['primary'], $primarymenu['custom']), 'navbar-nav'), ]; - $nav = $PAGE->flatnav; $templatecontext['flatnavigation'] = $nav; $templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel(); echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext); - diff --git a/theme/boost/scss/moodle.scss b/theme/boost/scss/moodle.scss index 9eb49651a86..9351930aba4 100644 --- a/theme/boost/scss/moodle.scss +++ b/theme/boost/scss/moodle.scss @@ -44,3 +44,5 @@ $breadcrumb-divider-rtl: "◀" !default; @import "moodle/toasts"; @import "moodle/navbar"; @import "moodle/reportbuilder"; +@import "moodle/moremenu"; +@import "moodle/primarynavigation"; diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index f5c33cb2c28..a33e8494ee9 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -378,10 +378,12 @@ img.resize { .action-menu .dropdown-toggle { text-decoration: none; + display: inline-block; } .action-menu { white-space: nowrap; + display: inline; } .block img.resize { diff --git a/theme/boost/scss/moodle/moremenu.scss b/theme/boost/scss/moodle/moremenu.scss new file mode 100644 index 00000000000..743fe123095 --- /dev/null +++ b/theme/boost/scss/moodle/moremenu.scss @@ -0,0 +1,36 @@ +.moremenu { + opacity: 0; + height: $moremenu-height; + &.observed { + opacity: 1; + } + .nav-link { + height: $moremenu-height; + display: flex; + align-items: center; + } + // Styling for dropdown menus inside the MoreButton. + .dropdownmoremenu > .dropdown-menu { + & > .dropdown-item { + padding: 0; + } + .dropdown-menu { + position: static; + padding: 0; + border: 0; + &.show { + display: block; + } + .dropdown-item { + background-color: $gray-100; + @include hover-focus() { + color: $dropdown-link-hover-color; + @include gradient-bg($dropdown-link-active-bg); + } + } + .dropdown-divider { + display: none; + } + } + } +} diff --git a/theme/boost/scss/moodle/primarynavigation.scss b/theme/boost/scss/moodle/primarynavigation.scss new file mode 100644 index 00000000000..e6f55f217a4 --- /dev/null +++ b/theme/boost/scss/moodle/primarynavigation.scss @@ -0,0 +1,8 @@ +.navbar.fixed-top { + .moremenu { + height: $navbar-height; + .nav-link { + height: $navbar-height; + } + } +} diff --git a/theme/boost/scss/moodle/variables.scss b/theme/boost/scss/moodle/variables.scss index 70f1be5febf..3b322a2e4ef 100644 --- a/theme/boost/scss/moodle/variables.scss +++ b/theme/boost/scss/moodle/variables.scss @@ -27,3 +27,5 @@ $course-content-maxwidth: 800px; $box-shadow-drawer-left: -0.25rem .25rem .8rem rgba($black, .025) !default; $box-shadow-drawer-right: 0 .25rem .8rem rgba($black, .025) !default; + +$moremenu-height: 40px !default; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index d4d8668592d..0445fab5085 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -9987,10 +9987,12 @@ img.resize { width: 1em; } .action-menu .dropdown-toggle { - text-decoration: none; } + text-decoration: none; + display: inline-block; } .action-menu { - white-space: nowrap; } + white-space: nowrap; + display: inline; } .block img.resize { height: 0.9em; @@ -20036,6 +20038,36 @@ div.editor_atto_toolbar button .icon { text-overflow: clip; word-break: break-all; } +.moremenu { + opacity: 0; + height: 40px; } + .moremenu.observed { + opacity: 1; } + .moremenu .nav-link { + height: 40px; + display: flex; + align-items: center; } + .moremenu .dropdownmoremenu > .dropdown-menu > .dropdown-item { + padding: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu { + position: static; + padding: 0; + border: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu.show { + display: block; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item { + background-color: #f8f9fa; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:hover, .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:focus { + color: #fff; + background-color: #0f6fc5; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-divider { + display: none; } + +.navbar.fixed-top .moremenu { + height: 50px; } + .navbar.fixed-top .moremenu .nav-link { + height: 50px; } + body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/theme/boost/templates/navbar.mustache b/theme/boost/templates/navbar.mustache index 993d899b7a9..70e10705ab1 100644 --- a/theme/boost/templates/navbar.mustache +++ b/theme/boost/templates/navbar.mustache @@ -53,9 +53,9 @@ {{{ sitename }}} + {{{primarymoremenu}}} + diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 958e79e1b87..3b6787d615d 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -10199,10 +10199,12 @@ img.resize { width: 1em; } .action-menu .dropdown-toggle { - text-decoration: none; } + text-decoration: none; + display: inline-block; } .action-menu { - white-space: nowrap; } + white-space: nowrap; + display: inline; } .block img.resize { height: 0.9em; @@ -20227,6 +20229,36 @@ div.editor_atto_toolbar button .icon { text-overflow: clip; word-break: break-all; } +.moremenu { + opacity: 0; + height: 40px; } + .moremenu.observed { + opacity: 1; } + .moremenu .nav-link { + height: 40px; + display: flex; + align-items: center; } + .moremenu .dropdownmoremenu > .dropdown-menu > .dropdown-item { + padding: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu { + position: static; + padding: 0; + border: 0; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu.show { + display: block; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item { + background-color: #f8f9fa; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:hover, .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-item:focus { + color: #fff; + background-color: #0f6fc5; } + .moremenu .dropdownmoremenu > .dropdown-menu .dropdown-menu .dropdown-divider { + display: none; } + +.navbar.fixed-top .moremenu { + height: 50px; } + .navbar.fixed-top .moremenu .nav-link { + height: 50px; } + body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } From 527562d12fe5bce0ebd57ce3fc749c4362fcfc6a Mon Sep 17 00:00:00 2001 From: Bas Brands Date: Tue, 1 Jun 2021 16:35:47 +0200 Subject: [PATCH 14/50] MDL-70202 theme_boost: frontend for secondary navigation - Part of: MDL-69588 --- lib/outputrenderers.php | 18 ++++++++++++++---- lib/templates/moremenu.mustache | 5 +++++ lib/templates/moremenu_children.mustache | 2 +- theme/boost/layout/columns2.php | 1 + theme/boost/scss/moodle.scss | 1 + theme/boost/scss/moodle/moremenu.scss | 3 +++ .../boost/scss/moodle/secondarynavigation.scss | 6 ++++++ theme/boost/style/moodle.css | 7 +++++++ theme/boost/templates/columns2.mustache | 4 +++- theme/classic/style/moodle.css | 7 +++++++ 10 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 theme/boost/scss/moodle/secondarynavigation.scss diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 9c5d98c9a26..cbc44bfb4d6 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3816,10 +3816,20 @@ EOD; * @return string */ public function more_menu($content, $navbarstyle) { - return $this->render_from_template('core/moremenu', (object) [ - 'nodearray' => $content, - 'navbarstyle' => $navbarstyle - ]); + if (is_object($content)) { + if (!isset($content->children) || count($content->children) == 0) { + return false; + } + return $this->render_from_template('core/moremenu', (object) [ + 'nodecollection' => $content, + 'navbarstyle' => $navbarstyle + ]); + } else { + return $this->render_from_template('core/moremenu', (object) [ + 'nodearray' => $content, + 'navbarstyle' => $navbarstyle + ]); + } } /** diff --git a/lib/templates/moremenu.mustache b/lib/templates/moremenu.mustache index 0b63ff6c355..b142edd9928 100644 --- a/lib/templates/moremenu.mustache +++ b/lib/templates/moremenu.mustache @@ -46,6 +46,11 @@ }}