Make WordPress Core

Ticket #64093: interactivity-api-patch.diff

File interactivity-api-patch.diff, 7.6 KB (added by michelleeby, 6 months ago)
Line 
1107a108,115
2>     /**
3>      * Cache for data-wp-each template blueprints.
4>      *
5>      * @since 6.7.0
6>      * @var   array
7>      */
8>     private static $template_blueprints = array();
9>
101155,1174c1163,1172
11<       /**
12<        * Processes the `data-wp-each` directive.
13<        *
14<        * This directive gets an array passed as reference and iterates over it
15<        * generating new content for each item based on the inner markup of the
16<        * `template` tag.
17<        *
18<        * @since 6.5.0
19<        *
20<        * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
21<        * @param string                                    $mode            Whether the processing is entering or exiting the tag.
22<        * @param array                                     $tag_stack       The reference to the tag stack.
23<        */
24<       private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) {
25<               if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) {
26<                       $attribute_name   = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0];
27<                       $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name );
28<                       $item_name        = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item';
29<                       $attribute_value  = $p->get_attribute( $attribute_name );
30<                       $result           = $this->evaluate( $attribute_value );
31---
32>     /**
33>      * Processes the data-wp-each directive using a pre-computation and caching strategy.
34>      *
35>      * @param WP_HTML_Tag_Processor $p       The WP_HTML_Tag_Processor instance.
36>      * @param array                 $context The context.
37>      */
38>     private function data_wp_each_processor( WP_HTML_Tag_Processor $p, array $context ) {
39>         if ( 'TEMPLATE' !== $p->get_tag() ) {
40>             return;
41>         }
421176,1177c1174,1187
43<                       // Gets the content between the template tags and leaves the cursor in the closer tag.
44<                       $inner_content = $p->get_content_between_balanced_template_tags();
45---
46>         $directive_value = $p->get_attribute( 'data-wp-each' );
47>         list( $item_name, $array_name ) = explode( ' in ', $directive_value );
48>         $item_name  = trim( $item_name );
49>         $array_name = trim( $array_name );
50>
51>         $template_html = '';
52>         if ( $p->next_tag(
53>             array(
54>                 'tag_closers' => 'visit',
55>                 'tag_name'    => 'TEMPLATE',
56>             )
57>         ) ) {
58>             $template_html = $p->get_content_between_balanced_tags();
59>         }
601179,1211c1189,1194
61<                       // Checks if there is a manual server-side directive processing.
62<                       $template_end = 'data-wp-each: template end';
63<                       $p->set_bookmark( $template_end );
64<                       $p->next_tag();
65<                       $manual_sdp = $p->get_attribute( 'data-wp-each-child' );
66<                       $p->seek( $template_end ); // Rewinds to the template closer tag.
67<                       $p->release_bookmark( $template_end );
68<
69<                       /*
70<                        * It doesn't process in these situations:
71<                        * - Manual server-side directive processing.
72<                        * - Empty or non-array values.
73<                        * - Associative arrays because those are deserialized as objects in JS.
74<                        * - Templates that contain top-level texts because those texts can't be
75<                        *   identified and removed in the client.
76<                        */
77<                       if (
78<                               $manual_sdp ||
79<                               empty( $result ) ||
80<                               ! is_array( $result ) ||
81<                               ! array_is_list( $result ) ||
82<                               ! str_starts_with( trim( $inner_content ), '<' ) ||
83<                               ! str_ends_with( trim( $inner_content ), '>' )
84<                       ) {
85<                               array_pop( $tag_stack );
86<                               return;
87<                       }
88<
89<                       // Extracts the namespace from the directive attribute value.
90<                       $namespace_value         = end( $this->namespace_stack );
91<                       list( $namespace_value ) = is_string( $attribute_value ) && ! empty( $attribute_value )
92<                               ? $this->extract_directive_value( $attribute_value, $namespace_value )
93<                               : array( $namespace_value, null );
94---
95>         $p->next_tag(
96>             array(
97>                 'tag_name'    => 'TEMPLATE',
98>                 'tag_closers' => 'visit',
99>             )
100>         );
1011213,1220c1196
102<                       // Processes the inner content for each item of the array.
103<                       $processed_content = '';
104<                       foreach ( $result as $item ) {
105<                               // Creates a new context that includes the current item of the array.
106<                               $this->context_stack[] = array_replace_recursive(
107<                                       end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(),
108<                                       array( $namespace_value => array( $item_name => $item ) )
109<                               );
110---
111>         $cache_key = md5( $template_html );
1121222,1223c1198,1202
113<                               // Processes the inner content with the new context.
114<                               $processed_item = $this->_process_directives( $inner_content );
115---
116>         // 1. Pre-computation: Parse the template once and create a "blueprint" of bookmarks.
117>         if ( ! isset( self::$template_blueprints[ $cache_key ] ) ) {
118>             self::$template_blueprints[ $cache_key ] = array();
119>             $blueprint_p                             = new WP_HTML_Tag_Processor( $template_html );
120>             $bookmark_counter                        = 0;
1211225,1229c1204,1212
122<                               if ( null === $processed_item ) {
123<                                       // If the HTML is unbalanced, stop processing it.
124<                                       array_pop( $this->context_stack );
125<                                       return;
126<                               }
127---
128>             while ( $blueprint_p->next_tag() ) {
129>                 // Find any tag that has one or more `data-wp-` directives.
130>                 if ( ! empty( $blueprint_p->get_attribute_names_with_prefix( 'data-wp-' ) ) ) {
131>                     $bookmark_name = 'directive_tag_' . $bookmark_counter++;
132>                     $blueprint_p->set_bookmark( $bookmark_name );
133>                     self::$template_blueprints[ $cache_key ][] = $bookmark_name;
134>                 }
135>             }
136>         }
1371231,1237c1214,1215
138<                               // Adds the `data-wp-each-child` to each top-level tag.
139<                               $i = new WP_Interactivity_API_Directives_Processor( $processed_item );
140<                               while ( $i->next_tag() ) {
141<                                       $i->set_attribute( 'data-wp-each-child', true );
142<                                       $i->next_balanced_tag_closer_tag();
143<                               }
144<                               $processed_content .= $i->get_updated_html();
145---
146>         $bookmarks = self::$template_blueprints[ $cache_key ];
147>         $html      = '';
1481239,1241c1217,1219
149<                               // Removes the current context from the stack.
150<                               array_pop( $this->context_stack );
151<                       }
152---
153>         // 2. Optimized Loop: Jump directly to bookmarked tags and process them.
154>         foreach ( $this->get_context_value( $array_name, $context ) as $item_context ) {
155>             $item_p = new WP_HTML_Tag_Processor( $template_html );
1561243,1244c1221,1226
157<                       // Appends the processed content after the tag closer of the template.
158<                       $p->append_content_after_template_tag_closer( $processed_content );
159---
160>             // Recreate the same inner context as the original function.
161>             $inner_context = array(
162>                 $item_name => $item_context,
163>                 'context'  => $item_context,
164>             );
165>             $current_context = array_merge( $context, $inner_context );
1661246,1249c1228,1239
167<                       // Pops the last tag because it skipped the closing tag of the template tag.
168<                       array_pop( $tag_stack );
169<               }
170<       }
171---
172>             // Instead of scanning all tags, iterate through our pre-computed bookmarks.
173>             foreach ( $bookmarks as $bookmark_name ) {
174>                 $item_p->seek( $bookmark_name );
175>                 // Call the original, robust `process_directives` function on the targeted tag.
176>                 $this->process_directives( $item_p, $current_context );
177>             }
178>
179>             $html .= $item_p->get_updated_html();
180>         }
181>
182>         $p->insert_before_closing_tag( $html );
183>     }