← 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. |