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