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