Last active
December 6, 2021 05:51
-
-
Save simplenotezy/b0eb1552c12fd13a46c8683fb79c3d6c to your computer and use it in GitHub Desktop.
Retrieve shop / archive / category products through custom WordPress REST API endpoint with associated filters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This allows you to get the archive page / shop page data through the WordPress REST APi. | |
* It's not beautiful, but it works. | |
* | |
* The REST URL: https://yourwebsite.com/wp-json/custom-woocommerce/v1/shop/slug | |
* | |
* Parameters: | |
* "filter_{attribute_name}=some_value", e.g. "/shop/?filter_color=red". You can filter multiple values: ?filter_color=red,blue&filter_size=1,2 | |
* "categories", e.g. "/shop/?categories=shoes,shirts&filter_color=blue" | |
* | |
* The response will be your products, as well as a key called "filters". This contains all relevant filters related to the _specific query_ you just made. | |
*/ | |
add_action( 'rest_api_init', function () { | |
register_rest_route( 'custom-woocommerce/v1', '/shop/(?P<slug>\S+)', array( | |
'methods' => 'GET', | |
'callback' => function($data) { | |
$page = (isset($_GET['page']) && $_GET['page']) ? $_GET['page'] : 1; | |
$seed = (isset($_GET['seed']) && $_GET['seed']) ? $_GET['seed'] : rand(11111,99999); | |
$args = [ | |
'post_type' => 'product', | |
'posts_per_page' => 12, | |
'orderby' => 'RAND(' . $seed . ')', | |
'tax_query' => ['relation' => 'AND'], | |
'page' => $page | |
]; | |
$response = [ | |
'seed' => $seed | |
]; | |
/** | |
* Search product attributes | |
*/ | |
$public_wc_taxonomies = array_values(array_map(function($taxonomy) { | |
return 'pa_' . $taxonomy->attribute_name; | |
}, array_filter(wc_get_attribute_taxonomies(), function($taxonomy) { | |
return $taxonomy->attribute_public; | |
}))); | |
$taxonomies = []; | |
foreach($_GET as $getParameter => $attribute_slugs) { | |
if(substr( $getParameter, 0, 7 ) === "filter_") { | |
if(!isset($attribute_tax_query)) | |
$attribute_tax_query = ['relation' => 'OR']; | |
$attribute_slugs = explode(',', $attribute_slugs); | |
$taxonomy = str_replace('filter_', 'pa_', $getParameter); | |
foreach($attribute_slugs as $attribute_slug) { | |
$taxonomies[$taxonomy][] = get_term_by('slug', $attribute_slug, $taxonomy); | |
$attribute_tax_query[] = [ | |
'taxonomy' => $taxonomy, | |
'field' => 'slug', | |
'terms' => $attribute_slug | |
]; | |
} | |
} | |
} | |
if(isset($attribute_tax_query)) | |
$args['tax_query'][] = $attribute_tax_query; | |
/** | |
* Search categories | |
*/ | |
if(isset($_GET['categories'])) { | |
$cats = explode(',', $_GET['categories']); | |
$categories = []; | |
$category_tax_query = ['relation' => 'OR']; | |
foreach($cats as $category_slug) { | |
$category = get_term_by('slug', $category_slug, 'product_cat'); | |
if(!$category) | |
continue; // we couldn't find a category with that slug | |
$categories[] = [ | |
'name' => $category->name, | |
'slug' => $category->slug, | |
'count' => $category->count, | |
]; | |
$category_tax_query[] = [ | |
'taxonomy' => 'product_cat', | |
'field' => 'slug', | |
'terms' => $category_slug | |
]; | |
} | |
$args['tax_query'][] = $category_tax_query; | |
$response['categories'] = $categories; | |
} | |
$query = new WP_Query($args); | |
$response['products'] = $query->posts; | |
/** | |
* If this is the first page, output attributes related to this search | |
*/ | |
if($args['page'] == 1) { | |
global $wp_the_query; | |
$wp_the_query = $query; // little hack to get interals of WooCommerce to function properly | |
(wc())->query->pre_get_posts($wp_the_query); | |
foreach($public_wc_taxonomies as $taxonomy) { | |
$terms = get_terms( $taxonomy, array( 'hide_empty' => '1' ) ); | |
$response['filters'][$taxonomy] = get_terms_and_count_for_current_query($terms, $taxonomy, (isset($args['tax_query'][0]['relation'])) ? $args['tax_query'][0]['relation'] : 'OR'); | |
if(!$response['filters'][$taxonomy]) { | |
unset($response['filters'][$taxonomy]); | |
} | |
} | |
} | |
/** | |
* Outut categories if they are not yet requested | |
*/ | |
if(!isset($_GET['categories'])) { | |
$response['filters']['product_cat'] = get_terms_and_count_for_current_query(get_terms( 'product_cat', array( 'hide_empty' => '1' ) ), 'product_cat', 'or'); | |
} | |
return $response; | |
})); | |
}); | |
/** | |
* Code mostly copied from WooCommerce internals, then adjusted. | |
* The reason for this is, that their API is private, so you can't access it outside without even nastier hacks | |
* So. I did a bit of workarounds to get the right response.. | |
*/ | |
function wc_get_filtered_term_product_counts($term_ids, $taxonomy, $query_type = 'or') { | |
global $wpdb; | |
$tax_query = WC_Query::get_main_tax_query(); | |
$meta_query = WC_Query::get_main_meta_query(); | |
if ( 'or' === $query_type ) { | |
foreach ( $tax_query as $key => $query ) { | |
if ( isset($query['taxonomy']) && is_array( $query ) && $taxonomy === $query['taxonomy'] ) { | |
unset( $tax_query[ $key ] ); | |
} | |
} | |
} | |
$meta_query = new WP_Meta_Query( $meta_query ); | |
$tax_query = new WP_Tax_Query( $tax_query ); | |
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' ); | |
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' ); | |
// Generate query. | |
$query = array(); | |
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, terms.term_id as term_count_id"; | |
$query['from'] = "FROM {$wpdb->posts}"; | |
$query['join'] = " | |
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id | |
INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id ) | |
INNER JOIN {$wpdb->terms} AS terms USING( term_id ) | |
" . $tax_query_sql['join'] . $meta_query_sql['join']; | |
$query['where'] = " | |
WHERE {$wpdb->posts}.post_type IN ( 'product' ) | |
AND {$wpdb->posts}.post_status = 'publish'" | |
. $tax_query_sql['where'] . $meta_query_sql['where'] . | |
'AND terms.term_id IN (' . implode( ',', array_map( 'absint', $term_ids ) ) . ')'; | |
$search = WC_Query::get_main_search_query_sql(); | |
if ( $search ) { | |
$query['where'] .= ' AND ' . $search; | |
} | |
$query['group_by'] = 'GROUP BY terms.term_id'; | |
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query ); | |
$query = implode( ' ', $query ); | |
// We have a query - let's see if cached results of this query already exist. | |
$query_hash = md5( $query ); | |
// Maybe store a transient of the count values. | |
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true ); | |
if ( true === $cache ) { | |
$cached_counts = (array) get_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ) ); | |
} else { | |
$cached_counts = array(); | |
} | |
if ( ! isset( $cached_counts[ $query_hash ] ) ) { | |
$results = $wpdb->get_results( $query, ARRAY_A ); // @codingStandardsIgnoreLine | |
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); | |
$cached_counts[ $query_hash ] = $counts; | |
if ( true === $cache ) { | |
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS ); | |
} | |
} | |
return array_map( 'absint', (array) $cached_counts[ $query_hash ] ); | |
} | |
function get_terms_and_count_for_current_query($terms, $taxonomy, $query_type = 'or') { | |
$term_counts = wc_get_filtered_term_product_counts( wp_list_pluck( $terms, 'term_id' ), $taxonomy, $query_type ); | |
$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes(); | |
$found = false; | |
$return_terms = []; | |
foreach ( $terms as $index => $term ) { | |
$current_values = isset( $_chosen_attributes[ $taxonomy ]['terms'] ) ? $_chosen_attributes[ $taxonomy ]['terms'] : array(); | |
$option_is_set = in_array( $term->slug, $current_values, true ); | |
$count = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : 0; | |
if ( 0 < $count ) { | |
$found = true; | |
} elseif ( 0 === $count && ! $option_is_set ) { | |
continue; | |
} | |
$return_terms[] = [ | |
'name' => html_entity_decode ($term->name), | |
'slug' => $term->slug, | |
'taxonomy' => $term->taxonomy, | |
'count' => $count, | |
'is_set' => $option_is_set | |
]; | |
} | |
return $return_terms; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment