Commit 0278e79028e for woocommerce
commit 0278e79028e717d4bbbf87d979197d30c87bdb4b
Author: Luigi Teschio <gigitux@gmail.com>
Date: Wed Aug 12 14:39:50 2026 +0200
Include product brands in term recounts (#67630)
* Include product brands in term recounts
* Add changelog for product brand recounts
diff --git a/plugins/woocommerce/changelog/fix-product-brand-term-recounts b/plugins/woocommerce/changelog/fix-product-brand-term-recounts
new file mode 100644
index 00000000000..7d3a52f7fc0
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-brand-term-recounts
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Include product brands in visibility-aware product term recounts.
diff --git a/plugins/woocommerce/includes/class-wc-brands.php b/plugins/woocommerce/includes/class-wc-brands.php
index 2a601715d50..4162033ac2b 100644
--- a/plugins/woocommerce/includes/class-wc-brands.php
+++ b/plugins/woocommerce/includes/class-wc-brands.php
@@ -74,10 +74,6 @@ class WC_Brands {
// Layered nav widget compatibility.
add_filter( 'woocommerce_layered_nav_term_html', array( $this, 'woocommerce_brands_update_layered_nav_link' ), 10, 4 );
- // Filter the list of taxonomies overridden for the original term count.
- add_action( 'woocommerce_product_set_stock_status', array( $this, 'recount_after_stock_change' ) );
- add_action( 'woocommerce_update_options_products_inventory', array( $this, 'recount_all_brands' ) );
-
// Block theme integration.
add_filter( 'hooked_block_types', array( $this, 'hook_product_brand_block' ), 10, 4 );
add_filter( 'hooked_block_core/post-terms', array( $this, 'configure_product_brand_block' ), 10, 5 );
diff --git a/plugins/woocommerce/includes/wc-term-functions.php b/plugins/woocommerce/includes/wc-term-functions.php
index fc66d4da6c5..c0b24a935ec 100644
--- a/plugins/woocommerce/includes/wc-term-functions.php
+++ b/plugins/woocommerce/includes/wc-term-functions.php
@@ -569,6 +569,11 @@ function wc_recount_after_stock_change( $product_id ) {
if ( is_array( $product_terms ) ) {
wp_update_term_count( array_column( $product_terms, 'term_taxonomy_id' ), 'product_tag' );
}
+
+ $product_terms = get_the_terms( $product_id, 'product_brand' );
+ if ( is_array( $product_terms ) ) {
+ wp_update_term_count( array_column( $product_terms, 'term_taxonomy_id' ), 'product_brand' );
+ }
} else {
_wc_recount_terms_by_product( $product_id );
}
@@ -577,7 +582,7 @@ add_action( 'woocommerce_product_set_stock_status', 'wc_recount_after_stock_chan
/**
- * Overrides the original term count for product categories and tags with the product count.
+ * Overrides the original term count for product categories, tags, and brands with the product count
* that takes catalog visibility into account.
*
* @param array $terms List of terms.
@@ -719,7 +724,7 @@ function wc_get_product_visibility_term_ids() {
}
/**
- * Recounts all terms for product categories and product tags.
+ * Recounts all terms for product categories, tags, and brands.
*
* @since 5.2
*
@@ -746,10 +751,23 @@ function wc_recount_all_terms( bool $include_callback = true ) {
)
);
_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), $include_callback, false );
+
+ $product_brands = get_terms(
+ array(
+ 'taxonomy' => 'product_brand',
+ 'hide_empty' => false,
+ 'fields' => 'id=>parent',
+ )
+ );
+ $product_brand_taxonomy = get_taxonomy( 'product_brand' );
+
+ if ( is_array( $product_brands ) && $product_brand_taxonomy instanceof WP_Taxonomy ) {
+ _wc_term_recount( $product_brands, $product_brand_taxonomy, $include_callback, false );
+ }
}
/**
- * Recounts terms by product.
+ * Recounts product category, tag, and brand terms by product.
*
* @since 5.2
* @param int $product_id The ID of the product.
@@ -783,4 +801,20 @@ function _wc_recount_terms_by_product( $product_id = '' ) {
_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), false, false );
}
+
+ $product_terms = get_the_terms( $product_id, 'product_brand' );
+
+ if ( is_array( $product_terms ) ) {
+ $product_brands = array();
+
+ foreach ( $product_terms as $term ) {
+ $product_brands[ $term->term_id ] = $term->parent;
+ }
+
+ $product_brand_taxonomy = get_taxonomy( 'product_brand' );
+
+ if ( $product_brand_taxonomy instanceof WP_Taxonomy ) {
+ _wc_term_recount( $product_brands, $product_brand_taxonomy, false, false );
+ }
+ }
}
diff --git a/plugins/woocommerce/tests/php/includes/wc-term-functions-tests.php b/plugins/woocommerce/tests/php/includes/wc-term-functions-tests.php
index 443bed7d650..7246e21fcb9 100644
--- a/plugins/woocommerce/tests/php/includes/wc-term-functions-tests.php
+++ b/plugins/woocommerce/tests/php/includes/wc-term-functions-tests.php
@@ -32,6 +32,13 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$this->terms['tag1'] = wp_insert_term( 'Tag 1', 'product_tag' );
$this->terms['tag2'] = wp_insert_term( 'Tag 2', 'product_tag' );
+ $this->terms['brand_parent'] = wp_insert_term( 'Parent brand', 'product_brand' );
+ $this->terms['brand_child'] = wp_insert_term(
+ 'Child brand',
+ 'product_brand',
+ array( 'parent' => $this->terms['brand_parent']['term_id'] )
+ );
+
$this->products['product1'] = WC_Helper_Product::create_simple_product(
true,
array(
@@ -54,6 +61,10 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
'tag_ids' => array( $this->terms['tag1']['term_id'], $this->terms['tag2']['term_id'] ),
)
);
+
+ wp_set_object_terms( $this->products['product1']->get_id(), array( $this->terms['brand_child']['term_id'] ), 'product_brand' );
+ wp_set_object_terms( $this->products['product2']->get_id(), array( $this->terms['brand_child']['term_id'] ), 'product_brand' );
+ wp_set_object_terms( $this->products['product3']->get_id(), array( $this->terms['brand_parent']['term_id'] ), 'product_brand' );
}
/**
@@ -81,7 +92,7 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
public function test_term_count_baseline(): void {
$terms = get_terms(
array(
- 'taxonomy' => array( 'product_cat', 'product_tag' ),
+ 'taxonomy' => array( 'product_cat', 'product_tag', 'product_brand' ),
'hide_empty' => false,
)
);
@@ -92,6 +103,8 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$this->assertEquals( 1, $term_counts[ $this->terms['child2']['term_id'] ] );
$this->assertEquals( 2, $term_counts[ $this->terms['tag1']['term_id'] ] );
$this->assertEquals( 2, $term_counts[ $this->terms['tag2']['term_id'] ] );
+ $this->assertEquals( 3, $term_counts[ $this->terms['brand_parent']['term_id'] ] );
+ $this->assertEquals( 2, $term_counts[ $this->terms['brand_child']['term_id'] ] );
}
/**
@@ -106,7 +119,7 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$terms = get_terms(
array(
- 'taxonomy' => array( 'product_cat', 'product_tag' ),
+ 'taxonomy' => array( 'product_cat', 'product_tag', 'product_brand' ),
'hide_empty' => false,
)
);
@@ -117,6 +130,8 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$this->assertEquals( 1, $term_counts[ $this->terms['child2']['term_id'] ] );
$this->assertEquals( 1, $term_counts[ $this->terms['tag1']['term_id'] ] );
$this->assertEquals( 2, $term_counts[ $this->terms['tag2']['term_id'] ] );
+ $this->assertEquals( 2, $term_counts[ $this->terms['brand_parent']['term_id'] ] );
+ $this->assertEquals( 1, $term_counts[ $this->terms['brand_child']['term_id'] ] );
}
/**
@@ -130,7 +145,7 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$terms = get_terms(
array(
- 'taxonomy' => array( 'product_cat', 'product_tag' ),
+ 'taxonomy' => array( 'product_cat', 'product_tag', 'product_brand' ),
'hide_empty' => false,
)
);
@@ -141,6 +156,28 @@ class WC_Term_Functions_Tests extends \WC_Unit_Test_Case {
$this->assertEquals( 0, $term_counts[ $this->terms['child2']['term_id'] ] );
$this->assertEquals( 2, $term_counts[ $this->terms['tag1']['term_id'] ] );
$this->assertEquals( 1, $term_counts[ $this->terms['tag2']['term_id'] ] );
+ $this->assertEquals( 2, $term_counts[ $this->terms['brand_parent']['term_id'] ] );
+ $this->assertEquals( 1, $term_counts[ $this->terms['brand_child']['term_id'] ] );
+
+ delete_option( 'woocommerce_hide_out_of_stock_items' );
+ }
+
+ /**
+ * @testdox Recounting terms for one product updates its brand and brand ancestors.
+ */
+ public function test_recount_terms_by_product_includes_brands(): void {
+ update_option( 'woocommerce_hide_out_of_stock_items', 'yes' );
+ wp_set_object_terms(
+ $this->products['product1']->get_id(),
+ ProductStockStatus::OUT_OF_STOCK,
+ 'product_visibility',
+ true
+ );
+
+ _wc_recount_terms_by_product( $this->products['product1']->get_id() );
+
+ $this->assertSame( '1', get_term_meta( $this->terms['brand_parent']['term_id'], 'product_count_product_brand', true ) );
+ $this->assertSame( '0', get_term_meta( $this->terms['brand_child']['term_id'], 'product_count_product_brand', true ) );
delete_option( 'woocommerce_hide_out_of_stock_items' );
}