PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 506 releases
← All changes | _inc/lib/admin-pages/class.jetpack-react-page.php +198 -191 13.8.3 → 16.3-a.7 View file →
@@ -1,21 +1,18 @@
1 1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 2
3 -use Automattic\Jetpack\Admin_UI\Admin_Menu;
4 3 use Automattic\Jetpack\Assets\Logo;
5 -use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
6 -use Automattic\Jetpack\Connection\Manager as Connection_Manager;
4 +use Automattic\Jetpack\Redirect;
7 5 use Automattic\Jetpack\Status;
8 6
9 7 require_once __DIR__ . '/class.jetpack-admin-page.php';
10 -require_once __DIR__ . '/class-jetpack-redux-state-helper.php';
11 8
12 9 /**
13 - * Builds the landing page and its menu.
10 + * Registers the Jetpack menu parent, whose page only redirects.
14 11 */
15 12 class Jetpack_React_Page extends Jetpack_Admin_Page {
16 13 /**
17 - * Show the landing page only when Jetpack is connected.
14 + * Register the menu parent before the site connects too.
18 15 *
19 16 * @var bool
20 17 */
21 18 protected $dont_show_if_not_active = false;
@@ -20,21 +17,59 @@
20 17 */
21 18 protected $dont_show_if_not_active = false;
22 19
23 20 /**
24 - * Used for fallback when REST API is disabled.
21 + * Legacy hashes the Settings page renders; they forward there with their hash.
25 22 *
26 - * @var bool
23 + * Mirrors `settingsRoutes` in `_inc/client/main.jsx`, plus the connection screens.
24 + *
25 + * @since 16.3
26 + * @var string[]
27 27 */
28 - protected $is_redirecting = false;
28 + const SETTINGS_ROUTES = array(
29 + '/settings',
30 + '/security',
31 + '/performance',
32 + '/writing',
33 + '/sharing',
34 + '/discussion',
35 + '/earn',
36 + '/reader',
37 + '/traffic',
38 + '/privacy',
39 + '/setup',
40 + '/connect-user',
41 + '/connect-user-setup',
42 + );
29 43
30 44 /**
45 + * Forwards Settings hashes with their hash, maps known routes, and falls back for the rest.
46 + *
47 + * @var string
48 + */
49 + const LEGACY_ROUTE_REDIRECT_SCRIPT = <<<'JS'
50 +function ( settings, forward, routes, fallback ) {
51 + var hash = window.location.hash;
52 + var path = hash.replace( /^#\/?/, '/' ).split( '?' )[ 0 ] || '/';
53 + var target = fallback;
54 + if ( forward.indexOf( path ) !== -1 ) {
55 + target = settings + hash;
56 + } else if ( Object.prototype.hasOwnProperty.call( routes, path ) ) {
57 + target = routes[ path ];
58 + }
59 + window.location.replace( target );
60 +}
61 +JS;
62 +
63 + /**
31 64 * Add the main admin Jetpack menu.
32 65 *
33 66 * @return string|false Return value from WordPress's `add_menu_page()`.
34 67 */
35 68 public function get_page_hook() {
36 - $icon = ( new Logo() )->get_base64_logo();
69 + $logo = new Logo();
70 + // Keep this fallback in sync with Jetpack_Network::add_network_admin_menu().
71 + $icon = method_exists( $logo, 'get_base64_admin_menu_logo' ) ? $logo->get_base64_admin_menu_logo() : $logo->get_base64_logo();
37 72 return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), $icon, 3 );
38 73 }
39 74
40 75 /**
@@ -54,22 +89,16 @@
54 89 if ( 'jetpack' !== $page ) {
55 90 if ( strpos( $page, 'jetpack/' ) === 0 ) {
56 91 $section = substr( $page, 8 );
57 92 wp_safe_redirect( admin_url( 'admin.php?page=jetpack#/' . $section ) );
58 - exit;
93 + exit( 0 );
59 94 }
60 95 return; // No need to handle the fallback redirection if we are not on the Jetpack page.
61 96 }
62 97
63 - // Adding a redirect meta tag if the REST API is disabled.
64 - if ( ! $this->is_rest_api_enabled() ) {
65 - $this->is_redirecting = true;
66 - add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
67 - }
98 + // After the action handlers and the connection controller, which exit when they act.
99 + add_action( "load-$hook", array( $this, 'render_redirect_document' ), PHP_INT_MAX );
68 100
69 - // Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled.
70 - add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
71 -
72 101 // If this is the first time the user is viewing the admin, don't show JITMs.
73 102 // This filter is added just in time because this function is called on admin_menu
74 103 // and JITMs are initialized on admin_init.
75 104 if ( Jetpack::is_connection_ready() && ! Jetpack_Options::get_option( 'first_admin_view', false ) ) {
@@ -78,172 +107,206 @@
78 107 }
79 108 }
80 109
81 110 /**
82 - * Remove the main Jetpack submenu if a site is in offline mode or connected.
111 + * Remove the main Jetpack submenu if a site is in offline mode or connected
112 + * or if My Jetpack is available.
83 113 * At that point, admins can access the Jetpack Dashboard instead.
84 114 *
85 115 * @since 13.8
86 116 */
87 117 public function remove_jetpack_menu() {
88 - if (
89 - ( new Status() )->is_offline_mode()
90 - || Jetpack::is_connection_ready()
91 - ) {
118 + $is_offline_mode = ( new Status() )->is_offline_mode();
119 + $has_my_jetpack = (
120 + class_exists( 'Automattic\Jetpack\My_Jetpack\Initializer' ) &&
121 + method_exists( 'Automattic\Jetpack\My_Jetpack\Initializer', 'should_initialize' ) &&
122 + \Automattic\Jetpack\My_Jetpack\Initializer::should_initialize()
123 + );
124 +
125 + if ( $is_offline_mode || $has_my_jetpack || Jetpack::is_connection_ready() ) {
92 126 remove_submenu_page( 'jetpack', 'jetpack' );
93 127 }
94 128 }
95 129
96 130 /**
97 - * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active.
131 + * Where links into page=jetpack land.
98 132 *
99 - * Works in Dev Mode or when user is connected.
133 + * Settings hashes keep their hash on the Settings page. Admins go to My Jetpack
134 + * wherever it runs; everyone else, and a request with a pending error, lands on
135 + * Settings. While the partner coupon screen applies, every route goes there.
100 136 *
101 - * @since 4.3.0
137 + * @return array{settings: string, forward: string[], routes: array<string, string>, fallback: string}
102 138 */
103 - public function jetpack_add_dashboard_sub_nav_item() {
104 - if ( ( new Status() )->is_offline_mode() || Jetpack::is_connection_ready() ) {
105 - Admin_Menu::add_menu(
106 - __( 'Dashboard', 'jetpack' ),
107 - __( 'Dashboard', 'jetpack' ),
108 - 'jetpack_admin_page',
109 - Jetpack::admin_url( array( 'page' => 'jetpack#/dashboard' ) ),
110 - null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- See https://core.trac.wordpress.org/ticket/52539.
111 - 14
139 + public static function get_legacy_route_redirects() {
140 + $settings_url = admin_url( 'admin.php?page=jetpack-settings' );
141 + $coupon_screen = self::get_partner_coupon_redirect();
142 + if ( $coupon_screen ) {
143 + return array(
144 + 'settings' => $settings_url,
145 + 'forward' => array(),
146 + 'routes' => array(),
147 + 'fallback' => $coupon_screen,
112 148 );
113 149 }
114 - }
115 150
116 - /**
117 - * Determine whether a user can access the Jetpack Settings page.
118 - *
119 - * Rules are:
120 - * - user is allowed to see the Jetpack Admin
121 - * - site is connected or in offline mode
122 - * - non-admins only need access to the settings when there are modules they can manage.
123 - *
124 - * @return bool $can_access_settings Can the user access settings.
125 - */
126 - private function can_access_settings() {
127 - $connection = new Connection_Manager( 'jetpack' );
128 - $status = new Status();
151 + $table = array(
152 + 'settings' => $settings_url,
153 + 'forward' => self::SETTINGS_ROUTES,
154 + 'routes' => array(),
155 + 'fallback' => $settings_url,
156 + );
129 157
130 - // User must have the necessary permissions to see the Jetpack settings pages.
131 - if ( ! current_user_can( 'edit_posts' ) ) {
132 - return false;
158 + if ( ! self::should_redirect_legacy_routes() ) {
159 + return $table;
133 160 }
134 161
135 - // In offline mode, allow access to admins.
136 - if ( $status->is_offline_mode() && current_user_can( 'manage_options' ) ) {
137 - return true;
138 - }
162 + $pricing_url = Redirect::get_url( 'jetpack-plans' );
163 + $table['routes'] = array(
164 + '/plans' => $pricing_url,
165 + '/plans-prompt' => $pricing_url,
166 + '/newsletter' => admin_url( 'admin.php?page=jetpack-newsletter' ),
167 + );
139 168
140 - // If not in offline mode but site is not connected, bail.
141 - if ( ! Jetpack::is_connection_ready() ) {
142 - return false;
143 - }
169 + if ( self::can_use_my_jetpack() ) {
170 + $my_jetpack = admin_url( 'admin.php?page=my-jetpack' );
144 171
145 - /*
146 - * Additional checks for non-admins.
147 - */
148 - if ( ! current_user_can( 'manage_options' ) ) {
149 - // If the site isn't connected at all, bail.
150 - if ( ! $connection->has_connected_owner() ) {
151 - return false;
172 + foreach ( array( 'akismet', 'backup', 'scan', 'search', 'security', 'videopress' ) as $product ) {
173 + $table['routes'][ '/product/' . $product ] = $my_jetpack . '#/add-' . $product;
152 174 }
153 175
154 - /*
155 - * If they haven't connected their own account yet,
156 - * they have no use for the settings page.
157 - * They will not be able to manage any settings.
158 - */
159 - if ( ! $connection->is_user_connected() ) {
160 - return false;
176 + $table['routes']['/license/activation'] = $my_jetpack . '#/add-license';
177 +
178 + foreach ( array( '/reconnect', '/disconnect', '/woo-setup' ) as $route ) {
179 + $table['routes'][ $route ] = $my_jetpack . '#/connection';
161 180 }
162 181
163 - /*
164 - * Non-admins only have access to settings
165 - * for the following modules:
166 - * - Publicize
167 - * - Post By Email
168 - * If those modules are not available, bail.
169 - */
170 - if (
171 - ! Jetpack::is_module_active( 'post-by-email' )
172 - && ! Jetpack::is_module_active( 'publicize' )
173 - ) {
174 - return false;
175 - }
182 + $table['fallback'] = $my_jetpack;
176 183 }
177 184
178 - // fallback.
179 - return true;
185 + return $table;
180 186 }
181 187
182 188 /**
183 - * Jetpack Settings sub-link.
189 + * Whether this request may leave Settings.
184 190 *
185 - * @since 4.3.0
186 - * @since 9.7.0 If Connection does not have an owner, restrict it to admins
191 + * @return bool
187 192 */
188 - public function jetpack_add_settings_sub_nav_item() {
189 - if ( $this->can_access_settings() ) {
190 - Admin_Menu::add_menu(
191 - __( 'Settings', 'jetpack' ),
192 - __( 'Settings', 'jetpack' ),
193 - 'jetpack_admin_page',
194 - Jetpack::admin_url( array( 'page' => 'jetpack#/settings' ) ),
195 - null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- See https://core.trac.wordpress.org/ticket/52539.
196 - 13
197 - );
193 + public static function should_redirect_legacy_routes() {
194 + // A pending error only renders via the Settings app's state notices.
195 + return ! Jetpack::state( 'error' );
196 + }
197 +
198 + /**
199 + * Print the legacy route redirect; it runs in the browser because the server never sees the hash.
200 + */
201 + public function print_legacy_route_redirect() {
202 + $table = self::get_legacy_route_redirects();
203 + $flags = JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP;
204 +
205 + wp_print_inline_script_tag(
206 + sprintf(
207 + '( %s )( %s, %s, %s, %s );',
208 + self::LEGACY_ROUTE_REDIRECT_SCRIPT,
209 + wp_json_encode( $table['settings'], $flags ),
210 + wp_json_encode( $table['forward'], $flags ),
211 + wp_json_encode( (object) $table['routes'], $flags ),
212 + wp_json_encode( $table['fallback'], $flags )
213 + )
214 + );
215 + }
216 +
217 + /**
218 + * Replace page=jetpack with the redirect document; nothing else renders here.
219 + *
220 + * @since 16.3
221 + *
222 + * @return never
223 + */
224 + public function render_redirect_document() {
225 + $table = self::get_legacy_route_redirects();
226 + if ( $table['settings'] === $table['fallback'] ) {
227 + // Settings renders this request's notices, so its state must survive the hop.
228 + Jetpack::restate();
198 229 }
230 +
231 + $this->print_redirect_document();
232 + exit( 0 );
199 233 }
200 234
201 235 /**
202 - * Fallback redirect meta tag if the REST API is disabled.
236 + * Print a bare document that only redirects.
203 237 *
204 - * @return void
238 + * @since 16.3
205 239 */
206 - public function add_fallback_head_meta() {
207 - echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">';
240 + public function print_redirect_document() {
241 + ?>
242 +<!DOCTYPE html>
243 +<html <?php language_attributes(); ?>>
244 +<head>
245 +<meta charset="<?php echo esc_attr( get_bloginfo( 'charset' ) ); ?>">
246 +<title>Jetpack</title><?php // "Jetpack" is a product name, do not translate. ?>
247 + <?php
248 + if ( $this->is_rest_api_enabled() ) {
249 + $this->print_legacy_route_redirect();
250 + $this->add_noscript_head_meta();
251 + } else {
252 + $this->add_fallback_head_meta();
253 + }
254 + ?>
255 +</head>
256 +<body></body>
257 +</html>
258 + <?php
208 259 }
209 260
210 261 /**
211 - * Fallback meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled.
262 + * Whether My Jetpack can take over for the current user.
212 263 *
213 - * @return void
264 + * @return bool
214 265 */
215 - public function add_noscript_head_meta() {
216 - echo '<noscript>';
217 - $this->add_fallback_head_meta();
218 - echo '</noscript>';
266 + private static function can_use_my_jetpack() {
267 + return current_user_can( 'manage_options' )
268 + && class_exists( 'Automattic\Jetpack\My_Jetpack\Initializer' )
269 + && method_exists( 'Automattic\Jetpack\My_Jetpack\Initializer', 'should_initialize' )
270 + && \Automattic\Jetpack\My_Jetpack\Initializer::should_initialize();
219 271 }
220 272
221 273 /**
222 - * Add action to render page specific HTML.
274 + * The My Jetpack coupon screen, while it should replace this page.
223 275 *
224 - * @return void
276 + * An older My Jetpack bounces showCouponRedemption back here, so only forward to one that renders it.
277 + *
278 + * @return string|null
225 279 */
226 - public function page_render() {
227 - /** This action is already documented in class.jetpack-admin-page.php */
228 - do_action( 'jetpack_notices' );
280 + private static function get_partner_coupon_redirect() {
281 + if (
282 + ! class_exists( 'Automattic\Jetpack\My_Jetpack\Initializer' )
283 + || ! method_exists( 'Automattic\Jetpack\My_Jetpack\Initializer', 'get_partner_coupon_screen' )
284 + || null === \Automattic\Jetpack\My_Jetpack\Initializer::get_partner_coupon_screen()
285 + ) {
286 + return null;
287 + }
229 288
230 - // Fetch static.html.
231 - $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' ); //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, Not fetching a remote file.
289 + return admin_url( 'admin.php?page=my-jetpack&showCouponRedemption=1' );
290 + }
232 291
233 - if ( false === $static_html ) {
292 + /**
293 + * Formerly added the Settings sub-link.
294 + *
295 + * @since 4.3.0
296 + * @deprecated 16.3 Jetpack_Settings_React_Page registers the Settings page.
297 + */
298 + public function jetpack_add_settings_sub_nav_item() {
299 + _deprecated_function( __METHOD__, 'jetpack-16.3' );
300 + }
234 301
235 - // If we still have nothing, display an error.
236 - echo '<p>';
237 - esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
238 - echo '<code>pnpm run distclean && pnpm jetpack build plugins/jetpack</code>';
239 - echo '</p>';
240 - } else {
241 - // We got the static.html so let's display it.
242 - echo $static_html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
243 - }
244 - }
245 302 /**
303 + * Nothing renders here: render_redirect_document() exits on load.
304 + *
305 + * @return void
306 + */
307 + public function page_render() {}
308 + /**
246 309 * Allow robust deep links to React.
247 310 *
248 311 * The Jetpack dashboard requires fragments/hash values to make
249 312 * a deep link to it but passing fragments as part of a return URL
@@ -259,9 +322,9 @@
259 322 return;
260 323 }
261 324
262 325 $allowed_paths = array(
263 - 'product-purchased' => admin_url( '/admin.php?page=jetpack#/recommendations/product-purchased' ),
326 + 'product-purchased' => admin_url( 'admin.php?page=jetpack' ),
264 327 );
265 328
266 329 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
267 330 $target = sanitize_text_field( wp_unslash( $_GET['jp-react-redirect'] ) );
@@ -266,69 +329,13 @@
266 329 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
267 330 $target = sanitize_text_field( wp_unslash( $_GET['jp-react-redirect'] ) );
268 331 if ( isset( $allowed_paths[ $target ] ) ) {
269 332 wp_safe_redirect( $allowed_paths[ $target ] );
270 - exit;
333 + exit( 0 );
271 334 }
272 335 }
273 336
274 337 /**
275 - * Load styles for static page.
338 + * Nothing loads here: render_redirect_document() exits on load.
276 339 */
277 - public function additional_styles() {
278 - Jetpack_Admin_Page::load_wrapper_styles();
279 - }
280 -
281 - /**
282 - * Load admin page scripts.
283 - */
284 - public function page_admin_scripts() {
285 - if ( $this->is_redirecting ) {
286 - return; // No need for scripts on a fallback page.
287 - }
288 -
289 - $status = new Status();
290 - $is_offline_mode = $status->is_offline_mode();
291 - $site_suffix = $status->get_site_suffix();
292 - $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php';
293 - $script_dependencies = array( 'jquery', 'wp-polyfill' );
294 - $version = JETPACK__VERSION;
295 - if ( file_exists( $script_deps_path ) ) {
296 - $asset_manifest = include $script_deps_path;
297 - $script_dependencies = $asset_manifest['dependencies'];
298 - $version = $asset_manifest['version'];
299 - }
300 -
301 - $blog_id_prop = '';
302 - if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
303 - $blog_id = Connection_Manager::get_site_id( true );
304 - if ( $blog_id ) {
305 - $blog_id_prop = ', currentBlogID: "' . (int) $blog_id . '"';
306 - }
307 - }
308 -
309 - wp_enqueue_script(
310 - 'react-plugin',
311 - plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ),
312 - $script_dependencies,
313 - $version,
314 - true
315 - );
316 -
317 - if ( ! $is_offline_mode && Jetpack::is_connection_ready() ) {
318 - // Required for Analytics.
319 - wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
320 - }
321 -
322 - wp_set_script_translations( 'react-plugin', 'jetpack' );
323 -
324 - // Add objects to be passed to the initial state of the app.
325 - // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
326 - wp_add_inline_script( 'react-plugin', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_initial_state() ) ) . '"));', 'before' );
327 -
328 - // This will set the default URL of the jp_redirects lib.
329 - wp_add_inline_script( 'react-plugin', 'var jetpack_redirects = { currentSiteRawUrl: "' . $site_suffix . '"' . $blog_id_prop . ' };', 'before' );
330 -
331 - // Adds Connection package initial state.
332 - Connection_Initial_State::render_script( 'react-plugin' );
333 - }
340 + public function page_admin_scripts() {}
334 341 }