Commit d92a5402862 for woocommerce
commit d92a5402862147ff4d14573efde2eaa25f9a805f
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Tue May 5 09:14:13 2026 +0200
Add color input to 'Create value' modal (#64493)
* Add color input to 'Create value' modal
* Add changelog
* Add spacing between inputs
* Remove unnecessary phpcs:ignore comment
* Test code cleanup
* Linting
* Test code cleanup (II)
* Create wc_taxonomy_is_attribute_type() util
* Add strict types declaration to wc-attribute-functions-test.php
* Revert "Create wc_taxonomy_is_attribute_type() util"
This reverts commit 5a4f6a0a8c489efb64598b03fcdbcb47183835e3.
diff --git a/plugins/woocommerce/changelog/fix-add-color-input-to-create-value b/plugins/woocommerce/changelog/fix-add-color-input-to-create-value
new file mode 100644
index 00000000000..e267b30e511
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-add-color-input-to-create-value
@@ -0,0 +1,5 @@
+Significance: patch
+Type: update
+Comment: Add color input to 'Create value' modal
+
+
diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss
index c64db9ea5d9..e1e3aee2e6a 100644
--- a/plugins/woocommerce/client/legacy/css/admin.scss
+++ b/plugins/woocommerce/client/legacy/css/admin.scss
@@ -992,6 +992,11 @@ $font-sf-pro-display: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe U
padding-bottom: 2em;
}
+ label:not(:first-child) {
+ display: block;
+ margin-top: 1.5em;
+ }
+
input[type="text"] {
display: block;
font-size: 13px;
@@ -1000,6 +1005,12 @@ $font-sf-pro-display: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Segoe U
padding: 12px;
width: 100%;
}
+
+ input[type="color"] {
+ display: block;
+ margin: 6px 0;
+ width: 100%;
+ }
}
/**
diff --git a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
index 4ec93fa91c4..08fd9d47c38 100644
--- a/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
+++ b/plugins/woocommerce/client/legacy/js/admin/meta-boxes-product.js
@@ -992,6 +992,14 @@ jQuery( function ( $ ) {
security: woocommerce_admin_meta_boxes.add_attribute_nonce,
};
+ if (
+ currentAttributeTermCreationContext.isVisualAttribute &&
+ postedData &&
+ postedData.term_color
+ ) {
+ data.term_color = postedData.term_color;
+ }
+
$.post(
woocommerce_admin_meta_boxes.ajax_url,
data,
@@ -1044,14 +1052,20 @@ jQuery( function ( $ ) {
const wrapper = this.closest( '.woocommerce_attribute' );
const attribute = wrapper ? wrapper.dataset.taxonomy : '';
+ const isVisualAttribute =
+ this.dataset.isVisualAttribute === 'yes';
currentAttributeTermCreationContext = {
wrapper,
attribute,
+ isVisualAttribute,
};
$( this ).WCBackboneModal( {
template: 'wc-modal-add-attribute-term',
+ variable: {
+ isVisualAttribute,
+ },
} );
}
);
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-taxonomies.php b/plugins/woocommerce/includes/admin/class-wc-admin-taxonomies.php
index 1ccbce52456..71da485fca6 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-taxonomies.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-taxonomies.php
@@ -324,15 +324,14 @@ class WC_Admin_Taxonomies {
return false;
}
- $attribute_slug = wc_attribute_taxonomy_slug( $taxonomy );
-
- foreach ( wc_get_attribute_taxonomies() as $attribute_taxonomy ) {
- if ( $attribute_slug === $attribute_taxonomy->attribute_name ) {
- return 'wc-visual' === $attribute_taxonomy->attribute_type;
- }
+ if ( ! array_key_exists( 'wc-visual', wc_get_attribute_types() ) ) {
+ return false;
}
- return false;
+ $attribute_id = wc_attribute_taxonomy_id_by_name( $taxonomy );
+ $attribute = $attribute_id ? wc_get_attribute( $attribute_id ) : null;
+
+ return $attribute && 'wc-visual' === $attribute->type;
}
/**
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
index 0b9fca21e01..163d8921209 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-attribute-inner.php
@@ -74,7 +74,7 @@ if ( ! defined( 'ABSPATH' ) ) {
</select>
<button class="button plus select_all_attributes"><?php esc_html_e( 'Select all', 'woocommerce' ); ?></button>
<button class="button minus select_no_attributes"><?php esc_html_e( 'Select none', 'woocommerce' ); ?></button>
- <button class="button fr plus add_new_attribute"><?php esc_html_e( 'Create value', 'woocommerce' ); ?></button>
+ <button class="button fr plus add_new_attribute" data-is-visual-attribute="<?php echo esc_attr( wc_bool_to_string( 'wc-visual' === $attribute_taxonomy->attribute_type ) ); ?>"><?php esc_html_e( 'Create value', 'woocommerce' ); ?></button>
<?php
}
diff --git a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
index cf948ccaddb..1c1300ede84 100644
--- a/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
+++ b/plugins/woocommerce/includes/admin/meta-boxes/views/html-product-data-attributes.php
@@ -77,6 +77,10 @@ $product_attributes = $product_object->get_attributes( 'edit' );
<form class="wc-add-attribute-term-fields" action="" method="post">
<label for="wc-modal-add-attribute-term-input"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label>
<input id="wc-modal-add-attribute-term-input" type="text" name="term" value="" />
+ <# if ( data.isVisualAttribute ) { #>
+ <label for="wc-modal-add-attribute-term-color"><?php esc_html_e( 'Color value', 'woocommerce' ); ?></label>
+ <input id="wc-modal-add-attribute-term-color" type="color" name="term_color" value="" />
+ <# } #>
</form>
</article>
<footer>
diff --git a/plugins/woocommerce/includes/class-wc-ajax.php b/plugins/woocommerce/includes/class-wc-ajax.php
index 55369cc6c64..fe87de8f76d 100644
--- a/plugins/woocommerce/includes/class-wc-ajax.php
+++ b/plugins/woocommerce/includes/class-wc-ajax.php
@@ -740,6 +740,29 @@ class WC_AJAX {
wp_die();
}
+ /**
+ * Check if a product attribute taxonomy supports visual term colors.
+ *
+ * @param string $taxonomy Taxonomy slug.
+ * @return bool
+ *
+ * @internal
+ */
+ private static function is_visual_product_attribute_taxonomy( $taxonomy ) {
+ if ( ! taxonomy_exists( $taxonomy ) || ! taxonomy_is_product_attribute( $taxonomy ) ) {
+ return false;
+ }
+
+ if ( ! array_key_exists( 'wc-visual', wc_get_attribute_types() ) ) {
+ return false;
+ }
+
+ $attribute_id = wc_attribute_taxonomy_id_by_name( $taxonomy );
+ $attribute = $attribute_id ? wc_get_attribute( $attribute_id ) : null;
+
+ return $attribute && 'wc-visual' === $attribute->type;
+ }
+
/**
* Add a new attribute via ajax function.
*
@@ -763,6 +786,14 @@ class WC_AJAX {
)
);
} else {
+ if ( self::is_visual_product_attribute_taxonomy( $taxonomy ) && isset( $_POST['term_color'] ) ) {
+ $color_value = sanitize_hex_color( wp_unslash( $_POST['term_color'] ) );
+
+ if ( $color_value ) {
+ update_term_meta( $result['term_id'], 'color', $color_value );
+ }
+ }
+
$term = get_term_by( 'id', $result['term_id'], $taxonomy );
wp_send_json(
array(
@@ -771,9 +802,9 @@ class WC_AJAX {
'slug' => $term->slug,
)
);
- }
- }
- }
+ }//end if
+ }//end if
+ }//end if
wp_die( -1 );
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index 9372fb92be5..9240b998ca2 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -130,6 +130,135 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
}
}
+ /**
+ * Test to verify that term color is saved in AJAX calls, but only for terms belonging to a visual attribute.
+ *
+ * @testdox Should save term color only when adding visual attribute terms via AJAX.
+ */
+ public function test_add_new_attribute_saves_color_only_for_visual_attributes(): void {
+ $original_theme = wp_get_theme()->get_stylesheet();
+ $visual_attribute_id = null;
+ $text_attribute_id = null;
+ $visual_taxonomy = null;
+ $text_taxonomy = null;
+ $visual_term_id = 0;
+ $text_term_id = 0;
+ $suffix = (string) wp_rand( 1000, 9999 );
+
+ $enable_visual_attribute_feature = function ( $features ) {
+ $features[] = 'wc-visual-attribute';
+ return array_unique( $features );
+ };
+
+ add_filter( 'woocommerce_admin_features', $enable_visual_attribute_feature );
+
+ try {
+ switch_theme( 'twentytwentyfour' );
+
+ $visual_attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Visual AJAX ' . $suffix,
+ 'type' => 'wc-visual',
+ )
+ );
+ $text_attribute_id = wc_create_attribute(
+ array(
+ 'name' => 'Text AJAX ' . $suffix,
+ 'type' => 'select',
+ )
+ );
+
+ $this->assertIsInt( $visual_attribute_id, 'The visual attribute should be created.' );
+ $this->assertIsInt( $text_attribute_id, 'The text attribute should be created.' );
+
+ $visual_taxonomy = $this->register_attribute_taxonomy_for_test( $visual_attribute_id );
+ $text_taxonomy = $this->register_attribute_taxonomy_for_test( $text_attribute_id );
+
+ $this->_setRole( 'administrator' );
+
+ $_POST['security'] = wp_create_nonce( 'add-attribute' );
+ $_POST['taxonomy'] = $visual_taxonomy;
+ $_POST['term'] = 'Cerulean ' . $suffix;
+ $_POST['term_color'] = '#336699';
+
+ $visual_response = $this->do_ajax( 'woocommerce_add_new_attribute' );
+ $visual_term_id = isset( $visual_response['term_id'] ) ? absint( $visual_response['term_id'] ) : 0;
+
+ $this->assertNotEmpty( $visual_term_id, 'The visual attribute term should be created.' );
+ $this->assertSame( '#336699', get_term_meta( $visual_term_id, 'color', true ), 'Visual attribute terms should store the posted color.' );
+
+ $_POST['security'] = wp_create_nonce( 'add-attribute' );
+ $_POST['taxonomy'] = $text_taxonomy;
+ $_POST['term'] = 'Plain ' . $suffix;
+ $_POST['term_color'] = '#abcdef';
+
+ $text_response = $this->do_ajax( 'woocommerce_add_new_attribute' );
+ $text_term_id = isset( $text_response['term_id'] ) ? absint( $text_response['term_id'] ) : 0;
+
+ $this->assertNotEmpty( $text_term_id, 'The text attribute term should be created.' );
+ $this->assertSame( '', get_term_meta( $text_term_id, 'color', true ), 'Text attribute terms should ignore posted colors.' );
+ } finally {
+ unset( $_POST['security'], $_POST['taxonomy'], $_POST['term'], $_POST['term_color'] );
+
+ if ( $visual_term_id && taxonomy_exists( $visual_taxonomy ) ) {
+ wp_delete_term( $visual_term_id, $visual_taxonomy );
+ }
+
+ if ( $text_term_id && taxonomy_exists( $text_taxonomy ) ) {
+ wp_delete_term( $text_term_id, $text_taxonomy );
+ }
+
+ if ( is_int( $visual_attribute_id ) ) {
+ wc_delete_attribute( $visual_attribute_id );
+ }
+
+ if ( is_int( $text_attribute_id ) ) {
+ wc_delete_attribute( $text_attribute_id );
+ }
+
+ global $wc_product_attributes;
+ foreach ( array_filter( array( $visual_taxonomy, $text_taxonomy ) ) as $taxonomy ) {
+ if ( taxonomy_exists( $taxonomy ) ) {
+ unregister_taxonomy( $taxonomy );
+ }
+ unset( $wc_product_attributes[ $taxonomy ] );
+ }
+
+ remove_filter( 'woocommerce_admin_features', $enable_visual_attribute_feature );
+ switch_theme( $original_theme );
+ }//end try
+ }
+
+ /**
+ * Register a product attribute taxonomy created inside a test.
+ *
+ * @param int $attribute_id Attribute ID.
+ * @return string
+ */
+ private function register_attribute_taxonomy_for_test( int $attribute_id ): string {
+ global $wc_product_attributes;
+
+ $taxonomy = wc_attribute_taxonomy_name_by_id( $attribute_id );
+ $attribute_taxonomies = wc_get_attribute_taxonomies();
+
+ $wc_product_attributes[ $taxonomy ] = $attribute_taxonomies[ 'id:' . $attribute_id ];
+
+ register_taxonomy(
+ $taxonomy,
+ array( 'product' ),
+ array(
+ 'capabilities' => array(
+ 'manage_terms' => 'manage_product_terms',
+ 'edit_terms' => 'edit_product_terms',
+ 'delete_terms' => 'delete_product_terms',
+ 'assign_terms' => 'assign_product_terms',
+ ),
+ )
+ );
+
+ return $taxonomy;
+ }
+
/**
* Test coupon and recalculation of totals sequences when product prices are tax inclusive.
*/
diff --git a/plugins/woocommerce/tests/php/includes/wc-attribute-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-attribute-functions-test.php
index 1b509fd30bd..fefa46bbcb4 100644
--- a/plugins/woocommerce/tests/php/includes/wc-attribute-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-attribute-functions-test.php
@@ -5,6 +5,8 @@
* @package WooCommerce\Tests\Functions.
*/
+declare( strict_types=1 );
+
use PHPUnit\Framework\MockObject\Matcher\InvokedRecorder;
/**