FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
woocommerce
/
vendor
/
automattic
/
block-delimiter
/
src
Edit File: class-block-delimiter.php
<?php /** * Efficiently working with block structure. * * @package automattic/block-delimiter */ declare( strict_types = 1 ); namespace Automattic; use Exception; /** * Class for efficiently working with block structure. * * This class follows design values of the HTML API: * - minimize allocations and strive for zero memory overhead * - make costs explicit; pay only for what you need * - follow a streaming, re-entrant design for pausing and aborting * * For usage, jump straight to {@see static::next_delimiter}. */ class Block_Delimiter { /** * Indicates if the last operation failed, otherwise * will be `null` for success. * * @var string|null */ private static $last_error = self::UNINITIALIZED; /** * Indicates failures from decoding JSON attributes. * * @var int */ private $last_json_error = JSON_ERROR_NONE; /** * Holds a reference to the original source text from which to * extract the parsed spans of the delimiter. * * @var string */ private $source_text; /** * Byte offset into source text where entire delimiter begins. * * @var int */ private $delimiter_at; /** * Byte length of full span of delimiter. * * @var int */ private $delimiter_length; /** * Byte offset where namespace span begins. * * @var int */ private $namespace_at; /** * Byte length of namespace span, or `0` if implicitly in the "core" namespace. * * @var int */ private $namespace_length; /** * Byte offset where block name span begins. * * @var int */ private $name_at; /** * Byte length of block name span. * * @var int */ private $name_length; /** * Whether the delimiter contains the block self-closing flag. * * This may be erroneous if present within a block closer, * therefore the {@see self::has_void_flag} can be used by * calling code to perform appropriate error-handling. * * @var bool */ private $has_void_flag = false; /** * Byte offset where JSON attributes span begins. * * @var int */ private $json_at; /** * Byte length of JSON attributes span, or `0` if none are present. * * @var int */ private $json_length; /** * Indicates what kind of block comment delimiter this represents. * * One of: * * - `static::OPENER` If the delimiter is opening a block. * - `static::CLOSER` If the delimiter is closing an open block. * - `static::VOID` If the delimiter represents a void block with no inner content. * * If a parsed comment delimiter contains both the closing and the void * flags then it will be interpreted as a void block to match the behavior * of the official block parser, however, this is a mistake and probably * the block ought to close an open block of the same name, if one is open. * * @var string */ private $type; /** * Finds the next block delimiter in a text document and returns a parsed * block delimiter info record if it parses, otherwise returns `null`. * * Block comment delimiters must be valid HTML comments and may contain JSON. * This search does not determine, however, if the JSON is valid. * * Example delimiters: * * `<!-- wp:paragraph {"dropCap": true} -->` * `<!-- wp:separator /-->` * `<!-- /wp:paragraph -->` * * In the case that a block comment delimiter contains both the void indicator and * also the closing indicator, it will be treated as a void block. * * Example: * * // Find all image block opening delimiters. * $at = 0; * $end = strlen( $html ); * $images = array(); * while ( $at < $end ) { * $delimiter = Block_Delimiter::next_delimiter( $html, $at, $next_at, $next_length ); * if ( ! isset( $delimiter ) ) { * break; * } * * if ( * Block_Delimiter::OPENER === $delimiter->get_delimiter_type() && * $delimiter->is_block_type( 'core/image' ) * ) { * $images[] = $delimiter; * } * * $at = $next_at + $next_length; * } * * @param string $text Input document possibly containing block comment delimiters. * @param int $starting_byte_offset Where in the input document to begin searching. * @param int|null $match_byte_offset Optional. When provided, will be set to the byte offset in * the input document where the delimiter was found, if one * is found, otherwise not set. * @param int|null $match_byte_length Optional. When provided, will be set to the byte length of * the matched delimiter if one is found, otherwise not set. * @return Block_Delimiter|null Parsed block delimiter info record if found, otherwise `null`. */ public static function next_delimiter( string $text, int $starting_byte_offset, ?int &$match_byte_offset = null, ?int &$match_byte_length = null ): ?Block_Delimiter { $end = strlen( $text ); $at = $starting_byte_offset; $delimiter = null; static::$last_error = null; while ( $at < $end ) { /* * Find the next possible opening. * * This follows the behavior in the official block parser, which treats a post * as a list of blocks with nested HTML. If HTML comment syntax appears within * an HTML attribute value, SCRIPT or STYLE element, or in other select places, * which it can do inside of HTML, then the block parsing may break. * * For a more robust parse scan through the document with the HTML API. In * practice, this has not been a problem in the entire history of blocks. */ $comment_opening_at = strpos( $text, '<!--', $at ); if ( false === $comment_opening_at ) { return null; } $opening_whitespace_at = $comment_opening_at + 4; $opening_whitespace_length = strspn( $text, " \t\f\r\n", $opening_whitespace_at ); if ( 0 === $opening_whitespace_length ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $wp_prefix_at = $opening_whitespace_at + $opening_whitespace_length; if ( $wp_prefix_at >= $end ) { static::$last_error = self::INCOMPLETE_INPUT; return null; } $has_closer = false; if ( '/' === $text[ $wp_prefix_at ] ) { $has_closer = true; ++$wp_prefix_at; } if ( 0 !== substr_compare( $text, 'wp:', $wp_prefix_at, 3 ) ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $namespace_at = $wp_prefix_at + 3; if ( $namespace_at >= $end ) { static::$last_error = self::INCOMPLETE_INPUT; return null; } $start_of_namespace = $text[ $namespace_at ]; // The namespace must start with a-z. if ( 'a' > $start_of_namespace || 'z' < $start_of_namespace ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $namespace_length = 1 + strspn( $text, 'abcdefghijklmnopqrstuvwxyz0123456789-_', $namespace_at + 1 ); $separator_at = $namespace_at + $namespace_length; if ( $separator_at >= $end ) { static::$last_error = self::INCOMPLETE_INPUT; return null; } $has_separator = '/' === $text[ $separator_at ]; if ( $has_separator ) { $name_at = $separator_at + 1; $start_of_name = $text[ $name_at ]; if ( 'a' > $start_of_name || 'z' < $start_of_name ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $name_length = 1 + strspn( $text, 'abcdefghijklmnopqrstuvwxyz0123456789-_', $name_at + 1 ); } else { $name_at = $namespace_at; $name_length = $namespace_length; $namespace_length = 0; } $after_name_whitespace_at = $name_at + $name_length; $after_name_whitespace_length = strspn( $text, " \t\f\r\n", $after_name_whitespace_at ); if ( 0 === $after_name_whitespace_length ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $json_at = $after_name_whitespace_at + $after_name_whitespace_length; if ( $json_at >= $end ) { static::$last_error = self::INCOMPLETE_INPUT; return null; } $has_json = '{' === $text[ $json_at ]; $json_length = 0; /* * For the final span of the delimiter it's most efficient to find the end * of the HTML comment and work backwards. This prevents complicated parsing * inside the JSON span, which cannot contain the HTML comment terminator. * * This also matches the behavior in the official block parser, though it * allows for matching invalid JSON content. */ $comment_closing_at = strpos( $text, '-->', $json_at ); if ( false === $comment_closing_at ) { static::$last_error = self::INCOMPLETE_INPUT; return null; } /* * It looks like this logic leaves an error in here, when the position * overlaps the JSON or block name. However, for neither of those is it * possible to parse a valid block if that last overlapping character * is the void flag. This, therefore, will be valid regardless of how * the rest of the comment delimiter is written. */ if ( '/' === $text[ $comment_closing_at - 1 ] ) { $has_void_flag = true; $void_flag_length = 1; } else { $has_void_flag = false; $void_flag_length = 0; } /* * If there's no JSON, then the span of text after the name * until the comment closing must be completely whitespace. */ if ( ! $has_json ) { $max_whitespace_length = $comment_closing_at - $json_at - $void_flag_length; // This shouldn't be possible, but it can't be allowed regardless. if ( $max_whitespace_length < 0 ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } $closing_whitespace_length = strspn( $text, " \t\f\r\n", $json_at, $comment_closing_at - $json_at - $void_flag_length ); if ( 0 === $after_name_whitespace_length + $closing_whitespace_length ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } // This must be a block delimiter! $delimiter = new static(); break; } // There's no JSON, so attempt to find its boundary. $after_json_whitespace_length = 0; for ( $char_at = $comment_closing_at - $void_flag_length - 1; $char_at > $json_at; $char_at-- ) { $char = $text[ $char_at ]; switch ( $char ) { case ' ': case "\t": case "\f": case "\r": case "\n": ++$after_json_whitespace_length; continue 2; case '}': $json_length = $char_at - $json_at + 1; break 2; default: ++$at; continue 3; } } if ( 0 === $json_length || 0 === $after_json_whitespace_length ) { $at = self::find_html_comment_end( $text, $comment_opening_at, $end ); continue; } // This must be a block delimiter! $delimiter = new static(); break; } if ( null === $delimiter ) { return null; } $delimiter->source_text = $text; $delimiter->delimiter_at = $comment_opening_at; $delimiter->delimiter_length = $comment_closing_at + 3 - $comment_opening_at; $delimiter->namespace_at = $namespace_at; $delimiter->namespace_length = $namespace_length; $delimiter->name_at = $name_at; $delimiter->name_length = $name_length; $delimiter->json_at = $json_at; $delimiter->json_length = $json_length; $delimiter->type = $has_closer ? static::CLOSER : ( $has_void_flag ? static::VOID : static::OPENER ); $delimiter->has_void_flag = $has_void_flag; $match_byte_offset = $delimiter->delimiter_at; $match_byte_length = $delimiter->delimiter_length; return $delimiter; } /** * Generator function to traverse block delimiters in a text and also * yield the position information for the delimiter as it scans. * * Example: * * foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $where => $delimiter ) { * echo "Found a {$delimiter->allocate_and_return_block_type()} at {$where[0]} (length is {$where[1]} bytes)\n"; * } * * @param string $text Document potentially containing blocks. * @param ?string $freeform_blocks Optional. 'visit' to visit virtual block delimiters for freeform content. * @return \Generator Visits each block delimiter and provides [ at, length ] => delimiter. */ public static function scan_delimiters( string $text, ?string $freeform_blocks = 'skip' ): \Generator { $at = 0; $depth = 0; /* * Although `phpcs` confidently asserts that `$match_at` and `$match_length` * are undefined, it is not aware enough to realize that they are set by the * call to `next_delimiter` and so it’s necessary to alter the code so it * doesn’t get confused and reject valid code. */ $match_at = 0; $match_length = 0; while ( null !== ( $delimiter = self::next_delimiter( $text, $at, $match_at, $match_length ) ) ) { // Handle top-level text as freeform blocks if ( 0 === $depth && $match_at > $at && 'visit' === $freeform_blocks ) { list( $text_opener, $text_closer ) = static::freeform_pair( $text, $at, $match_at - $at ); ++$depth; yield [ $at, 0 ] => $text_opener; --$depth; yield [ $match_at, 0 ] => $text_closer; } $delimiter_type = $delimiter->get_delimiter_type(); switch ( $delimiter_type ) { case static::OPENER: case static::VOID: ++$depth; break; case static::CLOSER: --$depth; break; } yield [ $match_at, $match_length ] => $delimiter; if ( static::VOID === $delimiter_type ) { --$depth; } $at = $match_at + $match_length; } $end = strlen( $text ); if ( 'visit' === $freeform_blocks && $at < $end ) { list( $text_opener, $text_closer ) = static::freeform_pair( $text, $at, $end - $at ); ++$depth; yield [ $at, 0 ] => $text_opener; --$depth; yield [ $end, 0 ] => $text_closer; } } /** * Constructor function. */ private function __construct() { // This is not to be called from the outside. } /** * Returns the byte-offset after the ending character of an HTML comment, * assuming the proper starting byte offset. * * @since 0.2.1 * * @param string $text Document in which to search for HTML comment end. * @param int $comment_starting_at Where the HTML comment started, the leading `<`. * @param int $search_end Last offset in which to search, for limiting search span. * @return int Offset after the current HTML comment ends, or `$end` if no end was found. */ private static function find_html_comment_end( string $text, int $comment_starting_at, int $search_end ): int { // Find span-of-dashes comments which look like `<!----->`. $span_of_dashes = strspn( $text, '-', $comment_starting_at + 2 ); if ( $comment_starting_at + 2 + $span_of_dashes < $search_end && '>' === $text[ $comment_starting_at + 2 + $span_of_dashes ] ) { return $comment_starting_at + $span_of_dashes + 1; } // Otherwise, there are other characters inside the comment, find the first `-->` or `--!>`. $now_at = $comment_starting_at + 4; while ( $now_at < $search_end ) { $dashes_at = strpos( $text, '--', $now_at ); if ( false === $dashes_at ) { static::$last_error = self::INCOMPLETE_INPUT; return $search_end; } $closer_must_be_at = $dashes_at + 2 + strspn( $text, '-', $dashes_at + 2 ); if ( $closer_must_be_at < $search_end && '!' === $text[ $closer_must_be_at ] ) { $closer_must_be_at++; } if ( $closer_must_be_at < $search_end && '>' === $text[ $closer_must_be_at ] ) { return $closer_must_be_at + 1; } $now_at++; } return $search_end; } /** * Creates a pair of delimiters for freeform text content * since there are no delimiters in a document for them. * * @param string $text Source document. * @param int $at Where the text region starts (byte offset). * @param int $length How long the text region spans (byte count). * @return static[] Opening and closing block delimiters for the text region. */ private static function freeform_pair( string $text, int $at, int $length ): array { $opener = new static(); $opener->source_text = $text; $opener->delimiter_at = $at; $opener->delimiter_length = 0; $opener->namespace_at = $at; $opener->namespace_length = 0; $opener->name_at = $at; $opener->name_length = 0; $opener->json_at = $at; $opener->json_length = 0; $opener->type = static::OPENER; $opener->has_void_flag = false; $closer = clone $opener; $end_at = $at + $length; $closer->delimiter_at = $end_at; $closer->namespace_at = $end_at; $closer->name_at = $end_at; $closer->json_at = $end_at; $closer->type = static::CLOSER; return [ $opener, $closer ]; } /** * Indicates if the last attempt to parse a block comment delimiter * failed, if set, otherwise `null` if the last attempt succeeded. * * @return string|null */ public static function get_last_error() { return static::$last_error; } /** * Indicates if the last attempt to parse a block’s JSON attributes failed. * * @see JSON_ERROR_NONE, JSON_ERROR_DEPTH, etc… * * @return int JSON_ERROR_ code from last attempt to parse block JSON attributes. */ public function get_last_json_error(): int { return $this->last_json_error; } /** * Allocates a substring from the source text containing the delimiter * and releases the reference to the source text. * * Use this function when the delimiter is holding on to the source * text and preventing it from being freed by PHP. This function incurs * a string allocation; if the source text will be retained anyway then * there's no need to detach as that memory cannot be freed. * * This is a low-level function available for controlling the performance * of sensitive hot-paths. You probably don't need this. * * Example: * * function first_block( $html ) { * return Block_Delimiter::next_delimiter( $really_long_html, 0 ); * } * * $delimiter = first_block( $really_long_html_document ); * // `$really_long_html_document` is still retained inside `$delimiter`, which could lead to a memory leak. * * $delimiter->allocate_and_detach_from_source_text(); * // `$really_long_html_document` is no longer referenced, and its memory may be freed or used for something else. * * @return void */ public function allocate_and_detach_from_source_text(): void { $this->source_text = substr( $this->source_text, $this->delimiter_at, $this->delimiter_length ); $byte_delta = $this->delimiter_at; $this->delimiter_at -= $byte_delta; $this->namespace_at -= $byte_delta; $this->name_at -= $byte_delta; $this->json_at -= $byte_delta; } /** * Returns the type of the block comment delimiter. * * One of: * * - `static::OPENER` * - `static::CLOSER` * - `static::VOID` * * @return string type of the block comment delimiter. */ public function get_delimiter_type(): string { return $this->type; } /** * Returns whether the delimiter contains the void flag. * * This should be avoided except in cases of handling errors with * block closers containing the void flag. For normative use, * {@see self::get_delimiter_type}. * * @return bool */ public function has_void_flag(): bool { return $this->has_void_flag; } /** * Indicates if the block delimiter represents a block of the given type. * * Since the "core" namespace may be implicit, it's allowable to pass * either the fully-qualified block type with namespace and block name * as well as the shorthand version only containing the block name, if * the desired block is in the "core" namespace. * * Example: * * $is_core_paragraph = $delimiter->is_block_type( 'paragraph' ); * $is_core_paragraph = $delimiter->is_block_type( 'core/paragraph' ); * $is_formula = $delimiter->is_block_type( 'math-block/formula' ); * * @param string $block_type Block type name for the desired block. * E.g. "paragraph", "core/paragraph", "math-blocks/formula". * @return bool Whether this delimiter represents a block of the given type. */ public function is_block_type( string $block_type ): bool { // This is a core/freeform text block, it’s special. if ( 0 === $this->name_length ) { return 'core/freeform' === $block_type || 'freeform' === $block_type; } $slash_at = strpos( $block_type, '/' ); if ( false === $slash_at ) { $namespace = 'core'; $block_name = $block_type; } else { $namespace = substr( $block_type, 0, $slash_at ); $block_name = substr( $block_type, $slash_at + 1 ); } // Only the 'core' namespace is allowed to be omitted. if ( 0 === $this->namespace_length && 'core' !== $namespace ) { return false; } // If given an explicit namespace, they must match. if ( 0 !== $this->namespace_length && ( strlen( $namespace ) !== $this->namespace_length || 0 !== substr_compare( $this->source_text, $namespace, $this->namespace_at, $this->namespace_length ) ) ) { return false; } // The block name must match. return ( strlen( $block_name ) === $this->name_length && 0 === substr_compare( $this->source_text, $block_name, $this->name_at, $this->name_length ) ); } /** * Allocates a substring for the block type and returns the * fully-qualified name, including the namespace. * * This function allocates a substring for the given block type. This * allocation will be small and likely fine in most cases, but it's * preferable to call {@link static::is_block_type} if only needing * to know whether the delimiter is for a given block type, as that * function is more efficient for this purpose and avoids the allocation. * * Example: * * 'core/paragraph' = $delimiter->allocate_and_return_block_type(); * * @return string Fully-qualified block namespace and type, e.g. "core/paragraph". */ public function allocate_and_return_block_type(): string { // This is a core/freeform text block, it’s special. if ( 0 === $this->name_length ) { return 'core/freeform'; } // This is implicitly in the "core" namespace. if ( 0 === $this->namespace_length ) { $block_name = substr( $this->source_text, $this->name_at, $this->name_length ); return "core/{$block_name}"; } return substr( $this->source_text, $this->namespace_at, $this->namespace_length + $this->name_length + 1 ); } /** * Returns a lazy wrapper around the block attributes, which can be used * for efficiently interacting with the JSON attributes. * * @throws Exception This function is not yet implemented. * * @todo Create a lazy JSON wrapper so specific attributes can be * efficiently queried without parsing everything and loading * the entire object into memory. * @todo After realistic benchmarking, see if JsonStreamingParser\Parser * could be used — it would need to be fast enough for the reduction * in memory use to be worth it, compared to {@see \json_decode}. * * @see \JsonStreamingParser\Parser * * @return never */ public function get_attributes(): void { throw new Exception( 'Lazy attribute parsing not yet supported' ); } /** * Attempts to parse and return the entire JSON attributes from the delimiter, * allocating memory and processing the JSON span in the process. * * This does not return any parsed attributes for a closing block delimiter * even if there is a span of JSON content; this JSON is a parsing error. * * Consider calling {@link static::get_attributes} instead if it's not * necessary to read all the attributes at the same time, as that provides * a more efficient mechanism for typical use cases. * * Since the JSON span inside the comment delimiter may not be valid JSON, * this function will return `null` if it cannot parse the span and set the * {@see static::get_last_json_error} to the appropriate JSON_ERROR_ constant. * * If the delimiter contains no JSON span, it will also return `null`, * but the last error will be set to {@see JSON_ERROR_NONE}. * * Example: * * $delimiter = Block_Delimiter::next_delimiter( '<!-- wp:image {"url": "https://wordpress.org/favicon.ico"} -->', 0 ); * $memory_hungry_and_slow_attributes = $delimiter->allocate_and_return_parsed_attributes(); * $memory_hungry_and_slow_attributes === array( 'url' => 'https://wordpress.org/favicon.ico' ); * * $delimiter = Block_Delimiter::next_delimiter( '<!-- /wp:image {"url": "https://wordpress.org/favicon.ico"} -->', 0 ); * null = $delimiter->allocate_and_return_parsed_attributes(); * JSON_ERROR_NONE = $delimiter->get_last_json_error(); * * $delimiter = Block_Delimiter::next_delimiter( '<!-- wp:separator {} /-->', 0 ); * array() === $delimiter->allocate_and_return_parsed_attributes(); * * $delimiter = Block_Delimiter::next_delimiter( '<!-- wp:separator /-->', 0 ); * null = $delimiter->allocate_and_return_parsed_attributes(); * * $delimiter = Block_Delimiter::next_delimiter( '<!-- wp:image {"url} -->', 0 ); * null = $delimiter->allocate_and_return_parsed_attributes(); * JSON_ERROR_CTRL_CHAR = $delimiter->get_last_json_error(); * * @return array|null Parsed JSON attributes, if present and valid, otherwise `null`. */ public function allocate_and_return_parsed_attributes(): ?array { $this->last_json_error = JSON_ERROR_NONE; if ( static::CLOSER === $this->type ) { return null; } if ( 0 === $this->json_length ) { return null; } $json_span = substr( $this->source_text, $this->json_at, $this->json_length ); $parsed = json_decode( $json_span, null, 512, JSON_OBJECT_AS_ARRAY | JSON_INVALID_UTF8_SUBSTITUTE ); $last_error = json_last_error(); $this->last_json_error = $last_error; return ( JSON_ERROR_NONE === $last_error && is_array( $parsed ) ) ? $parsed : null; } // Debugging methods not meant for production use. /** * Prints a debugging message showing the structure of the parsed delimiter. * * This is not meant to be used in production! * * @access private */ public function debug_print_structure(): void { $c = ( ! defined( 'STDOUT' ) || posix_isatty( STDOUT ) ) ? function ( $color = null ) { return $color; } // phpcs:ignore : function ( $color ) { return ''; }; // phpcs:ignore if ( $this->is_block_type( 'core/freeform' ) ) { $closer = static::CLOSER === $this->get_delimiter_type() ? '/' : ''; echo "{$c( "\e[90m" )}<!-- "; // phpcs:ignore echo "{$c( "\e[0;31m" )}{$closer}"; // phpcs:ignore echo "{$c("\e[90m" )}wp:"; // phpcs:ignore echo "{$c( "\e[0;34m" )}freeform"; // phpcs:ignore echo "{$c( "\e[0;36m" )} {$c("\e[90m")}-->\n"; // phpcs:ignore return; } $namespace = substr( $this->source_text, $this->namespace_at, $this->namespace_length ); $slash = 0 === $this->namespace_length ? '' : '/'; $block_name = substr( $this->source_text, $this->name_at, $this->name_length ); $closer = static::CLOSER === $this->type ? '/' : ''; $json = substr( $this->source_text, $this->json_at, $this->json_length ); $opener_whitespace_at = $this->delimiter_at + 4; $opener_whitespace_length = $this->namespace_at - 3 - $opener_whitespace_at - ( static::CLOSER === $this->type ? 1 : 0 ); $after_name_whitespace_at = $this->name_at + $this->name_length; $after_name_whitespace_length = $this->json_at - $after_name_whitespace_at; $closing_whitespace_at = $this->json_at + $this->json_length; $closing_whitespace_length = $this->delimiter_at + $this->delimiter_length - 3 - $closing_whitespace_at; if ( '/' === $this->source_text[ $this->delimiter_at + $this->delimiter_length - 4 ] ) { $void_flag = '/'; --$closing_whitespace_length; } else { $void_flag = ''; } $w = function ( $whitespace ) use ( $c ) { return $c( "\e[2;90m" ) . str_replace( array( ' ', "\t", "\f", "\r", "\n" ), array( '␣', '␉', '␌', '␍', '' ), $whitespace ); }; echo "{$c( "\e[90m" )}<!--"; // phpcs:ignore echo $w( substr( $this->source_text, $opener_whitespace_at, $opener_whitespace_length ) ); // phpcs:ignore echo "{$c( "\e[0;31m" )}{$closer}"; // phpcs:ignore echo "{$c("\e[90m" )}wp:{$c( "\e[2;34m" )}{$namespace}"; // phpcs:ignore echo "{$c( "\e[2;90m" )}{$slash}"; // phpcs:ignore echo "{$c( "\e[0;34m" )}{$block_name}"; // phpcs:ignore echo $w( substr( $this->source_text, $after_name_whitespace_at, $after_name_whitespace_length ) ); // phpcs:ignore echo "{$c("\e[0;2;32m" )}{$json}"; // phpcs:ignore echo $w( substr( $this->source_text, $closing_whitespace_at, $closing_whitespace_length ) ); // phpcs:ignore echo "{$c( "\e[0;36m" )}{$void_flag}{$c("\e[90m")}-->\n"; // phpcs:ignore } // Constant declarations that would otherwise pollute the top of the class. /** * Indicates that the block comment delimiter closes an open block. */ const CLOSER = 'closer'; /** * Indicates that the parser started parsing a block comment delimiter, but * the input document ended before it could finish. The document was likely truncated. */ const INCOMPLETE_INPUT = 'incomplete-input'; /** * Indicates that the block comment delimiter opens a block. */ const OPENER = 'opener'; /** * Indicates that the parser has not yet attempted to parse a block comment delimiter. */ const UNINITIALIZED = 'uninitialized'; /** * Indicates that the block comment delimiter represents a void block * with no inner content of any kind. */ const VOID = 'void'; }
Save
Back