No "main query" on WooCommerce shop page - wordpress

I'm trying to add filters to both the shop page and product category pages in WooCommerce to allow users to filter the product list by product_tag.
First I hook into woocommerce_before_shop_loop to output a list of tags before the products list
public function render_category_tag_id_filters(): void {
// Get a list of all product IDs
if ( is_shop() ) {
$product_ids = wc_get_products(
[
'limit' => PHP_INT_MAX,
'return' => 'ids',
]
);
// Get a list of product IDs in this category
} else if ( is_product_category() ) {
$product_cat = get_query_var( 'product_cat' );
$product_ids = wc_get_products(
[
'category' => $product_cat,
'limit' => PHP_INT_MAX,
'return' => 'ids',
]
);
// Don't continue
} else {
return;
}
// Render template part here etc. etc.
}
add_action( 'woocommerce_before_shop_loop', [ $this, 'render_category_tag_id_filters' ] );
Clicking on the filters builds up a comma separated list of product_tag IDs in a query var that looks like...
/product-category/candles/?tag_ids=31,32,35
Then I'm hooking into woocommerce_product_query_tax_query to filter the main product list using the values from the query var
public function apply_category_tag_id_filters( array $tax_query, \WC_Query $wc_query ): array {
// Abort if not the main query
if(
! ( $wc_query->get_main_query() instanceof \WP_Query )
|| ! $wc_query->get_main_query()->is_main_query()
) {
return $tax_query;
}
// Get the filter tag IDs
$filter_tag_ids = $this->get_category_filter_tag_ids();
if (
! is_array( $filter_tag_ids )
|| 0 === count( $filter_tag_ids )
) {
return $tax_query;
}
//
$tax_query[] = array(
'taxonomy' => 'product_tag',
'field' => 'term_id',
'terms' => $filter_tag_ids,
'operator' => 'AND',
);
//
return $tax_query;
}
This is where I'm coming unstuck. It works perfectly on the product category pages. The problem is that none of the queries on the main shop page seem to be flagged as the main query. That means that my filter function quits out early. If I remove the check for the main query it works but my list of tag filters at the top of the page is also filtered by the active tag ID
Is this a bug? Am I not targeting the main query correctly?

Related

Woocommerce search in custom fields

i have a woocommerce e-shop website. My search is searching only in product title and sku fields. I want to search also in some custom fields (like "_barcode" and "_mpn").
I have this script but when i use it in fuctions.php i can't search by product title.
function search_filter( $query ) {
$key_fields = array ( '_barcode', '_mpn', '_sku' );
$value_field = $query->query_vars['s'];
$query->query_vars['s'] = '';
if ( $value_field != '' ) {
$filter_query = array( 'relation' => 'OR' );
foreach ( $key_fields as $one_field ) {
array_push ( $filter_query , array (
'key' => $one_field,
'value' => $value_field,
'compare' => 'LIKE'
) );
}
$query->set( 'meta_query' , $filter_query );
}
}
add_filter( 'pre_get_posts' , 'search_filter');
Why i can't search the product title when i use this script?
Any idea?
Thank you.

Customizing Woocommerce Loop Query

I want to display my woocommerce products on two different sections on the site i.e. the shop page and on an archive page i created using Custom Post UI plugin, called artists.
Using the Advanced Custom Fields plugin, i created a field that will attach each product created to an individual artist. The plan now is, on each artist page, pull products for the artist.
This is what i have so far. I hook into woocommerce_product_query, check if i'm on the single artist page and implement my meta query:
function artist_products( $query )
{
if( is_singular('artists') )
{
$meta_query = ( is_array( $query->get('meta_query') ) ) ? $query->get('meta_query') : [];
$meta_query[] = array(
'key' => '_fld_select_artist',
'value' => get_the_ID(),
);
$query->set( 'meta_query', $meta_query );
}
}
add_action( 'woocommerce_product_query', 'artist_products' );
This is not working. When i visit my single artist page, i get a 500 server error. What I'm i missing?
woocommerce_product_query was breaking my site for some reason so i switched to pre_get_posts:
function artist_products( $query )
{
if ( $query->get('post_type') == 'nav_menu_item' )
{
return $query;
}
if( ! is_admin() && is_singular('artists') )
{
$meta_query = ( is_array( $query->get('meta_query') ) ) ? $query->get('meta_query') : [];
$meta_query[] = array(
'key' => '_fld_select_artist',
'value' => get_the_ID(),
'compare' => '=',
);
$query->set( 'meta_query', $meta_query );
}
}
add_action( 'pre_get_posts', 'artist_products' );

