I'm currently trying to extend the WooCommerce product search so that the search uses my custom field I've created within the general product data section:
add_action( 'woocommerce_product_options_general_product_data', 'woocommerce_product_options_general_product_data_action', 9999, 0 );
function woocommerce_product_options_general_product_data_action(): void {
global $post;
echo '<div >';
woocommerce_wp_text_input( [
'id' => '_pdc_notch',
'wrapper_class' => '',
'label' => 'Notch',
'desc_tip' => true,
'type' => 'text',
'description' => 'Enter notch PDC',
'value' => get_post_meta( $post->ID, '_pdc_notch', true )
] );
echo '</div>';
}
add_action( 'woocommerce_process_product_meta', 'woocommerce_process_product_meta_action', 10, 1 );
function woocommerce_process_product_meta_action( int $product_id ): void {
$pdc_notch = sanitize_text_field( wp_unslash( $_POST['_pdc_notch'] ?? null ) );
update_post_meta( $product_id, '_pdc_notch', $pdc_notch );
}
With the above code, I can add and save my custom field. Now I've extended the search with the below hook:
add_action( 'woocommerce_product_query', 'woocommerce_product_query_action', 10, 2 );
function woocommerce_product_query_action( WP_Query $q, object $instance ) {
if ( ! is_admin() && $q->is_main_query() && $q->is_search() ) {
$meta_query = $q->get( 'meta_query' );
$meta_query[] = [
'key' => '_pdc_notch',
'value' => $q->query['s'],
'compare' => 'LIKE'
];
$q->set( 'meta_query', $meta_query );
}
}
When I set a text like "Iamabigtestword" to my field and put it in the search, I'm still getting nothing. What am I missing? I really can't find the issue here. Normally, the hook should work (regarding an answer from StackOverflow: Include custom fields value in woocommerce search)
Note
You can copy/paste the code directly in the functions.php
file of your child theme to test it, since it has no dependencies and should add the field to the Products > Product > General
tab at the end.
CodePudding user response:
When you run a search query on wordpress, the 's'
argument which is the keyword you're searching for, gets included by default. What does that mean? It means the query tries to find the keyword in the title OR in the content. You're trying to intercept the query and change it to find your keyword in the title OR the content AND in meta_query. Which means 's'
has to be true (either in the title or in the content) AND 'meta_query'
has to be like your keyword! This scenario will never returns any results UNLESS either we write our custom sql or we alter the way wp_query
queries the database.
So to summarize:
- First scenario,
's'
has to return something (It finds searched keyword either in the title OR in the content). This is the default behavior ofwp_query
. - Second scenario, both
's'
AND'meta_query'
have to return something at the same time. For this scenario we would need to write our ownsql query
. - Third scenario, limit our search only to the products with a custom meta value set.
Since I'm not sure which scenario you're interested in, I'll solve the third scenario because I think it's closer to what you've asked for and it involves setting the custom meta field:
add_action('woocommerce_product_query', 'woocommerce_product_query_action');
function woocommerce_product_query_action($q)
{
if (!is_admin() && $q->is_search())
{
$q->set('meta_key', '_pdc_notch');
$q->set('meta_value', $q->query['s']);
$q->set('meta_compare', 'LIKE');
$q->set('s', '');
}
}
CodePudding user response:
I think I have found a way after some testing. First, I've debugged the WC function where the action gets applied, but changed my approach since I was making no progress. I've now extended the post search via the given WordPress filter:
add_filter( 'posts_search', 'filter_posts_search', 10, 2 );
/**
* Extend product search to use the new custom field within search
*
* @param string $search
* @param WP_Query $query
*
* @return string
*/
function filter_posts_search( string $search, WP_Query $query ): string {
global $wpdb;
if ( empty( $search ) || ! ( isset( $query->query_vars['s'], $query->query_vars['post_type'] ) && ! empty( $query->query_vars['s'] ) && $query->query_vars['post_type'] === 'product' ) || is_admin() || ! is_search() || ! is_main_query() ) {
return $search;
}
$product_ids = [];
$products = wc_get_products( [
'post_type' => 'product',
'limit' => - 1,
'meta_key' => '_pdc_notch',
'meta_value' => esc_attr( $query->query_vars['s'] ),
'meta_compare' => 'LIKE' // or '='
] );
/**
* @var WC_Product $product
*/
foreach ( $products as $product ) {
$product_ids[] = $product->get_id();
}
$product_ids = array_unique( $product_ids );
if ( count( $product_ids ) > 0 ) {
$search = str_replace( 'AND (((', "AND ((({$wpdb->posts}.ID IN (" . implode( ',', $product_ids ) . ")) OR (", $search );
}
return $search;
}
I hope it helps someone! If someone finds a better approach, I can test it for you.