WooCommerce REST API : query products by multiple attributes - wordpress

According to the API : http://woocommerce.github.io/woocommerce-rest-api-docs/#list-all-products we can filter products by a single attribute. But Is it impossible to search by multiple attributes through API ?
Example : "I want red shirts". Here attribute is color and attribute term is red. To accomplish the search, the query string goes like this : products?category=17&attribute=pa_color&attribute_term=22&
And we get the red shirts only.
But for "I want red medium shirts" , here an additional size attribute with value medium is encountered. And according to the API there is no way to associate both the color and the size attributes in the query string. So the query -
products?category=17&attribute=pa_color&attribute_term=22&attribute=pa_size&attribute_term=24&
returns all products from the store
Is there any workaround ?

I have written custom query that does it all :D
This is a callback function. All you need to do is create new endpoint with this callback:
Here is a documentation:
category - string (category slug) - filter by products category.
per_page - int (default - from admin) - show items on one page.
offset - int (default - 1) - show page number.
order - string (ASC/DESC, default: desc) - order products ascending or descending.
orderby - string (name, price, default: name) - order products by key.
filter - array
[pa_attribute_name (string) ] = array of ID's.
[min_price (string) ] = int.
[max_price (string) ] = int.
Example:
/wp-json/go/v1/products/?category=smartphones&filter[pa_brand]=87,88&filter[pa_colour]=17&filter[min_price]=1&filter[max_price]=50&per_page=10&offset=1&order=DESC&orderby=price
Here is a function:
public function get_products_list_callback( \WP_REST_Request $request ) {
$params = $request->get_params();
$category = General_Helper::get_array_value( 'category', $params );
$filters = General_Helper::get_array_value( 'filter', $params );
$per_page = General_Helper::get_array_value( 'per_page', $params );
$offset = General_Helper::get_array_value( 'offset', $params );
$order = General_Helper::get_array_value( 'order', $params );
$orderby = General_Helper::get_array_value( 'orderby', $params );
$output = [];
// Use default arguments.
$args = [
'post_type' => Config::POST_TYPE_SLUG_PRODUCT,
'posts_per_page' => get_option( 'posts_per_page' ),
'post_status' => 'publish',
'paged' => 1,
];
// Posts per page.
if ( ! empty( $per_page ) ) {
$args['posts_per_page'] = $per_page;
}
// Pagination, starts from 1.
if ( ! empty( $offset ) ) {
$args['paged'] = $offset;
}
// Order condition. ASC/DESC.
if ( ! empty( $order ) ) {
$args['order'] = $order;
}
// Orderby condition. Name/Price.
if ( ! empty( $orderby ) ) {
if ( $orderby === 'price' ) {
$args['orderby'] = 'meta_value_num';
} else {
$args['orderby'] = $orderby;
}
}
// If filter buy category or attributes.
if ( ! empty( $category ) || ! empty( $filters ) ) {
$args['tax_query']['relation'] = 'AND';
// Category filter.
if ( ! empty( $category ) ) {
$args['tax_query'][] = [
'taxonomy' => Config::TAXONOMY_SLUG_PRODUCT,
'field' => 'slug',
'terms' => [ $category ],
];
}
// Attributes filter.
if ( ! empty( $filters ) ) {
foreach ( $filters as $filter_key => $filter_value ) {
if ( $filter_key === 'min_price' || $filter_key === 'max_price' ) {
continue;
}
$args['tax_query'][] = [
'taxonomy' => $filter_key,
'field' => 'term_id',
'terms' => \explode( ',', $filter_value ),
];
}
}
// Min / Max price filter.
if ( isset( $filters['min_price'] ) || isset( $filters['max_price'] ) ) {
$price_request = [];
if ( isset( $filters['min_price'] ) ) {
$price_request['min_price'] = $filters['min_price'];
}
if ( isset( $filters['max_price'] ) ) {
$price_request['max_price'] = $filters['max_price'];
}
$args['meta_query'][] = \wc_get_min_max_price_meta_query( $price_request );
}
}
$the_query = new \WP_Query( $args );
if ( ! $the_query->have_posts() ) {
return $output;
}
while ( $the_query->have_posts() ) {
$the_query->the_post();
$output[] = get_the_title();
}
wp_reset_postdata();
return $output;
}
Hope it helps

