: 0 } } CSS; } /** * Deprecated. * * @since 6.5.0 * @deprecated 6.7.0 Use {@see WP_Interactivity_API::print_router_markup} instead. */ public function print_router_loading_and_screen_reader_markup() { _deprecated_function( __METHOD__, '6.7.0', 'WP_Interactivity_API::print_router_markup' ); // Call the new method. $this->print_router_markup(); } /** * Outputs markup for the @wordpress/interactivity-router script module. * * This method prints a div element representing a loading bar visible during * navigation. * * @since 6.7.0 */ public function print_router_markup() { echo << HTML; } /** * Processes the `data-wp-router-region` directive. * * It renders in the footer a set of HTML elements to notify users about * client-side navigations. More concretely, the elements added are 1) a * top loading bar to visually inform that a navigation is in progress * and 2) an `aria-live` region for accessible navigation announcements. * * @since 6.5.0 * * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. * @param string $mode Whether the processing is entering or exiting the tag. */ private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode && ! $this->has_processed_router_region ) { $this->has_processed_router_region = true; // Enqueues as an inline style. wp_register_style( 'wp-interactivity-router-animations', false ); wp_add_inline_style( 'wp-interactivity-router-animations', $this->get_router_animation_styles() ); wp_enqueue_style( 'wp-interactivity-router-animations' ); // Adds the necessary markup to the footer. add_action( 'wp_footer', array( $this, 'print_router_markup' ) ); } } /** * Processes the `data-wp-each` directive. * * This directive gets an array passed as reference and iterates over it * generating new content for each item based on the inner markup of the * `template` tag. * * @since 6.5.0 * * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance. * @param string $mode Whether the processing is entering or exiting the tag. * @param array $tag_stack The reference to the tag stack. */ private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$tag_stack ) { if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) { $attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0]; $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name ); $item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item'; $attribute_value = $p->get_attribute( $attribute_name ); $result = $this->evaluate( $attribute_value ); // Gets the content between the template tags and leaves the cursor in the closer tag. $inner_content = $p->get_content_between_balanced_template_tags(); // Checks if there is a manual server-side directive processing. $template_end = 'data-wp-each: template end'; $p->set_bookmark( $template_end ); $p->next_tag(); $manual_sdp = $p->get_attribute( 'data-wp-each-child' ); $p->seek( $template_end ); // Rewinds to the template closer tag. $p->release_bookmark( $template_end ); /* * It doesn't process in these situations: * - Manual server-side directive processing. * - Empty or non-array values. * - Associative arrays because those are deserialized as objects in JS. * - Templates that contain top-level texts because those texts can't be * identified and removed in the client. */ if ( $manual_sdp || empty( $result ) || ! is_array( $result ) || ! array_is_list( $result ) || ! str_starts_with( trim( $inner_content ), '<' ) || ! str_ends_with( trim( $inner_content ), '>' ) ) { array_pop( $tag_stack ); return; } // Extracts the namespace from the directive attribute value. $namespace_value = end( $this->namespace_stack ); list( $namespace_value ) = is_string( $attribute_value ) && ! empty( $attribute_value ) ? $this->extract_directive_value( $attribute_value, $namespace_value ) : array( $namespace_value, null ); // Processes the inner content for each item of the array. $processed_content = ''; foreach ( $result as $item ) { // Creates a new context that includes the current item of the array. $this->context_stack[] = array_replace_recursive( end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(), array( $namespace_value => array( $item_name => $item ) ) ); // Processes the inner content with the new context. $processed_item = $this->_process_directives( $inner_content ); if ( null === $processed_item ) { // If the HTML is unbalanced, stop processing it. array_pop( $this->context_stack ); return; } // Adds the `data-wp-each-child` to each top-level tag. $i = new WP_Interactivity_API_Directives_Processor( $processed_item ); while ( $i->next_tag() ) { $i->set_attribute( 'data-wp-each-child', true ); $i->next_balanced_tag_closer_tag(); } $processed_content .= $i->get_updated_html(); // Removes the current context from the stack. array_pop( $this->context_stack ); } // Appends the processed content after the tag closer of the template. $p->append_content_after_template_tag_closer( $processed_content ); // Pops the last tag because it skipped the closing tag of the template tag. array_pop( $tag_stack ); } } }