Commit e284581280 for woocommerce
commit e284581280f5ae9bb111bbc35e703cb31603aacc
Author: Abdalsalaam Halawa <abdalsalaamnafez@gmail.com>
Date: Tue Dec 16 18:41:08 2025 +0400
Add support for filtering product categories by parent level in the Store API (#62447)
* Add parent parameter to AbstractTermsRoute for filtering hierarchical terms
* Add changefile(s) from automation for the following project(s): woocommerce
* Apply suggested description
* use built-in is_taxonomy_hierarchical
* Test term parent
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Marin Atanasov <8436925+tyxla@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/62447-add-issue-62446 b/plugins/woocommerce/changelog/62447-add-issue-62446
new file mode 100644
index 0000000000..3e0a87a806
--- /dev/null
+++ b/plugins/woocommerce/changelog/62447-add-issue-62446
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add support for filtering product categories by parent level in the Store API.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractTermsRoute.php b/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractTermsRoute.php
index 2c9274e9fd..7b7f084fb9 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractTermsRoute.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/AbstractTermsRoute.php
@@ -96,6 +96,13 @@ abstract class AbstractTermsRoute extends AbstractRoute {
'default' => true,
);
+ $params['parent'] = array(
+ 'description' => __( 'Limit results to terms with a specific parent (hierarchical taxonomies only).', 'woocommerce' ),
+ 'type' => 'integer',
+ 'sanitize_callback' => 'absint',
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
return $params;
}
@@ -122,6 +129,10 @@ abstract class AbstractTermsRoute extends AbstractRoute {
'search' => $request['search'],
);
+ if ( isset( $request['parent'] ) && is_taxonomy_hierarchical( $taxonomy ) ) {
+ $prepared_args['parent'] = (int) $request['parent'];
+ }
+
$term_query = new WP_Term_Query();
$objects = $term_query->query( $prepared_args );
$return = [];
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductBrands.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductBrands.php
index 686b5ddbcc..c5b6cedc2c 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductBrands.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/ProductBrands.php
@@ -29,6 +29,13 @@ class ProductBrands extends ControllerTestCase {
)
);
+ $this->child_brand = $fixtures->get_product_brand(
+ array(
+ 'name' => 'Child Brand',
+ 'parent' => $this->product_brand['term_id'],
+ )
+ );
+
$this->products = array(
$fixtures->get_simple_product(
array(
@@ -43,6 +50,13 @@ class ProductBrands extends ControllerTestCase {
'brand_ids' => array( $this->product_brand['term_id'] ),
)
),
+ $fixtures->get_simple_product(
+ array(
+ 'name' => 'Test Product 3',
+ 'regular_price' => 50,
+ 'brand_ids' => array( $this->child_brand['term_id'] ),
+ )
+ ),
);
}
@@ -55,7 +69,7 @@ class ProductBrands extends ControllerTestCase {
// Assert correct response format.
$this->assertSame( 200, $response->get_status(), 'Unexpected status code.' );
- $this->assertSame( 1, count( $data ), 'Unexpected item count.' );
+ $this->assertSame( 2, count( $data ), 'Unexpected item count.' );
// Assert response items contain the correct properties.
$this->assertArrayHasKey( 'id', $data[0] );
@@ -66,6 +80,61 @@ class ProductBrands extends ControllerTestCase {
$this->assertArrayHasKey( 'count', $data[0] );
}
+ /**
+ * Test getting only top-level brands using parent=0 parameter.
+ */
+ public function test_get_items_with_parent_zero() {
+ $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/brands' );
+ $request->set_param( 'parent', 0 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' );
+ $this->assertSame( 1, count( $data ), 'Expected only top-level brands.' );
+ $this->assertSame( 'Test Brand 1', $data[0]['name'] );
+ $this->assertSame( 0, $data[0]['parent'] );
+ }
+
+ /**
+ * Test getting child brands using parent parameter with parent brand ID.
+ */
+ public function test_get_items_with_parent_id() {
+ $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/brands' );
+ $request->set_param( 'parent', $this->product_brand['term_id'] );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' );
+ $this->assertSame( 1, count( $data ), 'Expected only child brands of specified parent.' );
+ $this->assertSame( 'Child Brand', $data[0]['name'] );
+ $this->assertSame( $this->product_brand['term_id'], $data[0]['parent'] );
+ }
+
+ /**
+ * Test that parent parameter with non-existent ID returns empty results.
+ */
+ public function test_get_items_with_parent_nonexistent() {
+ $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products/brands' );
+ $request->set_param( 'parent', 9 );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertSame( 200, $response->get_status(), 'Unexpected status code.' );
+ $this->assertSame( 0, count( $data ), 'Expected no brands for non-existent parent.' );
+ }
+
+ /**
+ * Test that parent parameter is registered in collection params.
+ */
+ public function test_collection_params_include_parent() {
+ $request = new \WP_REST_Request( 'OPTIONS', '/wc/store/v1/products/brands' );
+ $response = rest_get_server()->dispatch( $request );
+ $data = $response->get_data();
+ $params = $data['endpoints'][0]['args'];
+
+ $this->assertArrayHasKey( 'parent', $params );
+ $this->assertSame( 'integer', $params['parent']['type'] );
+ }
/**
* Test getting brands from a specific product.