Commit bce757f34e8 for woocommerce
commit bce757f34e8cd1440a5b930f6e6069d8410e326c
Author: Bero <1199991+bgrgicak@users.noreply.github.com>
Date: Tue Nov 3 14:55:59 2026 +0100
Add Open Graph metadata to product search (#70327)
* Add Open Graph metadata to the shop page
* Add Open Graph metadata to product archives
* Add Open Graph metadata to product search
* Exclude private share key from catalog metadata
* Remove page numbers from taxonomy preview titles
* Add category archive coverage to Open Graph test
* Test paginated category preview title
diff --git a/plugins/woocommerce/changelog/add-product-search-open-graph b/plugins/woocommerce/changelog/add-product-search-open-graph
new file mode 100644
index 00000000000..ed7693a673e
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-product-search-open-graph
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Improve shared links to product search results.
diff --git a/plugins/woocommerce/src/Internal/StructuredData/CatalogOpenGraphTags.php b/plugins/woocommerce/src/Internal/StructuredData/CatalogOpenGraphTags.php
new file mode 100644
index 00000000000..c4a574f066d
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/StructuredData/CatalogOpenGraphTags.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Shared catalog Open Graph metadata.
+ *
+ * @package WooCommerce
+ */
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal\StructuredData;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Builds metadata shared by product archives and search results.
+ *
+ * @internal
+ */
+class CatalogOpenGraphTags {
+ /**
+ * Build metadata from a title, URL and optional term details.
+ *
+ * @since 11.4.0
+ * @param string $title Page title.
+ * @param string $url Canonical page URL.
+ * @param string $description Term description, when available.
+ * @param mixed $term_image_id Term image ID, when available.
+ * @return array<array<string, string>>
+ */
+ public function get_tags( string $title, string $url, string $description = '', $term_image_id = 0 ): array {
+ $shop = get_post( wc_get_page_id( 'shop' ) );
+ if ( '' === trim( wp_strip_all_tags( strip_shortcodes( $description ) ) ) && $shop instanceof \WP_Post && 'publish' === $shop->post_status && '' === $shop->post_password ) {
+ $description = $shop->post_excerpt;
+ }
+ if ( '' === trim( wp_strip_all_tags( strip_shortcodes( $description ) ) ) ) {
+ $description = get_bloginfo( 'description' );
+ }
+ $description = wc_get_container()->get( OpenGraphDescription::class )->clean( $description );
+ $tags = array(
+ array( 'og:title' => $title ),
+ array( 'og:type' => 'website' ),
+ array( 'og:url' => $url ),
+ array( 'og:description' => wc_trim_string( $description ) ),
+ array( 'og:site_name' => get_bloginfo( 'name' ) ),
+ );
+ $locale = get_locale();
+ if ( preg_match( '/^[a-z]{2,3}_[A-Z]{2}$/', $locale ) ) {
+ $tags[] = array( 'og:locale' => $locale );
+ }
+
+ $image_ids = array(
+ $term_image_id,
+ $shop instanceof \WP_Post && 'publish' === $shop->post_status && '' === $shop->post_password ? get_post_thumbnail_id( $shop ) : 0,
+ get_theme_mod( 'custom_logo' ),
+ get_option( 'site_icon' ),
+ );
+ $has_image = false;
+ foreach ( $image_ids as $image_id ) {
+ if ( ! is_numeric( $image_id ) || (int) $image_id <= 0 ) {
+ continue;
+ }
+ $image = wc_get_container()->get( OpenGraphImageTags::class )->get_tags( (int) $image_id, $title, true );
+ if ( ! $image ) {
+ continue;
+ }
+ $tags[] = $image;
+ $has_image = true;
+ break;
+ }
+ $tags[] = array( 'twitter:card' => $has_image ? 'summary_large_image' : 'summary' );
+
+ return $tags;
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/StructuredData/OpenGraph.php b/plugins/woocommerce/src/Internal/StructuredData/OpenGraph.php
index 0dfee6cf21e..8536576e285 100644
--- a/plugins/woocommerce/src/Internal/StructuredData/OpenGraph.php
+++ b/plugins/woocommerce/src/Internal/StructuredData/OpenGraph.php
@@ -82,8 +82,9 @@ class OpenGraph {
$this->default_card = array_column( $tags, 'twitter:card' )[0] ?? null;
/**
- * Filters Open Graph metadata before output. Applied to product, shop and product taxonomy pages.
+ * Filters Open Graph metadata before output. Applied to product, shop, product taxonomy and product search pages.
* Other page types may be supported later; callbacks must check the object type.
+ * Product searches may use the shop page as their queried object; use is_search() to distinguish them.
* The first tag in each group is its root; details follow it. Remove or replace an entire group to omit or replace its details.
*
* @since 11.4.0
@@ -136,6 +137,20 @@ class OpenGraph {
return $this->get_product_page_data();
}
+ if ( is_search() && is_post_type_archive( 'product' ) ) {
+ $search = get_search_query( false );
+ if ( is_string( $search ) && '' !== trim( $search ) ) {
+ $context = get_queried_object();
+ if ( ! $context instanceof \WP_Post && ! $context instanceof \WP_Term && ! $context instanceof \WP_Post_Type ) {
+ $context = null;
+ }
+ return array(
+ 'tags' => wc_get_container()->get( ProductSearchOpenGraphTags::class )->get_tags( $search ),
+ 'context' => $context,
+ );
+ }
+ }
+
if (
is_shop()
&& ! is_search()
diff --git a/plugins/woocommerce/src/Internal/StructuredData/ProductSearchOpenGraphTags.php b/plugins/woocommerce/src/Internal/StructuredData/ProductSearchOpenGraphTags.php
new file mode 100644
index 00000000000..b7cedd92720
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/StructuredData/ProductSearchOpenGraphTags.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Product search Open Graph metadata.
+ *
+ * @package WooCommerce
+ */
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal\StructuredData;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Builds Open Graph metadata for product search results.
+ *
+ * @internal
+ */
+class ProductSearchOpenGraphTags {
+ /**
+ * Build metadata for a product search.
+ *
+ * @since 11.4.0
+ * @param string $search Search query.
+ * @return array<array<string, string>>
+ */
+ public function get_tags( string $search ): array {
+ /* translators: %s: search query. */
+ $title = sprintf( __( 'Search results for “%s”', 'woocommerce' ), $search );
+ $page = max( 1, (int) get_query_var( 'paged' ) );
+ if ( $page > 1 ) {
+ /* translators: 1: search title, 2: page number. */
+ $title = sprintf( __( '%1$s – Page %2$d', 'woocommerce' ), $title, $page );
+ }
+ $url = wc_get_container()->get( CatalogOpenGraphUrl::class )->get_url();
+
+ return wc_get_container()->get( CatalogOpenGraphTags::class )->get_tags( $title, $url );
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/StructuredData/ProductTaxonomyOpenGraphTags.php b/plugins/woocommerce/src/Internal/StructuredData/ProductTaxonomyOpenGraphTags.php
index 6d427275f18..d8ce80cd204 100644
--- a/plugins/woocommerce/src/Internal/StructuredData/ProductTaxonomyOpenGraphTags.php
+++ b/plugins/woocommerce/src/Internal/StructuredData/ProductTaxonomyOpenGraphTags.php
@@ -30,48 +30,6 @@ class ProductTaxonomyOpenGraphTags {
$title = $term->name;
$url = wc_get_container()->get( CatalogOpenGraphUrl::class )->get_url();
- $shop = get_post( wc_get_page_id( 'shop' ) );
- $description = $term->description;
- if ( '' === trim( wp_strip_all_tags( strip_shortcodes( $description ) ) ) && $shop instanceof \WP_Post && 'publish' === $shop->post_status && '' === $shop->post_password ) {
- $description = $shop->post_excerpt;
- }
- if ( '' === trim( wp_strip_all_tags( strip_shortcodes( $description ) ) ) ) {
- $description = get_bloginfo( 'description' );
- }
- $description = wc_get_container()->get( OpenGraphDescription::class )->clean( $description );
- $tags = array(
- array( 'og:title' => $title ),
- array( 'og:type' => 'website' ),
- array( 'og:url' => $url ),
- array( 'og:description' => wc_trim_string( $description ) ),
- array( 'og:site_name' => get_bloginfo( 'name' ) ),
- );
- $locale = get_locale();
- if ( preg_match( '/^[a-z]{2,3}_[A-Z]{2}$/', $locale ) ) {
- $tags[] = array( 'og:locale' => $locale );
- }
-
- $image_ids = array(
- get_term_meta( $term->term_id, 'thumbnail_id', true ),
- $shop instanceof \WP_Post && 'publish' === $shop->post_status && '' === $shop->post_password ? get_post_thumbnail_id( $shop ) : 0,
- get_theme_mod( 'custom_logo' ),
- get_option( 'site_icon' ),
- );
- $has_image = false;
- foreach ( $image_ids as $image_id ) {
- if ( ! is_numeric( $image_id ) || (int) $image_id <= 0 ) {
- continue;
- }
- $image = wc_get_container()->get( OpenGraphImageTags::class )->get_tags( (int) $image_id, $title, true );
- if ( ! $image ) {
- continue;
- }
- $tags[] = $image;
- $has_image = true;
- break;
- }
- $tags[] = array( 'twitter:card' => $has_image ? 'summary_large_image' : 'summary' );
-
- return $tags;
+ return wc_get_container()->get( CatalogOpenGraphTags::class )->get_tags( $title, $url, $term->description, get_term_meta( $term->term_id, 'thumbnail_id', true ) );
}
}