10 /** |
10 /** |
11 * Core class used to implement the Toolbar API. |
11 * Core class used to implement the Toolbar API. |
12 * |
12 * |
13 * @since 3.1.0 |
13 * @since 3.1.0 |
14 */ |
14 */ |
|
15 #[AllowDynamicProperties] |
15 class WP_Admin_Bar { |
16 class WP_Admin_Bar { |
16 private $nodes = array(); |
17 private $nodes = array(); |
17 private $bound = false; |
18 private $bound = false; |
18 public $user; |
19 public $user; |
19 |
20 |
20 /** |
21 /** |
21 * @since 3.3.0 |
22 * Deprecated menu property. |
22 * |
23 * |
23 * @param string $name |
24 * @since 3.1.0 |
24 * @return string|array|void |
25 * @deprecated 3.3.0 Modify admin bar nodes with WP_Admin_Bar::get_node(), |
25 */ |
26 * WP_Admin_Bar::add_node(), and WP_Admin_Bar::remove_node(). |
26 public function __get( $name ) { |
27 * @var array |
27 switch ( $name ) { |
28 */ |
28 case 'proto': |
29 public $menu = array(); |
29 return is_ssl() ? 'https://' : 'http://'; |
|
30 |
|
31 case 'menu': |
|
32 _deprecated_argument( 'WP_Admin_Bar', '3.3.0', 'Modify admin bar nodes with WP_Admin_Bar::get_node(), WP_Admin_Bar::add_node(), and WP_Admin_Bar::remove_node(), not the <code>menu</code> property.' ); |
|
33 return array(); // Sorry, folks. |
|
34 } |
|
35 } |
|
36 |
30 |
37 /** |
31 /** |
38 * Initializes the admin bar. |
32 * Initializes the admin bar. |
39 * |
33 * |
40 * @since 3.1.0 |
34 * @since 3.1.0 |
41 */ |
35 */ |
42 public function initialize() { |
36 public function initialize() { |
43 $this->user = new stdClass; |
37 $this->user = new stdClass(); |
44 |
38 |
45 if ( is_user_logged_in() ) { |
39 if ( is_user_logged_in() ) { |
46 /* Populate settings we need for the menu based on the current user. */ |
40 /* Populate settings we need for the menu based on the current user. */ |
47 $this->user->blogs = get_blogs_of_user( get_current_user_id() ); |
41 $this->user->blogs = get_blogs_of_user( get_current_user_id() ); |
48 if ( is_multisite() ) { |
42 if ( is_multisite() ) { |
111 /** |
105 /** |
112 * Adds a node to the menu. |
106 * Adds a node to the menu. |
113 * |
107 * |
114 * @since 3.1.0 |
108 * @since 3.1.0 |
115 * @since 4.5.0 Added the ability to pass 'lang' and 'dir' meta data. |
109 * @since 4.5.0 Added the ability to pass 'lang' and 'dir' meta data. |
|
110 * @since 6.5.0 Added the ability to pass 'menu_title' for an ARIA menu name. |
116 * |
111 * |
117 * @param array $args { |
112 * @param array $args { |
118 * Arguments for adding a node. |
113 * Arguments for adding a node. |
119 * |
114 * |
120 * @type string $id ID of the item. |
115 * @type string $id ID of the item. |
121 * @type string $title Title of the node. |
116 * @type string $title Title of the node. |
122 * @type string $parent Optional. ID of the parent node. |
117 * @type string $parent Optional. ID of the parent node. |
123 * @type string $href Optional. Link for the item. |
118 * @type string $href Optional. Link for the item. |
124 * @type bool $group Optional. Whether or not the node is a group. Default false. |
119 * @type bool $group Optional. Whether or not the node is a group. Default false. |
125 * @type array $meta Meta data including the following keys: 'html', 'class', 'rel', 'lang', 'dir', |
120 * @type array $meta Meta data including the following keys: 'html', 'class', 'rel', 'lang', 'dir', |
126 * 'onclick', 'target', 'title', 'tabindex'. Default empty. |
121 * 'onclick', 'target', 'title', 'tabindex', 'menu_title'. Default empty. |
127 * } |
122 * } |
128 */ |
123 */ |
129 public function add_node( $args ) { |
124 public function add_node( $args ) { |
130 // Shim for old method signature: add_node( $parent_id, $menu_obj, $args ). |
125 // Shim for old method signature: add_node( $parent_id, $menu_obj, $args ). |
131 if ( func_num_args() >= 3 && is_string( $args ) ) { |
126 if ( func_num_args() >= 3 && is_string( $args ) ) { |
317 final protected function _bind() { |
312 final protected function _bind() { |
318 if ( $this->bound ) { |
313 if ( $this->bound ) { |
319 return; |
314 return; |
320 } |
315 } |
321 |
316 |
322 // Add the root node. |
317 /* |
323 // Clear it first, just in case. Don't mess with The Root. |
318 * Add the root node. |
|
319 * Clear it first, just in case. Don't mess with The Root. |
|
320 */ |
324 $this->remove_node( 'root' ); |
321 $this->remove_node( 'root' ); |
325 $this->add_node( |
322 $this->add_node( |
326 array( |
323 array( |
327 'id' => 'root', |
324 'id' => 'root', |
328 'group' => false, |
325 'group' => false, |
366 // Items in items aren't allowed. Wrap nested items in 'default' groups. |
363 // Items in items aren't allowed. Wrap nested items in 'default' groups. |
367 if ( 'item' === $parent->type && 'item' === $node->type ) { |
364 if ( 'item' === $parent->type && 'item' === $node->type ) { |
368 $default_id = $parent->id . '-default'; |
365 $default_id = $parent->id . '-default'; |
369 $default = $this->_get_node( $default_id ); |
366 $default = $this->_get_node( $default_id ); |
370 |
367 |
371 // The default group is added here to allow groups that are |
368 /* |
372 // added before standard menu items to render first. |
369 * The default group is added here to allow groups that are |
|
370 * added before standard menu items to render first. |
|
371 */ |
373 if ( ! $default ) { |
372 if ( ! $default ) { |
374 // Use _set_node because add_node can be overloaded. |
373 /* |
375 // Make sure to specify default settings for all properties. |
374 * Use _set_node because add_node can be overloaded. |
|
375 * Make sure to specify default settings for all properties. |
|
376 */ |
376 $this->_set_node( |
377 $this->_set_node( |
377 array( |
378 array( |
378 'id' => $default_id, |
379 'id' => $default_id, |
379 'parent' => $parent->id, |
380 'parent' => $parent->id, |
380 'type' => 'group', |
381 'type' => 'group', |
389 $default = $this->_get_node( $default_id ); |
390 $default = $this->_get_node( $default_id ); |
390 $parent->children[] = $default; |
391 $parent->children[] = $default; |
391 } |
392 } |
392 $parent = $default; |
393 $parent = $default; |
393 |
394 |
394 // Groups in groups aren't allowed. Add a special 'container' node. |
395 /* |
395 // The container will invisibly wrap both groups. |
396 * Groups in groups aren't allowed. Add a special 'container' node. |
|
397 * The container will invisibly wrap both groups. |
|
398 */ |
396 } elseif ( 'group' === $parent->type && 'group' === $node->type ) { |
399 } elseif ( 'group' === $parent->type && 'group' === $node->type ) { |
397 $container_id = $parent->id . '-container'; |
400 $container_id = $parent->id . '-container'; |
398 $container = $this->_get_node( $container_id ); |
401 $container = $this->_get_node( $container_id ); |
399 |
402 |
400 // We need to create a container for this group, life is sad. |
403 // We need to create a container for this group, life is sad. |
401 if ( ! $container ) { |
404 if ( ! $container ) { |
402 // Use _set_node because add_node can be overloaded. |
405 /* |
403 // Make sure to specify default settings for all properties. |
406 * Use _set_node because add_node can be overloaded. |
|
407 * Make sure to specify default settings for all properties. |
|
408 */ |
404 $this->_set_node( |
409 $this->_set_node( |
405 array( |
410 array( |
406 'id' => $container_id, |
411 'id' => $container_id, |
407 'type' => 'container', |
412 'type' => 'container', |
408 'children' => array( $parent ), |
413 'children' => array( $parent ), |
451 * @since 3.3.0 |
456 * @since 3.3.0 |
452 * |
457 * |
453 * @param object $root |
458 * @param object $root |
454 */ |
459 */ |
455 final protected function _render( $root ) { |
460 final protected function _render( $root ) { |
456 // Add browser classes. |
461 /* |
457 // We have to do this here since admin bar shows on the front end. |
462 * Add browser classes. |
|
463 * We have to do this here since admin bar shows on the front end. |
|
464 */ |
458 $class = 'nojq nojs'; |
465 $class = 'nojq nojs'; |
459 if ( wp_is_mobile() ) { |
466 if ( wp_is_mobile() ) { |
460 $class .= ' mobile'; |
467 $class .= ' mobile'; |
461 } |
468 } |
462 |
469 |
497 echo '</div>'; |
501 echo '</div>'; |
498 } |
502 } |
499 |
503 |
500 /** |
504 /** |
501 * @since 3.3.0 |
505 * @since 3.3.0 |
|
506 * @since 6.5.0 Added `$menu_title` parameter to allow an ARIA menu name. |
502 * |
507 * |
503 * @param object $node |
508 * @param object $node |
504 */ |
509 * @param string|bool $menu_title The accessible name of this ARIA menu or false if not provided. |
505 final protected function _render_group( $node ) { |
510 */ |
|
511 final protected function _render_group( $node, $menu_title = false ) { |
506 if ( 'container' === $node->type ) { |
512 if ( 'container' === $node->type ) { |
507 $this->_render_container( $node ); |
513 $this->_render_container( $node ); |
508 return; |
514 return; |
509 } |
515 } |
510 if ( 'group' !== $node->type || empty( $node->children ) ) { |
516 if ( 'group' !== $node->type || empty( $node->children ) ) { |
515 $class = ' class="' . esc_attr( trim( $node->meta['class'] ) ) . '"'; |
521 $class = ' class="' . esc_attr( trim( $node->meta['class'] ) ) . '"'; |
516 } else { |
522 } else { |
517 $class = ''; |
523 $class = ''; |
518 } |
524 } |
519 |
525 |
520 echo "<ul id='" . esc_attr( 'wp-admin-bar-' . $node->id ) . "'$class>"; |
526 if ( empty( $menu_title ) ) { |
|
527 echo "<ul role='menu' id='" . esc_attr( 'wp-admin-bar-' . $node->id ) . "'$class>"; |
|
528 } else { |
|
529 echo "<ul role='menu' aria-label='" . esc_attr( $menu_title ) . "' id='" . esc_attr( 'wp-admin-bar-' . $node->id ) . "'$class>"; |
|
530 } |
521 foreach ( $node->children as $item ) { |
531 foreach ( $node->children as $item ) { |
522 $this->_render_item( $item ); |
532 $this->_render_item( $item ); |
523 } |
533 } |
524 echo '</ul>'; |
534 echo '</ul>'; |
525 } |
535 } |
538 $has_link = ! empty( $node->href ); |
548 $has_link = ! empty( $node->href ); |
539 $is_root_top_item = 'root-default' === $node->parent; |
549 $is_root_top_item = 'root-default' === $node->parent; |
540 $is_top_secondary_item = 'top-secondary' === $node->parent; |
550 $is_top_secondary_item = 'top-secondary' === $node->parent; |
541 |
551 |
542 // Allow only numeric values, then casted to integers, and allow a tabindex value of `0` for a11y. |
552 // Allow only numeric values, then casted to integers, and allow a tabindex value of `0` for a11y. |
543 $tabindex = ( isset( $node->meta['tabindex'] ) && is_numeric( $node->meta['tabindex'] ) ) ? (int) $node->meta['tabindex'] : ''; |
553 $tabindex = ( isset( $node->meta['tabindex'] ) && is_numeric( $node->meta['tabindex'] ) ) ? (int) $node->meta['tabindex'] : ''; |
544 $aria_attributes = ( '' !== $tabindex ) ? ' tabindex="' . $tabindex . '"' : ''; |
554 $aria_attributes = ( '' !== $tabindex ) ? ' tabindex="' . $tabindex . '"' : ''; |
|
555 $aria_attributes .= ' role="menuitem"'; |
545 |
556 |
546 $menuclass = ''; |
557 $menuclass = ''; |
547 $arrow = ''; |
558 $arrow = ''; |
548 |
559 |
549 if ( $is_parent ) { |
560 if ( $is_parent ) { |
550 $menuclass = 'menupop '; |
561 $menuclass = 'menupop '; |
551 $aria_attributes .= ' aria-haspopup="true"'; |
562 $aria_attributes .= ' aria-expanded="false"'; |
552 } |
563 } |
553 |
564 |
554 if ( ! empty( $node->meta['class'] ) ) { |
565 if ( ! empty( $node->meta['class'] ) ) { |
555 $menuclass .= $node->meta['class']; |
566 $menuclass .= $node->meta['class']; |
556 } |
567 } |
562 |
573 |
563 if ( $menuclass ) { |
574 if ( $menuclass ) { |
564 $menuclass = ' class="' . esc_attr( trim( $menuclass ) ) . '"'; |
575 $menuclass = ' class="' . esc_attr( trim( $menuclass ) ) . '"'; |
565 } |
576 } |
566 |
577 |
567 echo "<li id='" . esc_attr( 'wp-admin-bar-' . $node->id ) . "'$menuclass>"; |
578 echo "<li role='group' id='" . esc_attr( 'wp-admin-bar-' . $node->id ) . "'$menuclass>"; |
568 |
579 |
569 if ( $has_link ) { |
580 if ( $has_link ) { |
570 $attributes = array( 'onclick', 'target', 'title', 'rel', 'lang', 'dir' ); |
581 $attributes = array( 'onclick', 'target', 'title', 'rel', 'lang', 'dir' ); |
571 echo "<a class='ab-item'$aria_attributes href='" . esc_url( $node->href ) . "'"; |
582 echo "<a class='ab-item'$aria_attributes href='" . esc_url( $node->href ) . "'"; |
572 } else { |
583 } else { |
595 } |
606 } |
596 |
607 |
597 if ( $is_parent ) { |
608 if ( $is_parent ) { |
598 echo '<div class="ab-sub-wrapper">'; |
609 echo '<div class="ab-sub-wrapper">'; |
599 foreach ( $node->children as $group ) { |
610 foreach ( $node->children as $group ) { |
600 $this->_render_group( $group ); |
611 if ( empty( $node->meta['menu_title'] ) ) { |
|
612 $this->_render_group( $group, false ); |
|
613 } else { |
|
614 $this->_render_group( $group, $node->meta['menu_title'] ); |
|
615 } |
601 } |
616 } |
602 echo '</div>'; |
617 echo '</div>'; |
603 } |
618 } |
604 |
619 |
605 if ( ! empty( $node->meta['html'] ) ) { |
620 if ( ! empty( $node->meta['html'] ) ) { |
631 * @since 3.1.0 |
646 * @since 3.1.0 |
632 */ |
647 */ |
633 public function add_menus() { |
648 public function add_menus() { |
634 // User-related, aligned right. |
649 // User-related, aligned right. |
635 add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_menu', 0 ); |
650 add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_menu', 0 ); |
636 add_action( 'admin_bar_menu', 'wp_admin_bar_search_menu', 4 ); |
651 add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_item', 9991 ); |
637 add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_item', 7 ); |
652 add_action( 'admin_bar_menu', 'wp_admin_bar_recovery_mode_menu', 9992 ); |
638 add_action( 'admin_bar_menu', 'wp_admin_bar_recovery_mode_menu', 8 ); |
653 add_action( 'admin_bar_menu', 'wp_admin_bar_search_menu', 9999 ); |
639 |
654 |
640 // Site-related. |
655 // Site-related. |
641 add_action( 'admin_bar_menu', 'wp_admin_bar_sidebar_toggle', 0 ); |
656 add_action( 'admin_bar_menu', 'wp_admin_bar_sidebar_toggle', 0 ); |
642 add_action( 'admin_bar_menu', 'wp_admin_bar_wp_menu', 10 ); |
657 add_action( 'admin_bar_menu', 'wp_admin_bar_wp_menu', 10 ); |
643 add_action( 'admin_bar_menu', 'wp_admin_bar_my_sites_menu', 20 ); |
658 add_action( 'admin_bar_menu', 'wp_admin_bar_my_sites_menu', 20 ); |