How to Populate a Drop-down field in WP

I have a gravity form form on my WP site and I recently changed a free text field into a drop down field.
The website is a store which hold several categories of goods and I want my drop-down to show the user all the possible categories he can choose from.
Please assist in how to "pull" the categories into the drop-down list.
Thanks in advance.
You can do using some filters of gravity form, code is following
// Here 1 is form id
add_filter( 'gform_pre_render_1', 'populate_category' );
add_filter( 'gform_pre_validation_1', 'populate_category' );
add_filter( 'gform_pre_submission_filter_1', 'populate_category' );
add_filter( 'gform_admin_pre_render_1', 'populate_category' );
function populate_category( $form ) {
foreach ( $form['fields'] as &$field ) {
if ( $field->type != 'select' || strpos( $field->cssClass, 'populate-category' ) === false ) {
continue;
}
// Get category list
$categories = get_categories( array(
'orderby' => 'name',
'order' => 'ASC'
) );
$choices = array();
foreach( $categories as $category ) {
$choices[] = array( 'text' => $category->name, 'value' => $category->name );
}
$field->placeholder = 'Select a Category';
$field->choices = $choices;
}
return $form;
}
This is working perfectly its tested code.

WP API V2: Query posts by ACF

I would like to query my posts by filtering for a custom meta added with Advanced Custom Fields. It's a boolean meta, so every post will have something like:
GET http://localhost/wp-json/wp/v2/posts
{
...
"acf" : {
"highlight" : true
}
...
}
I'm not able to filter by this meta value, even if I exposed meta_key and meta_value to the REST API in function.php:
function my_add_meta_vars ($current_vars) {
$current_vars = array_merge ($current_vars, array ('meta_key', 'meta_value'));
return $current_vars;
}
add_filter ('rest_query_vars', 'my_add_meta_vars');
But if I try:
GET
http://localhost/wp-json/wp/v2/posts?filter[meta_key]=highlight&filter[meta_value]=true
I see all the posts as if the filter is ignored.
I was able to get this solved with this customization:
add_filter( 'rest_query_vars', function ( $valid_vars ) {
return array_merge( $valid_vars, array( 'highlight', 'meta_query' ) );
} );
add_filter( 'rest_post_query', function( $args, $request ) {
$highlight = $request->get_param( 'highlight' );
if ( ! empty( $highlight ) ) {
$args['meta_query'] = array(
array(
'key' => 'highlight',
'value' => $highlight,
'compare' => '=',
)
);
}
return $args;
}, 10, 2 );
And do a query in this way (highlight is acf boolean)
GET /wp-json/wp/v2/posts?highlight=1

How to order categories in WordPress?

