See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace theme_boost; 18 19 use core\navigation\views\view; 20 use navigation_node; 21 use moodle_url; 22 use action_link; 23 use lang_string; 24 25 /** 26 * Creates a navbar for boost that allows easy control of the navbar items. 27 * 28 * @package theme_boost 29 * @copyright 2021 Adrian Greeve <adrian@moodle.com> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class boostnavbar implements \renderable { 33 34 /** @var array The individual items of the navbar. */ 35 protected $items = []; 36 /** @var moodle_page The current moodle page. */ 37 protected $page; 38 39 /** 40 * Takes a navbar object and picks the necessary parts for display. 41 * 42 * @param \moodle_page $page The current moodle page. 43 */ 44 public function __construct(\moodle_page $page) { 45 $this->page = $page; 46 foreach ($this->page->navbar->get_items() as $item) { 47 $this->items[] = $item; 48 } 49 $this->prepare_nodes_for_boost(); 50 } 51 52 /** 53 * Prepares the navigation nodes for use with boost. 54 */ 55 protected function prepare_nodes_for_boost(): void { 56 global $PAGE; 57 58 // Remove the navbar nodes that already exist in the primary navigation menu. 59 $this->remove_items_that_exist_in_navigation($PAGE->primarynav); 60 61 // Defines whether section items with an action should be removed by default. 62 $removesections = true; 63 64 if ($this->page->context->contextlevel == CONTEXT_COURSECAT) { 65 // Remove the 'Permissions' navbar node in the Check permissions page. 66 if ($this->page->pagetype === 'admin-roles-check') { 67 $this->remove('permissions'); 68 } 69 } 70 if ($this->page->context->contextlevel == CONTEXT_COURSE) { 71 // Remove any duplicate navbar nodes. 72 $this->remove_duplicate_items(); 73 // Remove 'My courses' and 'Courses' if we are in the course context. 74 $this->remove('mycourses'); 75 $this->remove('courses'); 76 // Remove the course category breadcrumb nodes. 77 foreach ($this->items as $key => $item) { 78 // Remove if it is a course category breadcrumb node. 79 $this->remove($item->key, \breadcrumb_navigation_node::TYPE_CATEGORY); 80 } 81 // Remove the course breadcrumb node. 82 $this->remove($this->page->course->id, \breadcrumb_navigation_node::TYPE_COURSE); 83 // Remove the navbar nodes that already exist in the secondary navigation menu. 84 $this->remove_items_that_exist_in_navigation($PAGE->secondarynav); 85 86 switch ($this->page->pagetype) { 87 case 'group-groupings': 88 case 'group-grouping': 89 case 'group-overview': 90 case 'group-assign': 91 // Remove the 'Groups' navbar node in the Groupings, Grouping, group Overview and Assign pages. 92 $this->remove('groups'); 93 case 'backup-backup': 94 case 'backup-restorefile': 95 case 'backup-copy': 96 case 'course-reset': 97 // Remove the 'Import' navbar node in the Backup, Restore, Copy course and Reset pages. 98 $this->remove('import'); 99 case 'course-user': 100 $this->remove('mygrades'); 101 $this->remove('grades'); 102 } 103 } 104 105 // Remove 'My courses' if we are in the module context. 106 if ($this->page->context->contextlevel == CONTEXT_MODULE) { 107 $this->remove('mycourses'); 108 $this->remove('courses'); 109 // Remove the course category breadcrumb nodes. 110 foreach ($this->items as $key => $item) { 111 // Remove if it is a course category breadcrumb node. 112 $this->remove($item->key, \breadcrumb_navigation_node::TYPE_CATEGORY); 113 } 114 $courseformat = course_get_format($this->page->course)->get_course(); 115 // Section items can be only removed if a course layout (coursedisplay) is not explicitly set in the 116 // given course format or the set course layout is not 'One section per page'. 117 $removesections = !isset($courseformat->coursedisplay) || 118 $courseformat->coursedisplay != COURSE_DISPLAY_MULTIPAGE; 119 if ($removesections) { 120 // If the course sections are removed, we need to add the anchor of current section to the Course. 121 $coursenode = $this->get_item($this->page->course->id); 122 if (!is_null($coursenode) && $this->page->cm->sectionnum !== null) { 123 $coursenode->action = course_get_format($this->page->course)->get_view_url($this->page->cm->sectionnum); 124 } 125 } 126 } 127 128 if ($this->page->context->contextlevel == CONTEXT_SYSTEM) { 129 // Remove the navbar nodes that already exist in the secondary navigation menu. 130 $this->remove_items_that_exist_in_navigation($PAGE->secondarynav); 131 } 132 133 // Set the designated one path for courses. 134 $mycoursesnode = $this->get_item('mycourses'); 135 if (!is_null($mycoursesnode)) { 136 $url = new \moodle_url('/my/courses.php'); 137 $mycoursesnode->action = $url; 138 $mycoursesnode->text = get_string('mycourses'); 139 } 140 141 $this->remove_no_link_items($removesections); 142 143 // Don't display the navbar if there is only one item. Apparently this is bad UX design. 144 if ($this->item_count() <= 1) { 145 $this->clear_items(); 146 return; 147 } 148 149 // Make sure that the last item is not a link. Not sure if this is always a good idea. 150 $this->remove_last_item_action(); 151 } 152 153 /** 154 * Get all the boostnavbaritem elements. 155 * 156 * @return boostnavbaritem[] Boost navbar items. 157 */ 158 public function get_items(): array { 159 return $this->items; 160 } 161 162 /** 163 * Removes all navigation items out of this boost navbar 164 */ 165 protected function clear_items(): void { 166 $this->items = []; 167 } 168 169 /** 170 * Retrieve a single navbar item. 171 * 172 * @param string|int $key The identifier of the navbar item to return. 173 * @return \breadcrumb_navigation_node|null The navbar item. 174 */ 175 protected function get_item($key): ?\breadcrumb_navigation_node { 176 foreach ($this->items as $item) { 177 if ($key === $item->key) { 178 return $item; 179 } 180 } 181 return null; 182 } 183 184 /** 185 * Counts all of the navbar items. 186 * 187 * @return int How many navbar items there are. 188 */ 189 protected function item_count(): int { 190 return count($this->items); 191 } 192 193 /** 194 * Remove a boostnavbaritem from the boost navbar. 195 * 196 * @param string|int $itemkey An identifier for the boostnavbaritem 197 * @param int|null $itemtype An additional type identifier for the boostnavbaritem (optional) 198 */ 199 protected function remove($itemkey, ?int $itemtype = null): void { 200 201 $itemfound = false; 202 foreach ($this->items as $key => $item) { 203 if ($item->key === $itemkey) { 204 // If a type identifier is also specified, check whether the type of the breadcrumb item matches the 205 // specified type. Skip if types to not match. 206 if (!is_null($itemtype) && $item->type !== $itemtype) { 207 continue; 208 } 209 unset($this->items[$key]); 210 $itemfound = true; 211 break; 212 } 213 } 214 if (!$itemfound) { 215 return; 216 } 217 218 $itemcount = $this->item_count(); 219 if ($itemcount <= 0) { 220 return; 221 } 222 223 $this->items = array_values($this->items); 224 // Set the last item to last item if it is not. 225 $lastitem = $this->items[$itemcount - 1]; 226 if (!$lastitem->is_last()) { 227 $lastitem->set_last(true); 228 } 229 } 230 231 /** 232 * Removes the action from the last item of the boostnavbaritem. 233 */ 234 protected function remove_last_item_action(): void { 235 $item = end($this->items); 236 $item->action = null; 237 reset($this->items); 238 } 239 240 /** 241 * Returns the second last navbar item. This is for use in the mobile view where we are showing just the second 242 * last item in the breadcrumb navbar. 243 * 244 * @return breakcrumb_navigation_node|null The second last navigation node. 245 */ 246 public function get_penultimate_item(): ?\breadcrumb_navigation_node { 247 $number = $this->item_count() - 2; 248 return ($number >= 0) ? $this->items[$number] : null; 249 } 250 251 /** 252 * Remove items that have no actions associated with them and optionally remove items that are sections. 253 * 254 * The only exception is the last item in the list which may not have a link but needs to be displayed. 255 * 256 * @param bool $removesections Whether section items should be also removed (only applies when they have an action) 257 */ 258 protected function remove_no_link_items(bool $removesections = true): void { 259 foreach ($this->items as $key => $value) { 260 if (!$value->is_last() && 261 (!$value->has_action() || ($value->type == \navigation_node::TYPE_SECTION && $removesections))) { 262 unset($this->items[$key]); 263 } 264 } 265 $this->items = array_values($this->items); 266 } 267 268 /** 269 * Remove breadcrumb items that already exist in a given navigation view. 270 * 271 * This method removes the breadcrumb items that have a text => action match in a given navigation view 272 * (primary or secondary). 273 * 274 * @param view $navigationview The navigation view object. 275 */ 276 protected function remove_items_that_exist_in_navigation(view $navigationview): void { 277 // Loop through the navigation view items and create a 'text' => 'action' array which will be later used 278 // to compare whether any of the breadcrumb items matches these pairs. 279 $navigationviewitems = []; 280 foreach ($navigationview->children as $child) { 281 list($childtext, $childaction) = $this->get_node_text_and_action($child); 282 if ($childaction) { 283 $navigationviewitems[$childtext] = $childaction; 284 } 285 } 286 // Loop through the breadcrumb items and if the item's 'text' and 'action' values matches with any of the 287 // existing navigation view items, remove it from the breadcrumbs. 288 foreach ($this->items as $item) { 289 list($itemtext, $itemaction) = $this->get_node_text_and_action($item); 290 if ($itemaction) { 291 if (array_key_exists($itemtext, $navigationviewitems) && 292 $navigationviewitems[$itemtext] === $itemaction) { 293 $this->remove($item->key); 294 } 295 } 296 } 297 } 298 299 /** 300 * Remove duplicate breadcrumb items. 301 * 302 * This method looks for breadcrumb items that have identical text and action values and removes the first item. 303 */ 304 protected function remove_duplicate_items(): void { 305 $taken = []; 306 // Reverse the order of the items before filtering so that the first occurrence is removed instead of the last. 307 $filtereditems = array_values(array_filter(array_reverse($this->items), function($item) use (&$taken) { 308 list($itemtext, $itemaction) = $this->get_node_text_and_action($item); 309 if ($itemaction) { 310 if (array_key_exists($itemtext, $taken) && $taken[$itemtext] === $itemaction) { 311 return false; 312 } 313 $taken[$itemtext] = $itemaction; 314 } 315 return true; 316 })); 317 // Reverse back the order. 318 $this->items = array_reverse($filtereditems); 319 } 320 321 /** 322 * Helper function that returns an array of the text and the outputted action url (if exists) for a given 323 * navigation node. 324 * 325 * @param navigation_node $node The navigation node object. 326 * @return array 327 */ 328 protected function get_node_text_and_action(navigation_node $node): array { 329 $text = $node->text instanceof lang_string ? $node->text->out() : $node->text; 330 $action = null; 331 if ($node->has_action()) { 332 if ($node->action instanceof moodle_url) { 333 $action = $node->action->out(); 334 } else if ($node->action instanceof action_link) { 335 $action = $node->action->url->out(); 336 } else { 337 $action = $node->action; 338 } 339 } 340 return [$text, $action]; 341 } 342 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body