WP_Interactivity_API::_process_directives( string $html ): string|null

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness.

Processes the interactivity directives contained within the HTML content and updates the markup accordingly.

Description

It uses the WP_Interactivity_API instance’s context and namespace stacks, which are shared between all calls.

This method returns null if the HTML contains unbalanced tags.

Parameters

$htmlstringrequired
The HTML content to process.

Return

string|null The processed HTML content. It returns null when the HTML contains unbalanced tags.

Source

private function _process_directives( string $html ) {
	$p          = new WP_Interactivity_API_Directives_Processor( $html );
	$tag_stack  = array();
	$unbalanced = false;

	$directive_processor_prefixes          = array_keys( self::$directive_processors );
	$directive_processor_prefixes_reversed = array_reverse( $directive_processor_prefixes );

	/*
	 * Save the current size for each stack to restore them in case
	 * the processing finds unbalanced tags.
	 */
	$namespace_stack_size = count( $this->namespace_stack );
	$context_stack_size   = count( $this->context_stack );

	while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
		$tag_name = $p->get_tag();

		/*
		 * Directives inside SVG and MATH tags are not processed,
		 * as they are not compatible with the Tag Processor yet.
		 * We still process the rest of the HTML.
		 */
		if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
			if ( $p->get_attribute_names_with_prefix( 'data-wp-' ) ) {
				/* translators: 1: SVG or MATH HTML tag, 2: Namespace of the interactive block. */
				$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 ) );
				_doing_it_wrong( __METHOD__, $message, '6.6.0' );
			}
			$p->skip_to_tag_closer();
			continue;
		}

		if ( $p->is_tag_closer() ) {
			list( $opening_tag_name, $directives_prefixes ) = ! empty( $tag_stack ) ? end( $tag_stack ) : array( null, null );

			if ( 0 === count( $tag_stack ) || $opening_tag_name !== $tag_name ) {

				/*
				 * If the tag stack is empty or the matching opening tag is not the
				 * same than the closing tag, it means the HTML is unbalanced and it
				 * stops processing it.
				 */
				$unbalanced = true;
				break;
			} else {
				// Remove the last tag from the stack.
				array_pop( $tag_stack );
			}
		} else {
			$each_child_attrs = $p->get_attribute_names_with_prefix( 'data-wp-each-child' );
			if ( null === $each_child_attrs ) {
				continue;
			}

			if ( 0 !== count( $each_child_attrs ) ) {
				/*
				 * If the tag has a `data-wp-each-child` directive, jump to its closer
				 * tag because those tags have already been processed.
				 */
				$p->next_balanced_tag_closer_tag();
				continue;
			} else {
				$directives_prefixes = array();

				// Checks if there is a server directive processor registered for each directive.
				foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) {
					$parsed_directive = $this->parse_directive_name( $attribute_name );
					if ( empty( $parsed_directive ) ) {
						continue;
					}
					$directive_prefix = 'data-wp-' . $parsed_directive['prefix'];
					if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) {
						$directives_prefixes[] = $directive_prefix;
					}
				}

				/*
				 * If this tag will visit its closer tag, it adds it to the tag stack
				 * so it can process its closing tag and check for unbalanced tags.
				 */
				if ( $p->has_and_visits_its_closer_tag() ) {
					$tag_stack[] = array( $tag_name, $directives_prefixes );
				}
			}
		}
		/*
		 * If the matching opener tag didn't have any directives, it can skip the
		 * processing.
		 */
		if ( 0 === count( $directives_prefixes ) ) {
			continue;
		}

		// Directive processing might be different depending on if it is entering the tag or exiting it.
		$modes = array(
			'enter' => ! $p->is_tag_closer(),
			'exit'  => $p->is_tag_closer() || ! $p->has_and_visits_its_closer_tag(),
		);

		// Get the element attributes to include them in the element representation.
		$element_attrs = array();
		$attr_names    = $p->get_attribute_names_with_prefix( '' ) ?? array();

		foreach ( $attr_names as $name ) {
			$element_attrs[ $name ] = $p->get_attribute( $name );
		}

		// Assign the current element right before running its directive processors.
		$this->current_element = array(
			'attributes' => $element_attrs,
		);

		foreach ( $modes as $mode => $should_run ) {
			if ( ! $should_run ) {
				continue;
			}

			/*
			 * Sorts the attributes by the order of the `directives_processor` array
			 * and checks what directives are present in this element.
			 */
			$existing_directives_prefixes = array_intersect(
				'enter' === $mode ? $directive_processor_prefixes : $directive_processor_prefixes_reversed,
				$directives_prefixes
			);
			foreach ( $existing_directives_prefixes as $directive_prefix ) {
				$func = is_array( self::$directive_processors[ $directive_prefix ] )
					? self::$directive_processors[ $directive_prefix ]
					: array( $this, self::$directive_processors[ $directive_prefix ] );

				call_user_func_array( $func, array( $p, $mode, &$tag_stack ) );
			}
		}

		// Clear the current element.
		$this->current_element = null;
	}

	if ( $unbalanced ) {
		// Reset the namespace and context stacks to their previous values.
		array_splice( $this->namespace_stack, $namespace_stack_size );
		array_splice( $this->context_stack, $context_stack_size );
	}

	/*
	 * It returns null if the HTML is unbalanced because unbalanced HTML is
	 * not safe to process. In that case, the Interactivity API runtime will
	 * update the HTML on the client side during the hydration. It will display
	 * a notice to the developer in the console to inform them about the issue.
	 */
	if ( $unbalanced || 0 < count( $tag_stack ) ) {
		return null;
	}

	return $p->get_updated_html();
}

Changelog

VersionDescription
6.6.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.