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-settings-react-page.php

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

366 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Jetpack Settings admin page.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Admin_UI\Admin_Menu;
9 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
10 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11 use Automattic\Jetpack\Feature_Flags\Feature_Flags;
12 use Automattic\Jetpack\Status;
13
14 require_once __DIR__ . '/class.jetpack-admin-page.php';
15 require_once __DIR__ . '/class-jetpack-redux-state-helper.php';
16 require_once __DIR__ . '/class-jetpack-wp-build-page.php';
17 require_once dirname( __DIR__ ) . '/class-jetpack-settings-feature-flags.php';
18
19 /**
20 * Renders the Settings app, whose connection screens also serve unconnected sites.
21 *
22 * @since 16.3
23 */
24 class Jetpack_Settings_React_Page extends Jetpack_Admin_Page {
25 /**
26 * Register the page before the site connects, for the connection screen.
27 *
28 * @var bool
29 */
30 protected $dont_show_if_not_active = false;
31
32 /**
33 * Whether the REST API is off and the page falls back to the modules list.
34 *
35 * @var bool
36 */
37 protected $is_redirecting = false;
38
39 /**
40 * The wp-build route's page id, which must not be the `jetpack-settings` menu slug.
41 *
42 * @var string
43 */
44 const WP_BUILD_PAGE_ID = 'jetpack-settings-dashboard';
45
46 /**
47 * Whether this request loaded the wp-build route.
48 *
49 * @var bool
50 */
51 private $is_wp_build_loaded = false;
52
53 /**
54 * Whether Settings renders through wp-build; off serves the webpack page at the same address.
55 *
56 * @since 16.3
57 *
58 * @return bool
59 */
60 public static function is_wp_build_enabled() {
61 return Feature_Flags::is_enabled( Jetpack_Settings_Feature_Flags::WP_BUILD );
62 }
63
64 /**
65 * Whether this request should load wp-build.
66 *
67 * An IDC-blocked page shows only the IDC banner, which the wp-build template would hide.
68 *
69 * @since 16.3
70 *
71 * @return bool
72 */
73 public function should_load_wp_build() {
74 if ( ! is_admin() || ! self::is_wp_build_enabled() ) {
75 return false;
76 }
77
78 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading the page slug only.
79 if ( ! isset( $_GET['page'] ) || 'jetpack-settings' !== sanitize_text_field( wp_unslash( $_GET['page'] ) ) ) {
80 return false;
81 }
82
83 return ! $this->block_page_rendering_for_idc();
84 }
85
86 /**
87 * Load wp-build before the admin menu is built, on the Settings request only.
88 *
89 * @since 16.3
90 *
91 * @return void
92 */
93 public function maybe_load_wp_build() {
94 if ( $this->should_load_wp_build() ) {
95 $this->is_wp_build_loaded = Jetpack_WP_Build_Page::load( self::WP_BUILD_PAGE_ID );
96 }
97 }
98
99 /**
100 * Whether this request renders through wp-build.
101 *
102 * @since 16.3
103 *
104 * @return bool
105 */
106 public function should_render_wp_build() {
107 return $this->is_wp_build_loaded && function_exists( 'jetpack_plugin_jetpack_settings_dashboard_wp_admin_render_page' );
108 }
109
110 /**
111 * The route bundle's classic script dependencies (e.g. `lodash`), which wp-build registers
112 * as a script module without them, leaving globals like `window.lodash` undefined.
113 *
114 * @since 16.3
115 *
116 * @return string[]
117 */
118 protected function get_wp_build_script_dependencies() {
119 $asset_path = JETPACK__PLUGIN_DIR . 'build/routes/settings/content.min.asset.php';
120 if ( ! file_exists( $asset_path ) ) {
121 return array();
122 }
123
124 $asset = include $asset_path;
125 return $asset['dependencies'] ?? array();
126 }
127
128 /**
129 * Register the page; only its sidebar entry keeps the Settings access gate.
130 *
131 * Shares the bottom tier with Beta Tester so it lands below the alphabetical run;
132 * the upsell still renders underneath because Admin_Menu appends it after sorting.
133 *
134 * @return string The page hook.
135 */
136 public function get_page_hook() {
137 if ( ! $this->can_access_settings() ) {
138 add_filter( 'jetpack_admin_menu_visibility', array( __CLASS__, 'hide_menu_item' ) );
139 }
140
141 $position = defined( Admin_Menu::class . '::POSITION_LAST' ) ? Admin_Menu::POSITION_LAST : 998;
142
143 $hook = Admin_Menu::add_menu(
144 __( 'Settings', 'jetpack' ),
145 __( 'Settings', 'jetpack' ),
146 'jetpack_admin_page',
147 'jetpack-settings',
148 array( $this, 'render' ),
149 $position,
150 array( 'key' => 'jetpack-settings' )
151 );
152
153 // The IDC banner is a core-style notice, and during IDC it is all this page shows.
154 remove_action( "load-$hook", array( Admin_Menu::class, 'hide_core_admin_notices' ) );
155
156 return $hook;
157 }
158
159 /**
160 * Hide the sidebar entry from users who cannot use Settings.
161 *
162 * @param array $states Menu item key to visibility state.
163 * @return array
164 */
165 public static function hide_menu_item( $states ) {
166 $states['jetpack-settings'] = 'hidden';
167 return $states;
168 }
169
170 /**
171 * Add page actions.
172 *
173 * @param string $hook Hook of current page.
174 * @return void
175 */
176 public function add_page_actions( $hook ) {
177 add_action( "load-$hook", array( $this, 'add_fallback_redirects' ) );
178 }
179
180 /**
181 * Send browsers that cannot run the app to the modules list.
182 *
183 * @return void
184 */
185 public function add_fallback_redirects() {
186 if ( ! $this->is_rest_api_enabled() ) {
187 $this->is_redirecting = true;
188 add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
189 }
190
191 add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
192 }
193
194 /**
195 * Determine whether a user can access the Jetpack Settings page.
196 *
197 * Rules are:
198 * - user is allowed to see the Jetpack Admin
199 * - site is connected or in offline mode
200 * - non-admins only need access to the settings when there are modules they can manage.
201 *
202 * @return bool $can_access_settings Can the user access settings.
203 */
204 private function can_access_settings() {
205 $connection = new Connection_Manager( 'jetpack' );
206 $status = new Status();
207
208 // User must have the necessary permissions to see the Jetpack settings pages.
209 if ( ! current_user_can( 'edit_posts' ) ) {
210 return false;
211 }
212
213 // In offline mode, allow access to admins.
214 if ( $status->is_offline_mode() && current_user_can( 'manage_options' ) ) {
215 return true;
216 }
217
218 // If not in offline mode but site is not connected, bail.
219 if ( ! Jetpack::is_connection_ready() ) {
220 return false;
221 }
222
223 /*
224 * Additional checks for non-admins.
225 */
226 if ( ! current_user_can( 'manage_options' ) ) {
227 // If the site isn't connected at all, bail.
228 if ( ! $connection->has_connected_owner() ) {
229 return false;
230 }
231
232 /*
233 * If they haven't connected their own account yet,
234 * they have no use for the settings page.
235 * They will not be able to manage any settings.
236 */
237 if ( ! $connection->is_user_connected() ) {
238 return false;
239 }
240
241 /*
242 * Non-admins only have access to settings
243 * for the following modules:
244 * - Publicize
245 * - Post By Email
246 * If those modules are not available, bail.
247 */
248 if (
249 ! Jetpack::is_module_active( 'post-by-email' )
250 && (
251 ! Jetpack::is_module_active( 'publicize' ) ||
252 ! current_user_can( 'publish_posts' )
253 )
254 ) {
255 return false;
256 }
257 }
258
259 // fallback.
260 return true;
261 }
262
263 /**
264 * Add action to render page specific HTML.
265 *
266 * @return void
267 */
268 public function page_render() {
269 /** This action is already documented in class.jetpack-admin-page.php */
270 do_action( 'jetpack_notices' );
271
272 if ( $this->should_render_wp_build() ) {
273 jetpack_plugin_jetpack_settings_dashboard_wp_admin_render_page(); // @phan-suppress-current-line PhanUndeclaredFunction -- should_render_wp_build() checks function_exists(); defined in the generated build/pages/, which Phan excludes.
274 return;
275 }
276
277 // Fetch static.html.
278 $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.
279
280 if ( false === $static_html ) {
281
282 // If we still have nothing, display an error.
283 echo '<p>';
284 esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
285 echo '<code>pnpm run distclean && pnpm jetpack build plugins/jetpack</code>';
286 echo '</p>';
287 } else {
288 // We got the static.html so let's display it.
289 echo $static_html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
290 }
291 }
292
293 /**
294 * Load styles for static page.
295 */
296 public function additional_styles() {
297 // The route bundle carries these styles.
298 if ( $this->should_render_wp_build() ) {
299 return;
300 }
301
302 Jetpack_Admin_Page::load_wrapper_styles();
303 }
304
305 /**
306 * Load admin page scripts.
307 */
308 public function page_admin_scripts() {
309 if ( $this->is_redirecting ) {
310 return; // No need for scripts on a fallback page.
311 }
312
313 $status = new Status();
314 $is_offline_mode = $status->is_offline_mode();
315 $site_suffix = $status->get_site_suffix();
316
317 if ( $this->should_render_wp_build() ) {
318 // wp-build enqueues the route bundle; this handle carries the inline state and classic dependencies.
319 wp_register_script( 'react-plugin', false, $this->get_wp_build_script_dependencies(), JETPACK__VERSION, true );
320 wp_enqueue_script( 'react-plugin' );
321 } else {
322 $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php';
323 $script_dependencies = array( 'jquery', 'wp-polyfill' );
324 $version = JETPACK__VERSION;
325 if ( file_exists( $script_deps_path ) ) {
326 $asset_manifest = include $script_deps_path;
327 $script_dependencies = $asset_manifest['dependencies'];
328 $version = $asset_manifest['version'];
329 }
330
331 wp_enqueue_script(
332 'react-plugin',
333 plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ),
334 $script_dependencies,
335 $version,
336 true
337 );
338
339 wp_set_script_translations( 'react-plugin', 'jetpack' );
340 }
341
342 $blog_id_prop = '';
343 if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
344 $blog_id = Connection_Manager::get_site_id( true );
345 if ( $blog_id ) {
346 $blog_id_prop = ', currentBlogID: "' . (int) $blog_id . '"';
347 }
348 }
349
350 if ( ! $is_offline_mode && Jetpack::is_connection_ready() ) {
351 // Required for Analytics.
352 wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
353 }
354
355 // Add objects to be passed to the initial state of the app.
356 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
357 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' );
358
359 // This will set the default URL of the jp_redirects lib.
360 wp_add_inline_script( 'react-plugin', 'var jetpack_redirects = { currentSiteRawUrl: "' . $site_suffix . '"' . $blog_id_prop . ' };', 'before' );
361
362 // Adds Connection package initial state.
363 Connection_Initial_State::render_script( 'react-plugin' );
364 }
365 }
366