PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.0.2
Jetpack – WP Security, Backup, Speed, & Growth v13.0.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
812 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( 'jetpack' ) ) {
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( 'jetpack' ) ) {
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.
507 if ( ! $has_submenus ) {
508 continue;
509 }
510
511 // If the first submenu item is hidden then we should also hide the parent.
512 // Since the submenus are ordered by self::HIDE_CSS_CLASS (hidden submenus should be at the end of the array),
513 // we can say that if the first submenu is hidden then we should also hide the menu.
514 $first_submenu_item = array_values( $submenu[ $menu_item[2] ] )[0];
515 $is_first_submenu_visible = $this->is_item_visible( $first_submenu_item );
516
517 // if the user does not have access to the menu and the first submenu is hidden, then hide the menu.
518 if ( ! current_user_can( $menu_item[1] ) && ! $is_first_submenu_visible ) {
519 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
520 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
521 }
522
523 // if the menu has the same slug as the first submenu then hide the submenu.
524 if ( $menu_item[2] === $first_submenu_item[2] && ! $is_first_submenu_visible ) {
525 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
526 $menu[ $menu_index ][4] = self::HIDE_CSS_CLASS;
527 }
528 }
529 }
530
531 /**
532 * Sort the hidden submenus by moving them at the end of the array in order to avoid WP using them as default URLs.
533 *
534 * This operation has to be done at the end of submenu manipulation in order to guarantee that the hidden submenus
535 * are at the end of the array.
536 */
537 public function sort_hidden_submenus() {
538 global $submenu;
539
540 foreach ( $submenu as $menu_slug => $submenu_items ) {
541 foreach ( $submenu_items as $submenu_index => $submenu_item ) {
542 if ( $this->is_item_visible( $submenu_item ) ) {
543 continue;
544 }
545
546 unset( $submenu[ $menu_slug ][ $submenu_index ] );
547 // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
548 $submenu[ $menu_slug ][] = $submenu_item;
549 }
550 }
551 }
552
553 /**
554 * Check if the given item is visible or not in the admin menu.
555 *
556 * @param array $item A menu or submenu array.
557 */
558 public function is_item_visible( $item ) {
559 return ! isset( $item[4] ) || ! str_contains( $item[4], self::HIDE_CSS_CLASS );
560 }
561
562 /**
563 * Adds a dashboard switcher to the list of screen meta links of the current page.
564 */
565 public function add_dashboard_switcher() {
566 $menu_mappings = require __DIR__ . '/menu-mappings.php';
567 $screen = $this->get_current_screen();
568
569 // Let's show the switcher only in screens that we have a Calypso mapping to switch to.
570 if ( empty( $menu_mappings[ $screen ] ) ) {
571 return;
572 }
573 ?>
574 <div id="view-link-wrap" class="hide-if-no-js screen-meta-toggle">
575 <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>
576 </div>
577 <div id="view-wrap" class="screen-options-tab__wrapper hide-if-no-js hidden" tabindex="-1">
578 <div class="screen-options-tab__dropdown" data-testid="screen-options-dropdown">
579 <div class="screen-switcher">
580 <a class="screen-switcher__button" href="<?php echo esc_url( add_query_arg( 'preferred-view', 'default' ) ); ?>" data-view="default">
581 <strong><?php esc_html_e( 'Default view', 'jetpack' ); ?></strong>
582 <?php esc_html_e( 'Our WordPress.com redesign for a better experience.', 'jetpack' ); ?>
583 </a>
584 <button class="screen-switcher__button" data-view="classic">
585 <strong><?php esc_html_e( 'Classic view', 'jetpack' ); ?></strong>
586 <?php esc_html_e( 'The classic WP-Admin WordPress interface.', 'jetpack' ); ?>
587 </button>
588 </div>
589 </div>
590 </div>
591 <?php
592 }
593
594 /**
595 * Adds a script to append the dashboard switcher to screen meta
596 */
597 public function dashboard_switcher_scripts() {
598 wp_add_inline_script(
599 'common',
600 "(function( $ ) {
601 $( '#view-link-wrap' ).appendTo( '#screen-meta-links' );
602
603 var viewLink = $( '#view-link' );
604 var viewWrap = $( '#view-wrap' );
605
606 viewLink.on( 'click', function() {
607 viewWrap.toggle();
608 viewLink.toggleClass( 'screen-meta-active' );
609 } );
610
611 $( document ).on( 'mouseup', function( event ) {
612 if ( ! viewLink.is( event.target ) && ! viewWrap.is( event.target ) && viewWrap.has( event.target ).length === 0 ) {
613 viewWrap.hide();
614 viewLink.removeClass( 'screen-meta-active' );
615 }
616 });
617 })( jQuery );"
618 );
619 }
620
621 /**
622 * Sets the given view as preferred for the givens screen.
623 *
624 * @param string $screen Screen identifier.
625 * @param string $view Preferred view.
626 */
627 public function set_preferred_view( $screen, $view ) {
628 $preferred_views = $this->get_preferred_views();
629 $screen = str_replace( '?post_type=post', '', $screen );
630 $preferred_views[ $screen ] = $view;
631 update_user_option( get_current_user_id(), 'jetpack_admin_menu_preferred_views', $preferred_views );
632 }
633
634 /**
635 * Get the preferred views for all screens.
636 *
637 * @return array
638 */
639 public function get_preferred_views() {
640 $preferred_views = get_user_option( 'jetpack_admin_menu_preferred_views' );
641
642 if ( ! $preferred_views ) {
643 return array();
644 }
645
646 return $preferred_views;
647 }
648
649 /**
650 * Get the preferred view for the given screen.
651 *
652 * @param string $screen Screen identifier.
653 * @param bool $fallback_global_preference (Optional) Whether the global preference for all screens should be used
654 * as fallback if there is no specific preference for the given screen.
655 * Default: true.
656 * @return string
657 */
658 public function get_preferred_view( $screen, $fallback_global_preference = true ) {
659 $preferred_views = $this->get_preferred_views();
660
661 if ( ! isset( $preferred_views[ $screen ] ) ) {
662 if ( ! $fallback_global_preference ) {
663 return self::UNKNOWN_VIEW;
664 }
665
666 $should_link_to_wp_admin = $this->should_link_to_wp_admin( $screen ) || $this->use_wp_admin_interface( $screen );
667 return $should_link_to_wp_admin ? self::CLASSIC_VIEW : self::DEFAULT_VIEW;
668 }
669
670 return $preferred_views[ $screen ];
671 }
672
673 /**
674 * Gets the identifier of the current screen.
675 *
676 * @return string
677 */
678 public function get_current_screen() {
679 // phpcs:disable WordPress.Security.NonceVerification
680 global $pagenow;
681 $screen = isset( $_REQUEST['screen'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['screen'] ) ) : $pagenow;
682 if ( isset( $_GET['post_type'] ) ) {
683 $screen = add_query_arg( 'post_type', sanitize_text_field( wp_unslash( $_GET['post_type'] ) ), $screen );
684 }
685 if ( isset( $_GET['taxonomy'] ) ) {
686 $screen = add_query_arg( 'taxonomy', sanitize_text_field( wp_unslash( $_GET['taxonomy'] ) ), $screen );
687 }
688 if ( isset( $_GET['page'] ) ) {
689 $screen = add_query_arg( 'page', sanitize_text_field( wp_unslash( $_GET['page'] ) ), $screen );
690 }
691 return $screen;
692 // phpcs:enable WordPress.Security.NonceVerification
693 }
694
695 /**
696 * Stores the preferred view for the current screen.
697 */
698 public function handle_preferred_view() {
699 // phpcs:disable WordPress.Security.NonceVerification
700 if ( ! isset( $_GET['preferred-view'] ) ) {
701 return;
702 }
703
704 // phpcs:disable WordPress.Security.NonceVerification
705 $preferred_view = sanitize_key( $_GET['preferred-view'] );
706
707 if ( ! in_array( $preferred_view, array( self::DEFAULT_VIEW, self::CLASSIC_VIEW ), true ) ) {
708 return;
709 }
710
711 $current_screen = $this->get_current_screen();
712
713 $this->set_preferred_view( $current_screen, $preferred_view );
714
715 /**
716 * Dashboard Quick switcher action triggered when a user switches to a different view.
717 *
718 * @module masterbar
719 *
720 * @since 9.9.1
721 *
722 * @param string The current screen of the user.
723 * @param string The preferred view the user selected.
724 */
725 \do_action( 'jetpack_dashboard_switcher_changed_view', $current_screen, $preferred_view );
726
727 if ( self::DEFAULT_VIEW === $preferred_view ) {
728 // Redirect to default view if that's the newly preferred view.
729 $menu_mappings = require __DIR__ . '/menu-mappings.php';
730 if ( isset( $menu_mappings[ $current_screen ] ) ) {
731 // Using `wp_redirect` intentionally because we're redirecting to Calypso.
732 wp_redirect( $menu_mappings[ $current_screen ] . $this->domain ); // phpcs:ignore WordPress.Security.SafeRedirect
733 exit;
734 }
735 } elseif ( self::CLASSIC_VIEW === $preferred_view ) {
736 // Removes the `preferred-view` param from the URL to avoid issues with
737 // screens that don't expect this param to be present in the URL.
738 wp_safe_redirect( remove_query_arg( 'preferred-view' ) );
739 exit;
740 }
741 // phpcs:enable WordPress.Security.NonceVerification
742 }
743
744 /**
745 * Adds the necessary CSS class to the admin body class.
746 *
747 * @param string $admin_body_classes Contains all the admin body classes.
748 *
749 * @return string
750 */
751 public function admin_body_class( $admin_body_classes ) {
752 return " is-nav-unification $admin_body_classes ";
753 }
754
755 /**
756 * Whether to use wp-admin pages rather than Calypso.
757 *
758 * Options:
759 * false - Calypso (Default).
760 * true - wp-admin.
761 *
762 * @return bool
763 */
764 public function should_link_to_wp_admin() {
765 return get_user_option( 'jetpack_admin_menu_link_destination' );
766 }
767
768 /**
769 * Injects the core's mobile toggle for proper positioning of the submenus.
770 *
771 * @see https://core.trac.wordpress.org/ticket/32747
772 *
773 * @return void
774 */
775 public function inject_core_mobile_toggle() {
776 echo '<span id="wp-admin-bar-menu-toggle" style="display: none!important">';
777 }
778
779 /**
780 * Whether the current user has indicated they want to use the wp-admin interface for the given screen.
781 *
782 * @param string $screen The current screen.
783 * @return bool
784 */
785 public function use_wp_admin_interface( $screen ) {
786 $screen_excluded = $this->is_excluded_wp_admin_interface_screen( $screen );
787 return ! $screen_excluded && 'wp-admin' === get_option( 'wpcom_admin_interface' );
788 }
789
790 /**
791 * Returns whether we should default to wp_admin for the given screen.
792 *
793 * Screens that should not default to wp_admin when the wpcom_admin_interface is set.
794 * This applies to screens that have both a wp-admin and a Calypso interface.
795 *
796 * @param string $screen The current screen.
797 *
798 * @return bool
799 */
800 protected function is_excluded_wp_admin_interface_screen( $screen ) {
801 $excluded_screens = array(
802 'import.php',
803 );
804 return in_array( $screen, $excluded_screens, true );
805 }
806
807 /**
808 * Create the desired menu output.
809 */
810 abstract public function reregister_menu_items();
811 }
812