Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Product Query: pass any product taxonomies existing in the URL parameters. #6152

Merged
merged 11 commits into from
Apr 21, 2022
28 changes: 25 additions & 3 deletions src/StoreApi/Routes/V1/Products.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,34 @@ public function get_collection_params() {
$params['category_operator'] = array(
'description' => __( 'Operator to compare product category terms.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => [ 'in', 'not in', 'and' ],
'enum' => [ 'in', 'not_in', 'and' ],
'default' => 'in',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);

// If the $_REQUEST contains a taxonomy query, add it to the params and sanitize it.
foreach ( $_REQUEST as $param => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should check whether the slug after tax_ is a real taxonomy on the site before adding this param?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see that as a problem, the parameter will be collected anyway but will be disregarded here
https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/5be9023bcbc1918f84e9951f0acca114dcca99d4/src/StoreApi/Utilities/ProductQuery.php#L101-L127

I don't have strong opinion on this though, just want to avoid two more operation (get all taxonomies, check against the parameter passed).

if ( str_starts_with( $param, '_unstable_tax_' ) && ! str_ends_with( $param, '_operator' ) ) {
$params[ $param ] = array(
'description' => __( 'Limit result set to products assigned a specific category ID.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'sanitize_callback' => 'wp_parse_id_list',
'validate_callback' => 'rest_validate_request_arg',
);
}
if ( str_starts_with( $param, '_unstable_tax_' ) && str_ends_with( $param, '_operator' ) ) {
$params[ $param ] = array(
'description' => __( 'Operator to compare product category terms.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => [ 'in', 'not_in', 'and' ],
'default' => 'in',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
);
}
}

$params['tag'] = array(
'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woo-gutenberg-products-block' ),
'type' => 'string',
Expand All @@ -291,7 +313,7 @@ public function get_collection_params() {
$params['tag_operator'] = array(
'description' => __( 'Operator to compare product tags.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => [ 'in', 'not in', 'and' ],
'enum' => [ 'in', 'not_in', 'and' ],
'default' => 'in',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
Expand Down Expand Up @@ -360,7 +382,7 @@ public function get_collection_params() {
'operator' => array(
'description' => __( 'Operator to compare product attribute terms.', 'woo-gutenberg-products-block' ),
'type' => 'string',
'enum' => [ 'in', 'not in', 'and' ],
'enum' => [ 'in', 'not_in', 'and' ],
),
),
),
Expand Down
14 changes: 13 additions & 1 deletion src/StoreApi/Utilities/ProductQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,24 @@ public function prepare_objects_query( $request ) {
'and' => 'AND',
];

// Gets all registered product taxonomies and prefixes them with `tax_`.
// This is neeeded to avoid situations where a users registers a new product taxonomy with the same name as default field.
// eg an `sku` taxonomy will be mapped to `tax_sku`.
$all_product_taxonomies = array_map(
function ( $value ) {
return '_unstable_tax_' . $value;
},
get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' )
);

// Map between taxonomy name and arg key.
$taxonomies = [
$default_taxonomies = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't these $default_taxonomies also get returned by get_taxonomies()? If so, is there any need for $default_taxonomies and $taxonomies = array_merge( $default_taxonomies, $all_product_taxonomies );

Copy link
Contributor Author

@cpapazoglou cpapazoglou Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question @tjcafferkey. get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' ); returns all product taxonomies including product_cat and product_tag. The thing is that the endpoint currently uses category instead of product_cat and tag instead of product_tag for parameters. So I have opted merging with $default_taxonomies for backwards compatibility. Would be preferable updating the docs and all code references (if any)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is that the endpoint currently uses category instead of product_cat and tag instead of product_tag for parameters.

@cpapazoglou it looks like after the merge it uses product_cat and product_tag unless I am wrong?

Screenshot 2022-04-01 at 11 32 28

Copy link
Contributor Author

@cpapazoglou cpapazoglou Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦, sorry for this and good catch. I was merging with wrong order. Fixed here 6885fd2

Works in my local machine 😁
CleanShot 2022-04-01 at 14 10 03@2x

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tjcafferkey is the following preferable?

$all_product_taxonomies = get_taxonomies( array( 'object_type' => array( 'product' ) ), 'names' );
$all_product_taxonomies['product_cat'] = 'category';
$all_product_taxonomies['product_tag'] = 'tag';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @cpapazoglou this seems to work as expected now. However like @Aljullu mentioned I will leave the final review for @opr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the following preferable?

I think as it currently is works fine.

'product_cat' => 'category',
'product_tag' => 'tag',
];

$taxonomies = array_merge( $all_product_taxonomies, $default_taxonomies );

// Set tax_query for each passed arg.
foreach ( $taxonomies as $taxonomy => $key ) {
if ( ! empty( $request[ $key ] ) ) {
Expand Down
69 changes: 36 additions & 33 deletions src/StoreApi/docs/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

The store products API provides public product data so it can be rendered on the client side.

- [List Products](#list-products)
- [Single Product](#single-product)
- [List Products](#list-products)
- [Single Product](#single-product)
- [](#)

## List Products

Expand All @@ -22,6 +23,7 @@ GET /products?type=simple
GET /products?sku=sku-1,sku-2
GET /products?featured=true
GET /products?category=t-shirts
GET /products?product-taxonomy=product-taxonomy-term-id
GET /products?tag=special-items
GET /products?attributes[0][attribute]=pa_color&attributes[0][slug]=red
GET /products?on_sale=true
Expand All @@ -35,34 +37,36 @@ GET /products?return_attribute_counts=pa_size,pa_color
GET /products?return_rating_counts=true
```

| Attribute | Type | Required | Description |
| :------------------- | :------ | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `search` | integer | no | Limit results to those matching a string. |
| `after` | string | no | Limit response to resources created after a given ISO8601 compliant date. |
| `before` | string | no | Limit response to resources created before a given ISO8601 compliant date. |
| `date_column` | string | no | When limiting response using after/before, which date column to compare against. Allowed values: `date`, `date_gmt`, `modified`, `modified_gmt` |
| `exclude` | array | no | Ensure result set excludes specific IDs. |
| `include` | array | no | Limit result set to specific ids. |
| `offset` | integer | no | Offset the result set by a specific number of items. |
| `order` | string | no | Order sort attribute ascending or descending. Allowed values: `asc`, `desc` |
| `orderby` | string | no | Sort collection by object attribute. Allowed values: `date`, `modified`, `id`, `include`, `title`, `slug`, `price`, `popularity`, `rating`, `menu_order`, `comment_count` |
| `parent` | array | no | Limit result set to those of particular parent IDs. |
| `parent_exclude` | array | no | Limit result set to all items except those of a particular parent ID. |
| `type` | string | no | Limit result set to products assigned a specific type. |
| `sku` | string | no | Limit result set to products with specific SKU(s). Use commas to separate. |
| `featured` | boolean | no | Limit result set to featured products. |
| `category` | string | no | Limit result set to products assigned a specific category ID. |
| `category_operator` | string | no | Operator to compare product category terms. Allowed values: `in`, `not_in`, `and` |
| `tag` | string | no | Limit result set to products assigned a specific tag ID. |
| `tag_operator` | string | no | Operator to compare product tags. Allowed values: `in`, `not_in`, `and` |
| `on_sale` | boolean | no | Limit result set to products on sale. |
| `min_price` | string | no | Limit result set to products based on a minimum price, provided using the smallest unit of the currency. |
| `max_price` | string | no | Limit result set to products based on a maximum price, provided using the smallest unit of the currency. |
| `stock_status` | array | no | Limit result set to products with specified stock statuses. Expects an array of strings containing 'instock', 'outofstock' or 'onbackorder'. |
| `attributes` | array | no | Limit result set to specific attribute terms. Expects an array of objects containing `attribute` (taxonomy), `term_id` or `slug`, and optional `operator` for comparison. |
| `attribute_relation` | string | no | The logical relationship between attributes when filtering across multiple at once. |
| `catalog_visibility` | string | no | Determines if hidden or visible catalog products are shown. Allowed values: `any`, `visible`, `catalog`, `search`, `hidden` |
| `rating` | boolean | no | Limit result set to products with a certain average rating. |
| Attribute | Type | Required | Description |
| :------------------------------------------ | :------ | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `search` | integer | no | Limit results to those matching a string. |
| `after` | string | no | Limit response to resources created after a given ISO8601 compliant date. |
| `before` | string | no | Limit response to resources created before a given ISO8601 compliant date. |
| `date_column` | string | no | When limiting response using after/before, which date column to compare against. Allowed values: `date`, `date_gmt`, `modified`, `modified_gmt` |
| `exclude` | array | no | Ensure result set excludes specific IDs. |
| `include` | array | no | Limit result set to specific ids. |
| `offset` | integer | no | Offset the result set by a specific number of items. |
| `order` | string | no | Order sort attribute ascending or descending. Allowed values: `asc`, `desc` |
| `orderby` | string | no | Sort collection by object attribute. Allowed values : `date`, `modified`, `id`, `include`, `title`, `slug`, `price`, `popularity`, `rating`, `menu_order`, `comment_count` |
| `parent` | array | no | Limit result set to those of particular parent IDs. |
| `parent_exclude` | array | no | Limit result set to all items except those of a particular parent ID. |
| `type` | string | no | Limit result set to products assigned a specific type. |
| `sku` | string | no | Limit result set to products with specific SKU(s). Use commas to separate. |
| `featured` | boolean | no | Limit result set to featured products. |
| `category` | string | no | Limit result set to products assigned a specific category ID. |
| `category_operator` | string | no | Operator to compare product category terms. Allowed values: `in`, `not_in`, `and` |
| `_unstable_tax_[product-taxonomy]` | string | no | Limit result set to products assigned to the term ID of that custom product taxonomy. `[product-taxonomy]` should be the key of the custom product taxonomy registered. |
| `_unstable_tax_[product-taxonomy]_operator` | string | no | Operator to compare custom product taxonomy terms. Allowed values: `in`, `not_in`, `and` |
| `tag` | string | no | Limit result set to products assigned a specific tag ID. |
| `tag_operator` | string | no | Operator to compare product tags. Allowed values: `in`, `not_in`, `and` |
| `on_sale` | boolean | no | Limit result set to products on sale. |
| `min_price` | string | no | Limit result set to products based on a minimum price, provided using the smallest unit of the currency. |
| `max_price` | string | no | Limit result set to products based on a maximum price, provided using the smallest unit of the currency. |
| `stock_status` | array | no | Limit result set to products with specified stock statuses. Expects an array of strings containing 'instock', 'outofstock' or 'onbackorder'. |
| `attributes` | array | no | Limit result set to specific attribute terms. Expects an array of objects containing `attribute` (taxonomy), `term_id` or `slug`, and optional `operator` for comparison. |
| `attribute_relation` | string | no | The logical relationship between attributes when filtering across multiple at once. |
| `catalog_visibility` | string | no | Determines if hidden or visible catalog products are shown. Allowed values: `any`, `visible`, `catalog`, `search`, `hidden` |
| `rating` | boolean | no | Limit result set to products with a certain average rating. |

```sh
curl "https://example-store.com/wp-json/wc/store/v1/products"
Expand Down Expand Up @@ -186,11 +190,10 @@ curl "https://example-store.com/wp-json/wc/store/v1/products/34"
}
```

<!-- FEEDBACK -->
---
## <!-- FEEDBACK -->

[We're hiring!](https://woocommerce.com/careers/) Come work with us!

🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./src/StoreApi/docs/products.md)
<!-- /FEEDBACK -->

<!-- /FEEDBACK -->