PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
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 | jetpack_vendor/automattic/jetpack-search/src/dashboard/class-dashboard.php +174 -20 16.2 → 16.3 View file →
@@ -7,13 +7,14 @@
7 7
8 8 namespace Automattic\Jetpack\Search;
9 9
10 10 use Automattic\Jetpack\Admin_UI\Admin_Menu;
11 -use Automattic\Jetpack\Assets;
12 11 use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
13 12 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
14 13 use Automattic\Jetpack\Status;
15 14 use Automattic\Jetpack\Tracking;
15 +use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Polyfills;
16 +use Automattic\Jetpack\WP_Build_Polyfills\WP_Build_Screen_Id;
16 17 /**
17 18 * Responsible for adding a search dashboard to wp-admin.
18 19 *
19 20 * @package Automattic\Jetpack\Search
@@ -19,14 +20,42 @@
19 20 * @package Automattic\Jetpack\Search
20 21 */
21 22 class Dashboard {
22 23 /**
24 + * Slug emitted by `@wordpress/build` (`wpPlugin.pages[0].id`). Must differ from the
25 + * `jetpack-search` menu slug: the generated page.php takes over, and exits, any request
26 + * whose `page` matches this id.
27 + */
28 + const WP_BUILD_PAGE_ID = 'jetpack-search-dashboard';
29 +
30 + /**
31 + * Render function generated by `@wordpress/build` into
32 + * `build/pages/jetpack-search-dashboard/page-wp-admin.php`. Naming convention:
33 + * `{wpPlugin.name}_{page-with-underscores}_wp_admin_render_page`.
34 + */
35 + const WP_BUILD_RENDER_FN = 'jetpack_search_jetpack_search_dashboard_wp_admin_render_page';
36 +
37 + /**
38 + * Classic script handle with no source, registered only so the dashboard has
39 + * something to hang {@see Initial_State} and the connection initial state on.
40 + */
41 + const DATA_SCRIPT_HANDLE = 'jetpack-search-dashboard-data';
42 +
43 + /**
23 44 * Whether the class has been initialized
24 45 *
25 46 * @var boolean
26 47 */
27 48 private static $initialized = false;
49 +
28 50 /**
51 + * The screen ID {@see self::alias_screen_id_for_wp_build()} replaced, until it is restored.
52 + *
53 + * @var string|null
54 + */
55 + private static $wp_build_original_screen_id = null;
56 +
57 + /**
29 58 * Plan instance
30 59 *
31 60 * @var \Automattic\Jetpack\Search\Plan
32 61 */
@@ -81,8 +110,11 @@
81 110 */
82 111 public function init_hooks() {
83 112 if ( ! self::$initialized ) {
84 113 self::$initialized = true;
114 + // Any priority works: render() reads the loaded build after admin_menu, and the
115 + // wpcom subclass overrides this to 100000.
116 + add_action( 'admin_menu', array( $this, 'maybe_load_wp_build' ), $this->search_menu_priority );
85 117 add_action( 'admin_menu', array( $this, 'add_wp_admin_submenu' ), $this->search_menu_priority );
86 118 // Check if the site plan changed and deactivate module accordingly.
87 119 add_action( 'current_screen', array( $this, 'check_plan_deactivate_search_module' ) );
88 120 }
@@ -88,8 +120,124 @@
88 120 }
89 121 }
90 122
91 123 /**
124 + * Load the wp-build dashboard bundle for this request.
125 + *
126 + * A no-op unless this is the Search page and `build/build.php` exists.
127 + */
128 + public function maybe_load_wp_build() {
129 + if ( ! $this->is_search_admin_request() ) {
130 + return;
131 + }
132 +
133 + $build_index = $this->wp_build_index();
134 + if ( ! file_exists( $build_index ) ) {
135 + return;
136 + }
137 +
138 + self::require_wp_build_with_screen_alias( $build_index );
139 +
140 + // wp-build hooks module registration to wp_default_scripts, which has already
141 + // fired by admin_menu — call it directly or the init module never registers.
142 + if ( function_exists( 'jetpack_search_register_script_modules' ) ) {
143 + jetpack_search_register_script_modules(); // @phan-suppress-current-line PhanUndeclaredFunction -- guarded by function_exists(); defined in the generated build/modules.php, which Phan excludes.
144 + }
145 +
146 + WP_Build_Polyfills::register(
147 + 'jetpack-search',
148 + array_merge( WP_Build_Polyfills::SCRIPT_HANDLES, WP_Build_Polyfills::MODULE_IDS )
149 + );
150 + }
151 +
152 + /**
153 + * Require the generated build file with the screen ID aliased across its enqueue check.
154 + *
155 + * @see WP_Build_Screen_Id::load_with_alias()
156 + * @param string $build_index Path to the generated `build.php`.
157 + * @return void
158 + */
159 + private static function require_wp_build_with_screen_alias( $build_index ) {
160 + // Fallback: an older wp-build-polyfills under the jetpack-autoloader may predate load_with_alias().
161 + if ( method_exists( WP_Build_Screen_Id::class, 'load_with_alias' ) ) {
162 + WP_Build_Screen_Id::load_with_alias(
163 + array( __CLASS__, 'alias_screen_id_for_wp_build' ),
164 + array( __CLASS__, 'restore_screen_id_after_wp_build' ),
165 + function () use ( $build_index ) {
166 + require_once $build_index;
167 + }
168 + );
169 + return;
170 + }
171 +
172 + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'alias_screen_id_for_wp_build' ) );
173 + require_once $build_index;
174 + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'restore_screen_id_after_wp_build' ) );
175 + }
176 +
177 + /**
178 + * Path to the generated build entry point. A seam, like the render function below:
179 + * tests point it at a stub so this runs without the package being built.
180 + *
181 + * @return string
182 + */
183 + protected function wp_build_index() {
184 + return dirname( __DIR__, 2 ) . '/build/build.php';
185 + }
186 +
187 + /**
188 + * Name of the generated render function. A seam: tests override it to render
189 + * the dashboard without depending on whether the package happens to be built.
190 + *
191 + * @return string
192 + */
193 + protected function wp_build_render_function() {
194 + return self::WP_BUILD_RENDER_FN;
195 + }
196 +
197 + /**
198 + * Whether the current request targets the Search admin page.
199 + *
200 + * @return bool
201 + */
202 + protected function is_search_admin_request() {
203 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
204 + if ( ! is_admin() || ! isset( $_GET['page'] ) ) {
205 + return false;
206 + }
207 +
208 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
209 + return 'jetpack-search' === sanitize_text_field( wp_unslash( $_GET['page'] ) );
210 + }
211 +
212 + /**
213 + * Alias the current screen id to wp-build's expected slug so its
214 + * auto-generated enqueue callback fires for our user-facing page.
215 + */
216 + public static function alias_screen_id_for_wp_build() {
217 + $screen = get_current_screen();
218 + if ( ! $screen ) {
219 + return;
220 + }
221 +
222 + self::$wp_build_original_screen_id = $screen->id;
223 + $screen->id = self::WP_BUILD_PAGE_ID;
224 + }
225 +
226 + /**
227 + * Undo alias_screen_id_for_wp_build(), since JITM builds its message path from the screen ID.
228 + */
229 + public static function restore_screen_id_after_wp_build() {
230 + $screen = get_current_screen();
231 + if ( ! $screen || null === self::$wp_build_original_screen_id ) {
232 + return;
233 + }
234 +
235 + $screen->id = self::$wp_build_original_screen_id;
236 + self::$wp_build_original_screen_id = null;
237 + }
238 +
239 + /**
92 240 * The page to be added to submenu
93 241 */
94 242 public function add_wp_admin_submenu() {
95 243 // Jetpack of version <= 10.5 would register `jetpack-search` submenu with its built-in search module.
@@ -101,9 +249,14 @@
101 249 'Jetpack Search',
102 250 'Search',
103 251 'manage_options',
104 252 'jetpack-search',
105 - array( $this, 'render' )
253 + array( $this, 'render' ),
254 + null,
255 + array(
256 + 'product' => 'search',
257 + 'key' => 'jetpack-search',
258 + )
106 259 );
107 260 } else {
108 261 // always add the page, but hide it from the menu.
109 262 $page_suffix = add_submenu_page(
@@ -122,16 +275,17 @@
122 275 }
123 276 }
124 277
125 278 /**
126 - * Override render funtion
279 + * Render the dashboard page.
280 + *
281 + * The generated render function is missing where the package was never built.
127 282 */
128 283 public function render() {
129 - ?>
130 - <div id="jp-search-dashboard" class="jp-search-dashboard">
131 - <div class="hide-if-js"><?php esc_html_e( 'Your Jetpack Search dashboard requires JavaScript to function properly.', 'jetpack-search-pkg' ); ?></div>
132 - </div>
133 - <?php
284 + $render_function = $this->wp_build_render_function();
285 + if ( function_exists( $render_function ) ) {
286 + call_user_func( $render_function );
287 + }
134 288 }
135 289
136 290 /**
137 291 * Test whether we should show Search menu.
@@ -171,29 +325,29 @@
171 325 // Required for Analytics.
172 326 Tracking::register_tracks_functions_scripts( true );
173 327 }
174 328
175 - Assets::register_script(
176 - 'jp-search-dashboard',
177 - '../../build/dashboard/jp-search-dashboard.js',
178 - __FILE__,
179 - array(
180 - 'in_footer' => true,
181 - 'textdomain' => 'jetpack-search-pkg',
182 - )
183 - );
184 - Assets::enqueue_script( 'jp-search-dashboard' );
329 + // wp-build enqueues the app itself; this empty handle exists only to
330 + // print the initial state before boot runs on DOMContentLoaded.
331 + wp_register_script( self::DATA_SCRIPT_HANDLE, false, array(), Package::VERSION, true );
332 + wp_enqueue_script( self::DATA_SCRIPT_HANDLE );
185 333
334 + // The i18n loader is registered on every admin page but only enqueued
335 + // when depended on; the esbuild bundle doesn't pull it in.
336 + if ( wp_script_is( 'wp-jp-i18n-loader', 'registered' ) ) {
337 + wp_enqueue_script( 'wp-jp-i18n-loader' );
338 + }
339 +
186 340 // Add objects to be passed to the initial state of the app.
187 341 // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
188 342 wp_add_inline_script(
189 - 'jp-search-dashboard',
343 + self::DATA_SCRIPT_HANDLE,
190 344 ( new Initial_State() )->render(),
191 345 'before'
192 346 );
193 347
194 348 // Connection initial state.
195 - Connection_Initial_State::render_script( 'jp-search-dashboard' );
349 + Connection_Initial_State::render_script( self::DATA_SCRIPT_HANDLE );
196 350 }
197 351
198 352 /**
199 353 * Check if we should enqueue the tracking script.