Try to request like this:
products?attribute=pa_color&attribute_term=51,50&per_page=100
Its worked for me.
Latest woocommerce-api wc/v2!

after test on WooRest API v3, you can pass multiple term taxonomy IDs.
You have to pass a string with ids separated by comma, this will be parsed as an array by WooRest API v3.
Like this :
/wp-json/wc/v3/products/?category=1,2,3,4
Don't forget to encode query parameter or use Automattic\WooCommerce package
Enjoy,

I just looked into the woocommerce rest api implementation at
plugins/woocommerce/includes/api/class-wc-rest-products-controller.php
file but unfortunately their codes doesn't support multiple attributes.
But you can write your own query to achieve your goal.
$args = array(
'post_type' => 'product',
'tax_query' => array(
array(
'relation' => 'AND',
array(
'taxonomy' => 'pa_color',
'field' => 'term_id',
'terms' => array( 'red' ),
),
array(
'taxonomy' => 'pa_size',
'field' => 'term_id',
'terms' => array( 'Long' ),
),
),
),
);
$products = new WP_Query( $args );
print_r($products);

Related

Woocommerce sync grouped product attributes with childrens

To achieve this, i have written 2 functions one for grouped product save and another for child product save.
1 - Adding all child attributes to the grouped product on grouped product save:
add_action('woocommerce_after_product_object_save', 'nd_update_group_product_attributes_before_save_func', 9993, 2);
function nd_update_group_product_attributes_before_save_func($product, $data_store) {
// echo 'has category <pre>';var_dump($product); echo '</pre>';die;
// exit if not the target post type
if ('product' !== $product->post_type) {
return;
}
// $product_type = $product->get_type();
$product_type = $product->post_type;
if ($product->is_type('grouped')) {
$group_product_id = $product->get_id();
nd_sync_child_attribute_to_group($group_product_id, 'pa_bedrooms');
}
}
function nd_sync_child_attribute_to_group($group_product_id, $attribute_slug){
$group_product = wc_get_product($group_product_id);
$child_product_ids = $group_product->get_children();
$all_child_attributes = array();
if($child_product_ids){
foreach ($child_product_ids as $child_product_id) {
$child_product = wc_get_product($child_product_id);
$child_attributes = wc_get_product_terms( $child_product_id, $attribute_slug, array( 'fields' => 'names' ) );
$all_child_attributes = array_unique(array_merge($all_child_attributes, $child_attributes));
}
}
if ($all_child_attributes) {
$group_attributes = wc_get_product_terms( $group_product_id, $attribute_slug, array( 'fields' => 'names' ) );
if($group_attributes){
wp_remove_object_terms( $group_product_id, $group_attributes, $attribute_slug );
}
foreach ($all_child_attributes as $attr) {
wp_set_object_terms($group_product_id, $attr, $attribute_slug, true);
}
}
}
2 - Adding all child attributes to the grouped product on child product save by triggering grouped product save.
add_action('woocommerce_update_product', 'nd_update_group_product_attributes_on_child_update', 1002, 2);
function nd_update_group_product_attributes_on_child_update($product_id) {
$product = wc_get_product( $product_id );
// exit if not the target post type
if ( !$product->is_type('simple') || !has_term( 'home-design-floor-plans', 'product_cat' , $product_id )) {
return;
}
if ( $product->is_type('simple') ) {
$children_id = $product->get_id();
$group_args = array(
'post_type' => 'product',
'meta_query' => array(
array(
'key' => '_children',
'value' => 'i:' . $product->get_id() . ';',
'compare' => 'LIKE',
)
),
'fields' => 'ids' // THIS LINE FILTERS THE SELECT SQL
);
$parent_ids = get_posts( $group_args );
if($parent_ids){
foreach ($parent_ids as $parent_grouped_id){
if($parent_grouped_id){
$parent_product = wc_get_product( $parent_grouped_id );
if($parent_product){
$parent_product->save();
}
}
}
}
}
}
I have an issue with this once I add an attribute value to a grouped product, it was not showing on the admin area group product edit > attributes.
Is there anything I missed or is there any better way to achieve this?
All I want is to sync child product attributes to parent i.e. group product.

