| 1 | 107a108,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 | > |
|---|
| 10 | 1155,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 | > } |
|---|
| 42 | 1176,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 | > } |
|---|
| 60 | 1179,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 | > ); |
|---|
| 101 | 1213,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 ); |
|---|
| 112 | 1222,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; |
|---|
| 121 | 1225,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 | > } |
|---|
| 137 | 1231,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 = ''; |
|---|
| 148 | 1239,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 ); |
|---|
| 156 | 1243,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 ); |
|---|
| 166 | 1246,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 | > } |
|---|