Hi I have to setup a rather complicated search that involves a repeatable group.
The site has an items catalog build with Toolset CPT and custom fields.
The items CPT uses custom fields and repeatable groups.
I've seen this thread:
https://toolset.com/forums/topic/front-end-search-for-repeating-group-field/
The proposed solution involves creating a view of related posts (group field) instead of listing the parent post.
It's very clear how it can work but I believe that I can't take this approach in my case.
a) I have to search both in the parent post and the field group
b) The field is repeatable thus can have more than 1 instance/row and I think this would result in duplicated results
Please correct me if I'm not evaluating the solution correctly.
I was then thinking that maybe something can be done by filtering the query and doing something at that level
e.g. build a view of the parent type and in the filter query the related post type and add a filter on the main query for the parent id to limit
or build a view of repeatables and the completely redo the query to use aggregation on the parent post id
I'm just brainstorming the problem but I don't want to go down the filter road unless you guys can confirm me that is a viable approach.
In this post
https://toolset.com/forums/topic/front-end-search-for-repeating-group-field/#post-1090420
Christian mentions a more complicated approach on the problem, is it something that could help me?
a) I have to search both in the parent post and the field group
Unfortunately Views does not support this search filter configuration. In the current software, it is only possible to filter a View of some post type using the fields/terms directly applied to that post type (not in its RFGs). Therefore, it is not possible to add custom search filters from both the parent post type and the RFG in the same View. You can either filter a View of RFGs by the fields in that RFG, or you can filter a View of the parent post type by fields in that parent post type (not its RFGs). This feature is on the long-term road map, but not currently scheduled for release in the near future.
b) The field is repeatable thus can have more than 1 instance/row andeI think this would result in duplicated results
Christian mentions a more complicated approach on the problem, is it something that could help me?
Yes, possibly. We provide some PHP filters for Views that can help here, like wpv_filter_query_post_process: https://toolset.com/documentation/programmer-reference/views-filters/#wpv_filter_query_post_process
This filter allows you to inspect and manipulate the filtered results of the custom search, using custom PHP, before they are displayed on the front-end of the site. You could use the toolset_get_related_post API - https://toolset.com/documentation/customizing-sites-using-php/post-relationships-api/#toolset_get_related_post - in conjunction with this filter to process each of those filtered RFG results and omit any RFG results that would produce duplicate parent posts on the front-end. This filter has some side effects you should be aware of. It can cause confusion with pagination and result counts, for example, so it must be used cautiously in Views that include pagination and result counts. It also can cause some confusion when sorting results on the front-end, since front-end sorting would be based on the RFG, not the parent post.
Even in this scenario, though, it's still not possible to create and implement front-end filters for both the parent and RFG fields using built-in Views filters. The parent field filters would have to be created using custom HTML, PHP and possibly JavaScript, and would likely not be integrated with Views AJAX updates or other advanced filtering features like "Show only available options for each input".
Hey thanks for the detailed answer.
a)
This feature would be a killer!
b)
Yes I understand the approach, I use wpv_filter_query_post_process in another filter to group results by year on page.
What about if I completely rewrite the search query when those search fields are involved? Possibly rewrite the whole thing with a custom query instead of wp_query or do a 2 step approach, first run the query on the group, get the unique parent ids and the limit the query on the parents by those ids?
Not asking you to code it if you don't have any viable example already but I'm trying to understand if it's worth putting effort on it or if I have to ditch Toolset for the catalog section and use something else building some concoction with woocommerce or something.
Well yes, just about anything is possible with extensive understanding of WordPress and PHP. However, there is no JavaScript API for Views filters so there is no support available for implementing your own custom filtering system with Views filters, and no code examples that explain how to do it. Frankly you would be creating the front-end filters from scratch, integrating them with a query created from scratch, and rendering the results from scratch. Views will be mostly useless in this scenario, other than maybe using its PHP APIs for rendering templates for each result: https://toolset.com/documentation/programmer-reference/views-api/
I can show you how to use any Toolset APIs that you need, but the vast majority of this scenario will be custom code that falls outside the scope of what we support here.
Proof of concept.
Do you see any more efficient way to query the rfg and get the rfg parents?
Am I doing anything here that might piss off the view?
// Put the code of your snippet below this comment.
add_filter('wpv_filter_query', 'enhance_product_search', 10, 3);
function enhance_product_search($query_args, $setting, $view_id) {
if($view_id == 795) {
foreach((array)$query_args['meta_query'] as $k=>$v){
// Check if rfg fields are involved
if(isset($v['key']) and $v['key']=='wpcf-height-cm'){
// Query the rfg
$args = array(
'post_type'=> array( 'product-variants' ),
'fields' => 'ids',
'meta_query' => array(
array(
'key' => $v['key'],
'type' => 'UNSIGNED',
'compare' => 'BETWEEN',
'value' => $query_args['meta_query'][$k]['value']
),
)
);
$query = new WP_Query( $args );
$children = array('child' => $query->posts);
// Get the parent (product item)
$parents = toolset_get_related_posts( $children, 'product-variants', array( 'role_to_return' => 'parent', 'return' => 'post_id')) ;
// do I need to filter unique values?
unset($query_args['meta_query'][$k]);
//Restrict the main view query to selected parents but leave the rest of the search alone
$query_args['post_in'] = $parents;
}
}
}
return $query_args;
}
The toolset_get_related_posts query looks pretty good offhand. The only thing that jumps out at me is here:
$query_args['post_in'] = ...
If I'm not mistaken, you need two underscores in post__in:
$query_args['post__in'] = ...
https://developer.wordpress.org/reference/classes/wp_query/#post-page-parameters
Thanks Christian, good catch, I would have probably ended up wasting an hour just to find that mistake!
This is a refined version, moved some stuff out of the foreach loop, some logic to check for results and a trick at the end to force a no result on the main query if the query on the rfg fails to produce results.
I still need to do some more testing but range queries on the single rfg field I'm testing seem to be working, we'll see.
I really hope I can make it work, i've achieved so much with Toolset considering I had almost no knowledge of wordpress development up to a couple of weeks ago, it would be a pity to get stuck here!
// Put the code of your snippet below this comment.
add_filter('wpv_filter_query', 'enhance_product_search', 10, 3);
function enhance_product_search($query_args, $setting, $view_id) {
if($view_id == 795) {
$args = array(
'post_type'=> array( 'product-variants' ),
'fields' => 'ids',
'relation' => 'AND',
'meta_query' => array()
);
foreach((array)$query_args['meta_query'] as $k=>$v){
// Check if rfg fields are involved
if(isset($v['key']) and $v['key']=='wpcf-height-cm'){
// Query the rfg
$args['meta_query'][] = array(
'key' => $v['key'],
'type' => 'NUMERIC',
'compare' => 'BETWEEN',
'value' => $query_args['meta_query'][$k]['value']
);
unset($query_args['meta_query'][$k]);
}
if(isset($v['key']) and $v['key']=='wpcf-height-in'){
// Query the rfg
$args['meta_query'][] = array(
'key' => $v['key'],
'type' => 'NUMERIC',
'compare' => 'BETWEEN',
'value' => $query_args['meta_query'][$k]['value']
);
unset($query_args['meta_query'][$k]);
}
if(isset($v['key']) and $v['key']=='wpcf-diam-cm'){
// Query the rfg
$args['meta_query'][] = array(
'key' => $v['key'],
'type' => 'UNSIGNED',
'compare' => 'BETWEEN',
'value' => $query_args['meta_query'][$k]['value']
);
unset($query_args['meta_query'][$k]);
}
if(isset($v['key']) and $v['key']=='wpcf-diam-in'){
// Query the rfg
$args['meta_query'][] = array(
'key' => $v['key'],
'type' => 'UNSIGNED',
'compare' => 'BETWEEN',
'value' => $query_args['meta_query'][$k]['value']
);
unset($query_args['meta_query'][$k]);
}
}
if(!empty($args['meta_query'])) {
$query = new WP_Query( $args );
$children = array('child' => $query->posts);
// Get the parent (product item)
if(!empty($query->posts)) {
$parents = toolset_get_related_posts( $children, 'product-variants', array( 'role_to_return' => 'parent', 'return' => 'post_id')) ;
// do I need to filter unique values?
//print_r($parents);
//Restrict the main view query to selected parents but leave the rest of the search alone
$query_args['post__in'] = $parents;
} else {
// if filtering by rfg field but has no results block the main query from giving any result
$query_args['post__in'] = array(0);
}
}
}
return $query_args;
}
Looks pretty good! I don't see anything obviously wrong here, but if you start seeing strange results we can investigate in more detail.
Seems to be working well, at least on the small scale I'm testing it on. Importing the whole catalog or at least a significant part of it is one of the next steps I have to take in this project, that will give me the chance to test the performance and all the edge cases more thoroughly.
Just to leave some notes on the script in case somebody else stumbles on it:
- it can be worth checking for isset($query_args['meta_query']) along with the view id, depending how the search is configured
- I hardcoded the type and compare values for the meta queries but they might as well be taken directly from $query_args['meta_query']
- actually I could probably take the various meta_query arrays and use them fully instead of rewriting the array, this would support changes made through the interface
- with some smart naming of the search parameters (e.g. something you could regex or partial match during the foreach) it might be possibile to identify the keys that are involved and programmatically build the sub query without having to hardcode anything
- when adding the [post__in] an array merge would be smarter in case you configure the view to actually limit the results by post ids
I also have to point out that I'm not using neither pagination nor ajax in the view. I'm not sure about how views ajax works but at least on the pagination I don't see why it wouldn't work since the main query gets exactly the results it's supposed to.
Okay thanks for the details, that's helpful for other users. As far as AJAX and pagination, you are correct - since you're filtering arguments before the main query using wpv_filter_uery, pagination and AJAX results should continue to work as expected. The wpv_filter_query_post_process filter I mentioned happens after the main query, so that's where you would need to be concerned about pagination.