Commit 6d3ca5fde30 for woocommerce

commit 6d3ca5fde30c2bf0ffc6ae33acd0978b49b27e1c
Author: SH Sajal Chowdhury <72102985+shsajalchowdhury@users.noreply.github.com>
Date:   Tue Jul 7 04:57:16 2026 +0600

    Fix: Skip brands product column when taxonomy is deregistered (#64612)

    * Fix: Skip brands product column when taxonomy is deregistered

    Avoid accessing the product_brand column when the taxonomy has been
    deregistered by a third-party plugin. This prevents PHP notices on the
    products list table and leaves the admin UI unchanged when brands are
    not registered.

    Closes #64065

    * Add tests for product_columns() and hide product_tag column when taxonomy deregistered

    - Add 3 PHPUnit tests for WC_Brands_Admin::product_columns(): normal
      reordering, taxonomy-not-registered guard, and column-not-present guard
    - Hide custom product_tag column when the taxonomy is deregistered
      (addresses reviewer feedback on PR #64612)

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Address review feedback: use is_object_in_taxonomy, fix test assertions

    - Change taxonomy_exists() to is_object_in_taxonomy() for product_tag
      guard, since unregister_taxonomy_for_object_type() removes the
      post-type association, not the taxonomy itself
    - Simplify brand guard to isset()-only (taxonomy_exists was redundant
      since WP only adds the column when taxonomy is registered)
    - Fix test_product_columns_reorders_brand_column: correct assertions
      to match actual array_slice reorder position (third-to-last)
    - Remove broken test_product_columns_returns_unchanged_when_taxonomy_not_registered
      (never actually unregistered the taxonomy)
    - Add tearDown() to restore taxonomy state between tests
    - Add more columns to reorder test fixture for realistic assertion

    * Fix test fixture: move brand column earlier so reorder is not a no-op

    The previous fixture placed taxonomy-product_brand in the third-to-last
    position, which is where the reorder moves it, making the test a no-op
    and causing assertNotEquals to fail. Move it earlier in the input
    (after name) so the reorder actually changes the column position.

    * Fix product_columns reorder test assertion and array alignment

    The reorder test used assertNotEquals to confirm the columns changed,
    but assertEquals/assertNotEquals compare associative arrays
    order-insensitively. product_columns() only reorders (same keys and
    values), so the two arrays are loosely equal and assertNotEquals
    always failed. Switch to assertNotSame, which is order-sensitive, so
    the reorder is detected correctly.

    Also correct the array double-arrow alignment in the test fixture to
    satisfy phpcs (WordPress.Arrays.MultipleStatementAlignment).

    * Realign featured/date column assignments after product_tag guard

    Wrapping the product_tag column in an is_object_in_taxonomy() guard
    removed it from the equals-sign alignment group, leaving the adjacent
    featured and date assignments over-padded. Reduce their alignment to
    match phpcs (Generic.Formatting.MultipleStatementAlignment).

    * fix: add trailing newline to changelog fragment

    * Add changefile(s) from automation for the following project(s): woocommerce

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
    Co-authored-by: Brandon Kraft <public@brandonkraft.com>

diff --git a/docs/block-development/reference/block-references.md b/docs/block-development/reference/block-references.md
index 297144044fd..aae0ac5f878 100644
--- a/docs/block-development/reference/block-references.md
+++ b/docs/block-development/reference/block-references.md
@@ -1235,7 +1235,7 @@ Display a product's description, attributes, and reviews

 ## Product Filters - woocommerce/product-filters

-Let shoppers filter products displayed on the page.
+Add a set of filters shoppers can use.

 - **Name:** woocommerce/product-filters
 - **Category:** woocommerce
@@ -1244,7 +1244,7 @@ Let shoppers filter products displayed on the page.

 ## Active Filters - woocommerce/product-filter-active

-Display the currently active filters.
+Display all active filters.

 - **Name:** woocommerce/product-filter-active
 - **Category:** woocommerce
@@ -1253,7 +1253,7 @@ Display the currently active filters.

 ## Attribute Filter - woocommerce/product-filter-attribute

-Enable customers to filter the product grid by selecting one or more attributes, such as color.
+Let shoppers filter products by attribute.

 - **Name:** woocommerce/product-filter-attribute
 - **Category:** woocommerce
@@ -1263,7 +1263,7 @@ Enable customers to filter the product grid by selecting one or more attributes,

 ## List - woocommerce/product-filter-checkbox-list

-Display a list of filter options.
+Display filter options as a list.

 - **Name:** woocommerce/product-filter-checkbox-list
 - **Category:** woocommerce
@@ -1283,7 +1283,7 @@ Display filter options as chips.

 ## Clear filters - woocommerce/product-filter-clear-button

-Allows shoppers to clear active filters.
+Let shoppers clear any active filters.

 - **Name:** woocommerce/product-filter-clear-button
 - **Category:** woocommerce
@@ -1292,7 +1292,7 @@ Allows shoppers to clear active filters.

 ## Price Filter - woocommerce/product-filter-price

-Let shoppers filter products by choosing a price range.
+Let shoppers filter products by price.

 - **Name:** woocommerce/product-filter-price
 - **Category:** woocommerce
@@ -1301,7 +1301,7 @@ Let shoppers filter products by choosing a price range.

 ## Price Slider - woocommerce/product-filter-price-slider

-A slider helps shopper choose a price range.
+Let shoppers choose a price range with a slider.

 - **Name:** woocommerce/product-filter-price-slider
 - **Category:** woocommerce
@@ -1311,7 +1311,7 @@ A slider helps shopper choose a price range.

 ## Rating Filter - woocommerce/product-filter-rating

-Enable customers to filter the product collection by rating.
+Let shoppers filter products by rating.

 - **Name:** woocommerce/product-filter-rating
 - **Category:** woocommerce
@@ -1321,7 +1321,7 @@ Enable customers to filter the product collection by rating.

 ## Chips - woocommerce/product-filter-removable-chips

-Display removable active filters as chips.
+Display active filters as removable chips.

 - **Name:** woocommerce/product-filter-removable-chips
 - **Category:** woocommerce
@@ -1329,7 +1329,7 @@ Display removable active filters as chips.
 - **Supports:** interactivity, layout (default, ~~allowInheriting~~, ~~allowSwitching~~, ~~allowVerticalAlignment~~)
 - **Attributes:** chipBackground, chipBorder, chipText, customChipBackground, customChipBorder, customChipText

-## Availability filter - woocommerce/product-filter-status
+## Availability Filter - woocommerce/product-filter-status

 Let shoppers filter products by availability.

@@ -1341,7 +1341,7 @@ Let shoppers filter products by availability.

 ## Taxonomy Filter - woocommerce/product-filter-taxonomy

-Enable customers to filter the product collection by selecting one or more taxonomy terms, such as categories, brands, or tags.
+Let shoppers filter products by category, brand, or tag.

 - **Name:** woocommerce/product-filter-taxonomy
 - **Category:** woocommerce
diff --git a/plugins/woocommerce/changelog/64612-fix-64065-brands-admin-deregistered b/plugins/woocommerce/changelog/64612-fix-64065-brands-admin-deregistered
new file mode 100644
index 00000000000..9a3f8f395fd
--- /dev/null
+++ b/plugins/woocommerce/changelog/64612-fix-64065-brands-admin-deregistered
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Skip the brands product column when the product_brand taxonomy is deregistered
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-brands.php b/plugins/woocommerce/includes/admin/class-wc-admin-brands.php
index 53c0a6fe200..411f980d69e 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-brands.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-brands.php
@@ -523,7 +523,12 @@ class WC_Brands_Admin {
 			return $columns;
 		}

-		$column_index  = 'taxonomy-product_brand';
+		$column_index = 'taxonomy-product_brand';
+
+		if ( ! isset( $columns[ $column_index ] ) ) {
+			return $columns;
+		}
+
 		$brands_column = $columns[ $column_index ];
 		unset( $columns[ $column_index ] );
 		return array_merge(
diff --git a/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php b/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
index 1cce6802fad..6591e4c2463 100644
--- a/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
+++ b/plugins/woocommerce/includes/admin/list-tables/class-wc-admin-list-table-products.php
@@ -209,9 +209,11 @@ class WC_Admin_List_Table_Products extends WC_Admin_List_Table {
 			$show_columns['cogs_value'] = __( 'Cost', 'woocommerce' );
 		}
 		$show_columns['product_cat'] = __( 'Categories', 'woocommerce' );
-		$show_columns['product_tag'] = __( 'Tags', 'woocommerce' );
-		$show_columns['featured']    = '<span class="wc-featured parent-tips" data-tip="' . esc_attr__( 'Featured', 'woocommerce' ) . '">' . __( 'Featured', 'woocommerce' ) . '</span>';
-		$show_columns['date']        = __( 'Date', 'woocommerce' );
+		if ( is_object_in_taxonomy( 'product', 'product_tag' ) ) {
+			$show_columns['product_tag'] = __( 'Tags', 'woocommerce' );
+		}
+		$show_columns['featured'] = '<span class="wc-featured parent-tips" data-tip="' . esc_attr__( 'Featured', 'woocommerce' ) . '">' . __( 'Featured', 'woocommerce' ) . '</span>';
+		$show_columns['date']     = __( 'Date', 'woocommerce' );

 		return array_merge( $show_columns, $columns );
 	}
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
index 60691f69c7a..7b437a0e9b4 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-brands-test.php
@@ -15,6 +15,92 @@ require_once WC_ABSPATH . '/includes/class-wc-brands.php';
  */
 class WC_Admin_Brands_Test extends WC_Unit_Test_Case {

+	/**
+	 * Keep track of whether we manually unregistered the taxonomy
+	 * so we can restore it in tearDown.
+	 *
+	 * @var bool
+	 */
+	private static $unregistered_brand_taxonomy = false;
+
+	/**
+	 * Restore the product_brand taxonomy after tests that unregister it.
+	 *
+	 * @return void
+	 */
+	public function tearDown(): void {
+		if ( self::$unregistered_brand_taxonomy ) {
+			WC_Brands::init_taxonomy();
+			self::$unregistered_brand_taxonomy = false;
+		}
+		parent::tearDown();
+	}
+
+	/**
+	 * Tests that product_columns() moves the taxonomy-product_brand column
+	 * to the third-to-last position (before the last two columns) when
+	 * the taxonomy is registered.
+	 *
+	 * The reorder logic uses array_slice( $columns, 0, -2 ) for the
+	 * "before" portion and array_slice( $columns, -2 ) for the last two
+	 * columns, inserting the brand column between them.
+	 *
+	 * @return void
+	 */
+	public function test_product_columns_reorders_brand_column() {
+		WC_Brands::init_taxonomy();
+
+		// Brand column starts after "name" so the reorder actually moves it.
+		$columns = array(
+			'cb'                     => '<input type="checkbox" />',
+			'name'                   => __( 'Name', 'woocommerce' ),
+			'taxonomy-product_brand' => __( 'Brands', 'woocommerce' ),
+			'sku'                    => __( 'SKU', 'woocommerce' ),
+			'product_cat'            => __( 'Categories', 'woocommerce' ),
+			'featured'               => __( 'Featured', 'woocommerce' ),
+			'date'                   => __( 'Date', 'woocommerce' ),
+		);
+
+		$brands_admin = new WC_Brands_Admin();
+		$result       = $brands_admin->product_columns( $columns );
+		$result_keys  = array_keys( $result );
+
+		/*
+		 * After reorder: cb, name, sku, product_cat, taxonomy-product_brand, featured, date.
+		 * Brand column is inserted before the last 2 columns (featured, date).
+		 * Use assertNotSame because the reorder only changes column order, and
+		 * assertEquals/assertNotEquals compare arrays order-insensitively.
+		 */
+		$this->assertNotSame( $columns, $result, 'Columns should have been reordered.' );
+
+		// The brand column should be at the third-to-last position.
+		$last_index = count( $result_keys ) - 1;
+		$this->assertEquals( 'date', $result_keys[ $last_index ], 'Last column should be date.' );
+		$this->assertEquals( 'featured', $result_keys[ $last_index - 1 ], 'Second-to-last column should be featured.' );
+		$this->assertEquals( 'taxonomy-product_brand', $result_keys[ $last_index - 2 ], 'Brand column should be third-to-last.' );
+	}
+
+	/**
+	 * Tests that product_columns() returns columns unchanged when
+	 * the product_brand column is not present in the columns array.
+	 *
+	 * @return void
+	 */
+	public function test_product_columns_returns_unchanged_when_column_not_present() {
+		WC_Brands::init_taxonomy();
+
+		$columns = array(
+			'cb'   => '<input type="checkbox" />',
+			'name' => __( 'Name', 'woocommerce' ),
+			'date' => __( 'Date', 'woocommerce' ),
+		);
+
+		$brands_admin = new WC_Brands_Admin();
+		$result       = $brands_admin->product_columns( $columns );
+
+		// Without the brand column present, columns should remain untouched.
+		$this->assertEquals( $columns, $result );
+	}

 	/**
 	 * Tests brands filter outputs as a standard dropdown.