Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
39.13% covered (danger)
39.13%
45 / 115
17.65% covered (danger)
17.65%
3 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Atomic_Admin_Menu
39.47% covered (danger)
39.47%
45 / 114
17.65% covered (danger)
17.65%
3 / 17
515.19
0.00% covered (danger)
0.00%
0 / 1
 __construct
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
2.00
 dequeue_scripts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_rtl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 reregister_menu_items
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 relabel_woocommerce_menu
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 get_site_purchases
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_preferred_view
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 add_appearance_menu
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 add_users_menu
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 add_plugins_menu
64.71% covered (warning)
64.71%
11 / 17
0.00% covered (danger)
0.00%
0 / 1
9.15
 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
3.14
 get_upsell_nudge
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 override_the_theme_installer
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 remove_gutenberg_menu
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ajax_sidebar_state
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 wp_ajax_jitm_dismiss
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Atomic Admin Menu file.
4 *
5 * @package automattic/jetpack-masterbar
6 */
7
8namespace Automattic\Jetpack\Masterbar;
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\JITMS\JITM;
12use Automattic\Jetpack\Modules;
13
14require_once __DIR__ . '/class-admin-menu.php';
15
16/**
17 * Class Atomic_Admin_Menu.
18 */
19class Atomic_Admin_Menu extends Admin_Menu {
20
21    /**
22     * Atomic_Admin_Menu constructor.
23     */
24    protected function __construct() {
25        parent::__construct();
26
27        add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_scripts' ), 20 );
28        add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_scripts' ), 20 );
29        add_action( 'wp_ajax_sidebar_state', array( $this, 'ajax_sidebar_state' ) );
30        add_action( 'wp_ajax_jitm_dismiss', array( $this, 'wp_ajax_jitm_dismiss' ) );
31        add_action( 'adminmenu', array( $this, 'render_upsell_nudge' ), 100 );
32
33        if ( ! $this->is_api_request ) {
34            add_filter( 'submenu_file', array( $this, 'override_the_theme_installer' ), 10, 2 );
35        }
36
37        add_action(
38            'admin_menu',
39            function () {
40                remove_action( 'admin_menu', 'gutenberg_menu', 9 );
41            },
42            0
43        );
44    }
45
46    /**
47     * Dequeues unnecessary scripts.
48     */
49    public function dequeue_scripts() {
50        wp_dequeue_script( 'a8c_wpcom_masterbar_overrides' ); // Initially loaded in modules/masterbar/masterbar/class-masterbar.php.
51    }
52
53    /**
54     * Determines whether the current locale is right-to-left (RTL).
55     *
56     * Performs the check against the current locale set on the WordPress.com's account settings.
57     * See `Masterbar::__construct` in `modules/masterbar/masterbar/class-masterbar.php`.
58     */
59    public function is_rtl() {
60        return get_user_option( 'jetpack_wpcom_is_rtl' );
61    }
62
63    /**
64     * Create the desired menu output.
65     */
66    public function reregister_menu_items() {
67        parent::reregister_menu_items();
68
69        $this->remove_gutenberg_menu();
70
71        // Not needed outside of wp-admin.
72        if ( ! $this->is_api_request ) {
73            $this->add_new_site_link();
74        }
75
76        $this->relabel_woocommerce_menu();
77
78        ksort( $GLOBALS['menu'] );
79    }
80
81    /**
82     * Relabels the WooCommerce menu item to "Store setup" on Commerce-plan sites.
83     *
84     * Only the sidebar label is changed; the page title is left untouched. On Atomic sites
85     * using the classic wp-admin interface this class doesn't load and jetpack-mu-wpcom
86     * relabels instead; on the nav-unified interface both run (harmless, both idempotently
87     * set "Store setup") — see wpcom_relabel_woocommerce_menu() in jetpack-mu-wpcom.
88     */
89    public function relabel_woocommerce_menu() {
90        global $menu;
91
92        if ( ! Store_Plan::purchases_include_commerce_plan( $this->get_site_purchases() ) ) {
93            return;
94        }
95
96        foreach ( $menu as $position => $item ) {
97            if ( isset( $item[2] ) && 'woocommerce' === $item[2] ) {
98                // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
99                $menu[ $position ][0] = __( 'Store setup', 'jetpack-masterbar' );
100                break;
101            }
102        }
103    }
104
105    /**
106     * The site's purchases, or an empty array when the WoA helper is unavailable.
107     *
108     * Exists as an overridable seam so tests can inject purchases without the WoA helper.
109     *
110     * @return array Product objects, each with at least a product_slug property.
111     */
112    protected function get_site_purchases() {
113        if ( ! function_exists( 'wpcom_get_site_purchases' ) ) {
114            return array();
115        }
116
117        return wpcom_get_site_purchases();
118    }
119
120    /**
121     * Get the preferred view for the given screen.
122     *
123     * @param string $screen Screen identifier.
124     * @param bool   $fallback_global_preference (Optional) Whether the global preference for all screens should be used
125     *                                           as fallback if there is no specific preference for the given screen.
126     *                                           Default: true.
127     * @return string
128     */
129    public function get_preferred_view( $screen, $fallback_global_preference = true ) {
130
131        // Export on Atomic sites are always managed on WP Admin.
132        if ( in_array( $screen, array( 'export.php' ), true ) ) {
133            return self::CLASSIC_VIEW;
134        }
135
136        /**
137         * When Jetpack SSO is disabled, we need to force Calypso because it might create confusion to be redirected to WP-Admin.
138         * Furthermore, because we don't display the quick switcher, users having an WP-Admin interface by default won't be able to go back to the Calyso version.
139         */
140        if ( ! ( new Modules() )->is_active( 'sso' ) ) {
141            return self::DEFAULT_VIEW;
142        }
143
144        return parent::get_preferred_view( $screen, $fallback_global_preference );
145    }
146
147    /**
148     * Add the appearance menu.
149     *
150     * @return string
151     */
152    public function add_appearance_menu() {
153        $customize_url                 = parent::add_appearance_menu();
154        $should_display_additional_css = current_user_can( 'customize' ) && ! wp_is_block_theme();
155
156        if ( ! $should_display_additional_css ) {
157            return $customize_url;
158        }
159
160        $customize_custom_css_url = add_query_arg( array( 'autofocus' => array( 'section' => 'custom_css' ) ), $customize_url );
161        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
162        add_submenu_page( 'themes.php', esc_attr__( 'Additional CSS', 'jetpack-masterbar' ), __( 'Additional CSS', 'jetpack-masterbar' ), 'customize', esc_url( $customize_custom_css_url ), null, 20 );
163
164        return $customize_url;
165    }
166
167    /**
168     * Adds Users menu.
169     */
170    public function add_users_menu() {
171        $slug = current_user_can( 'list_users' ) ? 'users.php' : 'profile.php';
172        if ( self::DEFAULT_VIEW === $this->get_preferred_view( 'users.php' ) ) {
173            $submenus_to_update = array(
174                'users.php' => 'https://wordpress.com/people/team/' . $this->domain,
175            );
176            $this->update_submenus( $slug, $submenus_to_update );
177        }
178    }
179
180    /**
181     * Adds Plugins menu.
182     */
183    public function add_plugins_menu() {
184
185        global $submenu;
186
187        // Calypso plugins screens link.
188        $plugins_slug = 'https://wordpress.com/plugins/' . $this->domain;
189
190        // Link to the Marketplace from Plugins > Add New on Atomic sites where the wpcom_admin_interface option is set to wp-admin.
191        if ( self::CLASSIC_VIEW === $this->get_preferred_view( 'plugins.php' ) ) {
192            $submenus_to_update = array( 'plugin-install.php' => $plugins_slug );
193            $this->update_submenus( 'plugins.php', $submenus_to_update );
194            return;
195        }
196
197        // Link to the Marketplace on sites that can't manage plugins.
198        if (
199            function_exists( 'wpcom_site_has_feature' ) &&
200            ! wpcom_site_has_feature( \WPCOM_Features::MANAGE_PLUGINS )
201        ) {
202            // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
203            add_menu_page( __( 'Plugins', 'jetpack-masterbar' ), __( 'Plugins', 'jetpack-masterbar' ), 'manage_options', $plugins_slug, null, 'dashicons-admin-plugins', 65 );
204            return;
205        }
206
207        if ( ! isset( $submenu['plugins.php'] ) ) {
208            return;
209        }
210
211        $plugins_submenu = $submenu['plugins.php'];
212
213        // Move "Add New" plugin submenu to the top position.
214        foreach ( $plugins_submenu as $submenu_key => $submenu_keys ) {
215            if ( 'plugin-install.php' === $submenu_keys[2] ) {
216                // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
217                $submenu['plugins.php'] = array( $submenu_key => $plugins_submenu[ $submenu_key ] ) + $plugins_submenu;
218            }
219        }
220
221        $submenus_to_update = array( 'plugin-install.php' => $plugins_slug );
222
223        $this->update_submenus( 'plugins.php', $submenus_to_update );
224    }
225
226    /**
227     * Adds a custom element class for Site Switcher menu item.
228     *
229     * @param array $menu Associative array of administration menu items.
230     *
231     * @return array
232     */
233    public function set_browse_sites_link_class( array $menu ) {
234        foreach ( $menu as $key => $menu_item ) {
235            if ( 'site-switcher' !== $menu_item[3] ) {
236                continue;
237            }
238
239            $menu[ $key ][4] = add_cssclass( 'site-switcher', $menu_item[4] );
240            break;
241        }
242
243        return $menu;
244    }
245
246    /**
247     * Adds a link to the menu to create a new site.
248     */
249    public function add_new_site_link() {
250        $site_count = get_user_option( 'wpcom_site_count' );
251        if ( $site_count && $site_count > 1 ) {
252            return;
253        }
254
255        // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
256        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' );
257    }
258
259    /**
260     * Returns the first available upsell nudge.
261     *
262     * @return array
263     */
264    public function get_upsell_nudge() {
265        $jitm         = JITM::get_instance();
266        $message_path = 'calypso:sites:sidebar_notice';
267        $message      = $jitm->get_messages( $message_path, array( 'message_path' => $message_path ), false );
268
269        if ( isset( $message[0] ) ) {
270            $message = $message[0];
271            return array(
272                'content'                      => $message->content->message,
273                'cta'                          => $message->CTA->message, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
274                'link'                         => $message->CTA->link, // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
275                'tracks_impression_event_name' => $message->tracks->display->name,
276                'tracks_impression_cta_name'   => $message->tracks->display->props->cta_name,
277                'tracks_click_event_name'      => $message->tracks->click->name,
278                'tracks_click_cta_name'        => $message->tracks->click->props->cta_name,
279                'dismissible'                  => $message->is_dismissible,
280                'feature_class'                => $message->feature_class,
281                'id'                           => $message->id,
282            );
283        }
284    }
285
286    /**
287     * Override the global submenu_file for theme-install.php page so the WP Admin menu item gets highlighted correctly.
288     *
289     * @param string $submenu_file The current pages $submenu_file global variable value.
290     * @return string | null
291     */
292    public function override_the_theme_installer( $submenu_file ) {
293        global $pagenow;
294
295        if ( 'themes.php' === $submenu_file && 'theme-install.php' === $pagenow ) {
296            return null;
297        }
298        return $submenu_file;
299    }
300
301    /**
302     * Also remove the Gutenberg plugin menu.
303     */
304    public function remove_gutenberg_menu() {
305        // Always remove the Gutenberg menu.
306        remove_menu_page( 'gutenberg' );
307    }
308
309    /**
310     * Saves the sidebar state ( expanded / collapsed ) via an ajax request.
311     */
312    public function ajax_sidebar_state() {
313        $expanded = isset( $_REQUEST['expanded'] ) ? filter_var( wp_unslash( $_REQUEST['expanded'] ), FILTER_VALIDATE_BOOLEAN ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
314        Client::wpcom_json_api_request_as_user(
315            '/me/preferences',
316            '2',
317            array(
318                'method' => 'POST',
319            ),
320            array( 'calypso_preferences' => (object) array( 'sidebarCollapsed' => ! $expanded ) ),
321            'wpcom'
322        );
323
324        wp_die();
325    }
326
327    /**
328     * Handle ajax requests to dismiss a just-in-time-message
329     */
330    public function wp_ajax_jitm_dismiss() {
331        check_ajax_referer( 'jitm_dismiss' );
332        $jitm = \Automattic\Jetpack\JITMS\JITM::get_instance();
333        if ( isset( $_REQUEST['id'] ) && isset( $_REQUEST['feature_class'] ) ) {
334            $jitm->dismiss( sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ), sanitize_text_field( wp_unslash( $_REQUEST['feature_class'] ) ) );
335        }
336        wp_die();
337    }
338}