Commit bb9d620f84d for woocommerce

commit bb9d620f84df364f9c7a40f932fb99600104b5bd
Author: Faisal Ahammad <faisalahammad24@gmail.com>
Date:   Fri Jul 10 18:42:42 2026 +0600

    fix: add missing layout styles for brand thumbnails description shortcode (#66280)

    * fix: add missing layout styles for brand thumbnails description shortcode

    Add float, clearfix, list-style, and column margin rules for
    .brand-thumbnails-description so the product_brand_thumbnails_description
    shortcode renders a proper grid instead of a bare unstyled list.

    Also add a columns-1 wrapper class to the template so the default
    single-column output does not overflow with the new margin rule.

    Fixes #59417

    * fix(brands): clamp columns and add regression test for brand thumbnails description shortcode

    * fix(brands): merge duplicate CSS for brand thumbnails description

    - Merge .brand-thumbnails-description selectors into the existing
      ul.brand-thumbnails block instead of duplicating rules
    - Add width: 100% to the thumbnail image so oversized brand images
      don't overflow the grid

    Addresses PR feedback.

    Refs #66280

    * fix(brands): address PR review feedback

    - Clamp columns in output_product_brand_thumbnails() to prevent division-by-zero
    - Bump template version to 11.1.0
    - Add regression test for columns=0 and columns=abc

    Refs #66280

    * fix(brands): preserve default columns for non-numeric shortcode value

    Non-numeric columns attribute now falls back to the shortcode default
    (4 for thumbnails, 1 for thumbnails description) instead of clamping
    to 1, matching trunk behavior for existing stores. Numeric zero still
    clamps to 1 to avoid division-by-zero.

    Addresses PR feedback.

    Refs #66280

    * fix(brands): wrap new selectors in :where() for lowest specificity

    Wrap all .brand-thumbnails-description selectors in :where() so they
    add zero specificity, matching the pattern already used elsewhere in
    brands.scss and woocommerce.scss. ul.brand-thumbnails is untouched.

    Addresses PR review feedback.

    Refs #66280

diff --git a/plugins/woocommerce/changelog/59417-brand-thumbnails-description-css b/plugins/woocommerce/changelog/59417-brand-thumbnails-description-css
new file mode 100644
index 00000000000..0a9b393c52f
--- /dev/null
+++ b/plugins/woocommerce/changelog/59417-brand-thumbnails-description-css
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix missing default layout styles for the product_brand_thumbnails_description shortcode.
diff --git a/plugins/woocommerce/client/legacy/css/brands.scss b/plugins/woocommerce/client/legacy/css/brands.scss
index 060d28a0278..143715ae5d4 100644
--- a/plugins/woocommerce/client/legacy/css/brands.scss
+++ b/plugins/woocommerce/client/legacy/css/brands.scss
@@ -24,20 +24,23 @@
 }

 /* Brand thumbnails widget */
