Skip to content

Instantly share code, notes, and snippets.

@simplenotezy
Last active December 6, 2021 05:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save simplenotezy/b0eb1552c12fd13a46c8683fb79c3d6c to your computer and use it in GitHub Desktop.
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
<?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