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