Query to get Attributes based on Product Category selected in WooCommerce?

I need to implement custom functionality like "Product filter by Attributes" widget works in woocommerce.
For example in Product category page:
In Parent Category like Clothing, it loads all the attributes filter (pa_color , pa_size).
Now, when you check sub-category of that parent category i.e., Hoodies. It gets filtered and loads only related attributes (pa_color).
Please suggest the query to achieve this requirement.
This is how I am getting the data as per my requirement :
$filter_raw = array();
$attrs_raw = wc_get_attribute_taxonomy_names(); // Getting data of attributes assign in backend.
$cat_name = get_term($request['category'], 'product_cat', ARRAY_A ); //Category data by category ID.
$args = array(
'category' => array($cat_name['slug'] )
);
foreach( wc_get_products($args) as $product ){
foreach( $product->get_attributes() as $attr_name => $attr ){
$filter_raw[] = $attr_name;
if(is_array($attr->get_terms())){
foreach( $attr->get_terms() as $term ){
$terms_raw[] = $term->name;
}
}
}
}
$filters = array_unique(array_intersect((array)$filter_raw,(array)$attrs_raw)); //Filtering the attributes used by products in particular category
if(is_array($filters)){
foreach ( $filters as $filter ){
$terms = get_terms( $filter );
if ( ! empty( $terms ) ) {
$return['items'][ $filter ] = array(
'id' => $filter,
'type' => 'checkbox',
'label' => $this->decode_html( wc_attribute_label( $filter ) ),
);
foreach ( $terms as $term ) {
if(in_array($term->name,$terms_raw)){ //Filtering the terms from attribute used by the products in a category and showing required result.
$return['items'][ $filter ]['values'][] = array(
'label' => $this->decode_html( $term->name ),
'value' => $term->slug,
);
}
}
}
}
}
print_r($return);

Show posts of a specific category first in WordPress loop

I'd like to show posts from a category first in the homepage and then continue the default order of WordPress posts.
Is it even possible?
I've tried using 2 loops and filter the first one with the category that I want however I don't think that the pagination will work as expected.
Try this (untested code, though).
$total_posts_to_display = 10; // Change accordingly
$id_of_category_1 = 4; // Change accordingly
$filtered_posts = array(); // Array to be filled in with all posts, ordered by "category 1" first
$posts_in_category_1 = get_posts( array(
'numberposts' => $total_posts_to_display,
'category' => $id_of_category_1
) );
$ramaining_posts_number = $total_posts_to_display - count( $posts_in_category_1 );
if ( $ramaining_posts_number > 1 ) {
$excluded_post_ids = array();
foreach( $posts_in_category_1 as $ep ) {
array_push( $excluded_post_ids, $ep->ID );
}
$remaining_posts = get_posts( array(
'numberposts' => $ramaining_posts_number,
'exclude' => $excluded_post_ids
) );
} else {
$remaining_posts = array();
}
$filtered_posts = array_merge( $posts_in_category_1, $remaining_posts );
I have a more sophisticated solution that will support pagination too! You will need to place it in functions.php:
add_action( 'pre_get_posts', 'my_custom_home_post_ordering' );
function my_custom_home_post_ordering( $query ) {
if ( ! is_home() ) {
return;
}
if ( ! $query->is_main_query() ) {
return;
}
$posts_in_category_1 = get_posts( array(
'posts_per_page' => -1,
'category' => 4 // Change accordingly. You may also use 'category_name', instead.
'fields' => 'ids'
) );
$query->set( 'post__in', $posts_in_category_1 );
$query->set( 'orderby', 'post__in' );
}

get_categories order by last post

