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