PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.5.2
Jetpack – WP Security, Backup, Speed, & Growth v13.5.2
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 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / masterbar / admin-menu / class-base-admin-menu.php
jetpack / modules / masterbar / admin-menu Last commit date
admin-menu-nav-unification-rtl.css 2 years ago admin-menu-nav-unification-rtl.min.css 2 years ago admin-menu-nav-unification.css 2 years ago admin-menu-nav-unification.min.css 2 years ago admin-menu-rtl.css 2 years ago admin-menu-rtl.min.css 2 years ago admin-menu.css 2 years ago admin-menu.js 2 years ago admin-menu.min.css 2 years ago class-admin-menu.php 2 years ago class-atomic-admin-menu.php 2 years ago class-base-admin-menu.php 2 years ago class-dashboard-switcher-tracking.php 3 years ago class-domain-only-admin-menu.php 2 years ago class-jetpack-admin-menu.php 2 years ago class-p2-admin-menu.php 4 years ago class-wpcom-admin-menu.php 2 years ago class-wpcom-email-subscription-checker.php 3 years ago dashicon-set.php 4 years ago globe-icon.svg 3 years ago load.php 2 years ago menu-mappings.php 3 years ago
class-base-admin-menu.php
802 lines
1 <?php
2 /**
3 * Base Admin Menu file.
4 *
5 * @package automattic/jetpack
6 */
7
8 namespace Automattic\Jetpack\Dashboard_Customizations;
9
10 use Automattic\Jetpack\Status;
11
12 /**
13 * Class Base_Admin_Menu
14 */
15 abstract class Base_Admin_Menu {
16 /**
17 * Holds class instances.
18 *
19 * @var array
20 */
21 protected static $instances;
22
23 /**
24 * Whether the current request is a REST API request.
25 *
26 * @var bool
27 */
28 protected $is_api_request = false;
29
30 /**
31 * Domain of the current site.
32 *
33 * @var string
34 */
35 protected $domain;
36
37 /**
38 * The CSS classes used to hide the submenu items in navigation.
39 *
40 * @var string
41 */
42 const HIDE_CSS_CLASS = 'hide-if-js';
43
44 /**
45 * Identifier denoting that the default WordPress.com view should be used for a certain screen.
46 *
47 * @var string
48 */
49 const DEFAULT_VIEW = 'default';
50
51 /**
52 * Identifier denoting that the classic WP Admin view should be used for a certain screen.
53 *
54 * @var string
55 */
56 const CLASSIC_VIEW = 'classic';
57
58 /**
59 * Identifier denoting no preferred view has been set for a certain screen.
60 *
61 * @var string
62 */
63 const UNKNOWN_VIEW = 'unknown';
64
65 /**
66 * Base_Admin_Menu constructor.
67 */
68 protected function __construct() {
69 $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' );
70 $this->domain = ( new Status() )->get_site_suffix();
71
72 add_action( 'admin_menu', array( $this, 'reregister_menu_items' ), 99998 );
73 add_action( 'admin_menu', array( $this, 'hide_parent_of_hidden_submenus' ), 99999 );
74
75 if ( ! $this->is_api_request ) {
76 add_filter( 'admin_menu', array( $this, 'override_svg_icons' ), 99999 );
77 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 11 );
78 add_action( 'admin_head', array( $this, 'set_site_icon_inline_styles' ) );
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 // Do not inject core mobile toggle when the user wants to use the WP Admin interface.
85 if ( ! $this->use_wp_admin_interface() ) {
86 add_action( 'adminmenu', array( $this, 'inject_core_mobile_toggle' ) );
87 }
88 }
89 }
90
91 /**
92 * Returns class instance.
93 *
94 * @return Admin_Menu
95 */
96 public static function get_instance() {
97 $class = static::class;
98
99 if ( empty( static::$instances[ $class ] ) ) {
100 static::$instances[ $class ] = new $class();
101 }
102
103 return static::$instances[ $class ];
104 }
105
106 /**
107 * Updates the menu data of the given menu slug.
108 *
109 * @param string $slug Slug of the menu to update.
110 * @param string $url New menu URL.
111 * @param string $title New menu title.
112 * @param string $cap New menu capability.
113 * @param string $icon New menu icon.
114 * @param int $position New menu position.
115 * @return bool Whether the menu has been updated.
116 */
117 public function update_menu( $slug, $url = null, $title = null, $cap = null, $icon = null, $position = null ) {
118 global $menu, $submenu;
119
120 $menu_item = null;
121 $menu_position = null;
122
123 foreach ( $menu as $i => $item ) {
124 if ( $slug === $item[2] ) {
125 $menu_item = $item;
126 $menu_position = $i;
127 break;
128 }
129 }
130
131 if ( ! $menu_item ) {
132 return false;
133 }
134
135 if ( $title ) {
136 $menu_item[0] = $title;
137 $menu_item[3] = esc_attr( $title );
138 }
139
140 if ( $cap ) {
141 $menu_item[1] = $cap;
142 }
143
144 // Change parent slug only if there are no submenus (the slug of the 1st submenu will be used if there are submenus).
145 if ( $url ) {
146 $this->hide_submenu_page( $slug, $slug );
147
148 if ( ! isset( $submenu[ $slug ] ) || ! $this->has_visible_items( $submenu[ $slug ] ) ) {
149 $menu_item[2] = $url;
150 }
151 }
152
153 if ( $icon ) {
154 $menu_item[4] = 'menu-top';
155 $menu_item[6] = $icon;
156 }
157
158 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
159 unset( $menu[ $menu_position ] );
160 if ( $position ) {
161 $menu_position = $position;
162 }
163 $this->set_menu_item( $menu_item, $menu_position );
164
165 // Only add submenu when there are other submenu items.
166 if ( $url && isset( $submenu[ $slug ] ) && $this->has_visible_items( $submenu[ $slug ] ) ) {
167 add_submenu_page( $slug, $menu_item[3], $menu_item[0], $menu_item[1], $url, null, 0 );
168 }
169
170 return true;
171 }
172
173 /**
174 * Updates the submenus of the given menu slug.
175 *
176 * It hides the menu by adding the `hide-if-js` css class and duplicates the submenu with the new slug.
177 *
178 * @param string $slug Menu slug.
179 * @param array $submenus_to_update Array of new submenu slugs.
180 */
181 public function update_submenus( $slug, $submenus_to_update ) {
182 global $submenu;
183
184 if ( ! isset( $submenu[ $slug ] ) ) {
185 return;
186 }
187
188 // This is needed for cases when the submenus to update have the same new slug.
189 $submenus_to_update = array_filter(
190 $submenus_to_update,
191 static function ( $item, $old_slug ) {
192 return $item !== $old_slug;
193 },
194 ARRAY_FILTER_USE_BOTH
195 );
196
197 /**
198 * Iterate over all submenu items and add the hide the submenus with CSS classes.
199 * This is done separately of the second foreach because the position of the submenu might change.
200 */
201 foreach ( $submenu[ $slug ] as $index => $item ) {
202 if ( ! array_key_exists( $item[2], $submenus_to_update ) ) {
203 continue;
204 }
205
206 $this->hide_submenu_element( $index, $slug, $item );
207 }
208
209 $submenu_items = array_values( $submenu[ $slug ] );
210
211 /**
212 * Iterate again over the submenu array. We need a copy of the array because add_submenu_page will add new elements
213 * to submenu array that might cause an infinite loop.
214 */
215 foreach ( $submenu_items as $i => $submenu_item ) {
216 if ( ! array_key_exists( $submenu_item[2], $submenus_to_update ) ) {
217 continue;
218 }
219
220 add_submenu_page(
221 $slug,
222 isset( $submenu_item[3] ) ? $submenu_item[3] : '',
223 isset( $submenu_item[0] ) ? $submenu_item[0] : '',
224 isset( $submenu_item[1] ) ? $submenu_item[1] : 'read',
225 $submenus_to_update[ $submenu_item[2] ],
226 '',
227 0 === $i ? 0 : $i + 1
228 );
229 }
230 }
231
232 /**
233 * Adds a menu separator.
234 *
235 * @param int $position The position in the menu order this item should appear.
236 * @param string $cap Optional. The capability required for this menu to be displayed to the user.
237 * Default: 'read'.
238 */
239 public function add_admin_menu_separator( $position = null, $cap = 'read' ) {
240 $menu_item = array(
241 '', // Menu title (ignored).
242 $cap, // Required capability.
243 wp_unique_id( 'separator-custom-' ), // URL or file (ignored, but must be unique).
244 '', // Page title (ignored).
245 'wp-menu-separator', // CSS class. Identifies this item as a separator.
246 );
247
248 $this->set_menu_item( $menu_item, $position );
249 }
250
251 /**
252 * Enqueues scripts and styles.
253 */
254 public function enqueue_scripts() {
255 if ( $this->is_rtl() ) {
256 $css_path = 'admin-menu-rtl.css';
257 } else {
258 $css_path = 'admin-menu.css';
259 }
260
261 wp_enqueue_style(
262 'jetpack-admin-menu',
263 plugins_url( $css_path, __FILE__ ),
264 array(),
265 JETPACK__VERSION
266 );
267
268 wp_style_add_data( 'jetpack-admin-menu', 'rtl', $this->is_rtl() );
269
270 // Load nav unification styles when the user isn't using wp-admin interface style.
271 if ( ! $this->use_wp_admin_interface() ) {
272 wp_enqueue_style(
273 'jetpack-admin-nav-unification',
274 plugins_url( 'admin-menu-nav-unification.css', __FILE__ ),
275 array(),
276 JETPACK__VERSION
277 );
278
279 wp_style_add_data( 'jetpack-admin-nav-unification', 'rtl', $this->is_rtl() );
280 }
281
282 $this->configure_colors_for_rtl_stylesheets();
283
284 wp_enqueue_script(
285 'jetpack-admin-menu',
286 plugins_url( 'admin-menu.js', __FILE__ ),
287 array(),
288 JETPACK__VERSION,
289 true
290 );
291
292 wp_localize_script(
293 'jetpack-admin-menu',
294 'jetpackAdminMenu',
295 array(
296 'upsellNudgeJitm' => wp_create_nonce( 'upsell_nudge_jitm' ),
297 'jitmDismissNonce' => wp_create_nonce( 'jitm_dismiss' ),
298 )
299 );
300 }
301
302 /**
303 * Mark the core colors stylesheets as RTL depending on the value from the environment.
304 * This fixes a core issue where the extra RTL data is not added to the colors stylesheet.
305 * https://core.trac.wordpress.org/ticket/53090
306 */
307 public function configure_colors_for_rtl_stylesheets() {
308 wp_style_add_data( 'colors', 'rtl', $this->is_rtl() );
309 }
310
311 /**
312 * Injects inline-styles for site icon for when third-party plugins remove enqueued stylesheets.
313 * Unable to use wp_add_inline_style as plugins remove styles from all non-standard handles
314 */
315 public function set_site_icon_inline_styles() {
316 echo '<style>
317 #adminmenu .toplevel_page_site-card .wp-menu-image,
318 #adminmenu .toplevel_page_site-card .wp-menu-image img {
319 height: 32px;
320 width: 32px;
321 }
322 </style>';
323 }
324
325 /**
326 * Hide the submenu page based on slug and return the item that was hidden.
327 *
328 * Instead of actually removing the submenu item, a safer approach is to hide it and filter it in the API response.
329 * In this manner we'll avoid breaking third-party plugins depending on items that no longer exist.
330 *
331 * A false|array value is returned to be consistent with remove_submenu_page() function
332 *
333 * @param string $menu_slug The parent menu slug.
334 * @param string $submenu_slug The submenu slug that should be hidden.
335 * @return false|array
336 */
337 public function hide_submenu_page( $menu_slug, $submenu_slug ) {
338 global $submenu;
339
340 if ( ! isset( $submenu[ $menu_slug ] ) ) {
341 return false;
342 }
343
344 foreach ( $submenu[ $menu_slug ] as $i => $item ) {
345 if ( $submenu_slug !== $item[2] ) {
346 continue;
347 }
348
349 $this->hide_submenu_element( $i, $menu_slug, $item );
350
351 return $item;
352 }
353
354 return false;
355 }
356
357 /**
358 * Apply the hide-if-js CSS class to a submenu item.
359 *
360 * @param int $index The position of a submenu item in the submenu array.
361 * @param string $parent_slug The parent slug.
362 * @param array $item The submenu item.
363 */
364 public function hide_submenu_element( $index, $parent_slug, $item ) {
365 global $submenu;
366
367 $css_classes = empty( $item[4] ) ? self::HIDE_CSS_CLASS : $item[4] . ' ' . self::HIDE_CSS_CLASS;
368
369 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
370 $submenu [ $parent_slug ][ $index ][4] = $css_classes;
371 }
372
373 /**
374 * Check if the menu has submenu items visible
375 *
376 * @param array $submenu_items The submenu items.
377 * @return bool
378 */
379 public function has_visible_items( $submenu_items ) {
380 $visible_items = array_filter(
381 $submenu_items,
382 array( $this, 'is_item_visible' )
383 );
384
385 return array() !== $visible_items;
386 }
387
388 /**
389 * Return the number of existing submenu items under the supplied parent slug.
390 *
391 * @param string $parent_slug The slug of the parent menu.
392 * @return int The number of submenu items under $parent_slug.
393 */
394 public function get_submenu_item_count( $parent_slug ) {
395 global $submenu;
396
397 if ( empty( $parent_slug ) || empty( $submenu[ $parent_slug ] ) || ! is_array( $submenu[ $parent_slug ] ) ) {
398 return 0;
399 }
400
401 return count( $submenu[ $parent_slug ] );
402 }
403
404 /**
405 * Adds the given menu item in the specified position.
406 *
407 * @param array $item The menu item to add.
408 * @param int $position The position in the menu order this item should appear.
409 */
410 public function set_menu_item( $item, $position = null ) {
411 global $menu;
412
413 // Handle position (avoids overwriting menu items already populated in the given position).
414 // Inspired by https://core.trac.wordpress.org/browser/trunk/src/wp-admin/menu.php?rev=49837#L160.
415 if ( null === $position ) {
416 $menu[] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
417 } elseif ( isset( $menu[ "$position" ] ) ) {
418 $position = $position + substr( base_convert( md5( $item[2] . $item[0] ), 16, 10 ), -5 ) * 0.00001;
419 $menu[ "$position" ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
420 } else {
421 $menu[ $position ] = $item; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
422 }
423 }
424
425 /**
426 * Determines whether the current locale is right-to-left (RTL).
427 */
428 public function is_rtl() {
429 return is_rtl();
430 }
431
432 /**
433 * Checks for any SVG icons in the menu, and overrides things so that
434 * we can display the icon in the correct colour for the theme.
435 */
436 public function override_svg_icons() {
437 global $menu;
438
439 $svg_items = array();
440 foreach ( $menu as $idx => $menu_item ) {
441 // Menu items that don't have icons, for example separators, have less than 7
442 // elements, partly because the 7th is the icon. So, if we have less than 7,
443 // let's skip it.
444 if ( ! is_countable( $menu_item ) || ( count( $menu_item ) < 7 ) ) {
445 continue;
446 }
447
448 // If the hookname contain a URL than sanitize it by replacing invalid characters.
449 if ( str_contains( $menu_item[5], '://' ) ) {
450 $menu_item[5] = preg_replace( '![:/.]+!', '_', $menu_item[5] );
451 }
452
453 if ( str_starts_with( $menu_item[6], 'data:image/svg+xml' ) && 'site-card' !== $menu_item[3] ) {
454 $svg_items[] = array(
455 'icon' => $menu_item[6],
456 'id' => $menu_item[5],
457 );
458 $menu_item[4] .= ' menu-svg-icon';
459 $menu_item[6] = 'none';
460 }
461 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
462 $menu[ $idx ] = $menu_item;
463 }
464 if ( $svg_items !== array() ) {
465 $styles = '.menu-svg-icon .wp-menu-image { background-repeat: no-repeat; background-position: center center } ';
466 foreach ( $svg_items as $svg_item ) {
467 $styles .= sprintf( '#%s .wp-menu-image { background-image: url( "%s" ) }', $svg_item['id'], $svg_item['icon'] );
468 }
469 $styles .= '@supports ( mask-image: none ) or ( -webkit-mask-image: none ) { ';
470 $styles .= '.menu-svg-icon .wp-menu-image { background-image: none; } ';
471 $styles .= '.menu-svg-icon .wp-menu-image::before { background-color: currentColor; ';
472 $styles .= 'mask-size: contain; mask-position: center center; mask-repeat: no-repeat; ';
473 $styles .= '-webkit-mask-size: contain; -webkit-mask-position: center center; -webkit-mask-repeat: no-repeat; content:"" } ';
474 foreach ( $svg_items as $svg_item ) {
475 $styles .= sprintf(
476 '#%s .wp-menu-image { background-image: none; } #%s .wp-menu-image::before{ mask-image: url( "%s" ); -webkit-mask-image: url( "%s" ) }',
477 $svg_item['id'],
478 $svg_item['id'],
479 $svg_item['icon'],
480 $svg_item['icon']
481 );
482 }
483 $styles .= '}';
484
485 wp_register_style( 'svg-menu-overrides', false, array(), '20210331' );
486 wp_enqueue_style( 'svg-menu-overrides' );
487 wp_add_inline_style( 'svg-menu-overrides', $styles );
488 }
489 }
490
491 /**
492 * Hide menus that are unauthorized and don't have visible submenus and cases when the menu has the same slug
493 * as the first submenu item.
494 *
495 * This must be done at the end of menu and submenu manipulation in order to avoid performing this check each time
496 * the submenus are altered.
497 */
498 public function hide_parent_of_hidden_submenus() {
499 global $menu, $submenu;
500
501 $this->sort_hidden_submenus();
502
503 foreach ( $menu as $menu_index => $menu_item ) {
504 $has_submenus = isset( $submenu[ $menu_item[2] ] );
505
506 // Skip if the menu doesn't have submenus or the submenu is not an array.
507 if ( ! $has_submenus || ! is_array( $submenu[ $menu_item[2] ] ) ) {
508 continue;
509 }
510
511 $submenu_items = array_values( $submenu[ $menu_item[2] ] );
512 if ( empty( $submenu_items ) ) {
513 continue;
514 }
515
516 // If the first submenu item is hidden then we should also hide the parent.
517 // Since the submenus are ordered by self::HIDE_CSS_CLASS (hidden submenus should be at the end of the array),
518 // we can say that if the first submenu is hidden then we should also hide the menu.
519 $first_submenu_item = $submenu_items[0];
520 $is_first_submenu_visible = $this->is_item_visible( $first_submenu_item );
521
522 // if the user does not have access to the menu and the first submenu is hidden, then hide the menu.
523 if ( ! current_user_can( $menu_item[1] ) && ! $is_first_submenu_visible ) {
524 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
525 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
526 }
527
528 // If the menu has the same slug as the first submenu then hide the submenu.
529 if ( isset( $first_submenu_item[2] ) && $menu_item[2] === $first_submenu_item[2] && ! $is_first_submenu_visible ) {
530 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
531 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
532 }
533 }
534 }
535
536 /**
537 * Sort the hidden submenus by moving them at the end of the array in order to avoid WP using them as default URLs.
538 *
539 * This operation has to be done at the end of submenu manipulation in order to guarantee that the hidden submenus
540 * are at the end of the array.
541 */
542 public function sort_hidden_submenus() {
543 global $submenu;
544
545 foreach ( $submenu as $menu_slug => $submenu_items ) {
546 if ( ! $submenu_items ) {
547 continue;
548 }
549
550 foreach ( $submenu_items as $submenu_index => $submenu_item ) {
551 if ( $this->is_item_visible( $submenu_item ) ) {
552 continue;
553 }
554
555 unset( $submenu[ $menu_slug ][ $submenu_index ] );
556 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
557 $submenu[ $menu_slug ][] = $submenu_item;
558 }
559 }
560 }
561
562 /**
563 * Check if the given item is visible or not in the admin menu.
564 *
565 * @param array $item A menu or submenu array.
566 */
567 public function is_item_visible( $item ) {
568 return ! isset( $item[4] ) || ! str_contains( $item[4], self::HIDE_CSS_CLASS );
569 }
570
571 /**
572 * Adds a dashboard switcher to the list of screen meta links of the current page.
573 */
574 public function add_dashboard_switcher() {
575 $menu_mappings = require __DIR__ . '/menu-mappings.php';
576 $screen = $this->get_current_screen();
577
578 // Let's show the switcher only in screens that we have a Calypso mapping to switch to.
579 if ( empty( $menu_mappings[ $screen ] ) ) {
580 return;
581 }
582 ?>
583 <div id="view-link-wrap" class="hide-if-no-js screen-meta-toggle">
584 <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' ); ?></button>
585 </div>
586 <div id="view-wrap" class="screen-options-tab__wrapper hide-if-no-js hidden" tabindex="-1">
587 <div class="screen-options-tab__dropdown" data-testid="screen-options-dropdown">
588 <div class="screen-switcher">
589 <a class="screen-switcher__button" href="<?php echo esc_url( add_query_arg( 'preferred-view', 'default' ) ); ?>" data-view="default">
590 <strong><?php esc_html_e( 'Default view', 'jetpack' ); ?></strong>
591 <?php esc_html_e( 'Our WordPress.com redesign for a better experience.', 'jetpack' ); ?>
592 </a>
593 <button class="screen-switcher__button" data-view="classic">
594 <strong><?php esc_html_e( 'Classic view', 'jetpack' ); ?></strong>
595 <?php esc_html_e( 'The classic WP-Admin WordPress interface.', 'jetpack' ); ?>
596 </button>
597 </div>
598 </div>
599 </div>
600 <?php
601 }
602
603 /**
604 * Adds a script to append the dashboard switcher to screen meta
605 */
606 public function dashboard_switcher_scripts() {
607 wp_add_inline_script(
608 'common',
609 "(function( $ ) {
610 $( '#view-link-wrap' ).appendTo( '#screen-meta-links' );
611
612 var viewLink = $( '#view-link' );
613 var viewWrap = $( '#view-wrap' );
614
615 viewLink.on( 'click', function() {
616 viewWrap.toggle();
617 viewLink.toggleClass( 'screen-meta-active' );
618 } );
619
620 $( document ).on( 'mouseup', function( event ) {
621 if ( ! viewLink.is( event.target ) && ! viewWrap.is( event.target ) && viewWrap.has( event.target ).length === 0 ) {
622 viewWrap.hide();
623 viewLink.removeClass( 'screen-meta-active' );
624 }
625 });
626 })( jQuery );"
627 );
628 }
629
630 /**
631 * Sets the given view as preferred for the givens screen.
632 *
633 * @param string $screen Screen identifier.
634 * @param string $view Preferred view.
635 */
636 public function set_preferred_view( $screen, $view ) {
637 $preferred_views = $this->get_preferred_views();
638 $screen = str_replace( '?post_type=post', '', $screen );
639 $preferred_views[ $screen ] = $view;
640 update_user_option( get_current_user_id(), 'jetpack_admin_menu_preferred_views', $preferred_views );
641 }
642
643 /**
644 * Get the preferred views for all screens.
645 *
646 * @return array
647 */
648 public function get_preferred_views() {
649 $preferred_views = get_user_option( 'jetpack_admin_menu_preferred_views' );
650
651 if ( ! $preferred_views ) {
652 return array();
653 }
654
655 return $preferred_views;
656 }
657
658 /**
659 * Get the preferred view for the given screen.
660 *
661 * @param string $screen Screen identifier.
662 * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used
663 * as fallback if there is no specific preference for the given screen.
664 * Default: true.
665 * @return string
666 */
667 public function get_preferred_view( $screen, $fallback_global_preference = true ) {
668 $preferred_views = $this->get_preferred_views();
669
670 if ( ! isset( $preferred_views[ $screen ] ) ) {
671 if ( ! $fallback_global_preference ) {
672 return self::UNKNOWN_VIEW;
673 }
674
675 $should_link_to_wp_admin = $this->should_link_to_wp_admin( $screen ) || $this->use_wp_admin_interface();
676 return $should_link_to_wp_admin ? self::CLASSIC_VIEW : self::DEFAULT_VIEW;
677 }
678
679 return $preferred_views[ $screen ];
680 }
681
682 /**
683 * Gets the identifier of the current screen.
684 *
685 * @return string
686 */
687 public function get_current_screen() {
688 // phpcs:disable WordPress.Security.NonceVerification
689 global $pagenow;
690 $screen = isset( $_REQUEST['screen'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['screen'] ) ) : $pagenow;
691 if ( isset( $_GET['post_type'] ) ) {
692 $screen = add_query_arg( 'post_type', sanitize_text_field( wp_unslash( $_GET['post_type'] ) ), $screen );
693 }
694 if ( isset( $_GET['taxonomy'] ) ) {
695 $screen = add_query_arg( 'taxonomy', sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ), $screen );
696 }
697 if ( isset( $_GET['page'] ) ) {
698 $screen = add_query_arg( 'page', sanitize_text_field( wp_unslash( $_GET['page'] ) ), $screen );
699 }
700 return $screen;
701 // phpcs:enable WordPress.Security.NonceVerification
702 }
703
704 /**
705 * Stores the preferred view for the current screen.
706 */
707 public function handle_preferred_view() {
708 // phpcs:disable WordPress.Security.NonceVerification
709 if ( ! isset( $_GET['preferred-view'] ) ) {
710 return;
711 }
712
713 // phpcs:disable WordPress.Security.NonceVerification
714 $preferred_view = sanitize_key( $_GET['preferred-view'] );
715
716 if ( ! in_array( $preferred_view, array( self::DEFAULT_VIEW, self::CLASSIC_VIEW ), true ) ) {
717 return;
718 }
719
720 $current_screen = $this->get_current_screen();
721
722 $this->set_preferred_view( $current_screen, $preferred_view );
723
724 /**
725 * Dashboard Quick switcher action triggered when a user switches to a different view.
726 *
727 * @module masterbar
728 *
729 * @since 9.9.1
730 *
731 * @param string The current screen of the user.
732 * @param string The preferred view the user selected.
733 */
734 \do_action( 'jetpack_dashboard_switcher_changed_view', $current_screen, $preferred_view );
735
736 if ( self::DEFAULT_VIEW === $preferred_view ) {
737 // Redirect to default view if that's the newly preferred view.
738 $menu_mappings = require __DIR__ . '/menu-mappings.php';
739 if ( isset( $menu_mappings[ $current_screen ] ) ) {
740 // Using `wp_redirect` intentionally because we're redirecting to Calypso.
741 wp_redirect( $menu_mappings[ $current_screen ] . $this->domain ); // phpcs:ignore WordPress.Security.SafeRedirect
742 exit;
743 }
744 } elseif ( self::CLASSIC_VIEW === $preferred_view ) {
745 // Removes the `preferred-view` param from the URL to avoid issues with
746 // screens that don't expect this param to be present in the URL.
747 wp_safe_redirect( remove_query_arg( 'preferred-view' ) );
748 exit;
749 }
750 // phpcs:enable WordPress.Security.NonceVerification
751 }
752
753 /**
754 * Adds the necessary CSS class to the admin body class.
755 *
756 * @param string $admin_body_classes Contains all the admin body classes.
757 *
758 * @return string
759 */
760 public function admin_body_class( $admin_body_classes ) {
761 return " is-nav-unification $admin_body_classes ";
762 }
763
764 /**
765 * Whether to use wp-admin pages rather than Calypso.
766 *
767 * Options:
768 * false - Calypso (Default).
769 * true - wp-admin.
770 *
771 * @return bool
772 */
773 public function should_link_to_wp_admin() {
774 return get_user_option( 'jetpack_admin_menu_link_destination' );
775 }
776
777 /**
778 * Injects the core's mobile toggle for proper positioning of the submenus.
779 *
780 * @see https://core.trac.wordpress.org/ticket/32747
781 *
782 * @return void
783 */
784 public function inject_core_mobile_toggle() {
785 echo '<span id="wp-admin-bar-menu-toggle" style="display: none!important">';
786 }
787
788 /**
789 * Whether the current user has indicated they want to use the wp-admin interface for the given screen.
790 *
791 * @return bool
792 */
793 public function use_wp_admin_interface() {
794 return 'wp-admin' === get_option( 'wpcom_admin_interface' );
795 }
796
797 /**
798 * Create the desired menu output.
799 */
800 abstract public function reregister_menu_items();
801 }
802