first commit
This commit is contained in:
@ -0,0 +1,277 @@
|
||||
<?php
|
||||
/**
|
||||
* Interactivity API: WP_Interactivity_API_Directives_Processor class.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage Interactivity API
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class used to iterate over the tags of an HTML string and help process the
|
||||
* directive attributes.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
final class WP_Interactivity_API_Directives_Processor extends WP_HTML_Tag_Processor {
|
||||
/**
|
||||
* List of tags whose closer tag is not visited by the WP_HTML_Tag_Processor.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @var string[]
|
||||
*/
|
||||
const TAGS_THAT_DONT_VISIT_CLOSER_TAG = array(
|
||||
'SCRIPT',
|
||||
'IFRAME',
|
||||
'NOEMBED',
|
||||
'NOFRAMES',
|
||||
'STYLE',
|
||||
'TEXTAREA',
|
||||
'TITLE',
|
||||
'XMP',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the content between two balanced template tags.
|
||||
*
|
||||
* It positions the cursor in the closer tag of the balanced template tag,
|
||||
* if it exists.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return string|null The content between the current opener template tag and its matching closer tag or null if it
|
||||
* doesn't find the matching closing tag or the current tag is not a template opener tag.
|
||||
*/
|
||||
public function get_content_between_balanced_template_tags() {
|
||||
if ( 'TEMPLATE' !== $this->get_tag() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$positions = $this->get_after_opener_tag_and_before_closer_tag_positions();
|
||||
if ( ! $positions ) {
|
||||
return null;
|
||||
}
|
||||
list( $after_opener_tag, $before_closer_tag ) = $positions;
|
||||
|
||||
return substr( $this->html, $after_opener_tag, $before_closer_tag - $after_opener_tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content between two balanced tags.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $new_content The string to replace the content between the matching tags.
|
||||
* @return bool Whether the content was successfully replaced.
|
||||
*/
|
||||
public function set_content_between_balanced_tags( string $new_content ): bool {
|
||||
$positions = $this->get_after_opener_tag_and_before_closer_tag_positions( true );
|
||||
if ( ! $positions ) {
|
||||
return false;
|
||||
}
|
||||
list( $after_opener_tag, $before_closer_tag ) = $positions;
|
||||
|
||||
$this->lexical_updates[] = new WP_HTML_Text_Replacement(
|
||||
$after_opener_tag,
|
||||
$before_closer_tag - $after_opener_tag,
|
||||
esc_html( $new_content )
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content after the closing tag of a template tag.
|
||||
*
|
||||
* It positions the cursor in the closer tag of the balanced template tag,
|
||||
* if it exists.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $new_content The string to append after the closing template tag.
|
||||
* @return bool Whether the content was successfully appended.
|
||||
*/
|
||||
public function append_content_after_template_tag_closer( string $new_content ): bool {
|
||||
if ( empty( $new_content ) || 'TEMPLATE' !== $this->get_tag() || ! $this->is_tag_closer() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flushes any changes.
|
||||
$this->get_updated_html();
|
||||
|
||||
$bookmark = 'append_content_after_template_tag_closer';
|
||||
$this->set_bookmark( $bookmark );
|
||||
$after_closing_tag = $this->bookmarks[ $bookmark ]->start + $this->bookmarks[ $bookmark ]->length + 1;
|
||||
$this->release_bookmark( $bookmark );
|
||||
|
||||
// Appends the new content.
|
||||
$this->lexical_updates[] = new WP_HTML_Text_Replacement( $after_closing_tag, 0, $new_content );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the positions right after the opener tag and right before the closer
|
||||
* tag in a balanced tag.
|
||||
*
|
||||
* By default, it positions the cursor in the closer tag of the balanced tag.
|
||||
* If $rewind is true, it seeks back to the opener tag.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param bool $rewind Optional. Whether to seek back to the opener tag after finding the positions. Defaults to false.
|
||||
* @return array|null Start and end byte position, or null when no balanced tag bookmarks.
|
||||
*/
|
||||
private function get_after_opener_tag_and_before_closer_tag_positions( bool $rewind = false ) {
|
||||
// Flushes any changes.
|
||||
$this->get_updated_html();
|
||||
|
||||
$bookmarks = $this->get_balanced_tag_bookmarks();
|
||||
if ( ! $bookmarks ) {
|
||||
return null;
|
||||
}
|
||||
list( $opener_tag, $closer_tag ) = $bookmarks;
|
||||
|
||||
$after_opener_tag = $this->bookmarks[ $opener_tag ]->start + $this->bookmarks[ $opener_tag ]->length + 1;
|
||||
$before_closer_tag = $this->bookmarks[ $closer_tag ]->start;
|
||||
|
||||
if ( $rewind ) {
|
||||
$this->seek( $opener_tag );
|
||||
}
|
||||
|
||||
$this->release_bookmark( $opener_tag );
|
||||
$this->release_bookmark( $closer_tag );
|
||||
|
||||
return array( $after_opener_tag, $before_closer_tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pair of bookmarks for the current opener tag and the matching
|
||||
* closer tag.
|
||||
*
|
||||
* It positions the cursor in the closer tag of the balanced tag, if it
|
||||
* exists.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array|null A pair of bookmarks, or null if there's no matching closing tag.
|
||||
*/
|
||||
private function get_balanced_tag_bookmarks() {
|
||||
static $i = 0;
|
||||
$opener_tag = 'opener_tag_of_balanced_tag_' . ++$i;
|
||||
|
||||
$this->set_bookmark( $opener_tag );
|
||||
if ( ! $this->next_balanced_tag_closer_tag() ) {
|
||||
$this->release_bookmark( $opener_tag );
|
||||
return null;
|
||||
}
|
||||
|
||||
$closer_tag = 'closer_tag_of_balanced_tag_' . ++$i;
|
||||
$this->set_bookmark( $closer_tag );
|
||||
|
||||
return array( $opener_tag, $closer_tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips processing the content between tags.
|
||||
*
|
||||
* It positions the cursor in the closer tag of the foreign element, if it
|
||||
* exists.
|
||||
*
|
||||
* This function is intended to skip processing SVG and MathML inner content
|
||||
* instead of bailing out the whole processing.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return bool Whether the foreign content was successfully skipped.
|
||||
*/
|
||||
public function skip_to_tag_closer(): bool {
|
||||
$depth = 1;
|
||||
$tag_name = $this->get_tag();
|
||||
while ( $depth > 0 && $this->next_tag(
|
||||
array(
|
||||
'tag_name' => $tag_name,
|
||||
'tag_closers' => 'visit',
|
||||
)
|
||||
) ) {
|
||||
if ( $this->has_self_closing_flag() ) {
|
||||
continue;
|
||||
}
|
||||
$depth += $this->is_tag_closer() ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0 === $depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the matching closing tag for an opening tag.
|
||||
*
|
||||
* When called while the processor is on an open tag, it traverses the HTML
|
||||
* until it finds the matching closer tag, respecting any in-between content,
|
||||
* including nested tags of the same name. Returns false when called on a
|
||||
* closer tag, a tag that doesn't have a closer tag (void), a tag that
|
||||
* doesn't visit the closer tag, or if no matching closing tag was found.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return bool Whether a matching closing tag was found.
|
||||
*/
|
||||
public function next_balanced_tag_closer_tag(): bool {
|
||||
$depth = 0;
|
||||
$tag_name = $this->get_tag();
|
||||
|
||||
if ( ! $this->has_and_visits_its_closer_tag() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ( $this->next_tag(
|
||||
array(
|
||||
'tag_name' => $tag_name,
|
||||
'tag_closers' => 'visit',
|
||||
)
|
||||
) ) {
|
||||
if ( ! $this->is_tag_closer() ) {
|
||||
++$depth;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 === $depth ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
--$depth;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current tag has and will visit its matching closer tag.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @return bool Whether the current tag has a closer tag.
|
||||
*/
|
||||
public function has_and_visits_its_closer_tag(): bool {
|
||||
$tag_name = $this->get_tag();
|
||||
|
||||
return null !== $tag_name && (
|
||||
! WP_HTML_Processor::is_void( $tag_name ) &&
|
||||
! in_array( $tag_name, self::TAGS_THAT_DONT_VISIT_CLOSER_TAG, true )
|
||||
);
|
||||
}
|
||||
}
|
1000
wp-includes/interactivity-api/class-wp-interactivity-api.php
Normal file
1000
wp-includes/interactivity-api/class-wp-interactivity-api.php
Normal file
File diff suppressed because it is too large
Load Diff
170
wp-includes/interactivity-api/interactivity-api.php
Normal file
170
wp-includes/interactivity-api/interactivity-api.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
/**
|
||||
* Interactivity API: Functions and hooks
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage Interactivity API
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Processes the directives on the rendered HTML of the interactive blocks.
|
||||
*
|
||||
* This processes only one root interactive block at a time because the
|
||||
* rendered HTML of that block contains the rendered HTML of all its inner
|
||||
* blocks, including any interactive block. It does so by ignoring all the
|
||||
* interactive inner blocks until the root interactive block is processed.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $parsed_block The parsed block.
|
||||
* @return array The same parsed block.
|
||||
*/
|
||||
function wp_interactivity_process_directives_of_interactive_blocks( array $parsed_block ): array {
|
||||
static $root_interactive_block = null;
|
||||
|
||||
/*
|
||||
* Checks whether a root interactive block is already annotated for
|
||||
* processing, and if it is, it ignores the subsequent ones.
|
||||
*/
|
||||
if ( null === $root_interactive_block ) {
|
||||
$block_name = $parsed_block['blockName'];
|
||||
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
|
||||
|
||||
if (
|
||||
isset( $block_name ) &&
|
||||
( ( isset( $block_type->supports['interactivity'] ) && true === $block_type->supports['interactivity'] ) ||
|
||||
( isset( $block_type->supports['interactivity']['interactive'] ) && true === $block_type->supports['interactivity']['interactive'] ) )
|
||||
) {
|
||||
// Annotates the root interactive block for processing.
|
||||
$root_interactive_block = array( $block_name, $parsed_block );
|
||||
|
||||
/*
|
||||
* Adds a filter to process the root interactive block once it has
|
||||
* finished rendering.
|
||||
*/
|
||||
$process_interactive_blocks = static function ( string $content, array $parsed_block ) use ( &$root_interactive_block, &$process_interactive_blocks ): string {
|
||||
// Checks whether the current block is the root interactive block.
|
||||
list($root_block_name, $root_parsed_block) = $root_interactive_block;
|
||||
if ( $root_block_name === $parsed_block['blockName'] && $parsed_block === $root_parsed_block ) {
|
||||
// The root interactive blocks has finished rendering, process it.
|
||||
$content = wp_interactivity_process_directives( $content );
|
||||
// Removes the filter and reset the root interactive block.
|
||||
remove_filter( 'render_block_' . $parsed_block['blockName'], $process_interactive_blocks );
|
||||
$root_interactive_block = null;
|
||||
}
|
||||
return $content;
|
||||
};
|
||||
|
||||
/*
|
||||
* Uses a priority of 100 to ensure that other filters can add additional
|
||||
* directives before the processing starts.
|
||||
*/
|
||||
add_filter( 'render_block_' . $block_name, $process_interactive_blocks, 100, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_block;
|
||||
}
|
||||
/*
|
||||
* Uses a priority of 100 to ensure that other filters can add additional attributes to
|
||||
* $parsed_block before the processing starts.
|
||||
*/
|
||||
add_filter( 'render_block_data', 'wp_interactivity_process_directives_of_interactive_blocks', 100, 1 );
|
||||
|
||||
/**
|
||||
* Retrieves the main WP_Interactivity_API instance.
|
||||
*
|
||||
* It provides access to the WP_Interactivity_API instance, creating one if it
|
||||
* doesn't exist yet.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @global WP_Interactivity_API $wp_interactivity
|
||||
*
|
||||
* @return WP_Interactivity_API The main WP_Interactivity_API instance.
|
||||
*/
|
||||
function wp_interactivity(): WP_Interactivity_API {
|
||||
global $wp_interactivity;
|
||||
if ( ! ( $wp_interactivity instanceof WP_Interactivity_API ) ) {
|
||||
$wp_interactivity = new WP_Interactivity_API();
|
||||
}
|
||||
return $wp_interactivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the interactivity directives contained within the HTML content
|
||||
* and updates the markup accordingly.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $html The HTML content to process.
|
||||
* @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags.
|
||||
*/
|
||||
function wp_interactivity_process_directives( string $html ): string {
|
||||
return wp_interactivity()->process_directives( $html );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and/or sets the initial state of an Interactivity API store for a
|
||||
* given namespace.
|
||||
*
|
||||
* If state for that store namespace already exists, it merges the new
|
||||
* provided state with the existing one.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $store_namespace The unique store namespace identifier.
|
||||
* @param array $state Optional. The array that will be merged with the existing state for the specified
|
||||
* store namespace.
|
||||
* @return array The state for the specified store namespace. This will be the updated state if a $state argument was
|
||||
* provided.
|
||||
*/
|
||||
function wp_interactivity_state( string $store_namespace, array $state = array() ): array {
|
||||
return wp_interactivity()->state( $store_namespace, $state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and/or sets the configuration of the Interactivity API for a given
|
||||
* store namespace.
|
||||
*
|
||||
* If configuration for that store namespace exists, it merges the new
|
||||
* provided configuration with the existing one.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $store_namespace The unique store namespace identifier.
|
||||
* @param array $config Optional. The array that will be merged with the existing configuration for the
|
||||
* specified store namespace.
|
||||
* @return array The configuration for the specified store namespace. This will be the updated configuration if a
|
||||
* $config argument was provided.
|
||||
*/
|
||||
function wp_interactivity_config( string $store_namespace, array $config = array() ): array {
|
||||
return wp_interactivity()->config( $store_namespace, $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a `data-wp-context` directive attribute by encoding a context
|
||||
* array.
|
||||
*
|
||||
* This helper function simplifies the creation of `data-wp-context` directives
|
||||
* by providing a way to pass an array of data, which encodes into a JSON string
|
||||
* safe for direct use as a HTML attribute value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <div <?php echo wp_interactivity_data_wp_context( array( 'isOpen' => true, 'count' => 0 ) ); ?>>
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $context The array of context data to encode.
|
||||
* @param string $store_namespace Optional. The unique store namespace identifier.
|
||||
* @return string A complete `data-wp-context` directive with a JSON encoded value representing the context array and
|
||||
* the store namespace if specified.
|
||||
*/
|
||||
function wp_interactivity_data_wp_context( array $context, string $store_namespace = '' ): string {
|
||||
return 'data-wp-context=\'' .
|
||||
( $store_namespace ? $store_namespace . '::' : '' ) .
|
||||
( empty( $context ) ? '{}' : wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ) ) .
|
||||
'\'';
|
||||
}
|
Reference in New Issue
Block a user