This commit is contained in:
Hanson.xyz Dev
2026-01-04 17:50:08 -06:00
parent 7e45ce0756
commit acc8ac87a0
4131 changed files with 232562 additions and 250244 deletions
@@ -92,6 +92,8 @@ final class WP_Interactivity_API_Directives_Processor extends WP_HTML_Tag_Proces
* It positions the cursor in the closer tag of the balanced template tag,
* if it exists.
*
* @since 6.5.0
*
* @access private
*
* @param string $new_content The string to append after the closing template tag.
@@ -58,6 +58,18 @@ final class WP_Interactivity_API {
*/
private $config_data = array();
/**
* Keeps track of all derived state closures accessed during server-side rendering.
*
* This data is serialized and sent to the client as part of the interactivity
* data, and is handled later in the client to support derived state props that
* are lazily hydrated.
*
* @since 6.9.0
* @var array
*/
private $derived_state_closures = array();
/**
* Flag that indicates whether the `data-wp-router-region` directive has
* been found in the HTML and processed.
@@ -73,6 +85,14 @@ final class WP_Interactivity_API {
*/
private $has_processed_router_region = false;
/**
* Set of script modules that can be loaded after client-side navigation.
*
* @since 6.9.0
* @var array<string, true>
*/
private $script_modules_that_can_load_on_client_navigation = array();
/**
* Stack of namespaces defined by `data-wp-interactive` directives, in
* the order they are processed.
@@ -236,12 +256,17 @@ final class WP_Interactivity_API {
* interactivity stores and the configuration will be available using a `getConfig` utility.
*
* @since 6.7.0
* @since 6.9.0 Serializes derived state props accessed during directive processing.
*
* @param array $data Data to filter.
* @return array Data for the Interactivity API script module.
*/
public function filter_script_module_interactivity_data( array $data ): array {
if ( empty( $this->state_data ) && empty( $this->config_data ) ) {
if (
empty( $this->state_data ) &&
empty( $this->config_data ) &&
empty( $this->derived_state_closures )
) {
return $data;
}
@@ -265,6 +290,16 @@ final class WP_Interactivity_API {
$data['state'] = $state;
}
$derived_props = array();
foreach ( $this->derived_state_closures as $key => $value ) {
if ( ! empty( $value ) ) {
$derived_props[ $key ] = $value;
}
}
if ( ! empty( $derived_props ) ) {
$data['derivedStateClosures'] = $derived_props;
}
return $data;
}
@@ -344,10 +379,56 @@ final class WP_Interactivity_API {
* Adds the necessary hooks for the Interactivity API.
*
* @since 6.5.0
* @since 6.9.0 Adds support for client-side navigation in script modules.
*/
public function add_hooks() {
add_filter( 'script_module_data_@wordpress/interactivity', array( $this, 'filter_script_module_interactivity_data' ) );
add_filter( 'script_module_data_@wordpress/interactivity-router', array( $this, 'filter_script_module_interactivity_router_data' ) );
add_filter( 'wp_script_attributes', array( $this, 'add_load_on_client_navigation_attribute_to_script_modules' ), 10, 1 );
}
/**
* Adds the `data-wp-router-options` attribute to script modules that
* support client-side navigation.
*
* This method filters the script attributes to include loading instructions
* for the Interactivity API router, indicating which modules can be loaded
* during client-side navigation.
*
* @since 6.9.0
*
* @param array<string, string|true>|mixed $attributes The script tag attributes.
* @return array The modified script tag attributes.
*/
public function add_load_on_client_navigation_attribute_to_script_modules( $attributes ) {
if (
is_array( $attributes ) &&
isset( $attributes['type'], $attributes['id'] ) &&
'module' === $attributes['type'] &&
array_key_exists(
preg_replace( '/-js-module$/', '', $attributes['id'] ),
$this->script_modules_that_can_load_on_client_navigation
)
) {
$attributes['data-wp-router-options'] = wp_json_encode( array( 'loadOnClientNavigation' => true ) );
}
return $attributes;
}
/**
* Marks a script module as compatible with client-side navigation.
*
* This method registers a script module to be loaded during client-side
* navigation in the Interactivity API router. Script modules marked with
* this method will have the `loadOnClientNavigation` option enabled in the
* `data-wp-router-options` directive.
*
* @since 6.9.0
*
* @param string $script_module_id The script module identifier.
*/
public function add_client_navigation_support_to_script_module( string $script_module_id ) {
$this->script_modules_that_can_load_on_client_navigation[ $script_module_id ] = true;
}
/**
@@ -423,7 +504,7 @@ final class WP_Interactivity_API {
}
if ( $p->is_tag_closer() ) {
list( $opening_tag_name, $directives_prefixes ) = end( $tag_stack );
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 ) {
@@ -439,7 +520,12 @@ final class WP_Interactivity_API {
array_pop( $tag_stack );
}
} else {
if ( 0 !== count( $p->get_attribute_names_with_prefix( 'data-wp-each-child' ) ) ) {
$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.
@@ -451,27 +537,11 @@ final class WP_Interactivity_API {
// Checks if there is a server directive processor registered for each directive.
foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) as $attribute_name ) {
if ( ! preg_match(
/*
* This must align with the client-side regex used by the interactivity API.
* @see https://github.com/WordPress/gutenberg/blob/ca616014255efbb61f34c10917d52a2d86c1c660/packages/interactivity/src/vdom.ts#L20-L32
*/
'/' .
'^data-wp-' .
// Match alphanumeric characters including hyphen-separated
// segments. It excludes underscore intentionally to prevent confusion.
// E.g., "custom-directive".
'([a-z0-9]+(?:-[a-z0-9]+)*)' .
// (Optional) Match '--' followed by any alphanumeric charachters. It
// excludes underscore intentionally to prevent confusion, but it can
// contain multiple hyphens. E.g., "--custom-prefix--with-more-info".
'(?:--([a-z0-9_-]+))?$' .
'/i',
$attribute_name
) ) {
$parsed_directive = $this->parse_directive_name( $attribute_name );
if ( empty( $parsed_directive ) ) {
continue;
}
list( $directive_prefix ) = $this->extract_prefix_and_suffix( $attribute_name );
$directive_prefix = 'data-wp-' . $parsed_directive['prefix'];
if ( array_key_exists( $directive_prefix, self::$directive_processors ) ) {
$directives_prefixes[] = $directive_prefix;
}
@@ -548,14 +618,10 @@ final class WP_Interactivity_API {
/*
* 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 also
* display a notice to the developer to inform them about the issue.
* 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 ) ) {
$tag_errored = 0 < count( $tag_stack ) ? end( $tag_stack )[0] : $tag_name;
/* translators: %1s: Namespace processed, %2s: The tag that caused the error; could be any HTML tag. */
$message = sprintf( __( 'Interactivity directives failed to process in "%1$s" due to a missing "%2$s" end tag.' ), end( $this->namespace_stack ), $tag_errored );
_doing_it_wrong( __METHOD__, $message, '6.6.0' );
return null;
}
@@ -570,18 +636,18 @@ final class WP_Interactivity_API {
* @since 6.6.0 The function now adds a warning when the namespace is null, falsy, or the directive value is empty.
* @since 6.6.0 Removed `default_namespace` and `context` arguments.
* @since 6.6.0 Add support for derived state.
* @since 6.9.0 Recieve $entry as an argument instead of the directive value string.
*
* @param string|true $directive_value The directive attribute value string or `true` when it's a boolean attribute.
* @param array $entry An array containing a whole directive entry with its namespace, value, suffix, or unique ID.
* @return mixed|null The result of the evaluation. Null if the reference path doesn't exist or the namespace is falsy.
*/
private function evaluate( $directive_value ) {
$default_namespace = end( $this->namespace_stack );
$context = end( $this->context_stack );
private function evaluate( $entry ) {
$context = end( $this->context_stack );
['namespace' => $ns, 'value' => $path] = $entry;
list( $ns, $path ) = $this->extract_directive_value( $directive_value, $default_namespace );
if ( ! $ns || ! $path ) {
/* translators: %s: The directive value referenced. */
$message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), $directive_value );
$message = sprintf( __( 'Namespace or reference path cannot be empty. Directive value referenced: %s' ), json_encode( $entry ) );
_doing_it_wrong( __METHOD__, $message, '6.6.0' );
return null;
}
@@ -598,7 +664,7 @@ final class WP_Interactivity_API {
// Extracts the value from the store using the reference path.
$path_segments = explode( '.', $path );
$current = $store;
foreach ( $path_segments as $path_segment ) {
foreach ( $path_segments as $index => $path_segment ) {
/*
* Special case for numeric arrays and strings. Add length
* property mimicking JavaScript behavior.
@@ -647,6 +713,20 @@ final class WP_Interactivity_API {
array_push( $this->namespace_stack, $ns );
try {
$current = $current();
/*
* Tracks derived state properties that are accessed during
* rendering.
*
* @since 6.9.0
*/
$this->derived_state_closures[ $ns ] = $this->derived_state_closures[ $ns ] ?? array();
// Builds path for the current property and add it to tracking if not already present.
$current_path = implode( '.', array_slice( $path_segments, 0, $index + 1 ) );
if ( ! in_array( $current_path, $this->derived_state_closures[ $ns ], true ) ) {
$this->derived_state_closures[ $ns ][] = $current_path;
}
} catch ( Throwable $e ) {
_doing_it_wrong(
__METHOD__,
@@ -671,25 +751,74 @@ final class WP_Interactivity_API {
}
/**
* Extracts the directive attribute name to separate and return the directive
* prefix and an optional suffix.
* Parse the directive name to extract the following parts:
* - Prefix: The main directive name without "data-wp-".
* - Suffix: An optional suffix used during directive processing, extracted after the first double hyphen "--".
* - Unique ID: An optional unique identifier, extracted after the first triple hyphen "---".
*
* The suffix is the string after the first double hyphen and the prefix is
* everything that comes before the suffix.
* This function has an equivalent version for the client side.
* See `parseDirectiveName` in https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/src/vdom.ts.:
*
* Example:
* See examples in the function unit tests `test_parse_directive_name`.
*
* extract_prefix_and_suffix( 'data-wp-interactive' ) => array( 'data-wp-interactive', null )
* extract_prefix_and_suffix( 'data-wp-bind--src' ) => array( 'data-wp-bind', 'src' )
* extract_prefix_and_suffix( 'data-wp-foo--and--bar' ) => array( 'data-wp-foo', 'and--bar' )
*
* @since 6.5.0
* @since 6.9.0
*
* @param string $directive_name The directive attribute name.
* @return array An array containing the directive prefix and optional suffix.
* @return array An array containing the directive prefix, optional suffix, and optional unique ID.
*/
private function extract_prefix_and_suffix( string $directive_name ): array {
return explode( '--', $directive_name, 2 );
private function parse_directive_name( string $directive_name ): ?array {
// Remove the first 8 characters (assumes "data-wp-" prefix)
$name = substr( $directive_name, 8 );
// Check for invalid characters (anything not a-z, 0-9, -, or _)
if ( preg_match( '/[^a-z0-9\-_]/i', $name ) ) {
return null;
}
// Find the first occurrence of '--' to separate the prefix
$suffix_index = strpos( $name, '--' );
if ( false === $suffix_index ) {
return array(
'prefix' => $name,
'suffix' => null,
'unique_id' => null,
);
}
$prefix = substr( $name, 0, $suffix_index );
$remaining = substr( $name, $suffix_index );
// If remaining starts with '---' but not '----', it's a unique_id
if ( '---' === substr( $remaining, 0, 3 ) && '-' !== ( $remaining[3] ?? '' ) ) {
return array(
'prefix' => $prefix,
'suffix' => null,
'unique_id' => '---' !== $remaining ? substr( $remaining, 3 ) : null,
);
}
// Otherwise, remove the first two dashes for a potential suffix
$suffix = substr( $remaining, 2 );
// Look for '---' in the suffix for a unique_id
$unique_id_index = strpos( $suffix, '---' );
if ( false !== $unique_id_index && '-' !== ( $suffix[ $unique_id_index + 3 ] ?? '' ) ) {
$unique_id = substr( $suffix, $unique_id_index + 3 );
$suffix = substr( $suffix, 0, $unique_id_index );
return array(
'prefix' => $prefix,
'suffix' => empty( $suffix ) ? null : $suffix,
'unique_id' => empty( $unique_id ) ? null : $unique_id,
);
}
return array(
'prefix' => $prefix,
'suffix' => empty( $suffix ) ? null : $suffix,
'unique_id' => null,
);
}
/**
@@ -741,9 +870,58 @@ final class WP_Interactivity_API {
return array( $default_namespace, $directive_value );
}
/**
* Parse the HTML element and get all the valid directives with the given prefix.
*
* @since 6.9.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $prefix The directive prefix to filter by.
* @return array An array of entries containing the directive namespace, value, suffix, and unique ID.
*/
private function get_directive_entries( WP_Interactivity_API_Directives_Processor $p, string $prefix ) {
$directive_attributes = $p->get_attribute_names_with_prefix( 'data-wp-' . $prefix );
$entries = array();
foreach ( $directive_attributes as $attribute_name ) {
[ 'prefix' => $attr_prefix, 'suffix' => $suffix, 'unique_id' => $unique_id] = $this->parse_directive_name( $attribute_name );
// Ensure it is the desired directive.
if ( $prefix !== $attr_prefix ) {
continue;
}
list( $namespace, $value ) = $this->extract_directive_value( $p->get_attribute( $attribute_name ), end( $this->namespace_stack ) );
$entries[] = array(
'namespace' => $namespace,
'value' => $value,
'suffix' => $suffix,
'unique_id' => $unique_id,
);
}
// Sort directive entries to ensure stable ordering with the client.
// Put nulls first, then sort by suffix and finally by uniqueIds.
usort(
$entries,
function ( $a, $b ) {
$a_suffix = $a['suffix'] ?? '';
$b_suffix = $b['suffix'] ?? '';
if ( $a_suffix !== $b_suffix ) {
return $a_suffix < $b_suffix ? -1 : 1;
}
$a_id = $a['unique_id'] ?? '';
$b_id = $b['unique_id'] ?? '';
if ( $a_id === $b_id ) {
return 0;
}
return $a_id > $b_id ? 1 : -1;
}
);
return $entries;
}
/**
* Transforms a kebab-case string to camelCase.
*
* @since 6.5.0
*
* @param string $str The kebab-case string to transform to camelCase.
* @return string The transformed camelCase string.
*/
@@ -820,31 +998,19 @@ final class WP_Interactivity_API {
return;
}
$attribute_value = $p->get_attribute( 'data-wp-context' );
$namespace_value = end( $this->namespace_stack );
$entries = $this->get_directive_entries( $p, 'context' );
$context = end( $this->context_stack ) !== false ? end( $this->context_stack ) : array();
foreach ( $entries as $entry ) {
if ( null !== $entry['suffix'] ) {
continue;
}
// Separates the namespace from the context JSON object.
list( $namespace_value, $decoded_json ) = is_string( $attribute_value ) && ! empty( $attribute_value )
? $this->extract_directive_value( $attribute_value, $namespace_value )
: array( $namespace_value, null );
/*
* If there is a namespace, it adds a new context to the stack merging the
* previous context with the new one.
*/
if ( is_string( $namespace_value ) ) {
$this->context_stack[] = array_replace_recursive(
end( $this->context_stack ) !== false ? end( $this->context_stack ) : array(),
array( $namespace_value => is_array( $decoded_json ) ? $decoded_json : array() )
$context = array_replace_recursive(
$context,
array( $entry['namespace'] => is_array( $entry['value'] ) ? $entry['value'] : array() )
);
} else {
/*
* If there is no namespace, it pushes the current context to the stack.
* It needs to do so because the function pops out the current context
* from the stack whenever it finds a `data-wp-context`'s closing tag.
*/
$this->context_stack[] = end( $this->context_stack );
}
$this->context_stack[] = $context;
}
/**
@@ -860,22 +1026,19 @@ final class WP_Interactivity_API {
*/
private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
if ( 'enter' === $mode ) {
$all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' );
foreach ( $all_bind_directives as $attribute_name ) {
list( , $bound_attribute ) = $this->extract_prefix_and_suffix( $attribute_name );
if ( empty( $bound_attribute ) ) {
return;
$entries = $this->get_directive_entries( $p, 'bind' );
foreach ( $entries as $entry ) {
if ( empty( $entry['suffix'] ) || null !== $entry['unique_id'] ) {
return;
}
$attribute_value = $p->get_attribute( $attribute_name );
$result = $this->evaluate( $attribute_value );
$result = $this->evaluate( $entry );
if (
null !== $result &&
(
false !== $result ||
( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] )
( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
)
) {
/*
@@ -887,13 +1050,13 @@ final class WP_Interactivity_API {
*/
if (
is_bool( $result ) &&
( strlen( $bound_attribute ) > 5 && '-' === $bound_attribute[4] )
( strlen( $entry['suffix'] ) > 5 && '-' === $entry['suffix'][4] )
) {
$result = $result ? 'true' : 'false';
}
$p->set_attribute( $bound_attribute, $result );
$p->set_attribute( $entry['suffix'], $result );
} else {
$p->remove_attribute( $bound_attribute );
$p->remove_attribute( $entry['suffix'] );
}
}
}
@@ -913,15 +1076,20 @@ final class WP_Interactivity_API {
private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
if ( 'enter' === $mode ) {
$all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' );
$entries = $this->get_directive_entries( $p, 'class' );
foreach ( $entries as $entry ) {
if ( empty( $entry['suffix'] ) ) {
continue;
}
$class_name = isset( $entry['unique_id'] ) && $entry['unique_id']
? "{$entry['suffix']}---{$entry['unique_id']}"
: $entry['suffix'];
foreach ( $all_class_directives as $attribute_name ) {
list( , $class_name ) = $this->extract_prefix_and_suffix( $attribute_name );
if ( empty( $class_name ) ) {
return;
}
$attribute_value = $p->get_attribute( $attribute_name );
$result = $this->evaluate( $attribute_value );
$result = $this->evaluate( $entry );
if ( $result ) {
$p->add_class( $class_name );
@@ -945,18 +1113,16 @@ final class WP_Interactivity_API {
*/
private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
if ( 'enter' === $mode ) {
$all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' );
foreach ( $all_style_attributes as $attribute_name ) {
list( , $style_property ) = $this->extract_prefix_and_suffix( $attribute_name );
if ( empty( $style_property ) ) {
$entries = $this->get_directive_entries( $p, 'style' );
foreach ( $entries as $entry ) {
$style_property = $entry['suffix'];
if ( empty( $style_property ) || null !== $entry['unique_id'] ) {
continue;
}
$directive_attribute_value = $p->get_attribute( $attribute_name );
$style_property_value = $this->evaluate( $directive_attribute_value );
$style_attribute_value = $p->get_attribute( 'style' );
$style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : '';
$style_property_value = $this->evaluate( $entry );
$style_attribute_value = $p->get_attribute( 'style' );
$style_attribute_value = ( $style_attribute_value && ! is_bool( $style_attribute_value ) ) ? $style_attribute_value : '';
/*
* Checks first if the style property is not falsy and the style
@@ -1036,8 +1202,19 @@ final class WP_Interactivity_API {
*/
private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
if ( 'enter' === $mode ) {
$attribute_value = $p->get_attribute( 'data-wp-text' );
$result = $this->evaluate( $attribute_value );
$entries = $this->get_directive_entries( $p, 'text' );
$valid_entry = null;
// Get the first valid `data-wp-text` entry without suffix or unique ID.
foreach ( $entries as $entry ) {
if ( null === $entry['suffix'] && null === $entry['unique_id'] && ! empty( $entry['value'] ) ) {
$valid_entry = $entry;
break;
}
}
if ( null === $valid_entry ) {
return;
}
$result = $this->evaluate( $valid_entry );
/*
* Follows the same logic as Preact in the client and only changes the
@@ -1158,6 +1335,7 @@ HTML;
* `template` tag.
*
* @since 6.5.0
* @since 6.9.0 Include the list path in the rendered `data-wp-each-child` directives.
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
@@ -1165,11 +1343,17 @@ HTML;
*/
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 );
$entries = $this->get_directive_entries( $p, 'each' );
if ( count( $entries ) > 1 || empty( $entries ) ) {
// There should be only one `data-wp-each` directive per template tag.
return;
}
$entry = $entries[0];
if ( null !== $entry['unique_id'] ) {
return;
}
$item_name = isset( $entry['suffix'] ) ? $this->kebab_to_camel_case( $entry['suffix'] ) : 'item';
$result = $this->evaluate( $entry );
// Gets the content between the template tags and leaves the cursor in the closer tag.
$inner_content = $p->get_content_between_balanced_template_tags();
@@ -1202,19 +1386,13 @@ HTML;
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 ) )
array( $entry['namespace'] => array( $item_name => $item ) )
);
// Processes the inner content with the new context.
@@ -1226,10 +1404,20 @@ HTML;
return;
}
// Adds the `data-wp-each-child` to each top-level tag.
/*
* Adds the `data-wp-each-child` directive to each top-level tag
* rendered by this `data-wp-each` directive. The value is the
* `data-wp-each` directive's namespace and path.
*
* Nested `data-wp-each` directives could render
* `data-wp-each-child` elements at the top level as well, and
* they should be overwritten.
*
* @since 6.9.0
*/
$i = new WP_Interactivity_API_Directives_Processor( $processed_item );
while ( $i->next_tag() ) {
$i->set_attribute( 'data-wp-each-child', true );
$i->set_attribute( 'data-wp-each-child', $entry['namespace'] . '::' . $entry['value'] );
$i->next_balanced_tag_closer_tag();
}
$processed_content .= $i->get_updated_html();