PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-masterbar / src / admin-menu / class-base-admin-menu.php

class-base-admin-menu.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-masterbar/src/admin-menu/class-base-admin-menu.php

749 lines 23.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base Admin Menu file.
4 *
5 * @package automattic/jetpack-masterbar
6 */
7
8 namespace Automattic\Jetpack\Masterbar;
9
10 use Automattic\Jetpack\Assets;
11 use Automattic\Jetpack\Status;
12
13 /**
14 * Class Base_Admin_Menu
15 */
16 abstract class Base_Admin_Menu {
17 /**
18 * Holds class instances.
19 *
20 * @var array
21 */
22 protected static $instances;
23
24 /**
25 * Whether the current request is a REST API request.
26 *
27 * @var bool
28 */
29 protected $is_api_request = false;
30
31 /**
32 * Domain of the current site.
33 *
34 * @var string
35 */
36 protected $domain;
37
38 /**
39 * The CSS classes used to hide the submenu items in navigation.
40 *
41 * @var string
42 */
43 const HIDE_CSS_CLASS = 'hide-if-js';
44
45 /**
46 * Identifier denoting that the default WordPress.com view should be used for a certain screen.
47 *
48 * @var string
49 */
50 const DEFAULT_VIEW = 'default';
51
52 /**
53 * Identifier denoting that the classic WP Admin view should be used for a certain screen.
54 *
55 * @var string
56 */
57 const CLASSIC_VIEW = 'classic';
58
59 /**
60 * Identifier denoting no preferred view has been set for a certain screen.
61 *
62 * @var string
63 */
64 const UNKNOWN_VIEW = 'unknown';
65
66 /**
67 * Base_Admin_Menu constructor.
68 */
69 protected function __construct() {
70 $this->is_api_request = defined( 'REST_REQUEST' ) && REST_REQUEST || isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/?rest_route=%2Fwpcom%2Fv2%2Fadmin-menu' );
71 $this->domain = ( new Status() )->get_site_suffix();
72
73 add_action( 'admin_menu', array( $this, 'reregister_menu_items' ), 99998 );
74 add_action( 'admin_menu', array( $this, 'hide_parent_of_hidden_submenus' ), 99999 );
75
76 if ( ! $this->is_api_request ) {
77 add_filter( 'admin_menu', array( $this, 'override_svg_icons' ), 99999 );
78 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 11 );
79 add_action( 'in_admin_header', array( $this, 'add_dashboard_switcher' ) );
80 add_action( 'admin_footer', array( $this, 'dashboard_switcher_scripts' ) );
81 add_action( 'admin_menu', array( $this, 'handle_preferred_view' ), 99997 );
82 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
83 }
84 }
85
86 /**
87 * Returns class instance.
88 *
89 * @return static
90 */
91 public static function get_instance() {
92 $class = static::class;
93
94 if ( empty( static::$instances[ $class ] ) ) {
95 // @phan-suppress-next-line PhanTypeInstantiateAbstract -- If someone calls `Admin_Menu_Base::get_instance()` they deserve what they get.
96 static::$instances[ $class ] = new $class();
97 }
98
99 return static::$instances[ $class ];
100 }
101
102 /**
103 * Updates the menu data of the given menu slug.
104 *
105 * @param string $slug Slug of the menu to update.
106 * @param ?string $url New menu URL. Defaults to null.
107 * @param ?string $title New menu title. Defaults to null.
108 * @param ?string $cap New menu capability. Defaults to null.
109 * @param ?string $icon New menu icon. Defaults to null.
110 * @param ?int $position New menu position. Defaults to null.
111 * @return bool Whether the menu has been updated.
112 */
113 public function update_menu( $slug, $url = null, $title = null, $cap = null, $icon = null, $position = null ) {
114 global $menu, $submenu;
115
116 $menu_item = null;
117 $menu_position = 0;
118
119 foreach ( $menu as $i => $item ) {
120 if ( $slug === $item[2] ) {
121 $menu_item = $item;
122 $menu_position = $i;
123 break;
124 }
125 }
126
127 if ( ! $menu_item ) {
128 return false;
129 }
130
131 if ( $title ) {
132 $menu_item[0] = $title;
133 $menu_item[3] = esc_attr( $title );
134 }
135
136 if ( $cap ) {
137 $menu_item[1] = $cap;
138 }
139
140 // Change parent slug only if there are no submenus (the slug of the 1st submenu will be used if there are submenus).
141 if ( $url ) {
142 $this->hide_submenu_page( $slug, $slug );
143
144 if ( ! isset( $submenu[ $slug ] ) || ! $this->has_visible_items( $submenu[ $slug ] ) ) {
145 $menu_item[2] = $url;
146 }
147 }
148
149 if ( $icon ) {
150 $menu_item[4] = 'menu-top';
151 $menu_item[6] = $icon;
152 }
153
154 unset( $menu[ $menu_position ] );
155 if ( $position ) {
156 $menu_position = $position;
157 }
158 $this->set_menu_item( $menu_item, $menu_position );
159
160 // Only add submenu when there are other submenu items.
161 if ( $url && isset( $submenu[ $slug ] ) && $this->has_visible_items( $submenu[ $slug ] ) ) {
162 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
163 add_submenu_page( $slug, $menu_item[3], $menu_item[0], $menu_item[1], $url, null, 0 );
164 }
165
166 return true;
167 }
168
169 /**
170 * Updates the submenus of the given menu slug.
171 *
172 * It hides the menu by adding the `hide-if-js` css class and duplicates the submenu with the new slug.
173 *
174 * @param string $slug Menu slug.
175 * @param array $submenus_to_update Array of new submenu slugs.
176 */
177 public function update_submenus( $slug, $submenus_to_update ) {
178 global $submenu;
179
180 if ( ! isset( $submenu[ $slug ] ) ) {
181 return;
182 }
183
184 // This is needed for cases when the submenus to update have the same new slug.
185 $submenus_to_update = array_filter(
186 $submenus_to_update,
187 static function ( $item, $old_slug ) {
188 return $item !== $old_slug;
189 },
190 ARRAY_FILTER_USE_BOTH
191 );
192
193 /**
194 * Iterate over all submenu items and add the hide the submenus with CSS classes.
195 * This is done separately of the second foreach because the position of the submenu might change.
196 */
197 foreach ( $submenu[ $slug ] as $index => $item ) {
198 if ( ! array_key_exists( $item[2], $submenus_to_update ) ) {
199 continue;
200 }
201
202 $this->hide_submenu_element( $index, $slug, $item );
203 }
204
205 $submenu_items = array_values( $submenu[ $slug ] );
206
207 /**
208 * Iterate again over the submenu array. We need a copy of the array because add_submenu_page will add new elements
209 * to submenu array that might cause an infinite loop.
210 */
211 foreach ( $submenu_items as $i => $submenu_item ) {
212 if ( ! array_key_exists( $submenu_item[2], $submenus_to_update ) ) {
213 continue;
214 }
215
216 add_submenu_page(
217 $slug,
218 $submenu_item[3] ?? '',
219 $submenu_item[0] ?? '',
220 $submenu_item[1] ?? 'read',
221 $submenus_to_update[ $submenu_item[2] ],
222 null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. https://core.trac.wordpress.org/ticket/52539.
223 0 === $i ? 0 : $i + 1
224 );
225 }
226 }
227
228 /**
229 * Adds a menu separator.
230 *
231 * @param int $position The position in the menu order this item should appear.
232 * @param string $cap Optional. The capability required for this menu to be displayed to the user.
233 * Default: 'read'.
234 */
235 public function add_admin_menu_separator( $position = null, $cap = 'read' ) {
236 $menu_item = array(
237 '', // Menu title (ignored).
238 $cap, // Required capability.
239 wp_unique_id( 'separator-custom-' ), // URL or file (ignored, but must be unique).
240 '', // Page title (ignored).
241 'wp-menu-separator', // CSS class. Identifies this item as a separator.
242 );
243
244 $this->set_menu_item( $menu_item, $position );
245 }
246
247 /**
248 * Enqueues scripts and styles.
249 */
250 public function enqueue_scripts() {
251 $assets_base_path = '../../dist/admin-menu/';
252
253 Assets::register_script(
254 'jetpack-admin-menu',
255 $assets_base_path . 'admin-menu.js',
256 __FILE__,
257 array(
258 'enqueue' => true,
259 'css_path' => $assets_base_path . 'admin-menu.css',
260 )
261 );
262
263 wp_localize_script(
264 'jetpack-admin-menu',
265 'jetpackAdminMenu',
266 array(
267 'jitmDismissNonce' => wp_create_nonce( 'jitm_dismiss' ),
268 )
269 );
270
271 $this->configure_colors_for_rtl_stylesheets();
272 }
273
274 /**
275 * Mark the core colors stylesheets as RTL depending on the value from the environment.
276 * This fixes a core issue where the extra RTL data is not added to the colors stylesheet.
277 * https://core.trac.wordpress.org/ticket/53090
278 */
279 public function configure_colors_for_rtl_stylesheets() {
280 wp_style_add_data( 'colors', 'rtl', $this->is_rtl() );
281 }
282
283 /**
284 * Hide the submenu page based on slug and return the item that was hidden.
285 *
286 * Instead of actually removing the submenu item, a safer approach is to hide it and filter it in the API response.
287 * In this manner we'll avoid breaking third-party plugins depending on items that no longer exist.
288 *
289 * A false|array value is returned to be consistent with remove_submenu_page() function
290 *
291 * @param string $menu_slug The parent menu slug.
292 * @param string $submenu_slug The submenu slug that should be hidden.
293 * @return false|array
294 */
295 public function hide_submenu_page( $menu_slug, $submenu_slug ) {
296 global $submenu;
297
298 if ( ! isset( $submenu[ $menu_slug ] ) ) {
299 return false;
300 }
301
302 foreach ( $submenu[ $menu_slug ] as $i => $item ) {
303 if ( $submenu_slug !== $item[2] ) {
304 continue;
305 }
306
307 $this->hide_submenu_element( $i, $menu_slug, $item );
308
309 return $item;
310 }
311
312 return false;
313 }
314
315 /**
316 * Apply the hide-if-js CSS class to a submenu item.
317 *
318 * @param int $index The position of a submenu item in the submenu array.
319 * @param string $parent_slug The parent slug.
320 * @param array $item The submenu item.
321 */
322 public function hide_submenu_element( $index, $parent_slug, $item ) {
323 global $submenu;
324
325 $css_classes = empty( $item[4] ) ? self::HIDE_CSS_CLASS : $item[4] . ' ' . self::HIDE_CSS_CLASS;
326
327 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
328 $submenu [ $parent_slug ][ $index ][4] = $css_classes;
329 }
330
331 /**
332 * Check if the menu has submenu items visible
333 *
334 * @param array $submenu_items The submenu items.
335 * @return bool
336 */
337 public function has_visible_items( $submenu_items ) {
338 $visible_items = array_filter(
339 $submenu_items,
340 array( $this, 'is_item_visible' )
341 );
342
343 return array() !== $visible_items;
344 }
345
346 /**
347 * Return the number of existing submenu items under the supplied parent slug.
348 *
349 * @param string $parent_slug The slug of the parent menu.
350 * @return int The number of submenu items under $parent_slug.
351 */
352 public function get_submenu_item_count( $parent_slug ) {
353 global $submenu;
354
355 if ( empty( $parent_slug ) || empty( $submenu[ $parent_slug ] ) || ! is_array( $submenu[ $parent_slug ] ) ) {
356 return 0;
357 }
358
359 return count( $submenu[ $parent_slug ] );
360 }
361
362 /**
363 * Adds the given menu item in the specified position.
364 *
365 * @param array $item The menu item to add.
366 * @param int $position The position in the menu order this item should appear.
367 */
368 public function set_menu_item( $item, $position = null ) {
369 global $menu;
370
371 // Handle position (avoids overwriting menu items already populated in the given position).
372 // Inspired by https://core.trac.wordpress.org/browser/trunk/src/wp-admin/menu.php?rev=49837#L160.
373 if ( null === $position ) {
374 $menu[] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
375 } elseif ( isset( $menu[ "$position" ] ) ) {
376 $position += (int) substr( base_convert( md5( $item[2] . $item[0] ), 16, 10 ), -5 ) * 0.00001;
377 $menu[ "$position" ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
378 } else {
379 $menu[ $position ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
380 }
381 }
382
383 /**
384 * Determines whether the current locale is right-to-left (RTL).
385 */
386 public function is_rtl() {
387 return is_rtl();
388 }
389
390 /**
391 * Checks for any SVG icons in the menu, and overrides things so that
392 * we can display the icon in the correct colour for the theme.
393 */
394 public function override_svg_icons() {
395 global $menu;
396
397 $svg_items = array();
398 foreach ( $menu as $idx => $menu_item ) {
399 // Menu items that don't have icons, for example separators, have less than 7
400 // elements, partly because the 7th is the icon. So, if we have less than 7,
401 // let's skip it.
402 if ( ! is_countable( $menu_item ) || ( count( $menu_item ) < 7 ) ) {
403 continue;
404 }
405
406 // If the hookname contain a URL than sanitize it by replacing invalid characters.
407 if ( str_contains( $menu_item[5], '://' ) ) {
408 $menu_item[5] = preg_replace( '![:/.]+!', '_', $menu_item[5] );
409 }
410
411 $menu_item[5] = preg_replace( '|[^a-zA-Z0-9_:.]|', '-', $menu_item[5] );
412
413 if ( str_starts_with( $menu_item[6], 'data:image/svg+xml' ) && 'site-card' !== $menu_item[3] ) {
414 $svg_items[] = array(
415 'icon' => $menu_item[6],
416 'id' => $menu_item[5],
417 );
418 $menu_item[4] .= ' menu-svg-icon';
419 $menu_item[6] = 'none';
420 }
421 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
422 $menu[ $idx ] = $menu_item;
423 }
424 if ( $svg_items !== array() ) {
425 $styles = '.menu-svg-icon .wp-menu-image { background-repeat: no-repeat; background-position: center center } ';
426 foreach ( $svg_items as $svg_item ) {
427 $styles .= sprintf( '#%s .wp-menu-image { background-image: url( "%s" ) }', $svg_item['id'], $svg_item['icon'] );
428 }
429 $styles .= '@supports ( mask-image: none ) or ( -webkit-mask-image: none ) { ';
430 $styles .= '.menu-svg-icon .wp-menu-image { background-image: none; } ';
431 $styles .= '.menu-svg-icon .wp-menu-image::before { background-color: currentColor; ';
432 $styles .= 'mask-size: contain; mask-position: center center; mask-repeat: no-repeat; ';
433 $styles .= '-webkit-mask-size: contain; -webkit-mask-position: center center; -webkit-mask-repeat: no-repeat; content:"" } ';
434 foreach ( $svg_items as $svg_item ) {
435 $styles .= sprintf(
436 '#%s .wp-menu-image { background-image: none; } #%s .wp-menu-image::before{ mask-image: url( "%s" ); -webkit-mask-image: url( "%s" ) }',
437 $svg_item['id'],
438 $svg_item['id'],
439 $svg_item['icon'],
440 $svg_item['icon']
441 );
442 }
443 $styles .= '}';
444
445 wp_register_style( 'svg-menu-overrides', false, array(), '20210331' );
446 wp_enqueue_style( 'svg-menu-overrides' );
447 wp_add_inline_style( 'svg-menu-overrides', $styles );
448 }
449 }
450
451 /**
452 * Hide menus that are unauthorized and don't have visible submenus and cases when the menu has the same slug
453 * as the first submenu item.
454 *
455 * This must be done at the end of menu and submenu manipulation in order to avoid performing this check each time
456 * the submenus are altered.
457 */
458 public function hide_parent_of_hidden_submenus() {
459 global $menu, $submenu;
460
461 $this->sort_hidden_submenus();
462
463 foreach ( $menu as $menu_index => $menu_item ) {
464 // Skip if the menu doesn't have submenus.
465 if ( empty( $submenu[ $menu_item[2] ] ) || ! is_array( $submenu[ $menu_item[2] ] ) ) {
466 continue;
467 }
468
469 // If the first submenu item is hidden then we should also hide the parent.
470 // Since the submenus are ordered by self::HIDE_CSS_CLASS (hidden submenus should be at the end of the array),
471 // we can say that if the first submenu is hidden then we should also hide the menu.
472 $first_submenu_item = array_values( $submenu[ $menu_item[2] ] )[0];
473 $is_first_submenu_visible = $this->is_item_visible( $first_submenu_item );
474
475 // if the user does not have access to the menu and the first submenu is hidden, then hide the menu.
476 if ( ! current_user_can( $menu_item[1] ) && ! $is_first_submenu_visible ) {
477 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
478 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
479 }
480
481 // if the menu has the same slug as the first submenu then hide the submenu.
482 if ( $menu_item[2] === $first_submenu_item[2] && ! $is_first_submenu_visible ) {
483 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
484 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
485 }
486 }
487 }
488
489 /**
490 * Sort the hidden submenus by moving them at the end of the array in order to avoid WP using them as default URLs.
491 *
492 * This operation has to be done at the end of submenu manipulation in order to guarantee that the hidden submenus
493 * are at the end of the array.
494 */
495 public function sort_hidden_submenus() {
496 global $submenu;
497
498 foreach ( $submenu as $menu_slug => $submenu_items ) {
499 if ( ! $submenu_items ) {
500 continue;
501 }
502
503 foreach ( $submenu_items as $submenu_index => $submenu_item ) {
504 if ( $this->is_item_visible( $submenu_item ) ) {
505 continue;
506 }
507
508 unset( $submenu[ $menu_slug ][ $submenu_index ] );
509 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
510 $submenu[ $menu_slug ][] = $submenu_item;
511 }
512 }
513 }
514
515 /**
516 * Check if the given item is visible or not in the admin menu.
517 *
518 * @param array $item A menu or submenu array.
519 */
520 public function is_item_visible( $item ) {
521 return ! isset( $item[4] ) || ! str_contains( $item[4], self::HIDE_CSS_CLASS );
522 }
523
524 /**
525 * Adds a dashboard switcher to the list of screen meta links of the current page.
526 */
527 public function add_dashboard_switcher() {
528 $menu_mappings = require __DIR__ . '/menu-mappings.php';
529 $screen = $this->get_current_screen();
530
531 // Let's show the switcher only in screens that we have a Calypso mapping to switch to.
532 if ( empty( $menu_mappings[ $screen ] ) ) {
533 return;
534 }
535 ?>
536 <div id="view-link-wrap" class="hide-if-no-js screen-meta-toggle">
537 <button type="button" id="view-link" class="button show-settings" aria-expanded="false"><?php echo esc_html_x( 'View', 'View options to switch between', 'jetpack-masterbar' ); ?></button>
538 </div>
539 <div id="view-wrap" class="screen-options-tab__wrapper hide-if-no-js hidden" tabindex="-1">
540 <div class="screen-options-tab__dropdown" data-testid="screen-options-dropdown">
541 <div class="screen-switcher">
542 <a class="screen-switcher__button" href="<?php echo esc_url( add_query_arg( 'preferred-view', 'default' ) ); ?>" data-view="default">
543 <strong><?php esc_html_e( 'Default view', 'jetpack-masterbar' ); ?></strong>
544 <?php esc_html_e( 'Our WordPress.com redesign for a better experience.', 'jetpack-masterbar' ); ?>
545 </a>
546 <button class="screen-switcher__button" data-view="classic">
547 <strong><?php esc_html_e( 'Classic view', 'jetpack-masterbar' ); ?></strong>
548 <?php esc_html_e( 'The classic WP-Admin WordPress interface.', 'jetpack-masterbar' ); ?>
549 </button>
550 </div>
551 </div>
552 </div>
553 <?php
554 }
555
556 /**
557 * Adds a script to append the dashboard switcher to screen meta
558 */
559 public function dashboard_switcher_scripts() {
560 wp_add_inline_script(
561 'common',
562 "(function( $ ) {
563 $( '#view-link-wrap' ).appendTo( '#screen-meta-links' );
564
565 var viewLink = $( '#view-link' );
566 var viewWrap = $( '#view-wrap' );
567
568 viewLink.on( 'click', function() {
569 viewWrap.toggle();
570 viewLink.toggleClass( 'screen-meta-active' );
571 } );
572
573 $( document ).on( 'mouseup', function( event ) {
574 if ( ! viewLink.is( event.target ) && ! viewWrap.is( event.target ) && viewWrap.has( event.target ).length === 0 ) {
575 viewWrap.hide();
576 viewLink.removeClass( 'screen-meta-active' );
577 }
578 });
579 })( jQuery );"
580 );
581 }
582
583 /**
584 * Sets the given view as preferred for the givens screen.
585 *
586 * @param string $screen Screen identifier.
587 * @param string $view Preferred view.
588 */
589 public function set_preferred_view( $screen, $view ) {
590 remove_filter( 'get_user_option_jetpack_admin_menu_preferred_views', 'wpcom_admin_get_user_option_jetpack' );
591 $preferred_views = $this->get_preferred_views();
592 if ( function_exists( 'wpcom_admin_get_user_option_jetpack' ) ) {
593 add_filter( 'get_user_option_jetpack_admin_menu_preferred_views', 'wpcom_admin_get_user_option_jetpack' );
594 }
595
596 $screen = str_replace( '?post_type=post', '', $screen );
597 $preferred_views[ $screen ] = $view;
598 update_user_option( get_current_user_id(), 'jetpack_admin_menu_preferred_views', $preferred_views );
599 }
600
601 /**
602 * Get the preferred views for all screens.
603 *
604 * @return array
605 */
606 public function get_preferred_views() {
607 $preferred_views = get_user_option( 'jetpack_admin_menu_preferred_views' );
608
609 if ( ! $preferred_views ) {
610 return array();
611 }
612
613 return $preferred_views;
614 }
615
616 /**
617 * Get the preferred view for the given screen.
618 *
619 * @param string $screen Screen identifier.
620 * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used
621 * as fallback if there is no specific preference for the given screen.
622 * Default: true.
623 * @return string
624 */
625 public function get_preferred_view( $screen, $fallback_global_preference = true ) {
626 $preferred_views = $this->get_preferred_views();
627
628 if ( ! isset( $preferred_views[ $screen ] ) ) {
629 if ( ! $fallback_global_preference ) {
630 return self::UNKNOWN_VIEW;
631 }
632
633 $should_link_to_wp_admin = $this->should_link_to_wp_admin() || $this->use_wp_admin_interface();
634 return $should_link_to_wp_admin ? self::CLASSIC_VIEW : self::DEFAULT_VIEW;
635 }
636
637 return $preferred_views[ $screen ];
638 }
639
640 /**
641 * Gets the identifier of the current screen.
642 *
643 * @return string
644 */
645 public function get_current_screen() {
646 // phpcs:disable WordPress.Security.NonceVerification
647 global $pagenow;
648 $screen = isset( $_REQUEST['screen'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['screen'] ) ) : $pagenow;
649 if ( isset( $_GET['post_type'] ) ) {
650 $screen = add_query_arg( 'post_type', sanitize_text_field( wp_unslash( $_GET['post_type'] ) ), $screen );
651 }
652 if ( isset( $_GET['taxonomy'] ) ) {
653 $screen = add_query_arg( 'taxonomy', sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ), $screen );
654 }
655 if ( isset( $_GET['page'] ) ) {
656 $screen = add_query_arg( 'page', sanitize_text_field( wp_unslash( $_GET['page'] ) ), $screen );
657 }
658 return $screen;
659 // phpcs:enable WordPress.Security.NonceVerification
660 }
661
662 /**
663 * Stores the preferred view for the current screen.
664 */
665 public function handle_preferred_view() {
666 // phpcs:disable WordPress.Security.NonceVerification
667 if ( ! isset( $_GET['preferred-view'] ) ) {
668 return;
669 }
670
671 // phpcs:disable WordPress.Security.NonceVerification
672 $preferred_view = sanitize_key( $_GET['preferred-view'] );
673
674 if ( ! in_array( $preferred_view, array( self::DEFAULT_VIEW, self::CLASSIC_VIEW ), true ) ) {
675 return;
676 }
677
678 $current_screen = $this->get_current_screen();
679
680 $this->set_preferred_view( $current_screen, $preferred_view );
681
682 /**
683 * Dashboard Quick switcher action triggered when a user switches to a different view.
684 *
685 * @module masterbar
686 *
687 * @since jetpack-9.9.1
688 *
689 * @param string The current screen of the user.
690 * @param string The preferred view the user selected.
691 */
692 \do_action( 'jetpack_dashboard_switcher_changed_view', $current_screen, $preferred_view );
693
694 if ( self::DEFAULT_VIEW === $preferred_view ) {
695 // Redirect to default view if that's the newly preferred view.
696 $menu_mappings = require __DIR__ . '/menu-mappings.php';
697 if ( isset( $menu_mappings[ $current_screen ] ) ) {
698 // Using `wp_redirect` intentionally because we're redirecting to Calypso.
699 wp_redirect( $menu_mappings[ $current_screen ] . $this->domain ); // phpcs:ignore WordPress.Security.SafeRedirect
700 exit( 0 );
701 }
702 } elseif ( self::CLASSIC_VIEW === $preferred_view ) {
703 // Removes the `preferred-view` param from the URL to avoid issues with
704 // screens that don't expect this param to be present in the URL.
705 wp_safe_redirect( remove_query_arg( 'preferred-view' ) );
706 exit( 0 );
707 }
708 // phpcs:enable WordPress.Security.NonceVerification
709 }
710
711 /**
712 * Adds the necessary CSS class to the admin body class.
713 *
714 * @param string $admin_body_classes Contains all the admin body classes.
715 *
716 * @return string
717 */
718 public function admin_body_class( $admin_body_classes ) {
719 return " is-nav-unification $admin_body_classes ";
720 }
721
722 /**
723 * Whether to use wp-admin pages rather than Calypso.
724 *
725 * Options:
726 * false - Calypso (Default).
727 * true - wp-admin.
728 *
729 * @return bool
730 */
731 public function should_link_to_wp_admin() {
732 return get_user_option( 'jetpack_admin_menu_link_destination' );
733 }
734
735 /**
736 * Whether the current user has indicated they want to use the wp-admin interface for the given screen.
737 *
738 * @return bool
739 */
740 public function use_wp_admin_interface() {
741 return 'wp-admin' === get_option( 'wpcom_admin_interface' );
742 }
743
744 /**
745 * Create the desired menu output.
746 */
747 abstract public function reregister_menu_items();
748 }
749