Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions plugins/embed-optimizer/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,130 @@ function embed_optimizer_add_hooks(): void {

add_action( 'od_init', 'embed_optimizer_init_optimization_detective' );
add_action( 'wp_loaded', 'embed_optimizer_add_non_optimization_detective_hooks' );

add_filter( 'embed_oembed_html', 'embed_optimizer_wrap_oembed_html_in_embed_block_markup', 9, 4 );
}

/**
* Determines whether a URL is already inside a core/embed block's FIGURE.wp-block-embed > DIV.wp-block-embed__wrapper
* markup in a post's stored content.
*
* The core/embed block is a static block: its saved markup already contains the FIGURE/DIV wrapper with the bare
* URL as its only content, e.g. `<div class="wp-block-embed__wrapper">\nhttps://example.com/x\n</div>`. That bare
* URL is what WP_Embed::autoembed() (hooked onto `the_content` at priority 8, before do_blocks() at priority 9)
* replaces with the resolved oEmbed HTML, via the same embed_oembed_html filter used for Classic Editor and
* Classic block auto-embedded URLs. Since that replacement happens before the block is even parsed, there is no
* reliable way to distinguish the two cases from block-rendering hooks; instead this looks at whether the URL
* appears immediately inside an existing wp-block-embed__wrapper DIV in the post's raw, unprocessed content.
*
* @since n.e.x.t
* @access private
*
* @param string $url The attempted embed URL.
* @param int|null $post_id Post ID, if any.
* @return bool Whether the URL is already wrapped by a core/embed block.
*/
function embed_optimizer_is_url_in_existing_embed_block_wrapper( string $url, ?int $post_id ): bool {
if ( null === $post_id ) {
return false;
}

$post = get_post( $post_id );
if ( ! $post instanceof WP_Post ) {
return false;
}

$pattern = sprintf(
'#<div\b[^>]*\bclass\s*=\s*"[^"]*\bwp-block-embed__wrapper\b[^"]*"[^>]*>\s*%s\s*<#i',
preg_quote( $url, '#' )
);

return 1 === preg_match( $pattern, $post->post_content );
}

/**
* Wraps oEmbed HTML in the FIGURE.wp-block-embed > DIV.wp-block-embed__wrapper markup used by the core/embed block.
*
* Embeds added via the Classic Editor (auto-embedded URLs, the `[embed]` shortcode) or the Classic block are
* resolved via WP_Embed::shortcode(), the same core method the core/embed block relies on to resolve the bare
* URL saved in its own FIGURE/DIV wrapper (see embed_optimizer_is_url_in_existing_embed_block_wrapper()). Both
* cases fire the embed_oembed_html filter. By wrapping classic embeds in the same FIGURE/DIV markup the block
* already has, they become recognizable to Embed_Optimizer_Tag_Visitor and Optimization Detective just like
* embeds added via the Embed block. Wrapping is skipped when the URL is already inside such a wrapper, to avoid
* nesting the markup twice.
*
* @since n.e.x.t
*
* @param string|mixed $html The oEmbed HTML.
* @param string $url The attempted embed URL.
* @param array<string, mixed> $attr Shortcode attributes.
* @param int|null $post_id Post ID, if any.
* @return string Filtered oEmbed HTML.
*/
function embed_optimizer_wrap_oembed_html_in_embed_block_markup( $html, string $url, array $attr, ?int $post_id ): string {
if ( ! is_string( $html ) ) {
$html = '';
}

if ( '' === $html || embed_optimizer_is_url_in_existing_embed_block_wrapper( $url, $post_id ) ) {
return $html;
}

$class_names = array( 'wp-block-embed' );
$type_class = embed_optimizer_get_embed_provider_class( $url );
if ( null !== $type_class ) {
$class_names[] = $type_class;
}

return sprintf(
'<figure class="%s"><div class="wp-block-embed__wrapper">%s</div></figure>',
esc_attr( implode( ' ', $class_names ) ),
$html
);
}

