- Timestamp:
- 06/04/2024 10:59:01 AM (13 months ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php
r58321 r58327 75 75 76 76 /** 77 * Stack of namespaces defined by `data-wp-interactive` directives, in 78 * the order they are processed. 79 * 80 * This is only available during directive processing, otherwise it is `null`. 81 * 82 * @since 6.6.0 83 * @var array<string>|null 84 */ 85 private $namespace_stack = null; 86 87 /** 88 * Stack of contexts defined by `data-wp-context` directives, in 89 * the order they are processed. 90 * 91 * This is only available during directive processing, otherwise it is `null`. 92 * 93 * @since 6.6.0 94 * @var array<array<mixed>>|null 95 */ 96 private $context_stack = null; 97 98 /** 77 99 * Gets and/or sets the initial state of an Interactivity API store for a 78 100 * given namespace. … … 81 103 * provided state with the existing one. 82 104 * 83 * @since 6.5.0 84 * 85 * @param string $store_namespace The unique store namespace identifier. 105 * When no namespace is specified, it returns the state defined for the 106 * current value in the internal namespace stack during a `process_directives` call. 107 * 108 * @since 6.5.0 109 * @since 6.6.0 The `$store_namespace` param is optional. 110 * 111 * @param string $store_namespace Optional. The unique store namespace identifier. 86 112 * @param array $state Optional. The array that will be merged with the existing state for the specified 87 113 * store namespace. … … 89 115 * argument was provided. 90 116 */ 91 public function state( string $store_namespace, array $state = array() ): array { 117 public function state( ?string $store_namespace = null, ?array $state = null ): array { 118 if ( ! $store_namespace ) { 119 if ( $state ) { 120 _doing_it_wrong( 121 __METHOD__, 122 __( 'The namespace is required when state data is passed.' ), 123 '6.6.0' 124 ); 125 return array(); 126 } 127 if ( null !== $store_namespace ) { 128 _doing_it_wrong( 129 __METHOD__, 130 __( 'The namespace should be a non-empty string.' ), 131 '6.6.0' 132 ); 133 return array(); 134 } 135 if ( null === $this->namespace_stack ) { 136 _doing_it_wrong( 137 __METHOD__, 138 __( 'The namespace can only be omitted during directive processing.' ), 139 '6.6.0' 140 ); 141 return array(); 142 } 143 144 $store_namespace = end( $this->namespace_stack ); 145 } 92 146 if ( ! isset( $this->state_data[ $store_namespace ] ) ) { 93 147 $this->state_data[ $store_namespace ] = array(); … … 213 267 214 268 /** 269 * Returns the latest value on the context stack with the passed namespace. 270 * 271 * When the namespace is omitted, it uses the current namespace on the 272 * namespace stack during a `process_directives` call. 273 * 274 * @since 6.6.0 275 * 276 * @param string $store_namespace Optional. The unique store namespace identifier. 277 */ 278 public function get_context( ?string $store_namespace = null ): array { 279 if ( null === $this->context_stack ) { 280 _doing_it_wrong( 281 __METHOD__, 282 __( 'The context can only be read during directive processing.' ), 283 '6.6.0' 284 ); 285 return array(); 286 } 287 288 if ( ! $store_namespace ) { 289 if ( null !== $store_namespace ) { 290 _doing_it_wrong( 291 __METHOD__, 292 __( 'The namespace should be a non-empty string.' ), 293 '6.6.0' 294 ); 295 return array(); 296 } 297 298 $store_namespace = end( $this->namespace_stack ); 299 } 300 301 $context = end( $this->context_stack ); 302 303 return ( $store_namespace && $context && isset( $context[ $store_namespace ] ) ) 304 ? $context[ $store_namespace ] 305 : array(); 306 } 307 308 /** 215 309 * Registers the `@wordpress/interactivity` script modules. 216 310 * … … 259 353 } 260 354 261 $context_stack = array(); 262 $namespace_stack = array(); 263 $result = $this->process_directives_args( $html, $context_stack, $namespace_stack ); 355 $this->namespace_stack = array(); 356 $this->context_stack = array(); 357 358 $result = $this->_process_directives( $html ); 359 360 $this->namespace_stack = null; 361 $this->context_stack = null; 362 264 363 return null === $result ? $html : $result; 265 364 } … … 269 368 * and updates the markup accordingly. 270 369 * 271 * It needs the context and namespace stacks to be passed by reference, and 272 * it returns null if the HTML contains unbalanced tags. 273 * 274 * @since 6.5.0 275 * @since 6.6.0 The function displays a warning message when the HTML contains unbalanced tags or a directive appears in a MATH or SVG tag. 276 * 277 * @param string $html The HTML content to process. 278 * @param array $context_stack The reference to the array used to keep track of contexts during processing. 279 * @param array $namespace_stack The reference to the array used to manage namespaces during processing. 370 * It uses the WP_Interactivity_API instance's context and namespace stacks, 371 * which are shared between all calls. 372 * 373 * This method returns null if the HTML contains unbalanced tags. 374 * 375 * @since 6.6.0 376 * 377 * @param string $html The HTML content to process. 280 378 * @return string|null The processed HTML content. It returns null when the HTML contains unbalanced tags. 281 379 */ 282 private function process_directives_args( string $html, array &$context_stack, array &$namespace_stack) {380 private function _process_directives( string $html ) { 283 381 $p = new WP_Interactivity_API_Directives_Processor( $html ); 284 382 $tag_stack = array(); … … 287 385 $directive_processor_prefixes = array_keys( self::$directive_processors ); 288 386 $directive_processor_prefixes_reversed = array_reverse( $directive_processor_prefixes ); 387 388 /* 389 * Save the current size for each stack to restore them in case 390 * the processing finds unbalanced tags. 391 */ 392 $namespace_stack_size = count( $this->namespace_stack ); 393 $context_stack_size = count( $this->context_stack ); 289 394 290 395 while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) { … … 299 404 if ( $p->get_attribute_names_with_prefix( 'data-wp-' ) ) { 300 405 /* translators: 1: SVG or MATH HTML tag, 2: Namespace of the interactive block. */ 301 $message = sprintf( __( 'Interactivity directives were detected on an incompatible %1$s tag when processing "%2$s". These directives will be ignored in the server side render.' ), $tag_name, end( $ namespace_stack ) );406 $message = sprintf( __( 'Interactivity directives were detected on an incompatible %1$s tag when processing "%2$s". These directives will be ignored in the server side render.' ), $tag_name, end( $this->namespace_stack ) ); 302 407 _doing_it_wrong( __METHOD__, $message, '6.6.0' ); 303 408 } … … 382 487 : array( $this, self::$directive_processors[ $directive_prefix ] ); 383 488 384 call_user_func_array( 385 $func, 386 array( $p, $mode, &$context_stack, &$namespace_stack, &$tag_stack ) 387 ); 388 } 389 } 390 } 489 call_user_func_array( $func, array( $p, $mode, &$tag_stack ) ); 490 } 491 } 492 } 493 494 if ( $unbalanced ) { 495 // Reset the namespace and context stacks to their previous values. 496 array_splice( $this->namespace_stack, $namespace_stack_size ); 497 array_splice( $this->context_stack, $context_stack_size ); 498 } 499 391 500 /* 392 501 * It returns null if the HTML is unbalanced because unbalanced HTML is … … 398 507 $tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name; 399 508 /* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag. */ 400 $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $ namespace_stack ), $tag_errored );509 $message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $this->namespace_stack ), $tag_errored ); 401 510 _doing_it_wrong( __METHOD__, $message, '6.6.0' ); 402 511 return null; … … 412 521 * @since 6.5.0 413 522 * @since 6.6.0 The function now adds a warning when the namespace is null, falsy, or the directive value is empty. 414 * 415 * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. 416 * @param string $default_namespace The default namespace to use if none is explicitly defined in the directive 417 * value. 418 * @param array|false $context The current context for evaluating the directive or false if there is no 419 * context. 523 * @since 6.6.0 Removed `default_namespace` and `context` arguments. 524 * 525 * @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute. 420 526 * @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy. 421 527 */ 422 private function evaluate( $directive_value, string $default_namespace, $context = false ) { 528 private function evaluate( $directive_value ) { 529 $default_namespace = end( $this->namespace_stack ); 530 $context = end( $this->context_stack ); 531 423 532 list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace ); 424 533 if ( ! $ns || ! $path ) { … … 451 560 } 452 561 562 if ( $current instanceof Closure ) { 563 /* 564 * This state getter's namespace is added to the stack so that 565 * `state()` or `get_config()` read that namespace when called 566 * without specifying one. 567 */ 568 array_push( $this->namespace_stack, $ns ); 569 try { 570 $current = $current(); 571 } catch ( Throwable $e ) { 572 _doing_it_wrong( 573 __METHOD__, 574 sprintf( 575 /* translators: 1: Path pointing to an Interactivity API state property, 2: Namespace for an Interactivity API store. */ 576 __( 'Uncaught error executing a derived state callback with path "%1$s" and namespace "%2$s".' ), 577 $path, 578 $ns 579 ), 580 '6.6.0' 581 ); 582 return null; 583 } finally { 584 // Remove the property's namespace from the stack. 585 array_pop( $this->namespace_stack ); 586 } 587 } 588 453 589 // Returns the opposite if it contains a negation operator (!). 454 590 return $should_negate_value ? ! $current : $current; … … 552 688 * @since 6.5.0 553 689 * 554 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 555 * @param string $mode Whether the processing is entering or exiting the tag. 556 * @param array $context_stack The reference to the context stack. 557 * @param array $namespace_stack The reference to the store namespace stack. 558 */ 559 private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 690 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 691 * @param string $mode Whether the processing is entering or exiting the tag. 692 */ 693 private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 560 694 // When exiting tags, it removes the last namespace from the stack. 561 695 if ( 'exit' === $mode ) { 562 array_pop( $ namespace_stack );696 array_pop( $this->namespace_stack ); 563 697 return; 564 698 } … … 584 718 } 585 719 } 586 $ namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) )720 $this->namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) ) 587 721 ? $new_namespace 588 : end( $ namespace_stack );722 : end( $this->namespace_stack ); 589 723 } 590 724 … … 599 733 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 600 734 * @param string $mode Whether the processing is entering or exiting the tag. 601 * @param array $context_stack The reference to the context stack. 602 * @param array $namespace_stack The reference to the store namespace stack. 603 */ 604 private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 735 */ 736 private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 605 737 // When exiting tags, it removes the last context from the stack. 606 738 if ( 'exit' === $mode ) { 607 array_pop( $ context_stack );739 array_pop( $this->context_stack ); 608 740 return; 609 741 } 610 742 611 743 $attribute_value = $p->get_attribute( 'data-wp-context' ); 612 $namespace_value = end( $ namespace_stack );744 $namespace_value = end( $this->namespace_stack ); 613 745 614 746 // Separates the namespace from the context JSON object. … … 622 754 */ 623 755 if ( is_string( $namespace_value ) ) { 624 $ context_stack[] = array_replace_recursive(625 end( $ context_stack ) !== false ? end( $context_stack ) : array(),756 $this->context_stack[] = array_replace_recursive( 757 end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), 626 758 array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() ) 627 759 ); … … 632 764 * from the stack whenever it finds a `data-wp-context`'s closing tag. 633 765 */ 634 $ context_stack[] = end( $context_stack );766 $this->context_stack[] = end( $this->context_stack ); 635 767 } 636 768 } … … 646 778 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 647 779 * @param string $mode Whether the processing is entering or exiting the tag. 648 * @param array $context_stack The reference to the context stack. 649 * @param array $namespace_stack The reference to the store namespace stack. 650 */ 651 private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 780 */ 781 private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 652 782 if ( 'enter' === $mode ) { 653 783 $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' ); … … 660 790 661 791 $attribute_value = $p->get_attribute( $attribute_name ); 662 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));792 $result = $this->evaluate( $attribute_value ); 663 793 664 794 if ( … … 700 830 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 701 831 * @param string $mode Whether the processing is entering or exiting the tag. 702 * @param array $context_stack The reference to the context stack. 703 * @param array $namespace_stack The reference to the store namespace stack. 704 */ 705 private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 832 */ 833 private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 706 834 if ( 'enter' === $mode ) { 707 835 $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' ); … … 714 842 715 843 $attribute_value = $p->get_attribute( $attribute_name ); 716 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));844 $result = $this->evaluate( $attribute_value ); 717 845 718 846 if ( $result ) { … … 735 863 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 736 864 * @param string $mode Whether the processing is entering or exiting the tag. 737 * @param array $context_stack The reference to the context stack. 738 * @param array $namespace_stack The reference to the store namespace stack. 739 */ 740 private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 865 */ 866 private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 741 867 if ( 'enter' === $mode ) { 742 868 $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' ); … … 749 875 750 876 $directive_attribute_value = $p->get_attribute( $attribute_name ); 751 $style_property_value = $this->evaluate( $directive_attribute_value , end( $namespace_stack ), end( $context_stack ));877 $style_property_value = $this->evaluate( $directive_attribute_value ); 752 878 $style_attribute_value = $p->get_attribute( 'style' ); 753 879 $style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : ''; … … 828 954 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 829 955 * @param string $mode Whether the processing is entering or exiting the tag. 830 * @param array $context_stack The reference to the context stack. 831 * @param array $namespace_stack The reference to the store namespace stack. 832 */ 833 private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) { 956 */ 957 private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { 834 958 if ( 'enter' === $mode ) { 835 959 $attribute_value = $p->get_attribute( 'data-wp-text' ); 836 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));960 $result = $this->evaluate( $attribute_value ); 837 961 838 962 /* … … 966 1090 * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. 967 1091 * @param string $mode Whether the processing is entering or exiting the tag. 968 * @param array $context_stack The reference to the context stack.969 * @param array $namespace_stack The reference to the store namespace stack.970 1092 * @param array $tag_stack The reference to the tag stack. 971 1093 */ 972 private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$ context_stack, array &$namespace_stack, array &$tag_stack ) {1094 private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) { 973 1095 if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) { 974 1096 $attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0]; … … 976 1098 $item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item'; 977 1099 $attribute_value = $p->get_attribute( $attribute_name ); 978 $result = $this->evaluate( $attribute_value , end( $namespace_stack ), end( $context_stack ));1100 $result = $this->evaluate( $attribute_value ); 979 1101 980 1102 // Gets the content between the template tags and leaves the cursor in the closer tag. … … 1010 1132 1011 1133 // Extracts the namespace from the directive attribute value. 1012 $namespace_value = end( $ namespace_stack );1134 $namespace_value = end( $this->namespace_stack ); 1013 1135 list( $namespace_value ) = is_string( $attribute_value ) && ! empty( $attribute_value ) 1014 1136 ? $this->extract_directive_value( $attribute_value, $namespace_value ) … … 1019 1141 foreach ( $result as $item ) { 1020 1142 // Creates a new context that includes the current item of the array. 1021 $ context_stack[] = array_replace_recursive(1022 end( $ context_stack ) !== false ? end( $context_stack ) : array(),1143 $this->context_stack[] = array_replace_recursive( 1144 end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), 1023 1145 array( $namespace_value => array( $item_name => $item ) ) 1024 1146 ); 1025 1147 1026 1148 // Processes the inner content with the new context. 1027 $processed_item = $this-> process_directives_args( $inner_content, $context_stack, $namespace_stack);1149 $processed_item = $this->_process_directives( $inner_content ); 1028 1150 1029 1151 if ( null === $processed_item ) { 1030 1152 // If the HTML is unbalanced, stop processing it. 1031 array_pop( $ context_stack );1153 array_pop( $this->context_stack ); 1032 1154 return; 1033 1155 } … … 1042 1164 1043 1165 // Removes the current context from the stack. 1044 array_pop( $ context_stack );1166 array_pop( $this->context_stack ); 1045 1167 } 1046 1168
Note: See TracChangeset
for help on using the changeset viewer.