What is the best and shortest way in Wordpress to get_categories ordered by last posted article?
Means that categories with recent posts should appear first, is it available somehow?
Try it:
function get_sorted_categories( $order_by = 'id', $args = array() ){
global $wpdb;
$category = get_categories( $args );
$order = [
'id' => 'post.ID',
'date' => 'post.post_date',
'modified' => 'post.post_modified',
];
$order_by = $order[ $order_by ];
$q = $wpdb->get_results("SELECT tax.term_id FROM `{$wpdb->prefix}term_taxonomy` tax
INNER JOIN `{$wpdb->prefix}term_relationships` rel ON rel.term_taxonomy_id = tax.term_id
INNER JOIN `{$wpdb->prefix}posts` post ON rel.object_id = post.ID WHERE tax.taxonomy = 'category' AND post.post_type = 'post' AND post.post_status = 'publish' ORDER BY {$order_by} DESC");
$sort = array_flip( array_unique( wp_list_pluck( $q, 'term_id' ) ) );
usort( $category, function( $a, $b ) use ( $sort, $category ) {
if( isset( $sort[ $a->term_id ], $sort[ $b->term_id ] ) && $sort[ $a->term_id ] != $sort[ $b->term_id ] )
$res = ($sort[ $a->term_id ] > $sort[ $b->term_id ]) ? 1 : -1;
else if( !isset( $sort[ $a->term_id ] ) && isset( $sort[ $b->term_id ] ) )
$res = 1;
else if( isset( $sort[ $a->term_id ] ) && !isset( $sort[ $b->term_id ] ) )
$res = -1;
else
$res = 0;
return $res;
} );
return $category;
}
print_r( get_sorted_categories() );
print_r( get_sorted_categories('date') );
print_r( get_sorted_categories('modified') );
Get categories order by (post ID | post date | post modified date). Without any loop and fast!
I think the easiest way would be to loop through all of your posts ordered by post_date and then save the categories to an array. You can then loop through the categories and display them. They will be in order.
Something like this:
<?php
// Initiate array
$cats = array();
// Query arguments
$args = array(
'post_type' => 'post',
'posts_per_page' => -1,
'orderby' = 'post_date'.
'order' => 'DESC'
);
// The query
$query = new WP_Query($args);
// The loop
if($query->have_posts()) {
while($query->have_posts()) {
$query->the_post();
// Get the term object
$term = get_the_category();
// Make sure the term doesn't already exist in the array
if(!array_key_exists($term[0]->ID, $cats)) {
// Add the terms to the array
$cats[$term[0]->ID] = $term;
}
}
}
foreach($cats as $cid => $cat) {
// Loop through the categories here
}
?>
Of course, as mentioned in the comment above you could also do it the other way around and loop through the categories first then sort the array. Something like this could do:
<?php
// Initiate array
$cats = get_categories();
$recent_cats = array();
foreach($cats as $k => $cat) {
// Query arguments
$args = array(
'post_type' => 'post',
'posts_per_page' => 1,
'orderby' = 'post_date'.
'order' => 'DESC',
'cat' => $cat->term_id
);
// The query
$query = new WP_Query($args);
// The loop
if($query->have_posts()) {
while($query->have_posts()) {
$query->the_post();
$date_str = strtotime(the_date());
if(!array_key_exists($date_str, $recent_cats)) {
$recent_cats[$date_str] = $cat->name;
}
}
}
}
krsort($recent_cats);
// Loop through $recent_cats here
?>

Hide specific category in listings

I'm working on a Wordpress theme. The theme is Classifieds theme from premiumpress. The theme has a shortcode to list all the listings. The corresponding shortcode is [LISTINGS].
The function for the shortcode is as follows
/* =============================================================================
[LISTINGS] - SHORTCODE
========================================================================== */
function wlt_page_listings( $atts, $content = null ) {
global $userdata, $wpdb, $CORE; $STRING = ""; $extra=""; $i=1; $stopcount = 4;
extract( shortcode_atts( array( 'query' => '', 'show' => '', 'type' => '', 'cat' => '', 'orderby' => '', 'order' => '', 'grid' => "no", 'featuredonly' => "no"), $atts ) );
// SETUP DEFAULTS
if(!isset($atts['show']) || (isset($atts['show']) && $atts['show'] == "") ){ $atts['show'] = 5; }
if($atts['type'] == ""){ $atts['type'] = THEME_TAXONOMY.'_type'; }
if($atts['orderby'] == ""){ $atts['orderby'] = "post_title"; }
if($atts['order'] == ""){ $atts['order'] = "desc"; }
// DEFAULT FOR LIST STYLE
if($grid == "yes"){
$sstyle = "grid_style";
$STRING .= '<script language="javascript">jQuery(window).load(function() { equalheight(\'.grid_style .item .thumbnail\');});</script>';
}else{
$sstyle = "list_style";
}
$query= str_replace("#038;","&",$query);
if(strlen($query) > 1){
// ADD ON POST TYPE FOR THOSE WHO FORGET
if(strpos($query,'post_type') == false){
$args = $query ."&post_type=".THEME_TAXONOMY."_type";
}else{
$args = $query;
}
}elseif($featuredonly == "yes"){
$args = array('posts_per_page' => $atts['show'],
'post_type' => $atts['type'], 'orderby' => $atts['orderby'], 'order' => $atts['order'],
'meta_query' => array (
array (
'key' => 'featured',
'value' => 'yes',
)
)
);
}else{
/*** default string ***/
$args = array('posts_per_page' => $atts['show'], 'post_type' => $atts['type'], 'orderby' => $atts['orderby'], 'order' => $atts['order'] );
}
/*** custom category ***/
if(strlen($atts['cat']) > 1){
$args = array('tax_query' => array( array( 'taxonomy' => str_replace("_type","",$atts['type']) ,'field' => 'term_id','terms' => array( $atts['cat'] ))), 'posts_per_page' => $atts['show'] );
}
// BUILD QUERY
$the_query = new WP_Query( hook_custom_queries($args) );
if ( $the_query->have_posts() ) {
$STRING .= '<div class="_searchresultsdata"><div class="wlt_search_results row '.$sstyle.'">';
while ( $the_query->have_posts() ) { $the_query->the_post(); $post = get_post();
$STRING .= '<div class="item '.hook_gallerypage_item_class('col-md-4').$CORE->FEATURED($post->ID).'">'.hook_item_cleanup(hook_gallerypage_item($CORE->ITEM_CONTENT($post))).'</div>';
}
$STRING .= '</div></div><div class="clearfix"></div>';
}
// END QUERY
wp_reset_postdata();
return $STRING;
}
add_shortcode( 'LISTINGS', array($this,'wlt_page_listings') );
The shortcode does not have an attribute to hide certain categories. I need to display all listings, except the ones in wedding category, which is a custom taxonomy. Is there any way to do that with the above code?
Will something like this work?
if ( is_tax( 'listing', 'wedding' ) ) {
do not display the wedding listings and display the rest}
Any suggestions?
EDITS:
This my online site url : http://webzer.comxa.com/
The main page shows the all the products.I like to have all but not one that is from wedding category coz i have separate page to list wedding category.
i have tried this where 51 is the page id of my home store page
if ( is_page( 51 ) && is_tax( 'listing', 'wedding' ) ) {
?><style>.caption {display:none!important;}</style>
<?php } ?>
this also didn't work
Consider this :
change
function wlt_page_listings( $atts, $content = null ) {
to
function wlt_page_listings( $atts, $content = null, $exclude=array(99) ) { // 99 is wedding cat id
where $exclude is an optional array of excluded cat names (99 in there for ease of testing/use)
Then in the
while ( $the_query->have_posts() ) {
add something like this:
$post = get_post(); // get post obj, will use the ID attr
$cats = wp_get_post_categories( $post->ID )); // returns array of IDs for all cats
foreach($exclude as $x){ // loop on the excluded cat ids
if(in_array($x, $cats))continue; // if excluded skip
}
http://codex.wordpress.org/Function_Reference/wp_get_post_categories
I think this will work or at least got you close.
display:none is bad mojo as the content will still be in your source code for others to see.
I hope I addressed the problem correctly for you, cheers.

Resources