diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-block-editor-assets.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-block-editor-assets.php index e921c39dcb79..f9a78b33dfa0 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-block-editor-assets.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-block-editor-assets.php @@ -14,18 +14,16 @@ class WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets extends WP_REST_Controller const CACHE_BUSTER = '2025-02-28'; /** - * List of allowed plugins whose assets should be preserved. - * Each entry should be a unique identifier that appears in the asset URL. + * List of allowed plugin handle prefixes whose assets should be preserved. + * Each entry should be a handle prefix that identifies assets from allowed plugins. * * @var array */ - const ALLOWED_PLUGINS = array( - '/plugins/gutenberg/', // Default plugin location - '/plugins/gutenberg-core/', // WPCOM Simple site location - '/plugins/jetpack/', // Default plugin location - '/plugins/jetpack-dev/', // Used for loading in-progress work - '/mu-plugins/jetpack-mu-wpcom-plugin/', // WPCOM Simple site location - '/mu-plugins/wpcomsh/', // WoA helpers, including Jetpack assets in vendor directories + const ALLOWED_PLUGIN_HANDLE_PREFIXES = array( + 'jetpack-', // E.g., jetpack-blocks-editor, jetpack-connection + 'jp-', // E.g., jp-forms-blocks + 'videopress-', // E.g., videopress-add-resumable-upload-support + 'wp-', // E.g., wp-block-styles, wp-jp-i18n-loader ); /** @@ -277,11 +275,11 @@ private function unregister_disallowed_plugin_assets() { // Unregister disallowed plugin scripts foreach ( $wp_scripts->registered as $handle => $script ) { // Skip core scripts and protected handles - if ( $this->is_core_asset( $script->src ) || $this->is_protected_handle( $handle ) ) { + if ( $this->is_core_or_gutenberg_asset( $script->src ) || $this->is_protected_handle( $handle ) ) { continue; } - if ( ! $this->is_allowed_plugin_asset( $script->src ) ) { + if ( ! $this->is_allowed_plugin_handle( $handle ) ) { unset( $wp_scripts->registered[ $handle ] ); } } @@ -289,23 +287,23 @@ private function unregister_disallowed_plugin_assets() { // Unregister disallowed plugin styles foreach ( $wp_styles->registered as $handle => $style ) { // Skip core styles and protected handles - if ( $this->is_core_asset( $style->src ) || $this->is_protected_handle( $handle ) ) { + if ( $this->is_core_or_gutenberg_asset( $style->src ) || $this->is_protected_handle( $handle ) ) { continue; } - if ( ! $this->is_allowed_plugin_asset( $style->src ) ) { + if ( ! $this->is_allowed_plugin_handle( $handle ) ) { unset( $wp_styles->registered[ $handle ] ); } } } /** - * Check if an asset is a core asset. + * Check if an asset is a core or Gutenberg asset. * * @param string $src The asset source URL. - * @return bool True if the asset is a core asset, false otherwise. + * @return bool True if the asset is a core or Gutenberg asset, false otherwise. */ - private function is_core_asset( $src ) { + private function is_core_or_gutenberg_asset( $src ) { if ( ! is_string( $src ) ) { return false; } @@ -313,7 +311,9 @@ private function is_core_asset( $src ) { return empty( $src ) || $src[0] === '/' || strpos( $src, 'wp-includes/' ) !== false || - strpos( $src, 'wp-admin/' ) !== false; + strpos( $src, 'wp-admin/' ) !== false || + strpos( $src, 'plugins/gutenberg/' ) !== false || + strpos( $src, 'plugins/gutenberg-core/' ) !== false; // WPCOM-specific path } /** @@ -327,18 +327,18 @@ private function is_protected_handle( $handle ) { } /** - * Check if an asset is from an allowed plugin. + * Check if a handle is from an allowed plugin. * - * @param string $src The asset source URL. - * @return bool True if the asset is from an allowed plugin, false otherwise. + * @param string $handle The asset handle. + * @return bool True if the handle is from an allowed plugin, false otherwise. */ - private function is_allowed_plugin_asset( $src ) { - if ( ! is_string( $src ) || empty( $src ) ) { + private function is_allowed_plugin_handle( $handle ) { + if ( ! is_string( $handle ) || empty( $handle ) ) { return false; } - foreach ( self::ALLOWED_PLUGINS as $allowed_plugin ) { - if ( strpos( $src, $allowed_plugin ) !== false ) { + foreach ( self::ALLOWED_PLUGIN_HANDLE_PREFIXES as $allowed_prefix ) { + if ( strpos( $handle, $allowed_prefix ) === 0 ) { return true; } } diff --git a/projects/plugins/jetpack/changelog/fix-reinstate-jetpack-assets-to-editor-assets-endpoint b/projects/plugins/jetpack/changelog/fix-reinstate-jetpack-assets-to-editor-assets-endpoint new file mode 100644 index 000000000000..c5754c2c3a0f --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-reinstate-jetpack-assets-to-editor-assets-endpoint @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Editor assets endpoint: reinstate missing Jetpack assets via handle-based exclusion logic diff --git a/projects/plugins/jetpack/tests/php/core-api/wpcom-endpoints/WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets_Test.php b/projects/plugins/jetpack/tests/php/core-api/wpcom-endpoints/WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets_Test.php index d80c90db596a..39c54c99b9e0 100644 --- a/projects/plugins/jetpack/tests/php/core-api/wpcom-endpoints/WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets_Test.php +++ b/projects/plugins/jetpack/tests/php/core-api/wpcom-endpoints/WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets_Test.php @@ -29,36 +29,9 @@ public function set_up() { parent::set_up(); $this->instance = new WPCOM_REST_API_V2_Endpoint_Block_Editor_Assets(); - // Mock the enqueue_block_editor_assets action to prevent loading non-existent files + // Remove existing actions to prevent failed loading of files that may or + // may not exist depending on the build output. remove_all_actions( 'enqueue_block_editor_assets' ); - add_action( 'enqueue_block_editor_assets', array( $this, 'mock_block_editor_assets' ) ); - } - - /** - * Clean up after each test. - */ - public function tear_down() { - // Remove our mock action - remove_action( 'enqueue_block_editor_assets', array( $this, 'mock_block_editor_assets' ) ); - parent::tear_down(); - } - - /** - * Mock function for block editor assets. - * This provides minimal required assets without loading actual files. - */ - public function mock_block_editor_assets() { - // Register minimal mock assets that don't require actual files - wp_register_script( 'mock-editor-script', 'http://example.org/plugins/jetpack/mock-editor.js', array(), '1.0', true ); - wp_register_style( 'mock-editor-style', 'http://example.org/plugins/jetpack/mock-editor.css', array(), '1.0' ); - wp_register_script( 'disallowed-plugin-script', 'http://example.org/plugins/disallowed-plugin/script.js', array(), '1.0', true ); - wp_register_script( 'disallowed-plugin-style', 'http://example.org/plugins/disallowed-plugin/style.css', array(), '1.0', true ); - - // Enqueue our mock assets - wp_enqueue_script( 'mock-editor-script' ); - wp_enqueue_style( 'mock-editor-style' ); - wp_enqueue_script( 'disallowed-plugin-script' ); - wp_enqueue_style( 'disallowed-plugin-style' ); } /** @@ -178,13 +151,30 @@ function ( $block_name ) { public function test_get_items_returns_allowed_plugin_assets() { wp_set_current_user( self::factory()->user->create( array( 'role' => 'editor' ) ) ); + add_action( 'enqueue_block_editor_assets', array( $this, 'mock_allowed_plugin_assets' ) ); + $request = new WP_REST_Request( Requests::GET, '/wpcom/v2/editor-assets' ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); // Verify the allowed plugin script and style are in the output - $this->assertStringContainsString( 'mock-editor-script', $data['scripts'] ); - $this->assertStringContainsString( 'mock-editor-style', $data['styles'] ); + $this->assertStringContainsString( 'jetpack-mock-script', $data['scripts'] ); + $this->assertStringContainsString( 'jetpack-mock-style', $data['styles'] ); + + remove_action( 'enqueue_block_editor_assets', array( $this, 'mock_allowed_plugin_assets' ) ); + } + + /** + * Enqueue allowed plugin assets. + */ + public function mock_allowed_plugin_assets() { + // Register minimal mock assets that don't require actual files + wp_register_script( 'jetpack-mock-script', 'http://example.org/mock-editor.js', array(), '1.0', true ); + wp_register_style( 'jetpack-mock-style', 'http://example.org/mock-editor.css', array(), '1.0' ); + + // Enqueue our mock assets + wp_enqueue_script( 'jetpack-mock-script' ); + wp_enqueue_style( 'jetpack-mock-style' ); } /** @@ -193,6 +183,8 @@ public function test_get_items_returns_allowed_plugin_assets() { public function test_disallowed_plugin_assets_are_filtered() { wp_set_current_user( self::factory()->user->create( array( 'role' => 'editor' ) ) ); + add_action( 'enqueue_block_editor_assets', array( $this, 'mock_disallowed_plugin_assets' ) ); + $request = new WP_REST_Request( Requests::GET, '/wpcom/v2/editor-assets' ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); @@ -200,6 +192,19 @@ public function test_disallowed_plugin_assets_are_filtered() { // Verify the disallowed plugin script and style are not in the output $this->assertStringNotContainsString( 'disallowed-plugin-script', $data['scripts'] ); $this->assertStringNotContainsString( 'disallowed-plugin-style', $data['styles'] ); + + remove_action( 'enqueue_block_editor_assets', array( $this, 'mock_disallowed_plugin_assets' ) ); + } + + /** + * Enqueue disallowed plugin assets. + */ + public function mock_disallowed_plugin_assets() { + wp_register_script( 'disallowed-plugin-script', 'http://example.org/script.js', array(), '1.0', true ); + wp_register_style( 'disallowed-plugin-style', 'http://example.org/style.css', array(), '1.0' ); + + wp_enqueue_script( 'disallowed-plugin-script' ); + wp_enqueue_style( 'disallowed-plugin-style' ); } /** @@ -216,6 +221,38 @@ public function test_protected_handles_are_preserved() { $this->assertStringContainsString( 'jquery', $data['scripts'] ); } + /** + * Test that WPCOM-specific Gutenberg assets are preserved. + */ + public function test_wpcom_gutenberg_assets_are_preserved() { + wp_set_current_user( self::factory()->user->create( array( 'role' => 'editor' ) ) ); + + add_action( 'enqueue_block_editor_assets', array( $this, 'mock_wpcom_gutenberg_assets' ) ); + + $request = new WP_REST_Request( Requests::GET, '/wpcom/v2/editor-assets' ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + // Verify the WPCOM Gutenberg assets are preserved in the output + $this->assertStringContainsString( 'wpcom-gutenberg-script', $data['scripts'] ); + $this->assertStringContainsString( 'wpcom-gutenberg-style', $data['styles'] ); + $this->assertStringContainsString( 'plugins/gutenberg-core/script.js', $data['scripts'] ); + $this->assertStringContainsString( 'plugins/gutenberg-core/style.css', $data['styles'] ); + + remove_action( 'enqueue_block_editor_assets', array( $this, 'mock_wpcom_gutenberg_assets' ) ); + } + + /** + * Enqueue assets using WPCOM's specific Gutenberg paths. + */ + public function mock_wpcom_gutenberg_assets() { + wp_register_script( 'wpcom-gutenberg-script', 'http://example.org/plugins/gutenberg-core/script.js', array(), '1.0', true ); + wp_register_style( 'wpcom-gutenberg-style', 'http://example.org/plugins/gutenberg-core/style.css', array(), '1.0' ); + + wp_enqueue_script( 'wpcom-gutenberg-script' ); + wp_enqueue_style( 'wpcom-gutenberg-style' ); + } + /** * Test that required WordPress actions are triggered. */