-ul.brand-thumbnails {
+ul.brand-thumbnails,
+:where(.brand-thumbnails-description) {
 	margin-left: 0;
 	margin-bottom: 0;
 	clear: both;
 	list-style: none;
 }

-ul.brand-thumbnails:before {
+ul.brand-thumbnails:before,
+:where(.brand-thumbnails-description):before {
 	clear: both;
 	content: "";
 	display: table;
 }

-ul.brand-thumbnails:after {
+ul.brand-thumbnails:after,
+:where(.brand-thumbnails-description):after {
 	clear: both;
 	content: "";
 	display: table;
@@ -55,11 +58,14 @@ ul.brand-thumbnails.fluid-columns li {
 	width: auto;
 }

-ul.brand-thumbnails:not(.fluid-columns) li.first {
+ul.brand-thumbnails:not(.fluid-columns) li.first,
+:where(.brand-thumbnails-description) li.first {
 	clear: both;
 }

-ul.brand-thumbnails:not(.fluid-columns) li.last {
+ul.brand-thumbnails:not(.fluid-columns) li.last,
+:where(.brand-thumbnails-description) li.last,
+:where(.brand-thumbnails-description.columns-1) li {
 	margin-right: 0;
 }

@@ -117,15 +123,20 @@ ul.brand-thumbnails.columns-6 li {
 }

 /* Brand thumbnails description */
-.brand-thumbnails-description li {
+:where(.brand-thumbnails-description) li {
+	float: left;
+	margin: 0 2% 1em 0;
+	padding: 0;
+	position: relative;
 	text-align: center;
 }

-.brand-thumbnails-description li .term-thumbnail img {
+:where(.brand-thumbnails-description) li .term-thumbnail img {
+	width: 100%;
 	display: inline;
 }

-.brand-thumbnails-description li .term-description {
+:where(.brand-thumbnails-description) li .term-description {
 	margin-top: 1em;
 	text-align: left;
 }
diff --git a/plugins/woocommerce/includes/class-wc-brands.php b/plugins/woocommerce/includes/class-wc-brands.php
index 5cb18913dba..da9956f3719 100644
--- a/plugins/woocommerce/includes/class-wc-brands.php
+++ b/plugins/woocommerce/includes/class-wc-brands.php
@@ -731,7 +731,7 @@ class WC_Brands {
 			'widgets/brand-thumbnails.php',
 			array(
 				'brands'        => $brands,
-				'columns'       => is_numeric( $args['columns'] ) ? intval( $args['columns'] ) : 4,
+				'columns'       => is_numeric( $args['columns'] ) ? max( 1, absint( $args['columns'] ) ) : 4,
 				'fluid_columns' => wp_validate_boolean( $args['fluid_columns'] ),
 			),
 			'woocommerce',
@@ -790,7 +790,7 @@ class WC_Brands {
 			'widgets/brand-thumbnails-description.php',
 			array(
 				'brands'  => $brands,
-				'columns' => $args['columns'],
+				'columns' => is_numeric( $args['columns'] ) ? max( 1, absint( $args['columns'] ) ) : 1,
 			),
 			'woocommerce',
 			WC()->plugin_path() . '/templates/brands/'
diff --git a/plugins/woocommerce/templates/brands/widgets/brand-thumbnails-description.php b/plugins/woocommerce/templates/brands/widgets/brand-thumbnails-description.php
index 777c3be4546..0af54ff2a68 100644
--- a/plugins/woocommerce/templates/brands/widgets/brand-thumbnails-description.php
+++ b/plugins/woocommerce/templates/brands/widgets/brand-thumbnails-description.php
@@ -12,12 +12,14 @@
  *
  * @see     https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates
- * @version 9.4.0
+ * @version 11.1.0
  */

 declare( strict_types = 1);
+
+$wrapper_class = 1 === (int) $columns ? 'columns-1' : '';
 ?>
-<ul class="brand-thumbnails-description">
+<ul class="brand-thumbnails-description <?php echo esc_attr( $wrapper_class ); ?>">

 	<?php
 	foreach ( $brands as $index => $brand ) :
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-brands-test.php b/plugins/woocommerce/tests/php/includes/class-wc-brands-test.php
index 75b82395356..74bbf6405f3 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-brands-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-brands-test.php
@@ -62,6 +62,46 @@ class WC_Brands_Test extends WC_Unit_Test_Case {
 		$this->assertStringContainsString( 'Empty Brand', $output );
 	}

+	/**
+	 * Test that `product_brand_thumbnails_description` shortcode clamps invalid `columns`
+	 * values instead of letting them reach the template raw.
+	 *
+	 * Regression test: `columns="0"` (or any non-numeric value) used to fall straight
+	 * through to the template, where `$index % $columns` and the width calculation
+	 * throw a ModuloByZeroError/DivisionByZeroError on PHP8.
+	 */
+	public function test_product_brand_thumbnails_description_shortcode_clamps_invalid_columns() {
+		$data            = $this->setup_brand_test_data();
+		$brands_instance = $data['brands_instance'];
+
+		$output = $brands_instance->output_product_brand_thumbnails_description( array( 'columns' => '0' ) );
+		$this->assertStringContainsString( 'columns-1', $output, 'columns="0" should be clamped to 1' );
+
+		$output = $brands_instance->output_product_brand_thumbnails_description( array( 'columns' => 'abc' ) );
+		$this->assertStringContainsString( 'columns-1', $output, 'Non-numeric columns should fall back to 1' );
+	}
+
+	/**
+	 * Test that `product_brand_thumbnails` shortcode clamps invalid `columns`
+	 * values instead of letting them reach the template raw.
+	 *
+	 * Regression test: `columns="0"` (or any non-numeric value) used to fall straight
+	 * through to the template, where modulo/width calculations throw a
+	 * ModuloByZeroError/DivisionByZeroError on PHP8.
+	 */
+	public function test_product_brand_thumbnails_shortcode_clamps_invalid_columns() {
+		$data            = $this->setup_brand_test_data();
+		$brands_instance = $data['brands_instance'];
+
+		// columns="0" should not throw a division-by-zero error.
+		$output = $brands_instance->output_product_brand_thumbnails( array( 'columns' => '0' ) );
+		$this->assertNotEmpty( $output, 'columns="0" should not produce empty output' );
+
+		// Non-numeric columns should not throw either.
+		$output = $brands_instance->output_product_brand_thumbnails( array( 'columns' => 'abc' ) );
+		$this->assertNotEmpty( $output, 'Non-numeric columns should not produce empty output' );
+	}
+
 	/**
 	 * Test that `product_brand_list` shortcode's `show_empty_brands` argument works as expected.
 	 */