PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
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.5, at _inc/lib/admin-pages/class-jetpack-settings-react-page.php

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