/wp-includes/class-wp-admin-bar.php

  1. <?php 
  2. /** 
  3. * Toolbar API: WP_Admin_Bar class 
  4. * 
  5. * @package WordPress 
  6. * @subpackage Toolbar 
  7. * @since 3.1.0 
  8. */ 
  9.  
  10. /** 
  11. * Core class used to implement the Toolbar API. 
  12. * 
  13. * @since 3.1.0 
  14. */ 
  15. class WP_Admin_Bar { 
  16. private $nodes = array(); 
  17. private $bound = false; 
  18. public $user; 
  19.  
  20. /** 
  21. * @param string $name 
  22. * @return string|array|void 
  23. */ 
  24. public function __get( $name ) { 
  25. switch ( $name ) { 
  26. case 'proto' : 
  27. return is_ssl() ? 'https://' : 'http://'; 
  28.  
  29. case 'menu' : 
  30. _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.' ); 
  31. return array(); // Sorry, folks. 
  32.  
  33. /** 
  34. * @access public 
  35. */ 
  36. public function initialize() { 
  37. $this->user = new stdClass; 
  38.  
  39. if ( is_user_logged_in() ) { 
  40. /** Populate settings we need for the menu based on the current user. */ 
  41. $this->user->blogs = get_blogs_of_user( get_current_user_id() ); 
  42. if ( is_multisite() ) { 
  43. $this->user->active_blog = get_active_blog_for_user( get_current_user_id() ); 
  44. $this->user->domain = empty( $this->user->active_blog ) ? user_admin_url() : trailingslashit( get_home_url( $this->user->active_blog->blog_id ) ); 
  45. $this->user->account_domain = $this->user->domain; 
  46. } else { 
  47. $this->user->active_blog = $this->user->blogs[get_current_blog_id()]; 
  48. $this->user->domain = trailingslashit( home_url() ); 
  49. $this->user->account_domain = $this->user->domain; 
  50.  
  51. add_action( 'wp_head', 'wp_admin_bar_header' ); 
  52.  
  53. add_action( 'admin_head', 'wp_admin_bar_header' ); 
  54.  
  55. if ( current_theme_supports( 'admin-bar' ) ) { 
  56. /** 
  57. * To remove the default padding styles from WordPress for the Toolbar, use the following code: 
  58. * add_theme_support( 'admin-bar', array( 'callback' => '__return_false' ) ); 
  59. */ 
  60. $admin_bar_args = get_theme_support( 'admin-bar' ); 
  61. $header_callback = $admin_bar_args[0]['callback']; 
  62.  
  63. if ( empty($header_callback) ) 
  64. $header_callback = '_admin_bar_bump_cb'; 
  65.  
  66. add_action('wp_head', $header_callback); 
  67.  
  68. wp_enqueue_script( 'admin-bar' ); 
  69. wp_enqueue_style( 'admin-bar' ); 
  70.  
  71. /** 
  72. * Fires after WP_Admin_Bar is initialized. 
  73. * 
  74. * @since 3.1.0 
  75. */ 
  76. do_action( 'admin_bar_init' ); 
  77.  
  78. /** 
  79. * @param array $node 
  80. */ 
  81. public function add_menu( $node ) { 
  82. $this->add_node( $node ); 
  83.  
  84. /** 
  85. * @param string $id 
  86. */ 
  87. public function remove_menu( $id ) { 
  88. $this->remove_node( $id ); 
  89.  
  90. /** 
  91. * Adds a node to the menu. 
  92. * 
  93. * @since 3.1.0 
  94. * @since 4.5.0 Added the ability to pass 'lang' and 'dir' meta data. 
  95. * @access public 
  96. * 
  97. * @param array $args { 
  98. * Arguments for adding a node. 
  99. * 
  100. * @type string $id ID of the item. 
  101. * @type string $title Title of the node. 
  102. * @type string $parent Optional. ID of the parent node. 
  103. * @type string $href Optional. Link for the item. 
  104. * @type bool $group Optional. Whether or not the node is a group. Default false. 
  105. * @type array $meta Meta data including the following keys: 'html', 'class', 'rel', 'lang', 'dir',  
  106. * 'onclick', 'target', 'title', 'tabindex'. Default empty. 
  107. * } 
  108. */ 
  109. public function add_node( $args ) { 
  110. // Shim for old method signature: add_node( $parent_id, $menu_obj, $args ) 
  111. if ( func_num_args() >= 3 && is_string( func_get_arg(0) ) ) 
  112. $args = array_merge( array( 'parent' => func_get_arg(0) ), func_get_arg(2) ); 
  113.  
  114. if ( is_object( $args ) ) 
  115. $args = get_object_vars( $args ); 
  116.  
  117. // Ensure we have a valid title. 
  118. if ( empty( $args['id'] ) ) { 
  119. if ( empty( $args['title'] ) ) 
  120. return; 
  121.  
  122. _doing_it_wrong( __METHOD__, __( 'The menu ID should not be empty.' ), '3.3.0' ); 
  123. // Deprecated: Generate an ID from the title. 
  124. $args['id'] = esc_attr( sanitize_title( trim( $args['title'] ) ) ); 
  125.  
  126. $defaults = array( 
  127. 'id' => false,  
  128. 'title' => false,  
  129. 'parent' => false,  
  130. 'href' => false,  
  131. 'group' => false,  
  132. 'meta' => array(),  
  133. ); 
  134.  
  135. // If the node already exists, keep any data that isn't provided. 
  136. if ( $maybe_defaults = $this->get_node( $args['id'] ) ) 
  137. $defaults = get_object_vars( $maybe_defaults ); 
  138.  
  139. // Do the same for 'meta' items. 
  140. if ( ! empty( $defaults['meta'] ) && ! empty( $args['meta'] ) ) 
  141. $args['meta'] = wp_parse_args( $args['meta'], $defaults['meta'] ); 
  142.  
  143. $args = wp_parse_args( $args, $defaults ); 
  144.  
  145. $back_compat_parents = array( 
  146. 'my-account-with-avatar' => array( 'my-account', '3.3' ),  
  147. 'my-blogs' => array( 'my-sites', '3.3' ),  
  148. ); 
  149.  
  150. if ( isset( $back_compat_parents[ $args['parent'] ] ) ) { 
  151. list( $new_parent, $version ) = $back_compat_parents[ $args['parent'] ]; 
  152. _deprecated_argument( __METHOD__, $version, sprintf( 'Use <code>%s</code> as the parent for the <code>%s</code> admin bar node instead of <code>%s</code>.', $new_parent, $args['id'], $args['parent'] ) ); 
  153. $args['parent'] = $new_parent; 
  154.  
  155. $this->_set_node( $args ); 
  156.  
  157. /** 
  158. * @param array $args 
  159. */ 
  160. final protected function _set_node( $args ) { 
  161. $this->nodes[ $args['id'] ] = (object) $args; 
  162.  
  163. /** 
  164. * Gets a node. 
  165. * 
  166. * @param string $id 
  167. * @return object Node. 
  168. */ 
  169. final public function get_node( $id ) { 
  170. if ( $node = $this->_get_node( $id ) ) 
  171. return clone $node; 
  172.  
  173. /** 
  174. * @param string $id 
  175. * @return object|void 
  176. */ 
  177. final protected function _get_node( $id ) { 
  178. if ( $this->bound ) 
  179. return; 
  180.  
  181. if ( empty( $id ) ) 
  182. $id = 'root'; 
  183.  
  184. if ( isset( $this->nodes[ $id ] ) ) 
  185. return $this->nodes[ $id ]; 
  186.  
  187. /** 
  188. * @return array|void 
  189. */ 
  190. final public function get_nodes() { 
  191. if ( ! $nodes = $this->_get_nodes() ) 
  192. return; 
  193.  
  194. foreach ( $nodes as &$node ) { 
  195. $node = clone $node; 
  196. return $nodes; 
  197.  
  198. /** 
  199. * @return array|void 
  200. */ 
  201. final protected function _get_nodes() { 
  202. if ( $this->bound ) 
  203. return; 
  204.  
  205. return $this->nodes; 
  206.  
  207. /** 
  208. * Add a group to a menu node. 
  209. * 
  210. * @since 3.3.0 
  211. * 
  212. * @param array $args { 
  213. * Array of arguments for adding a group. 
  214. * 
  215. * @type string $id ID of the item. 
  216. * @type string $parent Optional. ID of the parent node. Default 'root'. 
  217. * @type array $meta Meta data for the group including the following keys: 
  218. * 'class', 'onclick', 'target', and 'title'. 
  219. * } 
  220. */ 
  221. final public function add_group( $args ) { 
  222. $args['group'] = true; 
  223.  
  224. $this->add_node( $args ); 
  225.  
  226. /** 
  227. * Remove a node. 
  228. * 
  229. * @param string $id The ID of the item. 
  230. */ 
  231. public function remove_node( $id ) { 
  232. $this->_unset_node( $id ); 
  233.  
  234. /** 
  235. * @param string $id 
  236. */ 
  237. final protected function _unset_node( $id ) { 
  238. unset( $this->nodes[ $id ] ); 
  239.  
  240. /** 
  241. * @access public 
  242. */ 
  243. public function render() { 
  244. $root = $this->_bind(); 
  245. if ( $root ) 
  246. $this->_render( $root ); 
  247.  
  248. /** 
  249. * @return object|void 
  250. */ 
  251. final protected function _bind() { 
  252. if ( $this->bound ) 
  253. return; 
  254.  
  255. // Add the root node. 
  256. // Clear it first, just in case. Don't mess with The Root. 
  257. $this->remove_node( 'root' ); 
  258. $this->add_node( array( 
  259. 'id' => 'root',  
  260. 'group' => false,  
  261. ) ); 
  262.  
  263. // Normalize nodes: define internal 'children' and 'type' properties. 
  264. foreach ( $this->_get_nodes() as $node ) { 
  265. $node->children = array(); 
  266. $node->type = ( $node->group ) ? 'group' : 'item'; 
  267. unset( $node->group ); 
  268.  
  269. // The Root wants your orphans. No lonely items allowed. 
  270. if ( ! $node->parent ) 
  271. $node->parent = 'root'; 
  272.  
  273. foreach ( $this->_get_nodes() as $node ) { 
  274. if ( 'root' == $node->id ) 
  275. continue; 
  276.  
  277. // Fetch the parent node. If it isn't registered, ignore the node. 
  278. if ( ! $parent = $this->_get_node( $node->parent ) ) { 
  279. continue; 
  280.  
  281. // Generate the group class (we distinguish between top level and other level groups). 
  282. $group_class = ( $node->parent == 'root' ) ? 'ab-top-menu' : 'ab-submenu'; 
  283.  
  284. if ( $node->type == 'group' ) { 
  285. if ( empty( $node->meta['class'] ) ) 
  286. $node->meta['class'] = $group_class; 
  287. else 
  288. $node->meta['class'] .= ' ' . $group_class; 
  289.  
  290. // Items in items aren't allowed. Wrap nested items in 'default' groups. 
  291. if ( $parent->type == 'item' && $node->type == 'item' ) { 
  292. $default_id = $parent->id . '-default'; 
  293. $default = $this->_get_node( $default_id ); 
  294.  
  295. // The default group is added here to allow groups that are 
  296. // added before standard menu items to render first. 
  297. if ( ! $default ) { 
  298. // Use _set_node because add_node can be overloaded. 
  299. // Make sure to specify default settings for all properties. 
  300. $this->_set_node( array( 
  301. 'id' => $default_id,  
  302. 'parent' => $parent->id,  
  303. 'type' => 'group',  
  304. 'children' => array(),  
  305. 'meta' => array( 
  306. 'class' => $group_class,  
  307. ),  
  308. 'title' => false,  
  309. 'href' => false,  
  310. ) ); 
  311. $default = $this->_get_node( $default_id ); 
  312. $parent->children[] = $default; 
  313. $parent = $default; 
  314.  
  315. // Groups in groups aren't allowed. Add a special 'container' node. 
  316. // The container will invisibly wrap both groups. 
  317. } elseif ( $parent->type == 'group' && $node->type == 'group' ) { 
  318. $container_id = $parent->id . '-container'; 
  319. $container = $this->_get_node( $container_id ); 
  320.  
  321. // We need to create a container for this group, life is sad. 
  322. if ( ! $container ) { 
  323. // Use _set_node because add_node can be overloaded. 
  324. // Make sure to specify default settings for all properties. 
  325. $this->_set_node( array( 
  326. 'id' => $container_id,  
  327. 'type' => 'container',  
  328. 'children' => array( $parent ),  
  329. 'parent' => false,  
  330. 'title' => false,  
  331. 'href' => false,  
  332. 'meta' => array(),  
  333. ) ); 
  334.  
  335. $container = $this->_get_node( $container_id ); 
  336.  
  337. // Link the container node if a grandparent node exists. 
  338. $grandparent = $this->_get_node( $parent->parent ); 
  339.  
  340. if ( $grandparent ) { 
  341. $container->parent = $grandparent->id; 
  342.  
  343. $index = array_search( $parent, $grandparent->children, true ); 
  344. if ( $index === false ) 
  345. $grandparent->children[] = $container; 
  346. else 
  347. array_splice( $grandparent->children, $index, 1, array( $container ) ); 
  348.  
  349. $parent->parent = $container->id; 
  350.  
  351. $parent = $container; 
  352.  
  353. // Update the parent ID (it might have changed). 
  354. $node->parent = $parent->id; 
  355.  
  356. // Add the node to the tree. 
  357. $parent->children[] = $node; 
  358.  
  359. $root = $this->_get_node( 'root' ); 
  360. $this->bound = true; 
  361. return $root; 
  362.  
  363. /** 
  364. * 
  365. * @global bool $is_IE 
  366. * @param object $root 
  367. */ 
  368. final protected function _render( $root ) { 
  369. global $is_IE; 
  370.  
  371. // Add browser classes. 
  372. // We have to do this here since admin bar shows on the front end. 
  373. $class = 'nojq nojs'; 
  374. if ( $is_IE ) { 
  375. if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE 7' ) ) 
  376. $class .= ' ie7'; 
  377. elseif ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE 8' ) ) 
  378. $class .= ' ie8'; 
  379. elseif ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE 9' ) ) 
  380. $class .= ' ie9'; 
  381. } elseif ( wp_is_mobile() ) { 
  382. $class .= ' mobile'; 
  383.  
  384. ?> 
  385. <div id="wpadminbar" class="<?php echo $class; ?>"> 
  386. <?php if ( ! is_admin() ) { ?> 
  387. <a class="screen-reader-shortcut" href="#wp-toolbar" tabindex="1"><?php _e( 'Skip to toolbar' ); ?></a> 
  388. <?php } ?> 
  389. <div class="quicklinks" id="wp-toolbar" role="navigation" aria-label="<?php esc_attr_e( 'Toolbar' ); ?>" tabindex="0"> 
  390. <?php foreach ( $root->children as $group ) { 
  391. $this->_render_group( $group ); 
  392. } ?> 
  393. </div> 
  394. <?php if ( is_user_logged_in() ) : ?> 
  395. <a class="screen-reader-shortcut" href="<?php echo esc_url( wp_logout_url() ); ?>"><?php _e('Log Out'); ?></a> 
  396. <?php endif; ?> 
  397. </div> 
  398.  
  399. <?php 
  400.  
  401. /** 
  402. * @param object $node 
  403. */ 
  404. final protected function _render_container( $node ) { 
  405. if ( $node->type != 'container' || empty( $node->children ) ) 
  406. return; 
  407.  
  408. ?><div id="<?php echo esc_attr( 'wp-admin-bar-' . $node->id ); ?>" class="ab-group-container"><?php 
  409. foreach ( $node->children as $group ) { 
  410. $this->_render_group( $group ); 
  411. ?></div><?php 
  412.  
  413. /** 
  414. * @param object $node 
  415. */ 
  416. final protected function _render_group( $node ) { 
  417. if ( $node->type == 'container' ) { 
  418. $this->_render_container( $node ); 
  419. return; 
  420. if ( $node->type != 'group' || empty( $node->children ) ) 
  421. return; 
  422.  
  423. if ( ! empty( $node->meta['class'] ) ) 
  424. $class = ' class="' . esc_attr( trim( $node->meta['class'] ) ) . '"'; 
  425. else 
  426. $class = ''; 
  427.  
  428. ?><ul id="<?php echo esc_attr( 'wp-admin-bar-' . $node->id ); ?>"<?php echo $class; ?>><?php 
  429. foreach ( $node->children as $item ) { 
  430. $this->_render_item( $item ); 
  431. ?></ul><?php 
  432.  
  433. /** 
  434. * @param object $node 
  435. */ 
  436. final protected function _render_item( $node ) { 
  437. if ( $node->type != 'item' ) 
  438. return; 
  439.  
  440. $is_parent = ! empty( $node->children ); 
  441. $has_link = ! empty( $node->href ); 
  442.  
  443. // Allow only numeric values, then casted to integers, and allow a tabindex value of `0` for a11y. 
  444. $tabindex = ( isset( $node->meta['tabindex'] ) && is_numeric( $node->meta['tabindex'] ) ) ? (int) $node->meta['tabindex'] : ''; 
  445. $aria_attributes = ( '' !== $tabindex ) ? ' tabindex="' . $tabindex . '"' : ''; 
  446.  
  447. $menuclass = ''; 
  448.  
  449. if ( $is_parent ) { 
  450. $menuclass = 'menupop '; 
  451. $aria_attributes .= ' aria-haspopup="true"'; 
  452.  
  453. if ( ! empty( $node->meta['class'] ) ) 
  454. $menuclass .= $node->meta['class']; 
  455.  
  456. if ( $menuclass ) 
  457. $menuclass = ' class="' . esc_attr( trim( $menuclass ) ) . '"'; 
  458.  
  459. ?> 
  460.  
  461. <li id="<?php echo esc_attr( 'wp-admin-bar-' . $node->id ); ?>"<?php echo $menuclass; ?>><?php 
  462. if ( $has_link ): 
  463. ?><a class="ab-item"<?php echo $aria_attributes; ?> href="<?php echo esc_url( $node->href ) ?>"<?php 
  464. if ( ! empty( $node->meta['onclick'] ) ) : 
  465. ?> onclick="<?php echo esc_js( $node->meta['onclick'] ); ?>"<?php 
  466. endif; 
  467. if ( ! empty( $node->meta['target'] ) ) : 
  468. ?> target="<?php echo esc_attr( $node->meta['target'] ); ?>"<?php 
  469. endif; 
  470. if ( ! empty( $node->meta['title'] ) ) : 
  471. ?> title="<?php echo esc_attr( $node->meta['title'] ); ?>"<?php 
  472. endif; 
  473. if ( ! empty( $node->meta['rel'] ) ) : 
  474. ?> rel="<?php echo esc_attr( $node->meta['rel'] ); ?>"<?php 
  475. endif; 
  476. if ( ! empty( $node->meta['lang'] ) ) : 
  477. ?> lang="<?php echo esc_attr( $node->meta['lang'] ); ?>"<?php 
  478. endif; 
  479. if ( ! empty( $node->meta['dir'] ) ) : 
  480. ?> dir="<?php echo esc_attr( $node->meta['dir'] ); ?>"<?php 
  481. endif; 
  482. ?>><?php 
  483. else: 
  484. ?><div class="ab-item ab-empty-item"<?php echo $aria_attributes; 
  485. if ( ! empty( $node->meta['title'] ) ) : 
  486. ?> title="<?php echo esc_attr( $node->meta['title'] ); ?>"<?php 
  487. endif; 
  488. if ( ! empty( $node->meta['lang'] ) ) : 
  489. ?> lang="<?php echo esc_attr( $node->meta['lang'] ); ?>"<?php 
  490. endif; 
  491. if ( ! empty( $node->meta['dir'] ) ) : 
  492. ?> dir="<?php echo esc_attr( $node->meta['dir'] ); ?>"<?php 
  493. endif; 
  494. ?>><?php 
  495. endif; 
  496.  
  497. echo $node->title; 
  498.  
  499. if ( $has_link ) : 
  500. ?></a><?php 
  501. else: 
  502. ?></div><?php 
  503. endif; 
  504.  
  505. if ( $is_parent ) : 
  506. ?><div class="ab-sub-wrapper"><?php 
  507. foreach ( $node->children as $group ) { 
  508. $this->_render_group( $group ); 
  509. ?></div><?php 
  510. endif; 
  511.  
  512. if ( ! empty( $node->meta['html'] ) ) 
  513. echo $node->meta['html']; 
  514.  
  515. ?> 
  516. </li><?php 
  517.  
  518. /** 
  519. * @param string $id Unused. 
  520. * @param object $node 
  521. */ 
  522. public function recursive_render( $id, $node ) { 
  523. _deprecated_function( __METHOD__, '3.3.0', 'WP_Admin_bar::render(), WP_Admin_Bar::_render_item()' ); 
  524. $this->_render_item( $node ); 
  525.  
  526. /** 
  527. * @access public 
  528. */ 
  529. public function add_menus() { 
  530. // User related, aligned right. 
  531. add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_menu', 0 ); 
  532. add_action( 'admin_bar_menu', 'wp_admin_bar_search_menu', 4 ); 
  533. add_action( 'admin_bar_menu', 'wp_admin_bar_my_account_item', 7 ); 
  534.  
  535. // Site related. 
  536. add_action( 'admin_bar_menu', 'wp_admin_bar_sidebar_toggle', 0 ); 
  537. add_action( 'admin_bar_menu', 'wp_admin_bar_wp_menu', 10 ); 
  538. add_action( 'admin_bar_menu', 'wp_admin_bar_my_sites_menu', 20 ); 
  539. add_action( 'admin_bar_menu', 'wp_admin_bar_site_menu', 30 ); 
  540. add_action( 'admin_bar_menu', 'wp_admin_bar_customize_menu', 40 ); 
  541. add_action( 'admin_bar_menu', 'wp_admin_bar_updates_menu', 50 ); 
  542.  
  543. // Content related. 
  544. if ( ! is_network_admin() && ! is_user_admin() ) { 
  545. add_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 ); 
  546. add_action( 'admin_bar_menu', 'wp_admin_bar_new_content_menu', 70 ); 
  547. add_action( 'admin_bar_menu', 'wp_admin_bar_edit_menu', 80 ); 
  548.  
  549. add_action( 'admin_bar_menu', 'wp_admin_bar_add_secondary_groups', 200 ); 
  550.  
  551. /** 
  552. * Fires after menus are added to the menu bar. 
  553. * 
  554. * @since 3.1.0 
  555. */ 
  556. do_action( 'add_admin_bar_menus' ); 
.