PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3 16.3-beta 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 All 508 releases
← All changes | _inc/lib/admin-pages/class.jetpack-react-page.php +174 -185 16.3-beta → 16.3-a.1 View file →
@@ -1,18 +1,21 @@
1 1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 2
3 +use Automattic\Jetpack\Admin_UI\Admin_Menu;
3 4 use Automattic\Jetpack\Assets\Logo;
4 -use Automattic\Jetpack\Redirect;
5 +use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
6 +use Automattic\Jetpack\Connection\Manager as Connection_Manager;
5 7 use Automattic\Jetpack\Status;
6 8
7 9 require_once __DIR__ . '/class.jetpack-admin-page.php';
10 +require_once __DIR__ . '/class-jetpack-redux-state-helper.php';
8 11
9 12 /**
10 - * Registers the Jetpack menu parent, whose page only redirects.
13 + * Builds the landing page and its menu.
11 14 */
12 15 class Jetpack_React_Page extends Jetpack_Admin_Page {
13 16 /**
14 - * Register the menu parent before the site connects too.
17 + * Show the landing page only when Jetpack is connected.
15 18 *
16 19 * @var bool
17 20 */
18 21 protected $dont_show_if_not_active = false;
@@ -17,51 +20,15 @@
17 20 */
18 21 protected $dont_show_if_not_active = false;
19 22
20 23 /**
21 - * Legacy hashes the Settings page renders; they forward there with their hash.
24 + * Used for fallback when REST API is disabled.
22 25 *
23 - * Mirrors `settingsRoutes` in `_inc/client/main.jsx`, plus the connection screens.
24 - *
25 - * @since 16.3
26 - * @var string[]
26 + * @var bool
27 27 */
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 - );
28 + protected $is_redirecting = false;
43 29
44 30 /**
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 - /**
64 31 * Add the main admin Jetpack menu.
65 32 *
66 33 * @return string|false Return value from WordPress's `add_menu_page()`.
67 34 */
@@ -94,11 +61,17 @@
94 61 }
95 62 return; // No need to handle the fallback redirection if we are not on the Jetpack page.
96 63 }
97 64
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 );
65 + // Adding a redirect meta tag if the REST API is disabled.
66 + if ( ! $this->is_rest_api_enabled() ) {
67 + $this->is_redirecting = true;
68 + add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
69 + }
100 70
71 + // Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled.
72 + add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
73 +
101 74 // If this is the first time the user is viewing the admin, don't show JITMs.
102 75 // This filter is added just in time because this function is called on admin_menu
103 76 // and JITMs are initialized on admin_init.
104 77 if ( Jetpack::is_connection_ready() && ! Jetpack_Options::get_option( 'first_admin_view', false ) ) {
@@ -127,186 +100,146 @@
127 100 }
128 101 }
129 102
130 103 /**
131 - * Where links into page=jetpack land.
104 + * Determine whether a user can access the Jetpack Settings page.
132 105 *
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.
106 + * Rules are:
107 + * - user is allowed to see the Jetpack Admin
108 + * - site is connected or in offline mode
109 + * - non-admins only need access to the settings when there are modules they can manage.
136 110 *
137 - * @return array{settings: string, forward: string[], routes: array<string, string>, fallback: string}
111 + * @return bool $can_access_settings Can the user access settings.
138 112 */
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,
148 - );
113 + private function can_access_settings() {
114 + $connection = new Connection_Manager( 'jetpack' );
115 + $status = new Status();
116 +
117 + // User must have the necessary permissions to see the Jetpack settings pages.
118 + if ( ! current_user_can( 'edit_posts' ) ) {
119 + return false;
149 120 }
150 121
151 - $table = array(
152 - 'settings' => $settings_url,
153 - 'forward' => self::SETTINGS_ROUTES,
154 - 'routes' => array(),
155 - 'fallback' => $settings_url,
156 - );
122 + // In offline mode, allow access to admins.
123 + if ( $status->is_offline_mode() && current_user_can( 'manage_options' ) ) {
124 + return true;
125 + }
157 126
158 - if ( ! self::should_redirect_legacy_routes() ) {
159 - return $table;
127 + // If not in offline mode but site is not connected, bail.
128 + if ( ! Jetpack::is_connection_ready() ) {
129 + return false;
160 130 }
161 131
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 - );
132 + /*
133 + * Additional checks for non-admins.
134 + */
135 + if ( ! current_user_can( 'manage_options' ) ) {
136 + // If the site isn't connected at all, bail.
137 + if ( ! $connection->has_connected_owner() ) {
138 + return false;
139 + }
168 140
169 - if ( self::can_use_my_jetpack() ) {
170 - $my_jetpack = admin_url( 'admin.php?page=my-jetpack' );
171 -
172 - foreach ( array( 'akismet', 'backup', 'scan', 'search', 'security', 'videopress' ) as $product ) {
173 - $table['routes'][ '/product/' . $product ] = $my_jetpack . '#/add-' . $product;
141 + /*
142 + * If they haven't connected their own account yet,
143 + * they have no use for the settings page.
144 + * They will not be able to manage any settings.
145 + */
146 + if ( ! $connection->is_user_connected() ) {
147 + return false;
174 148 }
175 149
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';
150 + /*
151 + * Non-admins only have access to settings
152 + * for the following modules:
153 + * - Publicize
154 + * - Post By Email
155 + * If those modules are not available, bail.
156 + */
157 + if (
158 + ! Jetpack::is_module_active( 'post-by-email' )
159 + && (
160 + ! Jetpack::is_module_active( 'publicize' ) ||
161 + ! current_user_can( 'publish_posts' )
162 + )
163 + ) {
164 + return false;
180 165 }
181 -
182 - $table['fallback'] = $my_jetpack;
183 166 }
184 167
185 - return $table;
168 + // fallback.
169 + return true;
186 170 }
187 171
188 172 /**
189 - * Whether this request may leave Settings.
173 + * Jetpack Settings sub-link.
190 174 *
191 - * @return bool
192 - */
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.
175 + * Shares the bottom tier with Beta Tester so it lands below the alphabetical run
176 + * rather than inside it; the two sort by title within the tier. The upsell still
177 + * renders underneath — Admin_Menu appends that one after sorting, so it never
178 + * competes on position.
219 179 *
220 - * @since 16.3
221 - *
222 - * @return never
180 + * @since 4.3.0
181 + * @since 9.7.0 If Connection does not have an owner, restrict it to admins
223 182 */
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();
183 + public function jetpack_add_settings_sub_nav_item() {
184 + if ( $this->can_access_settings() ) {
185 + Admin_Menu::add_menu(
186 + __( 'Settings', 'jetpack' ),
187 + __( 'Settings', 'jetpack' ),
188 + 'jetpack_admin_page',
189 + Jetpack::admin_url( array( 'page' => 'jetpack#/settings' ) ),
190 + null,
191 + Admin_Menu::POSITION_LAST,
192 + array( 'key' => 'jetpack-settings' )
193 + );
229 194 }
230 -
231 - $this->print_redirect_document();
232 - exit( 0 );
233 195 }
234 196
235 197 /**
236 - * Print a bare document that only redirects.
198 + * Fallback redirect meta tag if the REST API is disabled.
237 199 *
238 - * @since 16.3
200 + * @return void
239 201 */
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
202 + public function add_fallback_head_meta() {
203 + echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">';
259 204 }
260 205
261 206 /**
262 - * Whether My Jetpack can take over for the current user.
207 + * Fallback meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled.
263 208 *
264 - * @return bool
209 + * @return void
265 210 */
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();
211 + public function add_noscript_head_meta() {
212 + echo '<noscript>';
213 + $this->add_fallback_head_meta();
214 + echo '</noscript>';
271 215 }
272 216
273 217 /**
274 - * The My Jetpack coupon screen, while it should replace this page.
218 + * Add action to render page specific HTML.
275 219 *
276 - * An older My Jetpack bounces showCouponRedemption back here, so only forward to one that renders it.
277 - *
278 - * @return string|null
220 + * @return void
279 221 */
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 - }
222 + public function page_render() {
223 + /** This action is already documented in class.jetpack-admin-page.php */
224 + do_action( 'jetpack_notices' );
288 225
289 - return admin_url( 'admin.php?page=my-jetpack&showCouponRedemption=1' );
290 - }
226 + // Fetch static.html.
227 + $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.
291 228
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' );
229 + if ( false === $static_html ) {
230 +
231 + // If we still have nothing, display an error.
232 + echo '<p>';
233 + esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
234 + echo '<code>pnpm run distclean && pnpm jetpack build plugins/jetpack</code>';
235 + echo '</p>';
236 + } else {
237 + // We got the static.html so let's display it.
238 + echo $static_html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
239 + }
300 240 }
301 -
302 241 /**
303 - * Nothing renders here: render_redirect_document() exits on load.
304 - *
305 - * @return void
306 - */
307 - public function page_render() {}
308 - /**
309 242 * Allow robust deep links to React.
310 243 *
311 244 * The Jetpack dashboard requires fragments/hash values to make
312 245 * a deep link to it but passing fragments as part of a return URL
@@ -322,9 +255,9 @@
322 255 return;
323 256 }
324 257
325 258 $allowed_paths = array(
326 - 'product-purchased' => admin_url( 'admin.php?page=jetpack' ),
259 + 'product-purchased' => admin_url( '/admin.php?page=jetpack#/recommendations/product-purchased' ),
327 260 );
328 261
329 262 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
330 263 $target = sanitize_text_field( wp_unslash( $_GET['jp-react-redirect'] ) );
@@ -334,8 +267,64 @@
334 267 }
335 268 }
336 269
337 270 /**
338 - * Nothing loads here: render_redirect_document() exits on load.
271 + * Load styles for static page.
339 272 */
340 - public function page_admin_scripts() {}
273 + public function additional_styles() {
274 + Jetpack_Admin_Page::load_wrapper_styles();
275 + }
276 +
277 + /**
278 + * Load admin page scripts.
279 + */
280 + public function page_admin_scripts() {
281 + if ( $this->is_redirecting ) {
282 + return; // No need for scripts on a fallback page.
283 + }
284 +
285 + $status = new Status();
286 + $is_offline_mode = $status->is_offline_mode();
287 + $site_suffix = $status->get_site_suffix();
288 + $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php';
289 + $script_dependencies = array( 'jquery', 'wp-polyfill' );
290 + $version = JETPACK__VERSION;
291 + if ( file_exists( $script_deps_path ) ) {
292 + $asset_manifest = include $script_deps_path;
293 + $script_dependencies = $asset_manifest['dependencies'];
294 + $version = $asset_manifest['version'];
295 + }
296 +
297 + $blog_id_prop = '';
298 + if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
299 + $blog_id = Connection_Manager::get_site_id( true );
300 + if ( $blog_id ) {
301 + $blog_id_prop = ', currentBlogID: "' . (int) $blog_id . '"';
302 + }
303 + }
304 +
305 + wp_enqueue_script(
306 + 'react-plugin',
307 + plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ),
308 + $script_dependencies,
309 + $version,
310 + true
311 + );
312 +
313 + if ( ! $is_offline_mode && Jetpack::is_connection_ready() ) {
314 + // Required for Analytics.
315 + wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
316 + }
317 +
318 + wp_set_script_translations( 'react-plugin', 'jetpack' );
319 +
320 + // Add objects to be passed to the initial state of the app.
321 + // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
322 + wp_add_inline_script( 'react-plugin', 'var Initial_State=' . wp_json_encode( Jetpack_Redux_State_Helper::get_initial_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' );
323 +
324 + // This will set the default URL of the jp_redirects lib.
325 + wp_add_inline_script( 'react-plugin', 'var jetpack_redirects = { currentSiteRawUrl: "' . $site_suffix . '"' . $blog_id_prop . ' };', 'before' );
326 +
327 + // Adds Connection package initial state.
328 + Connection_Initial_State::render_script( 'react-plugin' );
329 + }
341 330 }