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