PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.8-a.1
Jetpack – WP Security, Backup, Speed, & Growth v15.8-a.1
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 / class.jetpack-admin.php
jetpack Last commit date
3rd-party 2 months ago _inc 1 month ago css 4 months ago extensions 1 month ago images 2 months ago jetpack_vendor 1 month ago json-endpoints 2 months ago modules 1 month ago sal 2 months ago src 3 months ago vendor 1 month ago views 1 month ago CHANGELOG.md 1 month ago LICENSE.txt 5 months ago SECURITY.md 2 years ago class-jetpack-connection-status.php 2 years ago class-jetpack-gallery-settings.php 6 months ago class-jetpack-newsletter-dashboard-widget.php 6 months ago class-jetpack-pre-connection-jitms.php 2 years ago class-jetpack-stats-dashboard-widget.php 3 months ago class-jetpack-xmlrpc-methods.php 6 months ago class.frame-nonce-preview.php 6 months ago class.jetpack-admin.php 3 months ago class.jetpack-autoupdate.php 6 months ago class.jetpack-cli.php 5 months ago class.jetpack-client-server.php 2 years ago class.jetpack-gutenberg.php 2 months ago class.jetpack-heartbeat.php 3 months ago class.jetpack-modules-list-table.php 6 months ago class.jetpack-network-sites-list-table.php 6 months ago class.jetpack-network.php 1 month ago class.jetpack-plan.php 2 years ago class.jetpack-post-images.php 2 months ago class.jetpack-twitter-cards.php 3 months ago class.jetpack-user-agent.php 2 years ago class.jetpack.php 2 months ago class.json-api-endpoints.php 4 months ago class.json-api.php 5 months ago class.photon.php 3 years ago composer.json 1 month ago enhanced-open-graph.php 3 months ago functions.compat.php 3 months ago functions.cookies.php 2 years ago functions.global.php 6 months ago functions.is-mobile.php 2 years ago functions.opengraph.php 2 months ago functions.photon.php 2 years ago jetpack.php 1 month ago json-api-config.php 3 years ago json-endpoints.php 2 years ago load-jetpack.php 2 months ago locales.php 6 months ago readme.txt 1 month ago unauth-file-upload.php 6 months ago uninstall.php 6 months ago wpml-config.xml 3 years ago
class.jetpack-admin.php
667 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Build the Jetpack admin menu as a whole.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Admin_UI\Admin_Menu;
9 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
10 use Automattic\Jetpack\Partner_Coupon as Jetpack_Partner_Coupon;
11 use Automattic\Jetpack\Status;
12 use Automattic\Jetpack\Status\Host;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit( 0 );
16 }
17
18 /**
19 * Build the Jetpack admin menu as a whole.
20 */
21 class Jetpack_Admin {
22
23 /**
24 * Static instance.
25 *
26 * @var Jetpack_Admin
27 */
28 private static $instance = null;
29
30 /**
31 * Initialize and fetch the static instance.
32 *
33 * @return self
34 */
35 public static function init() {
36 if ( self::$instance === null ) {
37 self::$instance = new Jetpack_Admin();
38 }
39 return self::$instance;
40 }
41
42 /**
43 * Filter callback to add `no-store` to the `Cache-Control` header.
44 *
45 * @deprecated 14.9
46 * @param array $headers Headers array.
47 * @return array Modified headers array.
48 */
49 public static function add_no_store_header( $headers ) {
50 _deprecated_function( __METHOD__, '14.9' );
51 $headers['Cache-Control'] .= ', no-store';
52 return $headers;
53 }
54
55 /** Constructor. */
56 private function __construct() {
57 require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-react-page.php';
58 $jetpack_react = new Jetpack_React_Page();
59
60 require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class.jetpack-settings-page.php';
61 $fallback_page = new Jetpack_Settings_Page();
62
63 require_once JETPACK__PLUGIN_DIR . '_inc/lib/admin-pages/class-jetpack-about-page.php';
64 $jetpack_about = new Jetpack_About_Page();
65
66 add_action( 'admin_init', array( $jetpack_react, 'react_redirects' ), 0 );
67 add_action( 'admin_menu', array( $jetpack_react, 'add_actions' ), 998 );
68 add_action( 'admin_menu', array( $jetpack_react, 'remove_jetpack_menu' ), 2000 );
69 add_action( 'jetpack_admin_menu', array( $jetpack_react, 'jetpack_add_settings_sub_nav_item' ) );
70 add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_debugger' ) );
71 add_action( 'jetpack_admin_menu', array( $fallback_page, 'add_actions' ) );
72 add_action( 'jetpack_admin_menu', array( $jetpack_about, 'add_actions' ) );
73
74 // Add redirect to current page for activation/deactivation of modules.
75 add_action( 'jetpack_pre_activate_module', array( $this, 'fix_redirect' ), 10, 2 );
76 add_action( 'jetpack_pre_deactivate_module', array( $this, 'fix_redirect' ), 10, 2 );
77
78 // Add module bulk actions handler.
79 add_action( 'jetpack_unrecognized_action', array( $this, 'handle_unrecognized_action' ) );
80
81 if ( class_exists( 'Akismet_Admin' ) ) {
82 // If the site has Jetpack Anti-spam, change the Akismet menu label and logo accordingly.
83 $site_products = array_column( Jetpack_Plan::get_products(), 'product_slug' );
84 $has_anti_spam_product = count( array_intersect( array( 'jetpack_anti_spam', 'jetpack_anti_spam_monthly' ), $site_products ) ) > 0;
85
86 if ( Jetpack_Plan::supports( 'akismet' ) || Jetpack_Plan::supports( 'antispam' ) || $has_anti_spam_product ) {
87 // Prevent Akismet from adding a menu item.
88 add_action(
89 'admin_menu',
90 function () {
91 remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
92 },
93 4
94 );
95
96 // Add an Anti-spam menu item for Jetpack. This is handled automatically by the Admin_Menu as long as it has been initialized.
97 Admin_Menu::init();
98 }
99 }
100
101 // Ensure an Additional CSS menu item is added to the Appearance menu whenever Jetpack is connected.
102 add_action( 'admin_menu', array( $this, 'additional_css_menu' ) );
103
104 add_filter( 'jetpack_display_jitms_on_screen', array( $this, 'should_display_jitms_on_screen' ), 10, 2 );
105
106 // Register Jetpack partner coupon hooks.
107 Jetpack_Partner_Coupon::register_coupon_admin_hooks( 'jetpack', Jetpack::admin_url() );
108
109 // Remove default WordPress admin footer on Jetpack pages only.
110 add_filter( 'admin_footer_text', array( $this, 'maybe_remove_admin_footer_text' ) );
111 add_filter( 'update_footer', array( $this, 'maybe_remove_admin_footer_version' ), 11 );
112 add_filter( 'admin_body_class', array( $this, 'add_jetpack_admin_body_class' ) );
113 add_action( 'admin_head', array( $this, 'add_footer_removal_styles' ) );
114 }
115
116 /**
117 * Handle our Additional CSS menu item and legacy page declaration.
118 *
119 * @since 11.0 . Prior to that, this function was located in custom-css-4.7.php (now custom-css.php).
120 */
121 public static function additional_css_menu() {
122 /*
123 * Custom CSS for the Customizer is deprecated for block themes as of WP 6.1, so we only expose it with a menu
124 * if the site already has existing CSS code.
125 */
126 if ( wp_is_block_theme() ) {
127 $styles = wp_get_custom_css();
128 if ( ! $styles ) {
129 return;
130 }
131 }
132
133 // If the site is a WoA site and the custom-css feature is not available, return.
134 // See https://github.com/Automattic/jetpack/pull/19965 for more on how this menu item is dealt with on WoA sites.
135 if ( ( new Host() )->is_woa_site() && ! ( in_array( 'custom-css', Jetpack::get_available_modules(), true ) ) ) {
136 return;
137 } elseif (
138 class_exists( 'Jetpack' ) && (
139 Jetpack::is_module_active( 'custom-css' ) || // If the Custom CSS module is enabled, add the Additional CSS menu item and link to the Customizer.
140 ( wp_is_block_theme() && ! empty( wp_get_custom_css() ) ) // Do the same if the theme is block-based but has existing custom CSS.
141 )
142 ) {
143 // Add in our legacy page to support old bookmarks and such.
144 add_submenu_page( '', __( 'CSS', 'jetpack' ), __( 'Additional CSS', 'jetpack' ), 'edit_theme_options', 'editcss', array( __CLASS__, 'customizer_redirect' ) );
145
146 // Add in our new page slug that will redirect to the customizer.
147 $hook = add_theme_page( __( 'CSS', 'jetpack' ), __( 'Additional CSS', 'jetpack' ), 'edit_theme_options', 'editcss-customizer-redirect', array( __CLASS__, 'customizer_redirect' ) );
148 add_action( "load-{$hook}", array( __CLASS__, 'customizer_redirect' ) );
149 }
150 }
151
152 /**
153 * Handle the redirect for the customizer. This is necessary because
154 * we can't directly add customizer links to the admin menu.
155 *
156 * @since 11.0 . Prior to that, this function was located in custom-css-4.7.php (now custom-css.php).
157 *
158 * There is a core patch in trac that would make this unnecessary.
159 *
160 * @link https://core.trac.wordpress.org/ticket/39050
161 *
162 * @return never
163 */
164 public static function customizer_redirect() {
165 wp_safe_redirect(
166 self::customizer_link(
167 array(
168 'return_url' => wp_get_referer(),
169 )
170 )
171 );
172 exit( 0 );
173 }
174
175 /**
176 * Build the URL to deep link to the Customizer.
177 *
178 * You can modify the return url via $args.
179 *
180 * @since 11.0 in this file. This method is also located in custom-css-4.7.php to cover legacy scenarios.
181 *
182 * @param array $args Array of parameters.
183 * @return string
184 */
185 public static function customizer_link( $args = array() ) {
186 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
187 $args = wp_parse_args(
188 $args,
189 array(
190 'return_url' => rawurlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
191 )
192 );
193 }
194
195 return add_query_arg(
196 array(
197 array(
198 'autofocus' => array(
199 'section' => 'custom_css',
200 ),
201 ),
202 'return' => $args['return_url'],
203 ),
204 admin_url( 'customize.php' )
205 );
206 }
207
208 /**
209 * Sort callback to put modules with `requires_connection` last.
210 *
211 * @param array $module1 Module data.
212 * @param array $module2 Module data.
213 * @return int Indicating the relative ordering of module1 and module2.
214 */
215 public static function sort_requires_connection_last( $module1, $module2 ) {
216 return ( (bool) $module1['requires_connection'] ) <=> ( (bool) $module2['requires_connection'] );
217 }
218
219 /**
220 * Produce JS understandable objects of modules containing information for
221 * presentation like description, name, configuration url, etc.
222 */
223 public function get_modules() {
224 include_once JETPACK__PLUGIN_DIR . 'modules/module-info.php';
225 $available_modules = Jetpack::get_available_modules();
226 $active_modules = Jetpack::get_active_modules();
227 $modules = array();
228 $jetpack_active = Jetpack::is_connection_ready() || ( new Status() )->is_offline_mode();
229 $overrides = Jetpack_Modules_Overrides::instance();
230 foreach ( $available_modules as $module ) {
231 $module_array = Jetpack::get_module( $module );
232 if ( $module_array ) {
233 /**
234 * Filters each module's short description.
235 *
236 * @since 3.0.0
237 *
238 * @param string $module_array['description'] Module description.
239 * @param string $module Module slug.
240 */
241 $short_desc = apply_filters( 'jetpack_short_module_description', $module_array['description'], $module );
242 // Fix: correct multibyte strings truncate with checking for mbstring extension.
243 $short_desc_trunc = ( function_exists( 'mb_strlen' ) )
244 ? ( ( mb_strlen( $short_desc ) > 143 )
245 ? mb_substr( $short_desc, 0, 140 ) . '...'
246 : $short_desc )
247 : ( ( strlen( $short_desc ) > 143 )
248 ? substr( $short_desc, 0, 140 ) . '...'
249 : $short_desc );
250
251 $module_array['module'] = $module;
252
253 $is_available = self::is_module_available( $module_array );
254
255 $module_array['activated'] = ( $jetpack_active ? in_array( $module, $active_modules, true ) : false );
256 $module_array['deactivate_nonce'] = wp_create_nonce( 'jetpack_deactivate-' . $module );
257 $module_array['activate_nonce'] = wp_create_nonce( 'jetpack_activate-' . $module );
258 $module_array['available'] = $is_available;
259 $module_array['unavailable_reason'] = $is_available ? false : self::get_module_unavailable_reason( $module_array );
260 $module_array['short_description'] = $short_desc_trunc;
261 $module_array['configure_url'] = Jetpack::module_configuration_url( $module );
262 $module_array['override'] = $overrides->get_module_override( $module );
263 $module_array['disabled'] = $is_available ? '' : 'disabled="disabled"';
264
265 ob_start();
266 /**
267 * Allow the display of a "Learn More" button.
268 * The dynamic part of the action, $module, is the module slug.
269 *
270 * @since 3.0.0
271 */
272 do_action( 'jetpack_learn_more_button_' . $module );
273 $module_array['learn_more_button'] = ob_get_clean();
274
275 ob_start();
276 /**
277 * Allow the display of information text when Jetpack is connected to WordPress.com.
278 * The dynamic part of the action, $module, is the module slug.
279 *
280 * @since 3.0.0
281 */
282 do_action( 'jetpack_module_more_info_' . $module );
283
284 /**
285 * Filter the long description of a module.
286 *
287 * @since 3.5.0
288 *
289 * @param string ob_get_clean() The module long description.
290 * @param string $module The module name.
291 */
292 $module_array['long_description'] = apply_filters( 'jetpack_long_module_description', ob_get_clean(), $module );
293
294 ob_start();
295 /**
296 * Filter the search terms for a module
297 *
298 * Search terms are typically added to the module headers, under "Additional Search Queries".
299 *
300 * Use syntax:
301 * function jetpack_$module_search_terms( $terms ) {
302 * $terms = _x( 'term 1, term 2', 'search terms', 'jetpack' );
303 * return $terms;
304 * }
305 * add_filter( 'jetpack_search_terms_$module', 'jetpack_$module_search_terms' );
306 *
307 * @since 3.5.0
308 *
309 * @param string The search terms (comma-separated).
310 */
311 echo apply_filters( 'jetpack_search_terms_' . $module, $module_array['additional_search_queries'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
312 $module_array['search_terms'] = ob_get_clean();
313
314 $module_array['configurable'] = false;
315 if (
316 current_user_can( 'manage_options' ) &&
317 /**
318 * Allow the display of a configuration link in the Jetpack Settings screen.
319 *
320 * @since 3.0.0
321 *
322 * @param string $module Module name.
323 * @param bool false Should the Configure module link be displayed? Default to false.
324 */
325 apply_filters( 'jetpack_module_configurable_' . $module, false )
326 ) {
327 $module_array['configurable'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $module_array['configure_url'] ), __( 'Configure', 'jetpack' ) );
328 }
329
330 $modules[ $module ] = $module_array;
331 }
332 }
333
334 uasort( $modules, array( 'Jetpack', 'sort_modules' ) );
335
336 if ( ! Jetpack::is_connection_ready() ) {
337 uasort( $modules, array( __CLASS__, 'sort_requires_connection_last' ) );
338 }
339
340 return $modules;
341 }
342
343 /**
344 * Check if a module is available.
345 *
346 * @param array $module Module data.
347 */
348 public static function is_module_available( $module ) {
349 if ( ! is_array( $module ) || empty( $module ) ) {
350 return false;
351 }
352
353 /**
354 * We never want to show VaultPress as activatable through Jetpack.
355 */
356 if ( 'vaultpress' === $module['module'] ) {
357 return false;
358 }
359
360 /*
361 * WooCommerce Analytics should only be available
362 * when running WooCommerce 3+
363 */
364 if (
365 'woocommerce-analytics' === $module['module']
366 && (
367 ! class_exists( 'WooCommerce' )
368 || version_compare( WC_VERSION, '3.0', '<' )
369 )
370 ) {
371 return false;
372 }
373
374 /*
375 * In Offline mode, modules that require a site or user
376 * level connection should be unavailable.
377 */
378 if ( ( new Status() )->is_offline_mode() ) {
379 return ! ( $module['requires_connection'] || $module['requires_user_connection'] );
380 }
381
382 /*
383 * Jetpack not connected.
384 */
385 if ( ! Jetpack::is_connection_ready() ) {
386 return false;
387 }
388
389 /*
390 * Jetpack connected at a site level only. Make sure to make
391 * modules that require a user connection unavailable.
392 */
393 if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
394 return false;
395 }
396
397 return Jetpack_Plan::supports( $module['module'] );
398 }
399
400 /**
401 * Returns why a module is unavailable.
402 *
403 * @param array $module The module.
404 * @return string|false A string stating why the module is not available or false if the module is available.
405 */
406 public static function get_module_unavailable_reason( $module ) {
407 if ( ! is_array( $module ) || empty( $module ) ) {
408 return false;
409 }
410
411 if ( self::is_module_available( $module ) ) {
412 return false;
413 }
414
415 /**
416 * We never want to show VaultPress as activatable through Jetpack so return an empty string.
417 */
418 if ( 'vaultpress' === $module['module'] ) {
419 return '';
420 }
421
422 /*
423 * WooCommerce Analytics should only be available
424 * when running WooCommerce 3+
425 */
426 if (
427 'woocommerce-analytics' === $module['module']
428 && (
429 ! class_exists( 'WooCommerce' )
430 || version_compare( WC_VERSION, '3.0', '<' )
431 )
432 ) {
433 return __( 'Requires WooCommerce 3+ plugin', 'jetpack' );
434 }
435
436 /*
437 * In Offline mode, modules that require a site or user
438 * level connection should be unavailable.
439 */
440 if ( ( new Status() )->is_offline_mode() ) {
441 if ( $module['requires_connection'] || $module['requires_user_connection'] ) {
442 return __( 'Offline mode', 'jetpack' );
443 }
444 }
445
446 /*
447 * Jetpack not connected.
448 */
449 if ( ! Jetpack::is_connection_ready() ) {
450 return __( 'Jetpack is not connected', 'jetpack' );
451 }
452
453 /*
454 * Jetpack connected at a site level only and module requires a user connection.
455 */
456 if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
457 return __( 'Requires a connected WordPress.com account', 'jetpack' );
458 }
459
460 /*
461 * Plan restrictions.
462 */
463 if ( ! Jetpack_Plan::supports( $module['module'] ) ) {
464 return __( 'Not supported by current plan', 'jetpack' );
465 }
466
467 return '';
468 }
469
470 /**
471 * Handle an unrecognized action.
472 *
473 * @param string $action Action.
474 */
475 public function handle_unrecognized_action( $action ) {
476 switch ( $action ) {
477 case 'bulk-activate':
478 check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
479 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
480 break;
481 }
482
483 $modules = isset( $_GET['modules'] ) ? array_map( 'sanitize_key', wp_unslash( (array) $_GET['modules'] ) ) : array();
484 foreach ( $modules as $module ) {
485 Jetpack::log( 'activate', $module );
486 Jetpack::activate_module( $module, false );
487 }
488 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
489 wp_safe_redirect( wp_get_referer() );
490 exit( 0 );
491 case 'bulk-deactivate':
492 check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
493 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
494 break;
495 }
496
497 $modules = isset( $_GET['modules'] ) ? array_map( 'sanitize_key', wp_unslash( (array) $_GET['modules'] ) ) : array();
498 foreach ( $modules as $module ) {
499 Jetpack::log( 'deactivate', $module );
500 Jetpack::deactivate_module( $module );
501 Jetpack::state( 'message', 'module_deactivated' );
502 }
503 Jetpack::state( 'module', $modules );
504 wp_safe_redirect( wp_get_referer() );
505 exit( 0 );
506 default:
507 return;
508 }
509 }
510
511 /**
512 * Fix redirect.
513 *
514 * Apparently we redirect to the referrer instead of whatever WordPress
515 * wants to redirect to when activating and deactivating modules.
516 *
517 * @param string $module Module slug.
518 * @param bool $redirect Should we exit after the module has been activated. Default to true.
519 */
520 public function fix_redirect( $module, $redirect = true ) {
521 if ( ! $redirect ) {
522 return;
523 }
524 if ( wp_get_referer() ) {
525 add_filter( 'wp_redirect', 'wp_get_referer' );
526 }
527 }
528
529 /**
530 * Add debugger admin menu.
531 */
532 public function admin_menu_debugger() {
533 require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger.php';
534 Jetpack_Debugger::disconnect_and_redirect();
535 $debugger_hook = add_submenu_page(
536 '',
537 __( 'Debugging Center', 'jetpack' ),
538 '',
539 'manage_options',
540 'jetpack-debugger',
541 array( $this, 'wrap_debugger_page' )
542 );
543 add_action( "admin_head-$debugger_hook", array( 'Jetpack_Debugger', 'jetpack_debug_admin_head' ) );
544 }
545
546 /**
547 * Wrap debugger page.
548 */
549 public function wrap_debugger_page() {
550 nocache_headers();
551 if ( ! current_user_can( 'manage_options' ) ) {
552 die( '-1' );
553 }
554 Jetpack_Admin_Page::wrap_ui( array( $this, 'debugger_page' ), array( 'is-wide' => true ) );
555 }
556
557 /**
558 * Display debugger page.
559 */
560 public function debugger_page() {
561 require_once JETPACK__PLUGIN_DIR . '_inc/lib/debugger.php';
562 Jetpack_Debugger::jetpack_debug_display_handler();
563 }
564
565 /**
566 * Determines if JITMs should display on a particular screen.
567 *
568 * @param bool $value The default value of the filter.
569 * @param string $screen_id The ID of the screen being tested for JITM display.
570 *
571 * @return bool True if JITMs should display, false otherwise.
572 */
573 public function should_display_jitms_on_screen( $value, $screen_id ) {
574 // Disable all JITMs on these pages.
575 if (
576 in_array(
577 $screen_id,
578 array(
579 'jetpack_page_akismet-key-config',
580 'admin_page_jetpack_modules',
581 ),
582 true
583 ) ) {
584 return false;
585 }
586
587 return $value;
588 }
589
590 /**
591 * Check if we're on a Jetpack admin page.
592 *
593 * Similar to how WooCommerce checks for its admin pages by comparing
594 * against known screen ID patterns.
595 *
596 * @return bool True if on a Jetpack admin page, false otherwise.
597 */
598 private function is_jetpack_admin_page() {
599 $screen = get_current_screen();
600 if ( ! $screen ) {
601 return false;
602 }
603
604 // Check for Jetpack admin pages:
605 // - toplevel_page_jetpack (main Jetpack menu page)
606 // - toplevel_page_jetpack-network (Jetpack Network Admin menu page)
607 // - jetpack_page_* (Jetpack submenu pages)
608 // - admin_page_jetpack* (legacy/special Jetpack pages)
609 // Or check if parent_base is 'jetpack' or 'jetpack-network' (submenu pages)
610 return (
611 $screen->id === 'toplevel_page_jetpack' ||
612 $screen->id === 'toplevel_page_jetpack-network' ||
613 str_starts_with( $screen->id, 'jetpack_page_' ) ||
614 str_starts_with( $screen->id, 'admin_page_jetpack' ) ||
615 $screen->parent_base === 'jetpack' ||
616 $screen->parent_base === 'jetpack-network'
617 );
618 }
619
620 /**
621 * Add a body class to Jetpack admin pages.
622 *
623 * @param string $classes Space-separated list of CSS classes.
624 * @return string Modified class list.
625 */
626 public function add_jetpack_admin_body_class( $classes ) {
627 if ( $this->is_jetpack_admin_page() ) {
628 return trim( $classes ) . ' jetpack-admin-page ';
629 }
630 return $classes;
631 }
632
633 /**
634 * Add inline styles to remove footer padding on Jetpack pages.
635 *
636 * This needs to be inline because jetpack-admin.css is not loaded on
637 * React-powered admin pages (they use load_wrapper_styles instead).
638 */
639 public function add_footer_removal_styles() {
640 if ( ! $this->is_jetpack_admin_page() ) {
641 return;
642 }
643 echo '<style>.jetpack-admin-page #wpbody-content { padding-bottom: 0; } .jetpack-admin-page #wpfooter { display: none; }</style>';
644 }
645
646 /**
647 * Remove the admin footer text on Jetpack pages.
648 *
649 * @param string $content The default footer text.
650 * @return string Empty string on Jetpack pages, original content otherwise.
651 */
652 public function maybe_remove_admin_footer_text( $content ) {
653 return $this->is_jetpack_admin_page() ? '' : $content;
654 }
655
656 /**
657 * Remove the admin footer version on Jetpack pages.
658 *
659 * @param string $content The default footer version text.
660 * @return string Empty string on Jetpack pages, original content otherwise.
661 */
662 public function maybe_remove_admin_footer_version( $content ) {
663 return $this->is_jetpack_admin_page() ? '' : $content;
664 }
665 }
666 Jetpack_Admin::init();
667