1<?php 2/** 3 * Elgg Menu Item 4 * 5 * To create a menu item that is not a link, pass false for $href. 6 * 7 * Any undocumented properties set will be passed to the output/url view during rendering. E.g. 8 * to give a menu item a "target" attribute, set $item->target, or include a "target" key in 9 * the options array for factory(). 10 * 11 * @since 1.8.0 12 */ 13class ElggMenuItem implements \Elgg\Collections\CollectionItemInterface { 14 15 /** 16 * @var array Non-rendered data about the menu item 17 */ 18 protected $data = [ 19 // string Identifier of the menu 20 'name' => '', 21 22 // array Page contexts this menu item should appear on 23 'contexts' => ['all'], 24 25 // string Menu section identifier 26 'section' => 'default', 27 28 // int Smaller priorities float to the top 29 'priority' => 100, 30 31 // bool Is this the currently selected menu item (null for autodetection) 32 'selected' => null, 33 34 // string Identifier of this item's parent 35 'parent_name' => '', 36 37 // \ElggMenuItem The parent object or null 38 'parent' => null, 39 40 // array Array of children objects or empty array 41 'children' => [], 42 43 // array An array of options for child menu of the parent item 44 'child_menu' => [], 45 46 // array Classes to apply to the li tag 47 'itemClass' => [], 48 49 // array Classes to apply to the anchor tag 50 'linkClass' => [], 51 52 // array AMD modules required by this menu item 53 'deps' => [] 54 ]; 55 56 /** 57 * @var string The menu display string (HTML) 58 */ 59 protected $text; 60 61 /** 62 * @var string The menu url 63 */ 64 protected $href = null; 65 66 /** 67 * @var string Tooltip 68 */ 69 protected $title = false; 70 71 /** 72 * @var string The string to display if link is clicked 73 */ 74 protected $confirm = ''; 75 76 77 /** 78 * \ElggMenuItem constructor 79 * 80 * @param string $name Identifier of the menu item 81 * @param string $text Display text of the menu item (HTML) 82 * @param string $href URL of the menu item (false if not a link) 83 */ 84 public function __construct($name, $text, $href) { 85 $this->text = $text; 86 if ($href) { 87 $this->href = elgg_normalize_url($href); 88 } else { 89 $this->href = $href; 90 } 91 92 $this->data['name'] = $name; 93 } 94 95 /** 96 * Create an ElggMenuItem from an associative array. Required keys are name, text, and href. 97 * 98 * @param array $options Option array of key value pairs 99 * 100 * name => STR Menu item identifier (required) 101 * text => STR Menu item display text as HTML (required) 102 * href => STR Menu item URL (required) 103 * false = do not create a link. 104 * null = current URL. 105 * "" = current URL. 106 * "/" = site home page. 107 * @warning If href is false, the <a> tag will 108 * not appear, so the link_class will not apply. If you 109 * put <a> tags in manually through the 'text' option 110 * the default CSS selector .elgg-menu-$menu > li > a 111 * may affect formatting. Wrap in a <span> if it does.) 112 * 113 * section => STR Menu section identifier 114 * link_class => STR A class or classes for the <a> tag 115 * item_class => STR A class or classes for the <li> tag 116 * parent_name => STR Identifier of the parent menu item 117 * contexts => ARR Page context strings 118 * title => STR Menu item tooltip 119 * selected => BOOL Is this menu item currently selected? 120 * confirm => STR If set, the link will be drawn with the output/confirmlink view instead of output/url. 121 * deps => ARR AMD modules required by this menu item 122 * child_menu => ARR Options for the child menu 123 * data => ARR Custom attributes stored in the menu item. 124 * 125 * @return ElggMenuItem|null null on error 126 */ 127 public static function factory($options) { 128 if (!isset($options['name']) || !isset($options['text'])) { 129 elgg_log(__METHOD__ . ': $options "name" and "text" are required.', 'ERROR'); 130 return null; 131 } 132 if (!isset($options['href'])) { 133 $options['href'] = ''; 134 } 135 136 $item = new \ElggMenuItem($options['name'], $options['text'], $options['href']); 137 unset($options['name']); 138 unset($options['text']); 139 unset($options['href']); 140 141 // special catch in case someone uses context rather than contexts 142 if (isset($options['context'])) { 143 $options['contexts'] = $options['context']; 144 unset($options['context']); 145 } 146 147 // make sure contexts is set correctly 148 if (isset($options['contexts'])) { 149 $item->setContext($options['contexts']); 150 unset($options['contexts']); 151 } 152 153 if (isset($options['link_class'])) { 154 $item->setLinkClass($options['link_class']); 155 unset($options['link_class']); 156 } 157 158 if (isset($options['item_class'])) { 159 $item->setItemClass($options['item_class']); 160 unset($options['item_class']); 161 } 162 163 if (isset($options['data']) && is_array($options['data'])) { 164 $item->setData($options['data']); 165 unset($options['data']); 166 } 167 168 foreach ($options as $key => $value) { 169 if (array_key_exists($key, $item->data)) { 170 $item->data[$key] = $value; 171 } else { 172 $item->$key = $value; 173 } 174 } 175 176 return $item; 177 } 178 179 /** 180 * Set a data key/value pair or a set of key/value pairs 181 * 182 * This method allows storage of arbitrary data with this menu item. The 183 * data can be used for sorting, custom rendering, or any other use. 184 * 185 * @param mixed $key String key or an associative array of key/value pairs 186 * @param mixed $value The value if $key is a string 187 * @return void 188 */ 189 public function setData($key, $value = null) { 190 if (is_array($key)) { 191 $this->data = array_merge($this->data, $key); 192 } else { 193 $this->data[$key] = $value; 194 } 195 } 196 197 /** 198 * Get stored data 199 * 200 * @param string $key The key for the requested key/value pair 201 * @return mixed 202 */ 203 public function getData($key) { 204 if (isset($this->data[$key])) { 205 return $this->data[$key]; 206 } else { 207 return null; 208 } 209 } 210 211 /** 212 * Set the identifier of the menu item 213 * 214 * @param string $name Unique identifier 215 * @return void 216 */ 217 public function setName($name) { 218 $this->data['name'] = $name; 219 } 220 221 /** 222 * Get the identifier of the menu item 223 * 224 * @return string 225 */ 226 public function getName() { 227 return $this->data['name']; 228 } 229 230 /** 231 * Set the display text of the menu item 232 * 233 * @param string $text The display text as HTML 234 * @return void 235 */ 236 public function setText($text) { 237 $this->text = $text; 238 } 239 240 /** 241 * Get the display text of the menu item 242 * 243 * @return string The display text as HTML 244 */ 245 public function getText() { 246 return $this->text; 247 } 248 249 /** 250 * Set the URL of the menu item 251 * 252 * @param string $href URL or false if not a link 253 * @return void 254 * @todo this should probably normalize 255 */ 256 public function setHref($href) { 257 $this->href = $href; 258 } 259 260 /** 261 * Get the URL of the menu item 262 * 263 * @return string|false|null 264 */ 265 public function getHref() { 266 return $this->href; 267 } 268 269 /** 270 * Set the contexts that this menu item is available for 271 * 272 * @param array $contexts An array of context strings. Use 'all' to match all contexts. 273 * @return void 274 */ 275 public function setContext($contexts) { 276 if (is_string($contexts)) { 277 $contexts = [$contexts]; 278 } 279 $this->data['contexts'] = $contexts; 280 } 281 282 /** 283 * Get an array of context strings 284 * 285 * @return array 286 */ 287 public function getContext() { 288 return $this->data['contexts']; 289 } 290 291 /** 292 * Should this menu item be used given the current context 293 * 294 * @param string $context A context string (default is empty string for 295 * current context stack). 296 * @return bool 297 */ 298 public function inContext($context = '') { 299 if (in_array('all', $this->data['contexts'])) { 300 return true; 301 } 302 303 if ($context) { 304 return in_array($context, $this->data['contexts']); 305 } 306 307 foreach ($this->data['contexts'] as $context) { 308 if (elgg_in_context($context)) { 309 return true; 310 } 311 } 312 313 return false; 314 } 315 316 /** 317 * Set the selected flag 318 * 319 * @param bool $state Selected state (default is true) 320 * @return void 321 */ 322 public function setSelected($state = true) { 323 $this->data['selected'] = $state; 324 } 325 326 /** 327 * Get selected state 328 * 329 * @return bool 330 */ 331 public function getSelected() { 332 if (isset($this->data['selected'])) { 333 return $this->data['selected']; 334 } 335 336 return elgg_http_url_is_identical(current_page_url(), $this->getHref()); 337 } 338 339 /** 340 * Set the tool tip text 341 * 342 * @param string $text The text of the tool tip 343 * @return void 344 */ 345 public function setTooltip($text) { 346 $this->title = $text; 347 } 348 349 /** 350 * Get the tool tip text 351 * 352 * @return string 353 */ 354 public function getTooltip() { 355 return $this->title; 356 } 357 358 /** 359 * Set the confirm text shown when link is clicked 360 * 361 * @param string $text The text to show 362 * @return void 363 */ 364 public function setConfirmText($text) { 365 $this->confirm = $text; 366 } 367 368 /** 369 * Get the confirm text 370 * 371 * @return string 372 */ 373 public function getConfirmText() { 374 return $this->confirm; 375 } 376 377 /** 378 * Set the anchor class 379 * 380 * @param mixed $class An array of class names, or a single string class name. 381 * @return void 382 */ 383 public function setLinkClass($class) { 384 if (!is_array($class)) { 385 $this->data['linkClass'] = [$class]; 386 } else { 387 $this->data['linkClass'] = $class; 388 } 389 } 390 391 /** 392 * Get the anchor classes as text 393 * 394 * @return string 395 */ 396 public function getLinkClass() { 397 return implode(' ', $this->data['linkClass']); 398 } 399 400 /** 401 * Add a link class 402 * 403 * @param mixed $class An array of class names, or a single string class name. 404 * @return void 405 */ 406 public function addLinkClass($class) { 407 $this->addClass($this->data['linkClass'], $class); 408 } 409 410 /** 411 * Set required AMD modules 412 * 413 * @param string[]|string $modules One or more required AMD modules 414 * @return void 415 */ 416 public function setDeps($modules) { 417 $this->data['deps'] = (array) $modules; 418 } 419 420 /** 421 * Get required AMD modules 422 * 423 * @return string[] 424 */ 425 public function getDeps() { 426 $modules = (array) $this->data['deps']; 427 return array_filter($modules, function($m) { 428 return is_string($m) && !empty($m); 429 }); 430 } 431 432 /** 433 * Add required AMD modules 434 * 435 * @param string[]|string $modules One or more required AMD modules 436 * @return void 437 */ 438 public function addDeps($modules) { 439 $current = $this->getDeps(); 440 $this->setDeps($current + (array) $modules); 441 } 442 443 /** 444 * Set child menu options for a parent item 445 * 446 * @param array $options Options 447 * @return void 448 */ 449 public function setChildMenuOptions(array $options = []) { 450 $this->data['child_menu'] = $options; 451 } 452 453 /** 454 * Returns child menu options for parent items 455 * 456 * @return array 457 */ 458 public function getChildMenuOptions() { 459 return $this->data['child_menu']; 460 } 461 462 /** 463 * Set the li classes 464 * 465 * @param mixed $class An array of class names, or a single string class name. 466 * @return void 467 */ 468 public function setItemClass($class) { 469 if (!is_array($class)) { 470 $this->data['itemClass'] = [$class]; 471 } else { 472 $this->data['itemClass'] = $class; 473 } 474 } 475 476 /** 477 * Get the li classes as text 478 * 479 * @return string 480 */ 481 public function getItemClass() { 482 // allow people to specify name with underscores and colons 483 $name = preg_replace('/[^a-z0-9\-]/i', '-', strtolower($this->getName())); 484 485 $class = implode(' ', $this->data['itemClass']); 486 if ($class) { 487 return "elgg-menu-item-$name $class"; 488 } else { 489 return "elgg-menu-item-$name"; 490 } 491 } 492 493 /** 494 * Add a li class 495 * 496 * @param mixed $class An array of class names, or a single string class name. 497 * @return void 498 * @since 1.9.0 499 */ 500 public function addItemClass($class) { 501 $this->addClass($this->data['itemClass'], $class); 502 } 503 504 // @codingStandardsIgnoreStart 505 /** 506 * Add additional classes 507 * 508 * @param array $current The current array of classes 509 * @param mixed $additional Additional classes (either array of string) 510 * @return void 511 */ 512 protected function addClass(array &$current, $additional) { 513 if (!is_array($additional)) { 514 $current[] = $additional; 515 } else { 516 $current = array_merge($current, $additional); 517 } 518 } 519 // @codingStandardsIgnoreEnd 520 521 /** 522 * Set the priority of the menu item 523 * 524 * @param int $priority The smaller numbers mean higher priority (1 before 100) 525 * @return void 526 */ 527 public function setPriority(int $priority) { 528 $this->data['priority'] = $priority; 529 } 530 531 /** 532 * Get the priority of the menu item 533 * 534 * @return int 535 */ 536 public function getPriority() { 537 return (int) $this->data['priority']; 538 } 539 540 /** 541 * Set the section identifier 542 * 543 * @param string $section The identifier of the section 544 * @return void 545 */ 546 public function setSection($section) { 547 $this->data['section'] = $section; 548 } 549 550 /** 551 * Get the section identifier 552 * 553 * @return string 554 */ 555 public function getSection() { 556 return $this->data['section']; 557 } 558 559 /** 560 * Set the parent identifier 561 * 562 * @param string $name The identifier of the parent \ElggMenuItem 563 * @return void 564 */ 565 public function setParentName($name) { 566 $this->data['parent_name'] = $name; 567 } 568 569 /** 570 * Get the parent identifier 571 * 572 * @return string 573 */ 574 public function getParentName() { 575 return $this->data['parent_name']; 576 } 577 578 /** 579 * Set the parent menu item 580 * 581 * @param \ElggMenuItem $parent The parent of this menu item 582 * @return void 583 * 584 * @internal This is reserved for the \ElggMenuBuilder 585 */ 586 public function setParent($parent) { 587 $this->data['parent'] = $parent; 588 } 589 590 /** 591 * Get the parent menu item 592 * 593 * @return \ElggMenuItem or null 594 * 595 * @internal This is reserved for the \ElggMenuBuilder 596 */ 597 public function getParent() { 598 return $this->data['parent']; 599 } 600 601 /** 602 * Add a child menu item 603 * 604 * @param \ElggMenuItem $item A child menu item 605 * @return void 606 * 607 * @internal This is reserved for the \ElggMenuBuilder 608 */ 609 public function addChild($item) { 610 $this->data['children'][] = $item; 611 } 612 613 /** 614 * Set the menu item's children 615 * 616 * @param ElggMenuItem[] $children Array of items 617 * @return void 618 * 619 * @internal This is reserved for the \ElggMenuBuilder 620 */ 621 public function setChildren($children) { 622 $this->data['children'] = $children; 623 } 624 625 /** 626 * Get the children menu items 627 * 628 * @return ElggMenuItem[] 629 * 630 * @internal This is reserved for the \ElggMenuBuilder 631 */ 632 public function getChildren() { 633 return $this->data['children']; 634 } 635 636 /** 637 * Sort the children 638 * 639 * @param callable $sortFunction A function that is passed to usort() 640 * 641 * @return void 642 * 643 * @internal This is reserved for the \ElggMenuBuilder 644 */ 645 public function sortChildren($sortFunction) { 646 foreach ($this->data['children'] as $key => $node) { 647 $node->data['original_order'] = $key; 648 $node->sortChildren($sortFunction); 649 } 650 usort($this->data['children'], $sortFunction); 651 } 652 653 /** 654 * Get all the values for this menu item. Useful for rendering. 655 * 656 * @return array 657 * @since 1.9.0 658 */ 659 public function getValues() { 660 $values = get_object_vars($this); 661 unset($values['data']); 662 663 return $values; 664 } 665 666 /** 667 * Get unique item identifier within a collection 668 * @return string|int 669 */ 670 public function getID() { 671 return $this->getName(); 672 } 673} 674