Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
52.03% covered (warning)
52.03%
154 / 296
36.36% covered (danger)
36.36%
4 / 11
CRAP
n/a
0 / 0
maybe_add_origin_site_id_to_url
n/a
0 / 0
n/a
0 / 0
1
wpcom_enqueue_admin_bar_assets
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
20
wpcom_always_use_user_locale
33.33% covered (danger)
33.33%
4 / 12
0.00% covered (danger)
0.00%
0 / 1
8.74
wpcom_replace_wp_logo_with_wpcom_logo_menu
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
1 / 1
8
wpcom_add_shopping_cart
6.67% covered (danger)
6.67%
2 / 30
0.00% covered (danger)
0.00%
0 / 1
25.33
wpcom_replace_edit_profile_menu_to_me
63.64% covered (warning)
63.64%
7 / 11
0.00% covered (danger)
0.00%
0 / 1
4.77
wpcom_add_my_wpcom_account_submenu
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
wpcom_custom_wpcom_admin_bar_class
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
wpcom_edit_site_menu_override
16.67% covered (danger)
16.67%
1 / 6
0.00% covered (danger)
0.00%
0 / 1
4.31
wpcom_add_site_badges_and_plan
57.14% covered (warning)
57.14%
44 / 77
0.00% covered (danger)
0.00%
0 / 1
69.34
wpcom_add_stats_to_site_menu
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * WordPress.com admin bar
4 *
5 * Modifies the WordPress admin bar with WordPress.com-specific stuff.
6 *
7 * @package automattic/jetpack-mu-wpcom
8 */
9
10use Automattic\Jetpack\Connection\Urls;
11use Automattic\Jetpack\Current_Plan;
12use Automattic\Jetpack\Jetpack_Mu_Wpcom;
13use Automattic\Jetpack\Modules;
14use Automattic\Jetpack\Status;
15
16// The $icon-color variable for admin color schemes.
17// See: https://github.com/WordPress/wordpress-develop/blob/679cc0c4a261a77bd8fdb140cd9b0b2ff80ebf37/src/wp-admin/css/colors/_variables.scss#L9
18// Only Core schemes are listed here. Calypso schemes all use #ffffff.
19const WPCOM_ADMIN_ICON_COLORS = array(
20    'blue'      => '#e5f8ff',
21    'coffee'    => '#f3f2f1',
22    'ectoplasm' => '#ece6f6',
23    'midnight'  => '#f3f2f1',
24    'fresh'     => '#a7aaad',
25    'ocean'     => '#f2fcff',
26    'light'     => '#999',
27    'modern'    => '#f3f1f1',
28    'sunrise'   => '#f3f1f1',
29);
30
31/**
32 * Adds the origin_site_id query parameter to a URL.
33 *
34 * @deprecated 6.10.0 Use Automattic\Jetpack\Connection\Urls::maybe_add_origin_site_id instead.
35 *
36 * @param string $url The URL to add the query param to.
37 * @return string The URL with the origin_site_id query parameter maybe added.
38 */
39function maybe_add_origin_site_id_to_url( $url ) {
40    _deprecated_function( __FUNCTION__, 'jetpack-mu-wpcom-6.10.0', 'Automattic\Jetpack\Connection\Urls::maybe_add_origin_site_id' );
41    return Urls::maybe_add_origin_site_id( $url );
42}
43
44/**
45 * Enqueue assets needed by the WordPress.com admin bar.
46 */
47function wpcom_enqueue_admin_bar_assets() {
48    $asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-admin-bar/wpcom-admin-bar.asset.php';
49
50    wp_enqueue_script(
51        'wpcom-admin-bar',
52        plugins_url( 'build/wpcom-admin-bar/wpcom-admin-bar.js', Jetpack_Mu_Wpcom::BASE_FILE ),
53        $asset_file['dependencies'] ?? array(),
54        $asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-admin-bar/wpcom-admin-bar.js' ),
55        array(
56            'strategy'  => 'defer',
57            'in_footer' => true,
58        )
59    );
60
61    wp_enqueue_style(
62        'wpcom-admin-bar',
63        plugins_url( 'build/wpcom-admin-bar/wpcom-admin-bar.css', Jetpack_Mu_Wpcom::BASE_FILE ),
64        array(),
65        $asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-admin-bar/wpcom-admin-bar.css' )
66    );
67
68    /**
69     * Force the Atomic debug bar menu to be the first menu at the top-right.
70     */
71    if ( defined( 'AT_PROXIED_REQUEST' ) && AT_PROXIED_REQUEST ) {
72        wp_add_inline_style(
73            'wpcom-admin-bar',
74            <<<'CSS'
75                #wpadminbar .quicklinks #wp-admin-bar-top-secondary {
76                    display: flex;
77                }
78
79                #wpadminbar .quicklinks #wp-admin-bar-top-secondary #wp-admin-bar-debug-bar {
80                    order: -1;
81                }
82CSS
83        );
84    }
85
86    $admin_color      = is_admin() ? get_user_option( 'admin_color' ) : 'fresh';
87    $admin_icon_color = WPCOM_ADMIN_ICON_COLORS[ $admin_color ] ?? '#ffffff';
88
89    // Force the icon colors to have desktop color even on mobile viewport.
90    wp_add_inline_style(
91        'wpcom-admin-bar',
92        <<<CSS
93            #wpadminbar.mobile .quicklinks li:not(#wpwrap.wp-responsive-open #wp-admin-bar-menu-toggle) .ab-icon::before,
94            #wpadminbar.mobile .quicklinks li:not(#wpwrap.wp-responsive-open #wp-admin-bar-menu-toggle) .ab-item::before {
95                color: $admin_icon_color !important;
96            }
97CSS
98    );
99
100    // Force wpcom icons to have consistent color.
101    wp_add_inline_style(
102        'wpcom-admin-bar',
103        <<<CSS
104            :where(#wpadminbar .ab-icon) {
105                color: $admin_icon_color;
106            }
107CSS
108    );
109}
110add_action( 'wp_enqueue_scripts', 'wpcom_enqueue_admin_bar_assets' );
111add_action( 'admin_enqueue_scripts', 'wpcom_enqueue_admin_bar_assets' );
112
113/**
114 * Render the admin bar in user locale even on frontend screens.
115 */
116function wpcom_always_use_user_locale() {
117    if ( is_admin() || ! is_admin_bar_showing() ) {
118        return;
119    }
120
121    $site_locale = get_locale();
122    $user_locale = get_user_locale();
123
124    if ( $site_locale !== $user_locale ) {
125        switch_to_locale( $user_locale );
126        add_action(
127            'wp_after_admin_bar_render',
128            function () use ( $site_locale ) {
129                switch_to_locale( $site_locale );
130            }
131        );
132    }
133}
134add_action( 'admin_bar_menu', 'wpcom_always_use_user_locale', -1 );
135
136/**
137 * Replaces the WP logo with WP.com logo.
138 *
139 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
140 */
141function wpcom_replace_wp_logo_with_wpcom_logo_menu( $wp_admin_bar ) {
142    $about_node      = $wp_admin_bar->get_node( 'about' );
143    $contribute_node = $wp_admin_bar->get_node( 'contribute' );
144
145    foreach ( $wp_admin_bar->get_nodes() as $node ) {
146        if ( $node->parent === 'wp-logo' || $node->parent === 'wp-logo-external' ) {
147            $wp_admin_bar->remove_node( $node->id );
148        }
149    }
150    $wp_admin_bar->remove_node( 'wp-logo' );
151    $wp_admin_bar->add_node(
152        array(
153            'id'    => 'wp-logo',
154            'title' => '<span class="ab-icon" aria-hidden="true"></span><span class="screen-reader-text">' .
155                        /* translators: Hidden accessibility text. */
156                        'WordPress.com' .
157                        '</span>',
158            'href'  => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/sites' ),
159            'meta'  => array(
160                'menu_title' => 'WordPress.com',
161            ),
162        )
163    );
164
165    $wp_admin_bar->add_node(
166        array(
167            'parent' => 'wp-logo',
168            'id'     => 'wpcom-sites',
169            'title'  => __( 'Sites', 'jetpack-mu-wpcom' ),
170            'href'   => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/sites' ),
171        )
172    );
173
174    $wp_admin_bar->add_node(
175        array(
176            'parent' => 'wp-logo',
177            'id'     => 'wpcom-domains',
178            'title'  => __( 'Domains', 'jetpack-mu-wpcom' ),
179            'href'   => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/domains' ),
180        )
181    );
182
183    $wp_admin_bar->add_node(
184        array(
185            'parent' => 'wp-logo',
186            'id'     => 'wpcom-emails',
187            'title'  => __( 'Emails', 'jetpack-mu-wpcom' ),
188            'href'   => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/emails' ),
189        )
190    );
191
192    $wp_admin_bar->add_node(
193        array(
194            'parent' => 'wp-logo',
195            'id'     => 'wpcom-plugins',
196            'title'  => __( 'Plugins', 'jetpack-mu-wpcom' ),
197            'href'   => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/plugins' ),
198        )
199    );
200
201    if ( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) {
202        $wp_admin_bar->add_group(
203            array(
204                'parent' => 'wp-logo',
205                'id'     => 'wp-logo-external',
206                'meta'   => array(
207                    'class' => 'ab-sub-secondary',
208                ),
209            )
210        );
211
212        if ( $about_node ) {
213            $about_node->parent = 'wp-logo-external';
214            $wp_admin_bar->add_node( (array) $about_node );
215        }
216        if ( $contribute_node ) {
217            $contribute_node->parent = 'wp-logo-external';
218            $wp_admin_bar->add_node( (array) $contribute_node );
219        }
220    }
221}
222add_action( 'admin_bar_menu', 'wpcom_replace_wp_logo_with_wpcom_logo_menu', 11 );
223
224/**
225 * Adds the Cart menu to the WordPress admin bar.
226 *
227 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
228 */
229function wpcom_add_shopping_cart( $wp_admin_bar ) {
230    // Return if the site isn't a simple site
231    if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
232        return;
233    }
234
235    // Include the shopping cart functionality from the specified path.
236    require_once WP_CONTENT_DIR . '/admin-plugins/wpcom-billing/shopping-cart.php';
237
238    // Get the current blog ID.
239    $blog_id = get_current_blog_id();
240
241    $is_empty = \Store_Shopping_Cart::is_cart_empty(
242        array(
243            'blog_id' => $blog_id,
244            'user_id' => get_current_user_id(),
245        )
246    );
247
248    // If the cart is empty (no products), do not add the cart menu.
249    if ( $is_empty ) {
250        return;
251    }
252
253    // Get the Calypso site slug for the current blog.
254    $calypso_site_slug = \WPCOM_Masterbar::get_calypso_site_slug( $blog_id );
255
256    // If no Calypso site slug is found, return early.
257    if ( ! $calypso_site_slug ) {
258        return;
259    }
260
261    // Add the cart menu item to the WordPress admin bar.
262    $wp_admin_bar->add_menu(
263        array(
264            'id'     => 'cart', // Unique ID for the cart menu item.
265            'title'  => '<span class="ab-item cart-icon" aria-hidden="true"></span>' .
266                        '<div class="cart-icon__dot"></div>' .
267                        '<span class="screen-reader-text">' .
268                        /* translators: Hidden accessibility text. */
269                        __( 'Cart', 'jetpack-mu-wpcom' ) .
270                        '</span>',
271            'href'   => 'https://wordpress.com/checkout/' . esc_attr( $calypso_site_slug ), // Link to the checkout page.
272            'meta'   => array(
273                'class' => 'wp-admin-bar-cart', // Custom class for styling the cart menu item.
274            ),
275            'parent' => 'top-secondary', // Position the cart in the 'top-secondary' section of the admin bar.
276        )
277    );
278}
279
280// Hook the cart icon to the admin bar menu, placing it before the reader icon (same as Calypso).
281add_action( 'admin_bar_menu', 'wpcom_add_shopping_cart', 11 );
282
283// Add the reader icon to the admin bar before the help center icon.
284add_action(
285    'wp_loaded',
286    function () {
287        if ( class_exists( '\Automattic\Jetpack\Newsletter\Reader_Link' ) ) {
288            // @phan-suppress-next-line PhanUndeclaredClassMethod -- class_exists guarded above; provided by sibling autoloader.
289            \Automattic\Jetpack\Newsletter\Reader_Link::init();
290        }
291    }
292);
293
294/**
295 * Points the "Edit Profile" and "Howdy,..." to /me if the user is not member of the blog.
296 *
297 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
298 */
299function wpcom_replace_edit_profile_menu_to_me( $wp_admin_bar ) {
300    if ( is_user_member_of_blog() ) {
301        return;
302    }
303
304    $edit_profile_node = $wp_admin_bar->get_node( 'user-info' );
305    if ( $edit_profile_node ) {
306        $edit_profile_node->href  = Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/me/account' );
307        $edit_profile_node->title = preg_replace( "/(<span class='display-name edit-profile'>)(.*?)(<\/span>)/", '$1' . __( 'My Profile', 'jetpack-mu-wpcom' ) . '$3', $edit_profile_node->title );
308        $wp_admin_bar->add_node( (array) $edit_profile_node );
309    }
310    $my_account_node = $wp_admin_bar->get_node( 'my-account' );
311    if ( $my_account_node ) {
312        $my_account_node->href = Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/me/account' );
313        $wp_admin_bar->add_node( (array) $my_account_node );
314    }
315}
316// Run this function later than Core: https://github.com/WordPress/wordpress-develop/blob/5a30482419f1b0bcc713a7fdee3a14afd67a1bca/src/wp-includes/class-wp-admin-bar.php#L651
317add_action( 'admin_bar_menu', 'wpcom_replace_edit_profile_menu_to_me', 9999 );
318
319/**
320 * Adds "Howdy,..." -> My WP.com Account submenu pointing to /me/account.
321 *
322 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
323 */
324function wpcom_add_my_wpcom_account_submenu( $wp_admin_bar ) {
325    $wp_admin_bar->add_group(
326        array(
327            'parent' => 'my-account',
328            'id'     => 'wpcom-account',
329            'meta'   => array(
330                'class' => 'ab-sub-secondary',
331            ),
332        )
333    );
334
335    /* translators: %s: WordPress.com logo */
336    $button_text = sprintf( __( 'My %s WordPress.com Account', 'jetpack-mu-wpcom' ), '<span class="wpcom-logo"></span>' );
337
338    $wp_admin_bar->add_node(
339        array(
340            'parent' => 'wpcom-account',
341            'id'     => 'my-wpcom-account',
342            'title'  => '<span class="button wpcom-button">' . $button_text . '</span>',
343            'href'   => Urls::maybe_add_origin_site_id( 'https://my.wordpress.com/me/account' ),
344        )
345    );
346}
347add_action( 'admin_bar_menu', 'wpcom_add_my_wpcom_account_submenu' );
348
349/**
350 * Replaces the default admin bar class with our own.
351 *
352 * @param string $wp_admin_bar_class Admin bar class to use. Default 'WP_Admin_Bar'.
353 * @return string Name of the admin bar class.
354 */
355function wpcom_custom_wpcom_admin_bar_class( $wp_admin_bar_class ) {
356    remove_filter( 'pre_option_wpcom_admin_interface', 'wpcom_admin_interface_pre_get_option' );
357    $is_wp_admin = get_option( 'wpcom_admin_interface' ) === 'wp-admin';
358    add_filter( 'pre_option_wpcom_admin_interface', 'wpcom_admin_interface_pre_get_option', 10 );
359
360    if ( $is_wp_admin ) {
361        return $wp_admin_bar_class;
362    }
363
364    require_once __DIR__ . '/class-wpcom-admin-bar.php';
365    return '\Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Admin_Bar';
366}
367add_filter( 'wp_admin_bar_class', 'wpcom_custom_wpcom_admin_bar_class' );
368
369/**
370 * Changes the edit site menu to point to the top-level site editor.
371 *
372 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
373 */
374function wpcom_edit_site_menu_override( $wp_admin_bar ) {
375    if ( $wp_admin_bar->get_node( 'site-editor' ) ) {
376        $args = array(
377            'id'   => 'site-editor',
378            'href' => admin_url( 'site-editor.php' ),
379        );
380
381        $wp_admin_bar->add_node( $args );
382    }
383}
384add_action( 'admin_bar_menu', 'wpcom_edit_site_menu_override', 41 );
385
386/**
387 * Adds site badges and plan information to the site title dropdown menu.
388 *
389 * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar core object.
390 */
391function wpcom_add_site_badges_and_plan( $wp_admin_bar ) {
392    // Get the current blog ID
393    $blog_id = get_current_blog_id();
394    $status  = new Status();
395
396    // Check for various site types
397    $badge_text = '';
398
399    // Check if this is a P2 site
400    if ( str_contains( get_stylesheet(), 'pub/p2' ) ||
401        ( function_exists( '\WPForTeams\is_wpforteams_site' ) &&
402        \WPForTeams\is_wpforteams_site( $blog_id ) ) ) {
403        $badge_text = 'P2';
404    } elseif ( (bool) get_option( 'wpcom_is_staging_site' ) ) {
405        // Check for staging site
406        $badge_text = __( 'Staging', 'jetpack-mu-wpcom' );
407    } elseif ( function_exists( 'wpcom_site_has_feature' ) && wpcom_site_has_feature( 'trial' ) ) {
408        // Check for trial site
409        $badge_text = __( 'Trial', 'jetpack-mu-wpcom' );
410    } elseif ( get_option( 'launch-status' ) === 'unlaunched' || $status->is_coming_soon() ) {
411        // Check for Coming Soon site
412        $badge_text = __( 'Coming Soon', 'jetpack-mu-wpcom' );
413    } elseif ( $status->is_private_site() ) {
414        // Check for private site
415        $badge_text = __( 'Private', 'jetpack-mu-wpcom' );
416    } elseif ( ( function_exists( 'has_blog_sticker' ) && has_blog_sticker( 'difm-lite-in-progress' ) ) ||
417        ( function_exists( 'wpcomsh_is_site_sticker_active' ) && wpcomsh_is_site_sticker_active( 'difm-lite-in-progress' ) ) ) {
418        // Check for Express service
419        $badge_text = __( 'Express', 'jetpack-mu-wpcom' );
420    } elseif ( function_exists( 'is_simple_site_redirect' ) && is_simple_site_redirect( $status->get_site_suffix() ) ) {
421        // Check for Redirect site
422        $badge_text = __( 'Redirect', 'jetpack-mu-wpcom' );
423    } elseif ( ! empty( get_option( 'options' )['is_domain_only'] ) ) {
424        // Check for Domain Only site
425        $badge_text = __( 'Domain Only', 'jetpack-mu-wpcom' );
426    }
427
428    // Add badge to the site name dropdown if a badge is applicable
429    $status_text = '';
430    if ( $badge_text ) {
431        $status_text = '<div class="wp-admin-bar__site-info">
432                            <span class="wp-admin-bar__site-info-label">' . __( 'Status', 'jetpack-mu-wpcom' ) . '</span>
433                            <span class="wp-admin-bar__info-badges">' . esc_html( $badge_text ) . '</span>
434                        </div>';
435    }
436
437    // Add plan information for non-staging sites
438    $plan_text  = '';
439    $is_staging = (bool) get_option( 'wpcom_is_staging_site' );
440    if ( ! $is_staging ) {
441        if ( class_exists( '\WPCOM_Store_API' ) ) {
442            $current_plan = WPCOM_Store_API::get_current_plan( get_current_blog_id() );
443        } else {
444            $current_plan = Current_Plan::get();
445        }
446        $plan_name = $current_plan['product_name_short'] ?? '';
447
448        if ( $plan_name ) {
449            // wpcom_get_site_slug() resolves the Calypso slug on both Simple and
450            // Atomic (where \WPCOM_Masterbar is absent), falling back to the site
451            // URL so the link still renders.
452            $site_slug = wpcom_get_site_slug();
453
454            if ( $site_slug ) {
455                $plan_text = '<a class="wp-admin-bar__site-info" href="https://wordpress.com/plans/' . esc_attr( $site_slug ) . '">
456                                <span class="wp-admin-bar__site-info-label">' . __( 'Plan', 'jetpack-mu-wpcom' ) . '</span>
457                                <span class="wp-admin-bar__info-badges">' . esc_html( $plan_name ) . '</span>
458                            </a>';
459            } else {
460                $plan_text = '<div class="wp-admin-bar__site-info">
461                                <span class="wp-admin-bar__site-info-label">' . __( 'Plan', 'jetpack-mu-wpcom' ) . '</span>
462                                <span class="wp-admin-bar__info-badges">' . esc_html( $plan_name ) . '</span>
463                            </div>';
464            }
465        }
466    }
467
468    if ( $plan_text ) {
469        $wp_admin_bar->add_group(
470            array(
471                'parent' => 'site-name',
472                'id'     => 'site-plan',
473            )
474        );
475        $wp_admin_bar->add_node(
476            array(
477                'parent' => 'site-plan',
478                'id'     => 'site-plan-badge',
479                'title'  => $plan_text,
480            )
481        );
482    }
483
484    if ( $status_text ) {
485        $wp_admin_bar->add_group(
486            array(
487                'parent' => 'site-name',
488                'id'     => 'site-status',
489                'meta'   => array(
490                    'class' => 'ab-sub-secondary',
491                ),
492            )
493        );
494        $wp_admin_bar->add_node(
495            array(
496                'parent' => 'site-status',
497                'id'     => 'site-status-badge',
498                'title'  => $status_text,
499            )
500        );
501    }
502}
503add_action( 'admin_bar_menu', 'wpcom_add_site_badges_and_plan', 35 );
504
505/**
506 * Adds a "Stats" link to the site-name submenu, alongside "Dashboard"/"Visit Site" (STATS-287).
507 *
508 * Hooks `admin_bar_menu` at priority 40, after core's `wp_admin_bar_site_menu`
509 * (priority 30) has added the `site-name` node and either `dashboard` (front end)
510 * or `view-site` (wp-admin), so this shows in both contexts.
511 *
512 * `view_stats` is not a proxy for the module being on: the Stats package registers
513 * its `map_meta_cap` handler on any wp-admin request, so an administrator keeps the
514 * capability after the module is switched off, while `admin.php?page=stats` stops
515 * being registered. Linking there then renders "Sorry, you are not allowed to access
516 * this page", so check the module too. `is_active()` short-circuits to true on Simple,
517 * where the module can't be switched off; it only narrows anything on Atomic.
518 *
519 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
520 */
521function wpcom_add_stats_to_site_menu( $wp_admin_bar ) {
522    if (
523        ( ! $wp_admin_bar->get_node( 'dashboard' ) && ! $wp_admin_bar->get_node( 'view-site' ) )
524        || ! current_user_can( 'view_stats' )
525        || ! ( new Modules() )->is_active( 'stats' )
526    ) {
527        return;
528    }
529
530    $wp_admin_bar->add_node(
531        array(
532            'parent' => 'site-name',
533            'id'     => 'wpcom-stats',
534            'title'  => __( 'Stats', 'jetpack-mu-wpcom' ),
535            /** This filter is documented in projects/packages/stats-admin/src/class-admin-bar.php */
536            'href'   => apply_filters( 'jetpack_stats_url', admin_url( 'admin.php?page=stats' ), array( 'view' => 'dashboard' ) ),
537        )
538    );
539}
540add_action( 'admin_bar_menu', 'wpcom_add_stats_to_site_menu', 40 );