@@ -103,85 +103,116 @@ public function inject_color_swatches_in_block( string $block_content, array $bl
103103 * @return string Modified block content.
104104 */
105105 private function inject_swatches_with_html_processor ( string $ block_content ): string {
106- // Use regex to find and replace label elements with color swatches.
107- $ pattern = '/<label[^>]*class="[^"]*wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill[^"]*"[^>]*>(.*?)<\/label>/s ' ;
108-
109- $ result = preg_replace_callback (
110- $ pattern ,
111- function ( $ matches ) {
112- $ label_content = $ matches [0 ];
113- $ inner_content = $ matches [1 ];
114-
115- // Extract the color value from the input.
116- if ( preg_match ( '/name="attribute_pa_color"\s+value="([^"]*)"/ ' , $ inner_content , $ input_matches ) ) {
117- $ color_slug = $ input_matches [1 ];
118-
119- // Get color term and value.
120- $ color_term = get_term_by ( 'slug ' , $ color_slug , 'pa_color ' );
121-
122- if ( $ color_term ) {
123- $ color_data = $ this ->get_color_display_data_for_term ( $ color_term );
124-
125- if ( $ color_data ['is_valid ' ] && ! empty ( $ color_data ['value ' ] ) ) {
126- // Create accessible swatch HTML.
127- $ aria_label = sprintf (
128- /* translators: %s: color name */
129- __ ( 'Color option: %s ' , 'aggressive-apparel ' ),
130- $ color_term ->name
131- );
132-
133- $ classes = 'aggressive-apparel-color-swatch aggressive-apparel-color-swatch--interactive ' ;
134- if ( $ this ->show_label ) {
135- $ classes .= ' aggressive-apparel-color-swatch--with-label ' ;
136- }
137-
138- // Different styling based on color type.
139- if ( 'pattern ' === $ color_data ['type ' ] ) {
140- $ classes .= ' aggressive-apparel-color-swatch--pattern ' ;
141- $ background_style = 'background-image: url( \'' . esc_url ( $ color_data ['value ' ] ) . '\'); ' ;
142- $ data_attributes = 'data-pattern-url=" ' . esc_attr ( $ color_data ['value ' ] ) . '" ' ;
143- } else {
144- $ background_style = 'background-color: ' . esc_attr ( $ color_data ['value ' ] ) . '; ' ;
145- $ data_attributes = 'data-color=" ' . esc_attr ( $ color_data ['value ' ] ) . '" ' ;
146- }
147-
148- $ swatch_html = '<span class=" ' . esc_attr ( $ classes ) . ' aggressive-apparel-color-swatch__circle" ' .
149- 'style=" ' . $ background_style . '" ' .
150- 'aria-label=" ' . esc_attr ( $ aria_label ) . '" ' .
151- 'role="img" ' .
152- 'tabindex="0" ' .
153- 'title=" ' . esc_attr ( $ color_term ->name ) . '" ' .
154- $ data_attributes . ' ' .
155- 'data-color-name=" ' . esc_attr ( $ color_term ->name ) . '"> ' .
156- '</span> ' ;
157-
158- // Modify the label content based on show_label setting.
159- if ( ! $ this ->show_label ) {
160- // Replace content after input tag with swatch.
161- $ modified_content = preg_replace ( '/(<input[^>]*>)(.*?)<\/label>$/s ' , '$1 ' . $ swatch_html . '</label> ' , $ label_content );
162- } else {
163- // Insert swatch after input tag, keep text.
164- $ modified_content = preg_replace ( '/(<input[^>]*>)/ ' , '$1 ' . $ swatch_html , $ label_content , 1 );
165- }
166-
167- // Ensure we have valid content.
168- if ( null === $ modified_content ) {
169- $ modified_content = $ label_content ;
170- }
171-
172- return $ modified_content ;
173- }
174- }
106+ $ dom = new \DOMDocument ();
107+ $ loaded = $ dom ->loadHTML (
108+ $ block_content ,
109+ LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
110+ );
111+
112+ if ( ! $ loaded ) {
113+ return $ block_content ;
114+ }
115+
116+ $ labels = $ dom ->getElementsByTagName ( 'label ' );
117+ $ modified = false ;
118+
119+ // NodeList is live, so copy to array before mutations.
120+ $ label_nodes = array ();
121+ foreach ( $ labels as $ label ) {
122+ $ label_nodes [] = $ label ;
123+ }
124+
125+ foreach ( $ label_nodes as $ label ) {
126+ $ class_attr = $ label ->getAttribute ( 'class ' );
127+ if ( false === strpos ( $ class_attr , 'wc-block-add-to-cart-with-options-variation-selector-attribute-options__pill ' ) ) {
128+ continue ;
129+ }
130+
131+ $ inputs = $ label ->getElementsByTagName ( 'input ' );
132+ if ( 0 === $ inputs ->length ) {
133+ continue ;
134+ }
135+
136+ $ target_input = null ;
137+ foreach ( $ inputs as $ input ) {
138+ if ( 'attribute_pa_color ' === $ input ->getAttribute ( 'name ' ) ) {
139+ $ target_input = $ input ;
140+ break ;
175141 }
142+ }
176143
177- // Return unchanged if no color processing needed.
178- return $ label_content ;
179- },
180- $ block_content
181- );
144+ if ( ! $ target_input ) {
145+ continue ;
146+ }
147+
148+ $ color_slug = $ target_input ->getAttribute ( 'value ' );
149+ if ( ! $ color_slug ) {
150+ continue ;
151+ }
152+
153+ $ color_term = get_term_by ( 'slug ' , $ color_slug , 'pa_color ' );
154+ if ( ! $ color_term ) {
155+ continue ;
156+ }
157+
158+ $ color_data = $ this ->get_color_display_data_for_term ( $ color_term );
159+ if ( ! $ color_data ['is_valid ' ] || empty ( $ color_data ['value ' ] ) ) {
160+ continue ;
161+ }
162+
163+ $ aria_label = sprintf (
164+ /* translators: %s: color name */
165+ __ ( 'Color option: %s ' , 'aggressive-apparel ' ),
166+ $ color_term ->name
167+ );
168+
169+ $ classes = 'aggressive-apparel-color-swatch aggressive-apparel-color-swatch--interactive ' ;
170+ if ( $ this ->show_label ) {
171+ $ classes .= ' aggressive-apparel-color-swatch--with-label ' ;
172+ }
173+
174+ $ swatch = $ dom ->createElement ( 'span ' );
175+ $ swatch ->setAttribute ( 'class ' , $ classes . ' aggressive-apparel-color-swatch__circle ' );
176+ $ swatch ->setAttribute ( 'aria-label ' , $ aria_label );
177+ $ swatch ->setAttribute ( 'role ' , 'img ' );
178+ $ swatch ->setAttribute ( 'tabindex ' , '0 ' );
179+ $ swatch ->setAttribute ( 'title ' , $ color_term ->name );
180+ $ swatch ->setAttribute ( 'data-color-name ' , $ color_term ->name );
181+
182+ if ( 'pattern ' === $ color_data ['type ' ] ) {
183+ $ swatch ->setAttribute ( 'data-pattern-url ' , $ color_data ['value ' ] );
184+ $ swatch ->setAttribute ( 'style ' , 'background-image: url( ' . esc_url ( $ color_data ['value ' ] ) . '); ' );
185+ $ swatch ->setAttribute ( 'class ' , $ swatch ->getAttribute ( 'class ' ) . ' aggressive-apparel-color-swatch--pattern ' );
186+ } else {
187+ $ swatch ->setAttribute ( 'data-color ' , $ color_data ['value ' ] );
188+ $ swatch ->setAttribute ( 'style ' , 'background-color: ' . esc_attr ( $ color_data ['value ' ] ) . '; ' );
189+ }
190+
191+ // Preserve original label text (trimmed) to re-append when show_label is true.
192+ $ label_text = trim ( $ label ->textContent ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
193+
194+ // Rebuild label content: input + swatch (+ text when configured).
195+ while ( $ label ->firstChild ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
196+ $ label ->removeChild ( $ label ->firstChild ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
197+ }
198+
199+ $ label ->appendChild ( $ target_input ->cloneNode ( true ) );
200+ $ label ->appendChild ( $ swatch );
201+
202+ if ( $ this ->show_label && '' !== $ label_text ) {
203+ $ label ->appendChild ( $ dom ->createTextNode ( $ label_text ) );
204+ }
205+
206+ $ modified = true ;
207+ }
208+
209+ if ( ! $ modified ) {
210+ return $ block_content ;
211+ }
212+
213+ $ output = $ dom ->saveHTML ();
182214
183- // Ensure we always return a string, even if preg_replace_callback fails.
184- return ( null !== $ result ) ? $ result : $ block_content ;
215+ return $ output ? $ output : $ block_content ;
185216 }
186217
187218 /**
0 commit comments