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