Commit 3c3a8b7866c for woocommerce

commit 3c3a8b7866cc579e18a4200e289d285561bb0801
Author: Miroslav Mitev <m1r0@users.noreply.github.com>
Date:   Thu Jul 23 18:52:11 2026 +0300

    Fix CSV importer matching special columns anywhere in the name (#66825)

    * Fix CSV importer matching special columns anywhere in the name

diff --git a/plugins/woocommerce/changelog/33773-fix-csv-importer-special-column-matching b/plugins/woocommerce/changelog/33773-fix-csv-importer-special-column-matching
new file mode 100644
index 00000000000..d723833b2f8
--- /dev/null
+++ b/plugins/woocommerce/changelog/33773-fix-csv-importer-special-column-matching
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+CSV product importer: match special columns (attributes, downloads, meta) by prefix instead of with unanchored regexes, so columns that merely contain a special name no longer get the wrong formatting callback.
diff --git a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
index b16650628e7..c480bf6fa2b 100644
--- a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
+++ b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
@@ -825,14 +825,20 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
 		);

 		/**
-		 * Match special column names.
+		 * Match special column names by prefix.
+		 *
+		 * These prefixes must stay in sync with the `starts_with()` checks in `expand_data()`,
+		 * which is what decides how the column is actually consumed. Matching anywhere in the
+		 * column name instead of at the start would apply a formatting callback to columns
+		 * `expand_data()` never treats as special.
 		 */
-		$regex_match_data_formatting = array(
-			'/attributes:value*/'    => array( $this, 'parse_comma_field' ),
-			'/attributes:visible*/'  => array( $this, 'parse_bool_field' ),
-			'/attributes:taxonomy*/' => array( $this, 'parse_bool_field' ),
-			'/downloads:url*/'       => array( $this, 'parse_download_file_field' ),
-			'/meta:*/'               => 'wp_kses_post', // Allow some HTML in meta fields.
+		$prefix_match_data_formatting = array(
+			'attributes:value'    => array( $this, 'parse_comma_field' ),
+			'attributes:visible'  => array( $this, 'parse_bool_field' ),
+			'attributes:taxonomy' => array( $this, 'parse_bool_field' ),
+			'downloads:url'       => array( $this, 'parse_download_file_field' ),
+			// Allow some HTML in meta fields.
+			'meta:'               => 'wp_kses_post',
 		);

 		$callbacks = array();
@@ -844,9 +850,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
 			if ( isset( $data_formatting[ $heading ] ) ) {
 				$callback = $data_formatting[ $heading ];
 			} else {
-				foreach ( $regex_match_data_formatting as $regex => $regex_callback ) {
-					if ( preg_match( $regex, $heading ) ) {
-						$callback = $regex_callback;
+				foreach ( $prefix_match_data_formatting as $prefix => $prefix_callback ) {
+					if ( $this->starts_with( $heading, $prefix ) ) {
+						$callback = $prefix_callback;
 						break;
 					}
 				}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
index 33436c6dbc9..efa9ac50831 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
@@ -395,6 +395,40 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
 		$this->assertEquals( array_values( $args['mapping'] ), $importer->get_mapped_keys() );
 	}

+	/**
+	 * @testdox Special column formatting callbacks are only applied to columns whose name starts with the special prefix.
+	 */
+	public function test_get_formatting_callback_matches_special_columns_by_prefix() {
+		$importer = new WC_Product_CSV_Importer( $this->csv_file, array( 'lines' => 1 ) );
+
+		$mapped_keys = array(
+			// Canonical special columns.
+			'attributes:value1'    => array( $importer, 'parse_comma_field' ),
+			'attributes:visible1'  => array( $importer, 'parse_bool_field' ),
+			'attributes:taxonomy1' => array( $importer, 'parse_bool_field' ),
+			'downloads:url1'       => array( $importer, 'parse_download_file_field' ),
+			'meta:_my_field'       => 'wp_kses_post',
+			// Columns that merely contain a special name must fall back to wc_clean.
+			'metamask'             => 'wc_clean',
+			'hellometa:'           => 'wc_clean',
+			'product_metadata'     => 'wc_clean',
+			'my attributes:value'  => 'wc_clean',
+			'a downloads:url1'     => 'wc_clean',
+			// Special columns handled elsewhere still get the default callback.
+			'attributes:name1'     => 'wc_clean',
+			'attributes:default1'  => 'wc_clean',
+			'downloads:name1'      => 'wc_clean',
+		);
+
+		$reflected_keys = new ReflectionProperty( $importer, 'mapped_keys' );
+		$reflected_keys->setAccessible( true );
+		$reflected_keys->setValue( $importer, array_keys( $mapped_keys ) );
+
+		$callbacks = $this->invoke_protected( $importer, 'get_formatting_callback', array() );
+
+		$this->assertEquals( array_values( $mapped_keys ), $callbacks );
+	}
+
 	/**
 	 * Test get_raw_data.
 	 * @since 3.1.0