PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 10.5.3
Jetpack – WP Security, Backup, Speed, & Growth v10.5.3
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 All 500 releases
jetpack / class.jetpack-admin.php
class.jetpack-admin.php
474 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
4 use Automattic\Jetpack\Partner_Coupon as Jetpack_Partner_Coupon;
5 use Automattic\Jetpack\Status;
6
7 // Build the Jetpack admin menu as a whole
8 class Jetpack_Admin {
9
10 /**
11 * @var Jetpack_Admin
12 **/
13 private static $instance = null;
14
15 static function init() {
16 if ( isset( $_GET['page'] ) && $_GET['page'] === 'jetpack' ) {
17 add_filter( 'nocache_headers', array( 'Jetpack_Admin', 'add_no_store_header' ), 100 );
18 }
19
20 if ( is_null( self::$instance ) ) {
21 self::$instance = new Jetpack_Admin();
22 }
23 return self::$instance;
24 }
25
26 static function add_no_store_header( $headers ) {
27 $headers['Cache-Control'] .= ', no-store';
28 return $headers;
29 }
30
31 private function __construct() {
32 jetpack_require_lib( 'admin-pages/class.jetpack-react-page' );
33 $this->jetpack_react = new Jetpack_React_Page();
34
35 jetpack_require_lib( 'admin-pages/class.jetpack-settings-page' );
36 $this->fallback_page = new Jetpack_Settings_Page();
37
38 jetpack_require_lib( 'admin-pages/class-jetpack-about-page' );
39 $this->jetpack_about = new Jetpack_About_Page();
40
41 jetpack_require_lib( 'admin-pages/class-jetpack-search-dashboard-page' );
42 $this->jetpack_search = new Jetpack_Search_Dashboard_Page();
43
44 add_action( 'admin_init', array( $this->jetpack_react, 'react_redirects' ), 0 );
45 add_action( 'admin_menu', array( $this->jetpack_react, 'add_actions' ), 998 );
46 add_action( 'admin_menu', array( $this->jetpack_search, 'add_actions' ), 999 );
47 add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_dashboard_sub_nav_item' ) );
48 add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_settings_sub_nav_item' ) );
49 add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_debugger' ) );
50 add_action( 'jetpack_admin_menu', array( $this->fallback_page, 'add_actions' ) );
51 add_action( 'jetpack_admin_menu', array( $this->jetpack_about, 'add_actions' ) );
52
53 // Add redirect to current page for activation/deactivation of modules
54 add_action( 'jetpack_pre_activate_module', array( $this, 'fix_redirect' ), 10, 2 );
55 add_action( 'jetpack_pre_deactivate_module', array( $this, 'fix_redirect' ) );
56
57 // Add module bulk actions handler
58 add_action( 'jetpack_unrecognized_action', array( $this, 'handle_unrecognized_action' ) );
59
60 if ( class_exists( 'Akismet_Admin' ) ) {
61 // If the site has Jetpack Anti-Spam, change the Akismet menu label accordingly.
62 $site_products = Jetpack_Plan::get_products();
63 $anti_spam_products = array( 'jetpack_anti_spam_monthly', 'jetpack_anti_spam' );
64 if ( ! empty( array_intersect( $anti_spam_products, array_column( $site_products, 'product_slug' ) ) ) ) {
65 // Prevent Akismet from adding a menu item.
66 add_action(
67 'admin_menu',
68 function () {
69 remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
70 },
71 4
72 );
73
74 // Add an Anti-spam menu item for Jetpack.
75 add_action(
76 'jetpack_admin_menu',
77 function () {
78 add_submenu_page( 'jetpack', __( 'Anti-Spam', 'jetpack' ), __( 'Anti-Spam', 'jetpack' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
79 }
80 );
81 add_action( 'admin_enqueue_scripts', array( $this, 'akismet_logo_replacement_styles' ) );
82 }
83 }
84
85 add_filter( 'jetpack_display_jitms_on_screen', array( $this, 'should_display_jitms_on_screen' ), 10, 2 );
86
87 // Register Jetpack partner coupon hooks.
88 Jetpack_Partner_Coupon::register_coupon_admin_hooks( 'jetpack', Jetpack::admin_url() );
89 }
90
91 /**
92 * Generate styles to replace Akismet logo for the Jetpack logo. It's a workaround until we create a proper settings page for
93 * Jetpack Anti-Spam. Without this, we would have to change the logo from Akismet codebase and we want to avoid that.
94 */
95 public function akismet_logo_replacement_styles() {
96 $logo = new Jetpack_Logo();
97 $logo_base64 = base64_encode( $logo->get_jp_emblem_larger() );
98 $logo_base64_url = "data:image/svg+xml;base64,{$logo_base64}";
99 $style = ".akismet-masthead__logo-container { background: url({$logo_base64_url}) no-repeat .25rem; height: 1.8125rem; } .akismet-masthead__logo { display: none; }";
100 wp_add_inline_style( 'admin-bar', $style );
101 }
102
103 static function sort_requires_connection_last( $module1, $module2 ) {
104 if ( $module1['requires_connection'] == $module2['requires_connection'] ) {
105 return 0;
106 } elseif ( $module1['requires_connection'] ) {
107 return 1;
108 } elseif ( $module2['requires_connection'] ) {
109 return -1;
110 }
111
112 return 0;
113 }
114
115 // Produce JS understandable objects of modules containing information for
116 // presentation like description, name, configuration url, etc.
117 function get_modules() {
118 include_once JETPACK__PLUGIN_DIR . 'modules/module-info.php';
119 $available_modules = Jetpack::get_available_modules();
120 $active_modules = Jetpack::get_active_modules();
121 $modules = array();
122 $jetpack_active = Jetpack::is_connection_ready() || ( new Status() )->is_offline_mode();
123 $overrides = Jetpack_Modules_Overrides::instance();
124 foreach ( $available_modules as $module ) {
125 if ( $module_array = Jetpack::get_module( $module ) ) {
126 /**
127 * Filters each module's short description.
128 *
129 * @since 3.0.0
130 *
131 * @param string $module_array['description'] Module description.
132 * @param string $module Module slug.
133 */
134 $short_desc = apply_filters( 'jetpack_short_module_description', $module_array['description'], $module );
135 // Fix: correct multibyte strings truncate with checking for mbstring extension
136 $short_desc_trunc = ( function_exists( 'mb_strlen' ) )
137 ? ( ( mb_strlen( $short_desc ) > 143 )
138 ? mb_substr( $short_desc, 0, 140 ) . '...'
139 : $short_desc )
140 : ( ( strlen( $short_desc ) > 143 )
141 ? substr( $short_desc, 0, 140 ) . '...'
142 : $short_desc );
143
144 $module_array['module'] = $module;
145
146 $is_available = self::is_module_available( $module_array );
147
148 $module_array['activated'] = ( $jetpack_active ? in_array( $module, $active_modules, true ) : false );
149 $module_array['deactivate_nonce'] = wp_create_nonce( 'jetpack_deactivate-' . $module );
150 $module_array['activate_nonce'] = wp_create_nonce( 'jetpack_activate-' . $module );
151 $module_array['available'] = $is_available;
152 $module_array['unavailable_reason'] = $is_available ? false : self::get_module_unavailable_reason( $module_array );
153 $module_array['short_description'] = $short_desc_trunc;
154 $module_array['configure_url'] = Jetpack::module_configuration_url( $module );
155 $module_array['override'] = $overrides->get_module_override( $module );
156
157 ob_start();
158 /**
159 * Allow the display of a "Learn More" button.
160 * The dynamic part of the action, $module, is the module slug.
161 *
162 * @since 3.0.0
163 */
164 do_action( 'jetpack_learn_more_button_' . $module );
165 $module_array['learn_more_button'] = ob_get_clean();
166
167 ob_start();
168 /**
169 * Allow the display of information text when Jetpack is connected to WordPress.com.
170 * The dynamic part of the action, $module, is the module slug.
171 *
172 * @since 3.0.0
173 */
174 do_action( 'jetpack_module_more_info_' . $module );
175
176 /**
177 * Filter the long description of a module.
178 *
179 * @since 3.5.0
180 *
181 * @param string ob_get_clean() The module long description.
182 * @param string $module The module name.
183 */
184 $module_array['long_description'] = apply_filters( 'jetpack_long_module_description', ob_get_clean(), $module );
185
186 ob_start();
187 /**
188 * Filter the search terms for a module
189 *
190 * Search terms are typically added to the module headers, under "Additional Search Queries".
191 *
192 * Use syntax:
193 * function jetpack_$module_search_terms( $terms ) {
194 * $terms = _x( 'term 1, term 2', 'search terms', 'jetpack' );
195 * return $terms;
196 * }
197 * add_filter( 'jetpack_search_terms_$module', 'jetpack_$module_search_terms' );
198 *
199 * @since 3.5.0
200 *
201 * @param string The search terms (comma separated).
202 */
203 echo apply_filters( 'jetpack_search_terms_' . $module, $module_array['additional_search_queries'] );
204 $module_array['search_terms'] = ob_get_clean();
205
206 $module_array['configurable'] = false;
207 if (
208 current_user_can( 'manage_options' ) &&
209 /**
210 * Allow the display of a configuration link in the Jetpack Settings screen.
211 *
212 * @since 3.0.0
213 *
214 * @param string $module Module name.
215 * @param bool false Should the Configure module link be displayed? Default to false.
216 */
217 apply_filters( 'jetpack_module_configurable_' . $module, false )
218 ) {
219 $module_array['configurable'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $module_array['configure_url'] ), __( 'Configure', 'jetpack' ) );
220 }
221
222 $modules[ $module ] = $module_array;
223 }
224 }
225
226 uasort( $modules, array( 'Jetpack', 'sort_modules' ) );
227
228 if ( ! Jetpack::is_connection_ready() ) {
229 uasort( $modules, array( __CLASS__, 'sort_requires_connection_last' ) );
230 }
231
232 return $modules;
233 }
234
235 static function is_module_available( $module ) {
236 if ( ! is_array( $module ) || empty( $module ) ) {
237 return false;
238 }
239
240 /**
241 * We never want to show VaultPress as activatable through Jetpack.
242 */
243 if ( 'vaultpress' === $module['module'] ) {
244 return false;
245 }
246
247 /*
248 * WooCommerce Analytics should only be available
249 * when running WooCommerce 3+
250 */
251 if (
252 'woocommerce-analytics' === $module['module']
253 && (
254 ! class_exists( 'WooCommerce' )
255 || version_compare( WC_VERSION, '3.0', '<' )
256 )
257 ) {
258 return false;
259 }
260
261 /*
262 * In Offline mode, modules that require a site or user
263 * level connection should be unavailable.
264 */
265 if ( ( new Status() )->is_offline_mode() ) {
266 return ! ( $module['requires_connection'] || $module['requires_user_connection'] );
267 }
268
269 /*
270 * Jetpack not connected.
271 */
272 if ( ! Jetpack::is_connection_ready() ) {
273 return false;
274 }
275
276 /*
277 * Jetpack connected at a site level only. Make sure to make
278 * modules that require a user connection unavailable.
279 */
280 if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
281 return false;
282 }
283
284 return Jetpack_Plan::supports( $module['module'] );
285
286 }
287
288 /**
289 * Returns why a module is unavailable.
290 *
291 * @param array $module The module.
292 * @return string|false A string stating why the module is not available or false if the module is available.
293 */
294 public static function get_module_unavailable_reason( $module ) {
295 if ( ! is_array( $module ) || empty( $module ) ) {
296 return false;
297 }
298
299 if ( self::is_module_available( $module ) ) {
300 return false;
301 }
302
303 /**
304 * We never want to show VaultPress as activatable through Jetpack so return an empty string.
305 */
306 if ( 'vaultpress' === $module['module'] ) {
307 return '';
308 }
309
310 /*
311 * WooCommerce Analytics should only be available
312 * when running WooCommerce 3+
313 */
314 if (
315 'woocommerce-analytics' === $module['module']
316 && (
317 ! class_exists( 'WooCommerce' )
318 || version_compare( WC_VERSION, '3.0', '<' )
319 )
320 ) {
321 return __( 'Requires WooCommerce 3+ plugin', 'jetpack' );
322 }
323
324 /*
325 * In Offline mode, modules that require a site or user
326 * level connection should be unavailable.
327 */
328 if ( ( new Status() )->is_offline_mode() ) {
329 if ( $module['requires_connection'] || $module['requires_user_connection'] ) {
330 return __( 'Offline mode', 'jetpack' );
331 }
332 }
333
334 /*
335 * Jetpack not connected.
336 */
337 if ( ! Jetpack::is_connection_ready() ) {
338 return __( 'Jetpack is not connected', 'jetpack' );
339 }
340
341 /*
342 * Jetpack connected at a site level only and module requires a user connection.
343 */
344 if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
345 return __( 'Requires a connected WordPress.com account', 'jetpack' );
346 }
347
348 /*
349 * Plan restrictions.
350 */
351 if ( ! Jetpack_Plan::supports( $module['module'] ) ) {
352 return __( 'Not supported by current plan', 'jetpack' );
353 }
354
355 return '';
356 }
357
358 function handle_unrecognized_action( $action ) {
359 switch ( $action ) {
360 case 'bulk-activate':
361 if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
362 break;
363 }
364
365 $modules = (array) $_GET['modules'];
366 $modules = array_map( 'sanitize_key', $modules );
367 check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
368 foreach ( $modules as $module ) {
369 Jetpack::log( 'activate', $module );
370 Jetpack::activate_module( $module, false );
371 }
372 // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
373 wp_safe_redirect( wp_get_referer() );
374 exit;
375 case 'bulk-deactivate':
376 if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
377 break;
378 }
379
380 $modules = (array) $_GET['modules'];
381 $modules = array_map( 'sanitize_key', $modules );
382 check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
383 foreach ( $modules as $module ) {
384 Jetpack::log( 'deactivate', $module );
385 Jetpack::deactivate_module( $module );
386 Jetpack::state( 'message', 'module_deactivated' );
387 }
388 Jetpack::state( 'module', $modules );
389 wp_safe_redirect( wp_get_referer() );
390 exit;
391 default:
392 return;
393 }
394 }
395
396 function fix_redirect( $module, $redirect = true ) {
397 if ( ! $redirect ) {
398 return;
399 }
400 if ( wp_get_referer() ) {
401 add_filter( 'wp_redirect', 'wp_get_referer' );
402 }
403 }
404
405 function admin_menu_debugger() {
406 jetpack_require_lib( 'debugger' );
407 Jetpack_Debugger::disconnect_and_redirect();
408 $debugger_hook = add_submenu_page(
409 null,
410 __( 'Debugging Center', 'jetpack' ),
411 '',
412 'manage_options',
413 'jetpack-debugger',
414 array( $this, 'wrap_debugger_page' )
415 );
416 add_action( "admin_head-$debugger_hook", array( 'Jetpack_Debugger', 'jetpack_debug_admin_head' ) );
417 }
418
419 function wrap_debugger_page() {
420 nocache_headers();
421 if ( ! current_user_can( 'manage_options' ) ) {
422 die( '-1' );
423 }
424 Jetpack_Admin_Page::wrap_ui( array( $this, 'debugger_page' ) );
425 }
426
427 function debugger_page() {
428 jetpack_require_lib( 'debugger' );
429 Jetpack_Debugger::jetpack_debug_display_handler();
430 }
431
432 /**
433 * Determines if JITMs should display on a particular screen.
434 *
435 * @param bool $value The default value of the filter.
436 * @param string $screen_id The ID of the screen being tested for JITM display.
437 *
438 * @return bool True if JITMs should display, false otherwise.
439 */
440 public function should_display_jitms_on_screen( $value, $screen_id ) {
441 // Disable all JITMs on these pages.
442 if (
443 in_array(
444 $screen_id,
445 array(
446 'jetpack_page_akismet-key-config',
447 'admin_page_jetpack_modules',
448 ),
449 true
450 ) ) {
451 return false;
452 }
453
454 // Disable all JITMs on pages where the recommendations banner is displaying.
455 if (
456 in_array(
457 $screen_id,
458 array(
459 'dashboard',
460 'plugins',
461 'jetpack_page_stats',
462 ),
463 true
464 )
465 && \Jetpack_Recommendations_Banner::can_be_displayed()
466 ) {
467 return false;
468 }
469
470 return $value;
471 }
472 }
473 Jetpack_Admin::init();
474