I use wp_list_categories() to get the list of all the categories and generate the navigation bar. Is there a way to order these categories in a particular order other than alphabetical ordering.
eg: Connect, News & Views, Q&A, Hello Startup, Startup 101...
Most themes don't use the description of the category for anything. Easy workaround I did was to use numbers in description. The top post here currently has some jQuery hack from here, it's unneeded.
You can add custom order fields I suppose as well.
Just
$categories = get_categories( array(
'orderby' => 'description',
'order' => 'ASC'
) );
Technical approach
The problem in wordpress core is that the table wp_terms has no term_order column. That means, standard wordpress does not support the custom term order. If you look at this WP database structure you can find the table wp_term_relationships. This table is responsible for the relationships between posts and the taxonomy (your categories) AND this table has a term_order column.
Now, with a simple SQL statement ALTER TABLE wp_terms ADD term_order INT(11) NOT NULL DEFAULT 0 (not forget the $wpdb->wp_terms variable) you can add a column to the table, which is responsible for your custom category order. Then you can put your custom category order in this two columns of wp_term_relationships and wp_terms. When all is finished, you can hook into the filter of get_terms_args and change the orderby to term_order.
Here a list of all relevant links for the technical approach:
https://codex.wordpress.org/Database_Description for the wp database structure
https://codex.wordpress.org/Class_Reference/wpdb for the $wpdb->wp_terms
https://developer.wordpress.org/reference/hooks/get_terms_args/ for the WP filter
A plugin can do the job for you
Check my plugin to solve this: WordPress Real Categories Management. WP RCM creates an extra field term_order on the wp terms table. It also brings a lot of other useful features as you can see in the screenshot below. It allows you to organize your wordpress categories in a nice way. It is easy to use, just drag&drop your categories and move it to a specific order. The plugin works in all Custom post types.
From the product description i can quote. If you want to try the plugin, there is also a demo on the plugin page.
There are a lot of free plugins
This can be solved with a lot of free plugins available within the wordpress.org plugin repository. Simply search for "category order" in your Wordpress Dashboard > Plugins > Install.
This is inbuilt in wordpress_wp_list_categories
wp_list_categories('orderby=name');
I think that would help you out
I did it generating several term lists. I call it later by my own order. I'm a PHP beginner.
First, I store, in a different variable, the ID for each category term:
$terms = get_terms('my_taxonomy', 'hide_empty=0');
foreach ( $terms as $term ) {
${$term->slug} = get_term_by('slug', $term->slug, 'product_cat');
${$term->slug.'_array'} = (array)${$term->slug};
${$term->slug.'_array_id'} =${$term->slug.'_array'}['term_id'];
};
Then, I create several args for each wp_list_categories() excluding, with this variable the terms I want to:
$args = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'show_option_all' => 'Show all',
'exclude' => array( $term1_array_id, $term2_array_id )
);
$args_1 = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'exclude' => array( $term3_array_id, $term4_array_id, $term1_array_id )
);
$args_2 = array(
'taxonomy' => 'my_taxonomy',
'orderby' => 'name',
'show_count' => true,
'pad_counts' => false,
'hierarchical' => true,
'title_li' => '',
'hide_empty' => 0,
'exclude' => array( $term1_array_id, $term4_array_id, $term5_array_id )
);
Finally, I can call separately each term list:
<ul>
<?php wp_list_categories( $args ); ?>
<?php wp_list_categories( $args_1 ); ?>
<?php wp_list_categories( $args_2 ); ?>
</ul>
Use Category Order and Taxonomy Terms Order free plugin
I didn't find anything so I constructed my own method. I abstracted it away in an abstract class for my plugin, hence the extra code, but you can pull the methods.
The main method to look at is format_hierarchy()
// The parent class
abstract class Taxonomy {
protected bool $_terms_loaded;
protected array $terms;
protected array $formatted_terms;
public function __get( $property ) {
if ( $property === 'formatted_terms' ) {
if ( !isset( $this->formatted_terms ) ) $this->format_hierarchy();
return $this->formatted_terms;
}
if ( substr( $property, 0, 1 ) === '_' ) die( 'Cannot get private properties' );
if ( property_exists( $this, $property ) )
return $this->$property;
}
/**
* Formats the taxonomy's terms into a hierarchy of term_blocks and saves the value as a property to the class
*
* #return array an array of `[term_taxonomy_id:number] => term_block` like:
* array(
* array( // parent $term_block
* 'term' => WP_Term,
* 'children' => array( $term_blocks… )
* ),
* …$term_blocks…
* )
*/
public function format_hierarchy():array {
if ( !$this->_load_terms() ) return [];
// Holds a reference to every category, parents and children
$term_blocks = [];
// Holds a reference to every top most level category
$parents = [];
foreach ( $this->terms as $term ) {
// Add itself to the list of all categories
$term_block = [
'children' => [],
'term' => $term
];
// Add itself to the array of all categories
if ( !isset( $term_blocks[ $term->term_taxonomy_id ] ) )
$term_blocks[ $term->term_taxonomy_id ] =& $term_block;
// If it's a child category…
if ( $term->parent !== 0 ) {
// If the parent hasn't been created yet, create it
if ( !isset( $term_blocks[ $term->parent ] ) )
$term_blocks[ $term->parent ] = [
'children' => [],
'term' => null
];
$term_blocks[ $term->parent ][ 'children' ][] =& $term_block;
} else
// Otherwise it's a parent
$parents[ $term->term_taxonomy_id ] =& $term_blocks[ $term->term_taxonomy_id ];
// set the term block's WP_Term property
$term_blocks[ $term->term_taxonomy_id ][ 'term' ] =& $term;
// This is needed so that the loop doesn't readd the same reference over and over again
unset( $term ); unset( $term_block );
}
return $this->formatted_terms = $parents;
}
/**
* Given a WP_Term property value, and a property key, recursively searches through all of the terms for it
*
* #property $term_val mixed The property value to find
* #property $prop string The property key for the value
* #property $with_parent ?boolean Whether to return the top level parent as well
* #property $term_blocks ?array Array of term blocks
* #return array If $with_parent is true, returns an [ $found_term_block, $top_level_parent ]
* Otherwise returns only the found term block
*/
public function find_term_by(
$term_val,
string $prop,
bool $with_parent = false,
$term_blocks = null
):?array {
if ( is_null( $term_blocks ) ) $term_blocks = $this->formatted_terms;
foreach ( $term_blocks as $term_block ) {
if ( $term_block[ 'term' ]->{$prop} === $term_val ) return $term_block;
if ( count( $term_block[ 'children' ] ) &&
( $found = $this->find_term_by( $term_val, $prop, false, $term_block[ 'children' ] ) )
) return $with_parent ? [ $found, $term_block ] : $found;
}
return null;
}
/**
* Loads the taxonomy terms once from the DB
*/
protected function _load_terms():bool {
if ( isset( $this->_terms_loaded ) ) return $this->_terms_loaded;
$this->terms = get_terms(
array(static::$taxonomy),
array(
'hide_empty' => false,
)
);
if ( !$this->terms ) {
ClassErrorHandler::handle_exception(
new \WP_Error( 500, 'Failed to load category terms: \'' . static::$taxonomy . '\'' )
);
}
return $this->_terms_loaded = !!$this->terms;
}
}
// The Implementation
class TaxonomyProductService extends Taxonomy {
public static string $taxonomy;
public static string $slug;
/**
* To be called upon taxonomy registration long before any instance is required
*/
public static function define_taxonomy( string $slug, string $taxonomy ) {
static::$slug = $slug;
static::$taxonomy = $taxonomy;
}
}
Right after registering the custom taxonomy I call
TaxonomyProductService::define_taxonomy( 'url-slug', 'product-service' );
And finally how it's used
$tax = new TaxonomyProductService();
$terms = $tax->formatted_terms;
// search for a term whose slug === `my-term`, and return the parent category
list( $current_term, $parent_term ) = $tax->find_term_by( 'my-term', 'slug', true );
For the benefit of future visitors, here’s the easy solution to this problem:
There are now a number of plugins that allow you to order categories or other custom taxonomies in WordPress. You can see some of them in the WordPress plugin directory’s “category order” tag page. I can personally confirm that Custom Taxonomy Order NE plugin does the job.

Resources