A Better ‘pre_get_posts’ Search for WooCommerce

WPSSO + WooCommerce logos.

I recently wrote a plugin to provide missing GTIN, GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN), GTIN-14, ISBN, MPN, Depth, and Volume values for WooCommerce Products and Variations. As part of that plugin, I extended the WordPress search feature to search metadata from WooCommerce products (and their variations). The standard way to extend the WordPress search feature is to hook the “pre_get_posts” action and modify the WP_Query to include additional posts / products in search results. There are some serious drawbacks to doing this – with or without WooCommerce – but especially with WooCommerce.

After modifying the WP_Query in “pre_get_posts” you must unset the “s” (aka search string) query element, otherwise WordPress will continue its standard search function and include an “AND” SQL query for search strings in post titles, excerpts and content – which will almost certainly exclude the additional posts you added to the WP_Query.

add_action( 'pre_get_posts', 'extend_search' );

function extend_search( $wp_query ) {

	if ( ! $wp_query->is_main_query() ) {

		return;
	}

	if ( ! empty( $wp_query->is_search ) ) {

		/**
		 * Add code to modify the $wp_query here (ie. add a
		 * 'post__in' array to the query, for example).
		 */

		/**
		 * Bypass the default WordPress "AND" SQL query.
		 */
		unset( $wp_query->query[ 's' ] );
	}
}

Unsetting the “s” (aka search) query element (shown above) sounds like a good idea, but if you do, then the query is no longer a search, and the search string is no longer available. Displayed search results on the front-end usually start with something like “Search results for: ‘ABC'”. If you unset the “s” query element, then that becomes “Search results for: ””.

Instead of unsetting the “s” query element, you could hook the ‘posts_search’ filter to remove the “AND” SQL query, but this breaks the WordPress standard search feature (which includes posts if the search string is located in the title, excerpt or content). Alternatively, with a preg_replace() you could turn the search “AND” SQL query into an “OR”, but in the end – even if you were to find an elegant way to modify the WP_Query in “pre_get_posts” and work around the “s” query element issue – there remains one big problem specific to WooCommerce:

Although the WP_Query object is a proper search object (ie. $wp_query->is_search is true and the “s” query element is defined) – for both front-end and back-end admin searches – the WooCommerce Products > All Products > Search products button does not create a proper WP_Query search object. I’ve seen some truly krufty code to handle the WP_Query object from the WooCommerce Search products button – but why would you want to create a second function / method to handle that one exception anyway? Instead, I suggest bringing both the WordPress search and the WooCommerce search exception back into a single reliable object property value, and then applying the same SQL query to both types of searches.

First, we need to detect both the WordPress search and the WooCommerce search from the Search products button (and save the search string):

add_action( 'pre_get_posts', 'detect_search' );

function detect_search( $wp_query ) {

	if ( ! $wp_query->is_main_query() ) {

		return;
	}

	/**
	 * Save the WordPress front-end and admin search query.
	 */
	if ( ! empty( $wp_query->is_search ) ) {

		$wp_query->saved_search_s = array(
			's' => isset( $wp_query->query[ 's' ] ) ?
				$wp_query->query[ 's' ] : '',
		);

	/**
	 * Save the WooCommerce admin product search.
	 */
	} elseif ( ! empty( $wp_query->query[ 'product_search' ] ) ) {

		$wp_query->saved_search_s = array(
			's' => isset( $_GET[ 's' ] ) ?
				sanitize_text_field( $_GET[ 's' ] ) : '',
		);
	}
}

Second, we need to hook the ‘posts_search’ filter and modify the SQL query to include any additional post / product IDs, etc.

add_filter( 'posts_search', array( $this, 'extend_search' ), 10000, 2 );

public function extend_search( $search, $wp_query ) {

	if ( ! $wp_query->is_main_query() ) {

		return $search;

	} elseif ( empty( $wp_query->saved_search_s[ 's' ] ) ) {

		return $search;
	}

	/**
	 * Add code here to use the $wp_query->saved_search_s[ 's' ] value
	 * and define $product_ids as an array of post IDs.
	 */

	global $wpdb;

	/**
	 * Create an SQL query to include the product IDs.
	 */
	$post_id_query = ' OR (' . $wpdb->posts . '.ID IN (' . implode( ', ', $product_ids ) . ')) ';

	if ( empty( $search ) ) {

		$search = $post_id_query;

	} elseif ( preg_match( '/^( *AND  *\()(.*)(\)) *$/', $search, $matches ) ) {

		$search = $matches[ 1 ] . '(' . $matches[ 2 ] .') ' . $post_id_query . $matches[ 3 ];
	}

	return $search;
}

By using a preg_match() to modify the “AND” SQL query, we allow the normal WordPress search feature to work as intended (return posts and products with the search string in their title, excerpt or content), along with any additional post / product IDs we may want to include.

Schema JSON-LD Markup for Better WooCommerce SEO

The WooCommerce plugin by itself does not provide sufficient Schema JSON-LD markup for Google Rich Results. The WPSSO Core Premium edition reads WooCommerce product data and provides complete Schema Product JSON-LD markup for Google Rich Results, including additional product images, product variations, product information (brand, color, condition, EAN, dimensions, GTIN-8/12/13/14, ISBN, material, MPN, pattern, size, SKU, volume, weight, etc), product reviews, product ratings, sale start / end dates, sale prices, pre-tax prices, VAT prices, shipping rates, shipping times, and much, much more.

Find this content useful? Share it with your friends!