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
jetpack / _inc / lib / admin-pages / class.jetpack-react-page.php

class.jetpack-react-page.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at _inc/lib/admin-pages/class.jetpack-react-page.php

342 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3 use Automattic\Jetpack\Assets\Logo;
4 use Automattic\Jetpack\Redirect;
5 use Automattic\Jetpack\Status;
6
7 require_once __DIR__ . '/class.jetpack-admin-page.php';
8
9 /**
10 * Registers the Jetpack menu parent, whose page only redirects.
11 */
12 class Jetpack_React_Page extends Jetpack_Admin_Page {
13 /**
14 * Register the menu parent before the site connects too.
15 *
16 * @var bool
17 */
18 protected $dont_show_if_not_active = false;
19
20 /**
21 * Legacy hashes the Settings page renders; they forward there with their hash.
22 *
23 * Mirrors `settingsRoutes` in `_inc/client/main.jsx`, plus the connection screens.
24 *
25 * @since 16.3
26 * @var string[]
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 );
43
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 /**
64 * Add the main admin Jetpack menu.
65 *
66 * @return string|false Return value from WordPress's `add_menu_page()`.
67 */
68 public function get_page_hook() {
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();
72 return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), $icon, 3 );
73 }
74
75 /**
76 * Add page action.
77 *
78 * @param string $hook Hook of current page.
79 * @return void
80 */
81 public function add_page_actions( $hook ) {
82 /** This action is documented in class.jetpack-admin.php */
83 do_action( 'jetpack_admin_menu', $hook );
84
85 if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
86 return;
87 }
88 $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
89 if ( 'jetpack' !== $page ) {
90 if ( strpos( $page, 'jetpack/' ) === 0 ) {
91 $section = substr( $page, 8 );
92 wp_safe_redirect( admin_url( 'admin.php?page=jetpack#/' . $section ) );
93 exit( 0 );
94 }
95 return; // No need to handle the fallback redirection if we are not on the Jetpack page.
96 }
97
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 );
100
101 // If this is the first time the user is viewing the admin, don't show JITMs.
102 // This filter is added just in time because this function is called on admin_menu
103 // and JITMs are initialized on admin_init.
104 if ( Jetpack::is_connection_ready() && ! Jetpack_Options::get_option( 'first_admin_view', false ) ) {
105 Jetpack_Options::update_option( 'first_admin_view', true );
106 add_filter( 'jetpack_just_in_time_msgs', '__return_false' );
107 }
108 }
109
110 /**
111 * Remove the main Jetpack submenu if a site is in offline mode or connected
112 * or if My Jetpack is available.
113 * At that point, admins can access the Jetpack Dashboard instead.
114 *
115 * @since 13.8
116 */
117 public function remove_jetpack_menu() {
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() ) {
126 remove_submenu_page( 'jetpack', 'jetpack' );
127 }
128 }
129
130 /**
131 * Where links into page=jetpack land.
132 *
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.
136 *
137 * @return array{settings: string, forward: string[], routes: array<string, string>, fallback: string}
138 */
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 );
149 }
150
151 $table = array(
152 'settings' => $settings_url,
153 'forward' => self::SETTINGS_ROUTES,
154 'routes' => array(),
155 'fallback' => $settings_url,
156 );
157
158 if ( ! self::should_redirect_legacy_routes() ) {
159 return $table;
160 }
161
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 );
168
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;
174 }
175
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';
180 }
181
182 $table['fallback'] = $my_jetpack;
183 }
184
185 return $table;
186 }
187
188 /**
189 * Whether this request may leave Settings.
190 *
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.
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();
229 }
230
231 $this->print_redirect_document();
232 exit( 0 );
233 }
234
235 /**
236 * Print a bare document that only redirects.
237 *
238 * @since 16.3
239 */
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
259 }
260
261 /**
262 * Whether My Jetpack can take over for the current user.
263 *
264 * @return bool
265 */
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();
271 }
272
273 /**
274 * The My Jetpack coupon screen, while it should replace this page.
275 *
276 * An older My Jetpack bounces showCouponRedemption back here, so only forward to one that renders it.
277 *
278 * @return string|null
279 */
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 }
288
289 return admin_url( 'admin.php?page=my-jetpack&showCouponRedemption=1' );
290 }
291
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 }
301
302 /**
303 * Nothing renders here: render_redirect_document() exits on load.
304 *
305 * @return void
306 */
307 public function page_render() {}
308 /**
309 * Allow robust deep links to React.
310 *
311 * The Jetpack dashboard requires fragments/hash values to make
312 * a deep link to it but passing fragments as part of a return URL
313 * will most often be discarded throughout the process.
314 * This logic aims to bridge this gap and reduce the chance of React
315 * specific links being broken while passing them along.
316 */
317 public function react_redirects() {
318 global $pagenow;
319
320 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
321 if ( 'admin.php' !== $pagenow || ! isset( $_GET['jp-react-redirect'] ) ) {
322 return;
323 }
324
325 $allowed_paths = array(
326 'product-purchased' => admin_url( 'admin.php?page=jetpack' ),
327 );
328
329 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
330 $target = sanitize_text_field( wp_unslash( $_GET['jp-react-redirect'] ) );
331 if ( isset( $allowed_paths[ $target ] ) ) {
332 wp_safe_redirect( $allowed_paths[ $target ] );
333 exit( 0 );
334 }
335 }
336
337 /**
338 * Nothing loads here: render_redirect_document() exits on load.
339 */
340 public function page_admin_scripts() {}
341 }
342