Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
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 node. 77 $this->remove($this->page->course->category, \breadcrumb_navigation_node::TYPE_CATEGORY); 78 // Remove the course breadcrumb node. 79 $this->remove($this->page->course->id, \breadcrumb_navigation_node::TYPE_COURSE); 80 // Remove the navbar nodes that already exist in the secondary navigation menu. 81 $this->remove_items_that_exist_in_navigation($PAGE->secondarynav); 82 83 switch ($this->page->pagetype) { 84 case 'group-groupings': 85 case 'group-grouping': 86 case 'group-overview': 87 case 'group-assign': 88 // Remove the 'Groups' navbar node in the Groupings, Grouping, group Overview and Assign pages. 89 $this->remove('groups'); 90 case 'backup-backup': 91 case 'backup-restorefile': 92 case 'backup-copy': 93 case 'course-reset': 94 // Remove the 'Import' navbar node in the Backup, Restore, Copy course and Reset pages. 95 $this->remove('import'); 96 case 'course-user': 97 $this->remove('mygrades'); 98 $this->remove('grades'); 99 } 100 } 101 102 // Remove 'My courses' if we are in the module context. 103 if ($this->page->context->contextlevel == CONTEXT_MODULE) { 104 $this->remove('mycourses'); 105 $this->remove('courses'); 106 // Remove the course category breadcrumb node. 107 $this->remove($this->page->course->category, \breadcrumb_navigation_node::TYPE_CATEGORY); 108 $courseformat = course_get_format($this->page->course)->get_course(); 109 // Section items can be only removed if a course layout (coursedisplay) is not explicitly set in the 110 // given course format or the set course layout is not 'One section per page'. 111 $removesections = !isset($courseformat->coursedisplay) || 112 $courseformat->coursedisplay != COURSE_DISPLAY_MULTIPAGE; 113 if ($removesections) { 114 // If the course sections are removed, we need to add the anchor of current section to the Course. 115 $coursenode = $this->get_item($this->page->course->id); 116 if (!is_null($coursenode) && $this->page->cm->sectionnum !== null) { 117 $coursenode->action = course_get_format($this->page->course)->get_view_url($this->page->cm->sectionnum); 118 } 119 } 120 } 121 122 if ($this->page->context->contextlevel == CONTEXT_SYSTEM) { 123 // Remove the navbar nodes that already exist in the secondary navigation menu. 124 $this->remove_items_that_exist_in_navigation($PAGE->secondarynav); 125 } 126 127 // Set the designated one path for courses. 128 $mycoursesnode = $this->get_item('mycourses'); 129 if (!is_null($mycoursesnode)) { 130 $url = new \moodle_url('/my/courses.php'); 131 $mycoursesnode->action = $url; 132 $mycoursesnode->text = get_string('mycourses'); 133 } 134 135 $this->remove_no_link_items($removesections); 136 137 // Don't display the navbar if there is only one item. Apparently this is bad UX design. 138 if ($this->item_count() <= 1) { 139 $this->clear_items(); 140 return; 141 } 142 143 // Make sure that the last item is not a link. Not sure if this is always a good idea. 144 $this->remove_last_item_action(); 145 } 146 147 /** 148 * Get all the boostnavbaritem elements. 149 * 150 * @return boostnavbaritem[] Boost navbar items. 151 */ 152 public function get_items(): array { 153 return $this->items; 154 } 155 156 /** 157 * Removes all navigation items out of this boost navbar 158 */ 159 protected function clear_items(): void { 160 $this->items = []; 161 } 162 163 /** 164 * Retrieve a single navbar item. 165 * 166 * @param string|int $key The identifier of the navbar item to return. 167 * @return \breadcrumb_navigation_node|null The navbar item. 168 */ 169 protected function get_item($key): ?\breadcrumb_navigation_node { 170 foreach ($this->items as $item) { 171 if ($key === $item->key) { 172 return $item; 173 } 174 } 175 return null; 176 } 177 178 /** 179 * Counts all of the navbar items. 180 * 181 * @return int How many navbar items there are. 182 */ 183 protected function item_count(): int { 184 return count($this->items); 185 } 186 187 /** 188 * Remove a boostnavbaritem from the boost navbar. 189 * 190 * @param string|int $itemkey An identifier for the boostnavbaritem 191 * @param int|null $itemtype An additional type identifier for the boostnavbaritem (optional) 192 */ 193 protected function remove($itemkey, ?int $itemtype = null): void { 194 195 $itemfound = false; 196 foreach ($this->items as $key => $item) { 197 if ($item->key === $itemkey) { 198 // If a type identifier is also specified, check whether the type of the breadcrumb item matches the 199 // specified type. Skip if types to not match. 200 if (!is_null($itemtype) && $item->type !== $itemtype) { 201 continue; 202 } 203 unset($this->items[$key]); 204 $itemfound = true; 205 break; 206 } 207 } 208 if (!$itemfound) { 209 return; 210 } 211 212 $itemcount = $this->item_count(); 213 if ($itemcount <= 0) { 214 return; 215 } 216 217 $this->items = array_values($this->items); 218 // Set the last item to last item if it is not. 219 $lastitem = $this->items[$itemcount - 1]; 220 if (!$lastitem->is_last()) { 221 $lastitem->set_last(true); 222 } 223 } 224 225 /** 226 * Removes the action from the last item of the boostnavbaritem. 227 */ 228 protected function remove_last_item_action(): void { 229 $item = end($this->items); 230 $item->action = null; 231 reset($this->items); 232 } 233 234 /** 235 * Returns the second last navbar item. This is for use in the mobile view where we are showing just the second 236 * last item in the breadcrumb navbar. 237 * 238 * @return breakcrumb_navigation_node|null The second last navigation node. 239 */ 240 public function get_penultimate_item(): ?\breadcrumb_navigation_node { 241 $number = $this->item_count() - 2; 242 return ($number >= 0) ? $this->items[$number] : null; 243 } 244 245 /** 246 * Remove items that have no actions associated with them and optionally remove items that are sections. 247 * 248 * The only exception is the last item in the list which may not have a link but needs to be displayed. 249 * 250 * @param bool $removesections Whether section items should be also removed (only applies when they have an action) 251 */ 252 protected function remove_no_link_items(bool $removesections = true): void { 253 foreach ($this->items as $key => $value) { 254 if (!$value->is_last() && 255 (!$value->has_action() || ($value->type == \navigation_node::TYPE_SECTION && $removesections))) { 256 unset($this->items[$key]); 257 } 258 } 259 $this->items = array_values($this->items); 260 } 261 262 /** 263 * Remove breadcrumb items that already exist in a given navigation view. 264 * 265 * This method removes the breadcrumb items that have a text => action match in a given navigation view 266 * (primary or secondary). 267 * 268 * @param view $navigationview The navigation view object. 269 */ 270 protected function remove_items_that_exist_in_navigation(view $navigationview): void { 271 // Loop through the navigation view items and create a 'text' => 'action' array which will be later used 272 // to compare whether any of the breadcrumb items matches these pairs. 273 $navigationviewitems = []; 274 foreach ($navigationview->children as $child) { 275 list($childtext, $childaction) = $this->get_node_text_and_action($child); 276 if ($childaction) { 277 $navigationviewitems[$childtext] = $childaction; 278 } 279 } 280 // Loop through the breadcrumb items and if the item's 'text' and 'action' values matches with any of the 281 // existing navigation view items, remove it from the breadcrumbs. 282 foreach ($this->items as $item) { 283 list($itemtext, $itemaction) = $this->get_node_text_and_action($item); 284 if ($itemaction) { 285 if (array_key_exists($itemtext, $navigationviewitems) && 286 $navigationviewitems[$itemtext] === $itemaction) { 287 $this->remove($item->key); 288 } 289 } 290 } 291 } 292 293 /** 294 * Remove duplicate breadcrumb items. 295 * 296 * This method looks for breadcrumb items that have identical text and action values and removes the first item. 297 */ 298 protected function remove_duplicate_items(): void { 299 $taken = []; 300 // Reverse the order of the items before filtering so that the first occurrence is removed instead of the last. 301 $filtereditems = array_values(array_filter(array_reverse($this->items), function($item) use (&$taken) { 302 list($itemtext, $itemaction) = $this->get_node_text_and_action($item); 303 if ($itemaction) { 304 if (array_key_exists($itemtext, $taken) && $taken[$itemtext] === $itemaction) { 305 return false; 306 } 307 $taken[$itemtext] = $itemaction; 308 } 309 return true; 310 })); 311 // Reverse back the order. 312 $this->items = array_reverse($filtereditems); 313 } 314 315 /** 316 * Helper function that returns an array of the text and the outputted action url (if exists) for a given 317 * navigation node. 318 * 319 * @param navigation_node $node The navigation node object. 320 * @return array 321 */ 322 protected function get_node_text_and_action(navigation_node $node): array { 323 $text = $node->text instanceof lang_string ? $node->text->out() : $node->text; 324 $action = null; 325 if ($node->has_action()) { 326 if ($node->action instanceof moodle_url) { 327 $action = $node->action->out(); 328 } else if ($node->action instanceof action_link) { 329 $action = $node->action->url->out(); 330 } else { 331 $action = $node->action; 332 } 333 } 334 return [$text, $action]; 335 } 336 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body