/**
* Gets the wp-block-embed-{provider} CSS class for a given embed URL.
*
* This mirrors (for the subset of providers Embed Optimizer already recognizes for dns-prefetching, see
* Embed_Optimizer_Tag_Visitor::get_dns_prefetch_urls()) the provider class that the block editor applies to
* FIGURE.wp-block-embed. It is best-effort since the actual oEmbed provider name is not available from the
* embed_oembed_html filter, only the requested URL.
*
* @since n.e.x.t
*
* @param string $url Embed URL.
* @return non-empty-string|null Provider class, or null if the provider is not recognized.
*/
function embed_optimizer_get_embed_provider_class( string $url ): ?string {
$host = wp_parse_url( $url, PHP_URL_HOST );
if ( ! is_string( $host ) || '' === $host ) {
return null;
}
$host = strtolower( $host );

$host_provider_map = array(
'youtube.com' => 'youtube',
'youtu.be' => 'youtube',
'twitter.com' => 'twitter',
'x.com' => 'twitter',
'vimeo.com' => 'vimeo',
'open.spotify.com' => 'spotify',
'video.wordpress.com' => 'wordpress-tv',
'instagram.com' => 'instagram',
'tiktok.com' => 'tiktok',
'amazon.com' => 'amazon',
'soundcloud.com' => 'soundcloud',
'pinterest.com' => 'pinterest',
);

foreach ( $host_provider_map as $provider_host => $provider_slug ) {
if ( $host === $provider_host || str_ends_with( $host, ".{$provider_host}" ) ) {
return "wp-block-embed-{$provider_slug}";
}
}

return null;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion plugins/embed-optimizer/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The other major feature in Embed Optimizer enabled by Optimization Detective is

Since Optimization Detective relies on page visits to learn how the page is laid out, you’ll need to wait until you have visits from a mobile and desktop device to start seeing optimizations applied. Also, note that Optimization Detective does not apply optimizations by default for logged-in admin users.

Please note that the optimizations are intended to apply to Embed blocks. So if you do not see optimizations applied, make sure that your embeds are not inside a Classic Block.
Embeds added via the Embed block, the Classic block, or the Classic Editor (auto-embedded URLs and the `[embed]` shortcode) are all supported.

Your site must have the **REST API accessible** to unauthenticated frontend visitors since this is how metrics are collected about how a page should be optimized. There are currently **no settings** and no user interface for this plugin since it is designed to work without any configuration.

Expand Down
164 changes: 164 additions & 0 deletions plugins/embed-optimizer/tests/test-hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,118 @@ public function test_embed_optimizer_add_hooks(): void {
remove_all_actions( 'od_init' );
remove_all_actions( 'wp_head' );
remove_all_actions( 'wp_loaded' );
remove_all_filters( 'embed_oembed_html' );
embed_optimizer_add_hooks();
$this->assertSame( 10, has_action( 'od_init', 'embed_optimizer_init_optimization_detective' ) );
$this->assertSame( 10, has_action( 'wp_head', 'embed_optimizer_render_generator' ) );
$this->assertSame( 10, has_action( 'wp_loaded', 'embed_optimizer_add_non_optimization_detective_hooks' ) );
$this->assertSame( 9, has_filter( 'embed_oembed_html', 'embed_optimizer_wrap_oembed_html_in_embed_block_markup' ) );
}

/**
* @covers ::embed_optimizer_is_url_in_existing_embed_block_wrapper
*/
public function test_embed_optimizer_is_url_in_existing_embed_block_wrapper(): void {
$this->assertFalse( embed_optimizer_is_url_in_existing_embed_block_wrapper( 'https://example.com/x', null ), 'Expected false when no post ID is provided.' );
$this->assertFalse( embed_optimizer_is_url_in_existing_embed_block_wrapper( 'https://example.com/x', PHP_INT_MAX ), 'Expected false for a non-existent post.' );

$embed_block_post_id = self::factory()->post->create(
array(
'post_content' => "<!-- wp:embed {\"url\":\"https://example.com/x\"} -->\n<figure class=\"wp-block-embed\"><div class=\"wp-block-embed__wrapper\">\nhttps://example.com/x\n</div></figure>\n<!-- /wp:embed -->",
)
);
$this->assertTrue( embed_optimizer_is_url_in_existing_embed_block_wrapper( 'https://example.com/x', $embed_block_post_id ) );
$this->assertFalse( embed_optimizer_is_url_in_existing_embed_block_wrapper( 'https://example.com/y', $embed_block_post_id ), 'Expected false for a URL not present in the post.' );

$classic_post_id = self::factory()->post->create(
array(
'post_content' => "Check this out:\n\nhttps://example.com/x\n\nPretty cool, right?",
)
);
$this->assertFalse( embed_optimizer_is_url_in_existing_embed_block_wrapper( 'https://example.com/x', $classic_post_id ), 'Expected false for a bare classic auto-embedded URL.' );
}

/**
* @return array<string, array{0: string, 1: string|null}>
*/
public function data_provider_to_test_embed_optimizer_get_embed_provider_class(): array {
return array(
'youtube' => array( 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'wp-block-embed-youtube' ),
'youtu.be' => array( 'https://youtu.be/dQw4w9WgXcQ', 'wp-block-embed-youtube' ),
'twitter' => array( 'https://twitter.com/WordPress/status/123', 'wp-block-embed-twitter' ),
'x.com' => array( 'https://x.com/WordPress/status/123', 'wp-block-embed-twitter' ),
'vimeo' => array( 'https://vimeo.com/123456', 'wp-block-embed-vimeo' ),
'spotify' => array( 'https://open.spotify.com/track/123', 'wp-block-embed-spotify' ),
'wordpress-tv' => array( 'https://video.wordpress.com/embed/abc123', 'wp-block-embed-wordpress-tv' ),
'instagram' => array( 'https://www.instagram.com/p/abc123/', 'wp-block-embed-instagram' ),
'tiktok' => array( 'https://www.tiktok.com/@user/video/123', 'wp-block-embed-tiktok' ),
'amazon' => array( 'https://read.amazon.com/kp/embed?asin=123', 'wp-block-embed-amazon' ),
'soundcloud' => array( 'https://soundcloud.com/user/track', 'wp-block-embed-soundcloud' ),
'pinterest' => array( 'https://www.pinterest.com/pin/123/', 'wp-block-embed-pinterest' ),
'unrecognized' => array( 'https://example.com/some-page', null ),
'invalid_url' => array( 'not a url', null ),
);
}

/**
* @dataProvider data_provider_to_test_embed_optimizer_get_embed_provider_class
* @covers ::embed_optimizer_get_embed_provider_class
*/
public function test_embed_optimizer_get_embed_provider_class( string $url, ?string $expected ): void {
$this->assertSame( $expected, embed_optimizer_get_embed_provider_class( $url ) );
}

/**
* @return array<string, array{0: string, 1: string, 2: string}>
*/
public function data_provider_to_test_embed_optimizer_wrap_oembed_html_in_embed_block_markup(): array {
return array(
'iframe_embed' => array(
'<iframe src="https://www.youtube.com/embed/123"></iframe>',
'https://www.youtube.com/watch?v=123',
'<figure class="wp-block-embed wp-block-embed-youtube"><div class="wp-block-embed__wrapper"><iframe src="https://www.youtube.com/embed/123"></iframe></div></figure>',
),
'unknown_provider' => array(
'<div class="example-embed"></div>',
'https://example.com/some-page',
'<figure class="wp-block-embed"><div class="wp-block-embed__wrapper"><div class="example-embed"></div></div></figure>',
),
'empty_html' => array(
'',
'https://www.youtube.com/watch?v=123',
'',
),
);
}

/**
* @dataProvider data_provider_to_test_embed_optimizer_wrap_oembed_html_in_embed_block_markup
* @covers ::embed_optimizer_wrap_oembed_html_in_embed_block_markup
*/
public function test_embed_optimizer_wrap_oembed_html_in_embed_block_markup( string $html, string $url, string $expected ): void {
$this->assertSame( $expected, embed_optimizer_wrap_oembed_html_in_embed_block_markup( $html, $url, array(), null ) );
}

/**
* Tests that wrapping is skipped when the URL is already inside an existing core/embed block wrapper, to avoid
* double-wrapping the block's own FIGURE.wp-block-embed > DIV.wp-block-embed__wrapper markup.
*
* @covers ::embed_optimizer_wrap_oembed_html_in_embed_block_markup
*/
public function test_embed_optimizer_wrap_oembed_html_in_embed_block_markup_skips_existing_wrapper(): void {
$html = '<iframe src="https://www.youtube.com/embed/123"></iframe>';
$url = 'https://www.youtube.com/watch?v=123';

$post_id = self::factory()->post->create(
array(
'post_content' => sprintf(
"<!-- wp:embed {\"url\":\"%1\$s\"} -->\n<figure class=\"wp-block-embed\"><div class=\"wp-block-embed__wrapper\">\n%1\$s\n</div></figure>\n<!-- /wp:embed -->",
$url
),
)
);

$this->assertSame( $html, embed_optimizer_wrap_oembed_html_in_embed_block_markup( $html, $url, array(), $post_id ) );
}

/**
Expand Down Expand Up @@ -263,4 +371,60 @@ public function test_embed_optimizer_render_generator(): void {
$this->assertStringContainsString( 'generator', $tag );
$this->assertStringContainsString( 'embed-optimizer ' . EMBED_OPTIMIZER_VERSION, $tag );
}

/**
* Tests that a classic (non-block) auto-embedded URL gets wrapped in the same FIGURE/DIV markup as the
* core/embed block, while an actual core/embed block's own bare URL does not get double-wrapped.
*
* WP_Embed::autoembed() (and WP_Embed::run_shortcode() for the `[embed]` shortcode) resolve a bare URL via
* WP_Embed::shortcode(), which fires the embed_oembed_html filter. This is the same mechanism that resolves
* the bare URL saved inside a core/embed block's own FIGURE/DIV wrapper, since that resolution happens before
* the block comment is even parsed (see embed_optimizer_is_url_in_existing_embed_block_wrapper()).
*
* @covers ::embed_optimizer_wrap_oembed_html_in_embed_block_markup
* @covers ::embed_optimizer_is_url_in_existing_embed_block_wrapper
*/
public function test_embed_optimizer_classic_and_block_embed_wrapping_integration(): void {
remove_all_filters( 'embed_oembed_html' );
remove_all_filters( 'pre_oembed_result' );
add_filter( 'embed_oembed_html', 'embed_optimizer_wrap_oembed_html_in_embed_block_markup', 9, 4 );

// Short-circuit oEmbed HTTP discovery/fetching so this stays a canned response, while still flowing
// through WP_Embed::shortcode() and the embed_oembed_html filter like a real oEmbed provider would.
add_filter(
'pre_oembed_result',
static function ( $pre, string $url ) {
if ( 1 === preg_match( '#^https://example\.test/video/(?P<id>\d+)$#i', $url, $matches ) ) {
return sprintf( '<iframe src="https://example.test/embed/%s"></iframe>', $matches['id'] );
}
return $pre;
},
10,
2
);

global $post, $wp_embed;
$original_post = $post;
try {
// Classic Editor / Classic block scenario: a bare auto-embedded URL is not wrapped by the editor,
// so Embed Optimizer must add the FIGURE/DIV wrapper itself.
$classic_post_content = "Check this out:\n\nhttps://example.test/video/123\n\nPretty cool, right?";
$post = get_post( self::factory()->post->create( array( 'post_content' => $classic_post_content ) ) );
$classic_content = $wp_embed->autoembed( $classic_post_content );
$this->assertStringContainsString(
'<figure class="wp-block-embed"><div class="wp-block-embed__wrapper"><iframe src="https://example.test/embed/123"></iframe></div></figure>',
$classic_content
);

// Block editor scenario: the core/embed block already saves its own FIGURE/DIV wrapper around the bare
// URL, so Embed Optimizer must not wrap it a second time.
$block_post_content = "<!-- wp:embed {\"url\":\"https://example.test/video/456\"} -->\n<figure class=\"wp-block-embed\"><div class=\"wp-block-embed__wrapper\">\nhttps://example.test/video/456\n</div></figure>\n<!-- /wp:embed -->";
$post = get_post( self::factory()->post->create( array( 'post_content' => $block_post_content ) ) );
$block_content = $wp_embed->autoembed( $block_post_content );
$this->assertSame( 1, substr_count( $block_content, 'wp-block-embed__wrapper' ), 'Expected the block markup to not be double-wrapped.' );
$this->assertStringContainsString( '<iframe src="https://example.test/embed/456"></iframe>', $block_content );
} finally {
$post = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
}
}
Loading