Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
23.97% covered (danger)
23.97%
29 / 121
20.00% covered (danger)
20.00%
3 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPcom_Admin_Menu
24.17% covered (danger)
24.17%
29 / 120
20.00% covered (danger)
20.00%
3 / 15
737.75
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 reregister_menu_items
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 get_preferred_view
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 get_current_user_blog_count
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 set_browse_sites_link_class
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 add_new_site_link
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 get_upsell_nudge
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 add_appearance_menu
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 add_users_menu
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 remove_gutenberg_menu
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 should_link_to_wp_admin
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 ajax_sidebar_state
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 wp_ajax_jitm_dismiss
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 sync_sidebar_collapsed_state
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 remove_submenus
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * WP.com Admin Menu file.
4 *
5 * @package automattic/jetpack-masterbar
6 */
7
8namespace Automattic\Jetpack\Masterbar;
9
10use Jetpack_Custom_CSS;
11use JITM;
12
13require_once __DIR__ . '/class-admin-menu.php';
14
15/**
16 * Class WPcom_Admin_Menu.
17 */
18class WPcom_Admin_Menu extends Admin_Menu {
19    /**
20     * WPcom_Admin_Menu constructor.
21     */
22    protected function __construct() {
23        parent::__construct();
24
25        add_action( 'wp_ajax_sidebar_state', array( $this, 'ajax_sidebar_state' ) );
26        add_action( 'wp_ajax_jitm_dismiss', array( $this, 'wp_ajax_jitm_dismiss' ) );
27        add_action( 'adminmenu', array( $this, 'render_upsell_nudge' ), 100 );
28        add_action( 'admin_init', array( $this, 'sync_sidebar_collapsed_state' ) );
29        add_action( 'admin_menu', array( $this, 'remove_submenus' ), 140 ); // After hookpress hook at 130.
30    }
31
32    /**
33     * Create the desired menu output.
34     */
35    public function reregister_menu_items() {
36        parent::reregister_menu_items();
37
38        $this->remove_gutenberg_menu();
39
40        // Not needed outside of wp-admin.
41        if ( ! $this->is_api_request ) {
42            $this->add_new_site_link();
43        }
44
45        ksort( $GLOBALS['menu'] );
46    }
47
48    /**
49     * Get the preferred view for the given screen.
50     *
51     * @param string $screen Screen identifier.
52     * @param bool   $fallback_global_preference (Optional) Whether the global preference for all screens should be used
53     *                                           as fallback if there is no specific preference for the given screen.
54     *                                           Default: true.
55     * @return string
56     */
57    public function get_preferred_view( $screen, $fallback_global_preference = true ) {
58        // When no preferred view has been set for Themes, keep the previous behavior that forced the default view
59        // regardless of the global preference.
60        if ( $fallback_global_preference && 'themes.php' === $screen ) {
61            $preferred_view = parent::get_preferred_view( $screen, false );
62            if ( self::UNKNOWN_VIEW === $preferred_view ) {
63                return self::DEFAULT_VIEW;
64            }
65            return $preferred_view;
66        }
67
68        // Plugins on Simple sites are always managed on Calypso.
69        if ( 'plugins.php' === $screen ) {
70            return self::DEFAULT_VIEW;
71        }
72
73        return parent::get_preferred_view( $screen, $fallback_global_preference );
74    }
75
76    /**
77     * Retrieve the number of blogs that the current user has.
78     *
79     * @return int
80     */
81    public function get_current_user_blog_count() {
82        if ( function_exists( '\get_blog_count_for_user' ) ) {
83            return \get_blog_count_for_user( get_current_user_id() );
84        }
85
86        $blogs = get_blogs_of_user( get_current_user_id() );
87        return is_countable( $blogs ) ? count( $blogs ) : 0;
88    }
89
90    /**
91     * Adds a custom element class for Site Switcher menu item.
92     *
93     * @param array $menu Associative array of administration menu items.
94     * @return array
95     */
96    public function set_browse_sites_link_class( array $menu ) {
97        foreach ( $menu as $key => $menu_item ) {
98            if ( 'site-switcher' !== $menu_item[3] ) {
99                continue;
100            }
101
102            $menu[ $key ][4] = add_cssclass( 'site-switcher', $menu_item[4] );
103            break;
104        }
105
106        return $menu;
107    }
108
109    /**
110     * Adds a link to the menu to create a new site.
111     */
112    public function add_new_site_link() {
113        if ( $this->get_current_user_blog_count() > 1 ) {
114            return;
115        }
116
117        $this->add_admin_menu_separator();
118        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
119        add_menu_page( __( 'Add New Site', 'jetpack-masterbar' ), __( 'Add New Site', 'jetpack-masterbar' ), 'read', 'https://wordpress.com/start?ref=calypso-sidebar', null, 'dashicons-plus-alt' );
120    }
121
122    /**
123     * Returns the first available upsell nudge.
124     *
125     * @return array
126     */
127    public function get_upsell_nudge() {
128        require_lib( 'jetpack-jitm/jitm-engine' );
129        $jitm_engine = new JITM\Engine();
130
131        $message_path = 'calypso:sites:sidebar_notice';
132        $current_user = wp_get_current_user();
133        $user_id      = $current_user->ID;
134        $user_roles   = implode( ',', $current_user->roles );
135        $query_string = array(
136            'message_path' => $message_path,
137        );
138
139        // Get the top message only.
140        $message = $jitm_engine->get_top_messages( $message_path, $user_id, $user_roles, $query_string );
141
142        if ( isset( $message[0] ) ) {
143            $message = $message[0];
144            return array(
145                'content'                      => $message->content['message'],
146                'cta'                          => $message->CTA['message'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
147                'link'                         => $message->CTA['link'], // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
148                'tracks_impression_event_name' => $message->tracks['display']['name'] ?? null,
149                'tracks_impression_cta_name'   => $message->tracks['display']['props']['cta_name'] ?? null,
150                'tracks_click_event_name'      => $message->tracks['click']['name'] ?? null,
151                'tracks_click_cta_name'        => $message->tracks['click']['props']['cta_name'] ?? null,
152                'dismissible'                  => $message->is_dismissible,
153                'feature_class'                => $message->feature_class,
154                'id'                           => $message->id,
155            );
156        }
157    }
158
159    /**
160     * Adds Appearance menu.
161     *
162     * @return string The Customizer URL.
163     */
164    public function add_appearance_menu() {
165        $customize_url = parent::add_appearance_menu();
166
167        $this->hide_submenu_page( 'themes.php', 'theme-editor.php' );
168
169        $user_can_customize = current_user_can( 'customize' );
170
171        if ( wp_is_block_theme() ) {
172            add_filter( 'safecss_is_freetrial', '__return_false', PHP_INT_MAX );
173            if ( class_exists( 'Jetpack_Custom_CSS' ) && empty( Jetpack_Custom_CSS::get_css() ) ) {
174                $user_can_customize = false;
175            }
176            remove_filter( 'safecss_is_freetrial', '__return_false', PHP_INT_MAX );
177        }
178
179        if ( $user_can_customize ) {
180            $customize_custom_css_url = add_query_arg( array( 'autofocus' => array( 'section' => 'jetpack_custom_css' ) ), $customize_url );
181            // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
182            add_submenu_page( 'themes.php', esc_attr__( 'Additional CSS', 'jetpack-masterbar' ), __( 'Additional CSS', 'jetpack-masterbar' ), 'customize', esc_url( $customize_custom_css_url ), null, 20 );
183        }
184
185        return $customize_url;
186    }
187
188    /**
189     * Adds Users menu.
190     */
191    public function add_users_menu() {
192        $submenus_to_update = array(
193            'grofiles-editor'        => 'https://wordpress.com/me',
194            'grofiles-user-settings' => 'https://wordpress.com/me/account',
195        );
196
197        if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'users.php' ) ) {
198            $submenus_to_update['users.php'] = 'https://wordpress.com/people/team/' . $this->domain;
199        }
200
201        $slug = current_user_can( 'list_users' ) ? 'users.php' : 'profile.php';
202        $this->update_submenus( $slug, $submenus_to_update );
203        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
204        add_submenu_page( 'users.php', esc_attr__( 'Add New User', 'jetpack-masterbar' ), __( 'Add New User', 'jetpack-masterbar' ), 'promote_users', 'https://wordpress.com/people/new/' . $this->domain, null, 1 );
205    }
206
207    /**
208     * Also remove the Gutenberg plugin menu.
209     */
210    public function remove_gutenberg_menu() {
211        // Always remove the Gutenberg menu.
212        remove_menu_page( 'gutenberg' );
213    }
214
215    /**
216     * Whether to use wp-admin pages rather than Calypso.
217     *
218     * @return bool
219     */
220    public function should_link_to_wp_admin() {
221        $result = false; // Calypso.
222
223        $user_attribute = get_user_attribute( get_current_user_id(), 'calypso_preferences' );
224        if ( ! empty( $user_attribute['linkDestination'] ) ) {
225            $result = $user_attribute['linkDestination'];
226        }
227
228        return $result;
229    }
230
231    /**
232     * Saves the sidebar state ( expanded / collapsed ) via an ajax request.
233     *
234     * @return never
235     */
236    public function ajax_sidebar_state() {
237        $expanded    = isset( $_REQUEST['expanded'] ) ? filter_var( wp_unslash( $_REQUEST['expanded'] ), FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
238        $user_id     = get_current_user_id();
239        $preferences = get_user_attribute( $user_id, 'calypso_preferences' );
240        if ( empty( $preferences ) ) {
241            $preferences = array();
242        }
243
244        $value = array_merge( (array) $preferences, array( 'sidebarCollapsed' => ! $expanded ) );
245        $value = array_filter(
246            $value,
247            function ( $preference ) {
248                return null !== $preference;
249            }
250        );
251
252        update_user_attribute( $user_id, 'calypso_preferences', $value );
253
254        die( 0 );
255    }
256
257    /**
258     * Handle ajax requests to dismiss a just-in-time-message
259     */
260    public function wp_ajax_jitm_dismiss() {
261        check_ajax_referer( 'jitm_dismiss' );
262        require_lib( 'jetpack-jitm/jitm-engine' );
263        if ( isset( $_REQUEST['id'] ) && isset( $_REQUEST['feature_class'] ) ) {
264            JITM\Engine::dismiss( sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['feature_class'] ) ) );
265        }
266        wp_die();
267    }
268
269    /**
270     * Syncs the sidebar collapsed state from Calypso Preferences.
271     */
272    public function sync_sidebar_collapsed_state() {
273        $calypso_preferences = get_user_attribute( get_current_user_id(), 'calypso_preferences' );
274
275        $sidebar_collapsed = $calypso_preferences['sidebarCollapsed'] ?? false;
276
277        // Read the current stored setting and convert it to boolean in order to be able to compare the values later.
278        $current_sidebar_collapsed_setting = 'f' === get_user_setting( 'mfold' );
279
280        // Only set the setting if the value differs, as `set_user_setting` always updates at least the timestamp
281        // which leads to unnecessary user meta cache purging on all wp-admin screen requests.
282        if ( $current_sidebar_collapsed_setting !== $sidebar_collapsed ) {
283            set_user_setting( 'mfold', $sidebar_collapsed ? 'f' : 'o' );
284        }
285    }
286
287    /**
288     * Removes unwanted submenu items.
289     *
290     * These submenus are added across wp-content and should be removed together with these function calls.
291     */
292    public function remove_submenus() {
293        global $_registered_pages;
294
295        remove_submenu_page( 'index.php', 'akismet-stats' );
296        remove_submenu_page( 'index.php', 'my-comments' );
297        remove_submenu_page( 'index.php', 'stats' );
298        remove_submenu_page( 'index.php', 'subscriptions' );
299
300        /* @see https://github.com/Automattic/wp-calypso/issues/49210 */
301        remove_submenu_page( 'index.php', 'my-blogs' );
302        $_registered_pages['admin_page_my-blogs'] = true; // phpcs:ignore
303
304        remove_submenu_page( 'themes.php', 'customize.php?autofocus[panel]=amp_panel&return=' . rawurlencode( admin_url() ) );
305
306        remove_submenu_page( 'users.php', 'wpcom-invite-users' ); // Wpcom_Invite_Users::action_admin_menu.
307
308        remove_submenu_page( 'options-general.php', 'adcontrol' );
309
310        // Remove menu item but continue allowing access.
311        foreach ( array( 'openidserver', 'webhooks' ) as $page_slug ) {
312            remove_submenu_page( 'options-general.php', $page_slug );
313            $_registered_pages[ 'admin_page_' . $page_slug ] = true; // phpcs:ignore
314        }
315    }
316}