diff --git a/gutenberg.php b/gutenberg.php
index 44146b0c57d305..3e9febccbe7371 100644
--- a/gutenberg.php
+++ b/gutenberg.php
@@ -3,7 +3,7 @@
* Plugin Name: Gutenberg
* Plugin URI: https://github.com/WordPress/gutenberg
* Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality.
- * Requires at least: 6.1
+ * Requires at least: 6.2
* Requires PHP: 7.0
* Version: 16.6.0-rc.1
* Author: Gutenberg Team
diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php
index 4029c91aa40a35..328d371c3e4fd8 100644
--- a/lib/block-supports/elements.php
+++ b/lib/block-supports/elements.php
@@ -5,16 +5,6 @@
* @package gutenberg
*/
-/**
- * Get the elements class names.
- *
- * @param array $block Block object.
- * @return string The unique class name.
- */
-function gutenberg_get_elements_class_name( $block ) {
- return 'wp-elements-' . md5( serialize( $block ) );
-}
-
/**
* Update the block content with elements class names.
*
@@ -103,7 +93,7 @@ function gutenberg_render_elements_support( $block_content, $block ) {
// Add the class name to the first element, presuming it's the wrapper, if it exists.
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
- $tags->add_class( gutenberg_get_elements_class_name( $block ) );
+ $tags->add_class( wp_get_elements_class_name( $block ) );
}
return $tags->get_updated_html();
@@ -140,7 +130,7 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) {
return null;
}
- $class_name = gutenberg_get_elements_class_name( $block );
+ $class_name = wp_get_elements_class_name( $block );
$element_types = array(
'button' => array(
diff --git a/lib/block-supports/settings.php b/lib/block-supports/settings.php
index 7c0dd719f41c3a..aac4774b62fe8e 100644
--- a/lib/block-supports/settings.php
+++ b/lib/block-supports/settings.php
@@ -5,18 +5,6 @@
* @package gutenberg
*/
-/**
- * Get the class name used on block level presets.
- *
- * @access private
- *
- * @param array $block Block object.
- * @return string The unique class name.
- */
-function _gutenberg_get_presets_class_name( $block ) {
- return 'wp-settings-' . md5( serialize( $block ) );
-}
-
/**
* Update the block content with block level presets class name.
*
@@ -47,7 +35,7 @@ function _gutenberg_add_block_level_presets_class( $block_content, $block ) {
// Add the class name to the first element, presuming it's the wrapper, if it exists.
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
- $tags->add_class( _gutenberg_get_presets_class_name( $block ) );
+ $tags->add_class( _wp_get_presets_class_name( $block ) );
}
return $tags->get_updated_html();
@@ -76,7 +64,7 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) {
return null;
}
- $class_name = '.' . _gutenberg_get_presets_class_name( $block );
+ $class_name = '.' . _wp_get_presets_class_name( $block );
// the root selector for preset variables needs to target every possible block selector
// in order for the general setting to override any bock specific setting of a parent block or
@@ -129,7 +117,12 @@ function _gutenberg_add_block_level_preset_styles( $pre_render, $block ) {
);
if ( ! empty( $styles ) ) {
- gutenberg_enqueue_block_support_styles( $styles );
+ /*
+ * This method is deprecated since WordPress 6.2.
+ * We could enqueue these styles separately,
+ * or print them out with other settings presets.
+ */
+ wp_enqueue_block_support_styles( $styles );
}
return null;
diff --git a/lib/compat/wordpress-6.2/block-editor.php b/lib/compat/wordpress-6.2/block-editor.php
deleted file mode 100644
index 4df2b6b2652e05..00000000000000
--- a/lib/compat/wordpress-6.2/block-editor.php
+++ /dev/null
@@ -1,45 +0,0 @@
- get_theme_support( 'disable-custom-colors' ),
- 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
- 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ),
- 'disableLayoutStyles' => get_theme_support( 'disable-layout-styles' ),
- 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ),
- 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ),
- 'enableCustomUnits' => get_theme_support( 'custom-units' ),
- );
-
- // Theme settings.
- $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
- if ( false !== $color_palette ) {
- $theme_settings['colors'] = $color_palette;
- }
-
- $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
- if ( false !== $font_sizes ) {
- $theme_settings['fontSizes'] = $font_sizes;
- }
-
- $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) );
- if ( false !== $gradient_presets ) {
- $theme_settings['gradients'] = $gradient_presets;
- }
-
- return $theme_settings;
- }
-}
diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php
deleted file mode 100644
index 12b19bdf4c54ea..00000000000000
--- a/lib/compat/wordpress-6.2/block-patterns.php
+++ /dev/null
@@ -1,333 +0,0 @@
- _x( 'Banners', 'Block pattern category', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'buttons',
- array(
- 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Patterns that contain buttons and call to actions.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'columns',
- array(
- 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Multi-column patterns with more complex layouts.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'text',
- array(
- 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'query',
- array(
- 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'featured',
- array(
- 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A set of high quality curated patterns.', 'gutenberg' ),
- )
- );
-
- // Register new core block pattern categories.
- register_block_pattern_category(
- 'call-to-action',
- array(
- 'label' => _x( 'Call to Action', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Sections whose purpose is to trigger a specific action.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'team',
- array(
- 'label' => _x( 'Team', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of designs to display your team members.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'testimonials',
- array(
- 'label' => _x( 'Testimonials', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Share reviews and feedback about your brand/business.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'services',
- array(
- 'label' => _x( 'Services', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Briefly describe what your business does and how you can help.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'contact',
- array(
- 'label' => _x( 'Contact', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your contact information.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'about',
- array(
- 'label' => _x( 'About', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Introduce yourself.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'portfolio',
- array(
- 'label' => _x( 'Portfolio', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Showcase your latest work.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'gallery',
- array(
- 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Different layouts for displaying images.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'media',
- array(
- 'label' => _x( 'Media', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Different layouts containing video or audio.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'posts',
- array(
- 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
- )
- );
- // Site building pattern categories.
- register_block_pattern_category(
- 'footer',
- array(
- 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of footer designs displaying information and site navigation.', 'gutenberg' ),
- )
- );
- register_block_pattern_category(
- 'header',
- array(
- 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ),
- 'description' => __( 'A variety of header designs displaying your site title and navigation.', 'gutenberg' ),
- )
- );
-}
-add_action( 'init', 'gutenberg_register_core_block_patterns_categories' );
-
-/**
- * Register any patterns that the active theme may provide under its
- * `./patterns/` directory. Each pattern is defined as a PHP file and defines
- * its metadata using plugin-style headers. The minimum required definition is:
- *
- * /**
- * * Title: My Pattern
- * * Slug: my-theme/my-pattern
- * *
- *
- * The output of the PHP source corresponds to the content of the pattern, e.g.:
- *
- *
- *
- * If applicable, this will collect from both parent and child theme.
- *
- * Other settable fields include:
- *
- * - Description
- * - Viewport Width
- * - Categories (comma-separated values)
- * - Keywords (comma-separated values)
- * - Block Types (comma-separated values)
- * - Post Types (comma-separated values)
- * - Inserter (yes/no)
- *
- * @since 6.0.0
- * @access private
- */
-function gutenberg_register_theme_block_patterns() {
- $default_headers = array(
- 'title' => 'Title',
- 'slug' => 'Slug',
- 'description' => 'Description',
- 'viewportWidth' => 'Viewport Width',
- 'categories' => 'Categories',
- 'keywords' => 'Keywords',
- 'blockTypes' => 'Block Types',
- 'postTypes' => 'Post Types',
- 'inserter' => 'Inserter',
- 'templateTypes' => 'Template Types',
- );
-
- /*
- * Register patterns for the active theme. If the theme is a child theme,
- * let it override any patterns from the parent theme that shares the same slug.
- */
- $themes = array();
- $wp_theme = wp_get_theme();
- if ( $wp_theme->parent() ) {
- $themes[] = $wp_theme->parent();
- }
- $themes[] = $wp_theme;
-
- foreach ( $themes as $theme ) {
- $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
- if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
- continue;
- }
- if ( file_exists( $dirpath ) ) {
- $files = glob( $dirpath . '*.php' );
- if ( $files ) {
- foreach ( $files as $file ) {
- $pattern_data = get_file_data( $file, $default_headers );
-
- if ( empty( $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %s: file name. */
- __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ),
- $file
- ),
- '6.0.0'
- );
- continue;
- }
-
- if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ),
- $file,
- $pattern_data['slug']
- ),
- '6.0.0'
- );
- }
-
- if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
- continue;
- }
-
- // Title is a required property.
- if ( ! $pattern_data['title'] ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ),
- $file
- ),
- '6.0.0'
- );
- continue;
- }
-
- // For properties of type array, parse data as comma-separated.
- foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = array_filter(
- preg_split(
- '/[\s,]+/',
- (string) $pattern_data[ $property ]
- )
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type int.
- foreach ( array( 'viewportWidth' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = (int) $pattern_data[ $property ];
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type bool.
- foreach ( array( 'inserter' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = in_array(
- strtolower( $pattern_data[ $property ] ),
- array( 'yes', 'true' ),
- true
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Translate the pattern metadata.
- $text_domain = $theme->get( 'TextDomain' );
- //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
- if ( ! empty( $pattern_data['description'] ) ) {
- //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
- }
-
- // The actual pattern content is the output of the file.
- ob_start();
- include $file;
- $pattern_data['content'] = ob_get_clean();
- if ( ! $pattern_data['content'] ) {
- continue;
- }
-
- register_block_pattern( $pattern_data['slug'], $pattern_data );
- }
- }
- }
- }
-}
-remove_action( 'init', '_register_theme_block_patterns' );
-add_action( 'init', 'gutenberg_register_theme_block_patterns' );
-
-/**
- * Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase).
- *
- * @since 6.2.0
- *
- * @param array $pattern Pattern as returned from the Pattern Directory API.
- */
-function gutenberg_normalize_remote_pattern( $pattern ) {
- if ( isset( $pattern['block_types'] ) ) {
- $pattern['blockTypes'] = $pattern['block_types'];
- unset( $pattern['block_types'] );
- }
-
- if ( isset( $pattern['viewport_width'] ) ) {
- $pattern['viewportWidth'] = $pattern['viewport_width'];
- unset( $pattern['viewport_width'] );
- }
-
- return (array) $pattern;
-}
diff --git a/lib/compat/wordpress-6.2/block-template-utils.php b/lib/compat/wordpress-6.2/block-template-utils.php
deleted file mode 100644
index db9bf427e6b471..00000000000000
--- a/lib/compat/wordpress-6.2/block-template-utils.php
+++ /dev/null
@@ -1,97 +0,0 @@
- The template hierarchy.
- */
-function gutenberg_get_template_hierarchy( $slug, $is_custom = false, $template_prefix = '' ) {
- if ( 'index' === $slug ) {
- return array( 'index' );
- }
- if ( $is_custom ) {
- return array( 'page', 'singular', 'index' );
- }
- if ( 'front-page' === $slug ) {
- return array( 'front-page', 'home', 'index' );
- }
-
- $matches = array();
-
- $template_hierarchy = array( $slug );
- // Most default templates don't have `$template_prefix` assigned.
- if ( ! empty( $template_prefix ) ) {
- list( $type ) = explode( '-', $template_prefix );
- // We need these checks because we always add the `$slug` above.
- if ( ! in_array( $template_prefix, array( $slug, $type ), true ) ) {
- $template_hierarchy[] = $template_prefix;
- }
- if ( $slug !== $type ) {
- $template_hierarchy[] = $type;
- }
- } elseif ( preg_match( '/^(author|category|archive|tag|page)-.+$/', $slug, $matches ) ) {
- $template_hierarchy[] = $matches[1];
- } elseif ( preg_match( '/^(taxonomy|single)-(.+)$/', $slug, $matches ) ) {
- $type = $matches[1];
- $slug_remaining = $matches[2];
-
- $items = 'single' === $type ? get_post_types() : get_taxonomies();
- foreach ( $items as $item ) {
- if ( ! str_starts_with( $slug_remaining, $item ) ) {
- continue;
- }
-
- // If $slug_remaining is equal to $post_type or $taxonomy we have
- // the single-$post_type template or the taxonomy-$taxonomy template.
- if ( $slug_remaining === $item ) {
- $template_hierarchy[] = $type;
- break;
- }
-
- // If $slug_remaining is single-$post_type-$slug template.
- if ( strlen( $slug_remaining ) > strlen( $item ) + 1 ) {
- $template_hierarchy[] = "$type-$item";
- $template_hierarchy[] = $type;
- break;
- }
- }
- }
- // Handle `archive` template.
- if (
- str_starts_with( $slug, 'author' ) ||
- str_starts_with( $slug, 'taxonomy' ) ||
- str_starts_with( $slug, 'category' ) ||
- str_starts_with( $slug, 'tag' ) ||
- 'date' === $slug
- ) {
- $template_hierarchy[] = 'archive';
- }
- // Handle `single` template.
- if ( 'attachment' === $slug ) {
- $template_hierarchy[] = 'single';
- }
- // Handle `singular` template.
- if (
- str_starts_with( $slug, 'single' ) ||
- str_starts_with( $slug, 'page' ) ||
- 'attachment' === $slug
- ) {
- $template_hierarchy[] = 'singular';
- }
- $template_hierarchy[] = 'index';
- return $template_hierarchy;
-}
diff --git a/lib/compat/wordpress-6.2/blocks.php b/lib/compat/wordpress-6.2/blocks.php
deleted file mode 100644
index 94c6eaabcef9b3..00000000000000
--- a/lib/compat/wordpress-6.2/blocks.php
+++ /dev/null
@@ -1,173 +0,0 @@
-= 6.2.
- *
- * @param string[] $attrs Array of allowed CSS attributes.
- * @return string[] CSS attributes.
- */
-function gutenberg_safe_style_attrs_6_2( $attrs ) {
- $attrs[] = 'position';
- $attrs[] = 'top';
- $attrs[] = 'right';
- $attrs[] = 'bottom';
- $attrs[] = 'left';
- $attrs[] = 'z-index';
- $attrs[] = 'box-shadow';
- $attrs[] = 'aspect-ratio';
-
- return $attrs;
-}
-add_filter( 'safe_style_css', 'gutenberg_safe_style_attrs_6_2' );
-
-/**
- * Helper function that constructs a WP_Query args array from
- * a `Query` block properties.
- *
- * It's used in QueryLoop, QueryPaginationNumbers and QueryPaginationNext blocks.
- *
- * `build_query_vars_from_query_block` was introduced in 5.8, for 6.1 we just need
- * to update that function and not create a new one.
- *
- * @param WP_Block $block Block instance.
- * @param int $page Current query's page.
- *
- * @return array Returns the constructed WP_Query arguments.
- */
-function gutenberg_build_query_vars_from_query_block( $block, $page ) {
- $query = array(
- 'post_type' => 'post',
- 'order' => 'DESC',
- 'orderby' => 'date',
- 'post__not_in' => array(),
- );
-
- if ( isset( $block->context['query'] ) ) {
- if ( ! empty( $block->context['query']['postType'] ) ) {
- $post_type_param = $block->context['query']['postType'];
- if ( is_post_type_viewable( $post_type_param ) ) {
- $query['post_type'] = $post_type_param;
- }
- }
- if ( isset( $block->context['query']['sticky'] ) && ! empty( $block->context['query']['sticky'] ) ) {
- $sticky = get_option( 'sticky_posts' );
- if ( 'only' === $block->context['query']['sticky'] ) {
- /**
- * Passing an empty array to post__in will return have_posts() as true (and all posts will be returned).
- * Logic should be used before hand to determine if WP_Query should be used in the event that the array
- * being passed to post__in is empty.
- *
- * @see https://core.trac.wordpress.org/ticket/28099
- */
- $query['post__in'] = ! empty( $sticky ) ? $sticky : array( 0 );
- $query['ignore_sticky_posts'] = 1;
- } else {
- $query['post__not_in'] = array_merge( $query['post__not_in'], $sticky );
- }
- }
- if ( ! empty( $block->context['query']['exclude'] ) ) {
- $excluded_post_ids = array_map( 'intval', $block->context['query']['exclude'] );
- $excluded_post_ids = array_filter( $excluded_post_ids );
- $query['post__not_in'] = array_merge( $query['post__not_in'], $excluded_post_ids );
- }
- if (
- isset( $block->context['query']['perPage'] ) &&
- is_numeric( $block->context['query']['perPage'] )
- ) {
- $per_page = absint( $block->context['query']['perPage'] );
- $offset = 0;
-
- if (
- isset( $block->context['query']['offset'] ) &&
- is_numeric( $block->context['query']['offset'] )
- ) {
- $offset = absint( $block->context['query']['offset'] );
- }
-
- $query['offset'] = ( $per_page * ( $page - 1 ) ) + $offset;
- $query['posts_per_page'] = $per_page;
- }
-
- // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
- if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
- $tax_query = array();
- if ( ! empty( $block->context['query']['categoryIds'] ) ) {
- $tax_query[] = array(
- 'taxonomy' => 'category',
- 'terms' => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
- 'include_children' => false,
- );
- }
- if ( ! empty( $block->context['query']['tagIds'] ) ) {
- $tax_query[] = array(
- 'taxonomy' => 'post_tag',
- 'terms' => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
- 'include_children' => false,
- );
- }
- $query['tax_query'] = $tax_query;
- }
- if ( ! empty( $block->context['query']['taxQuery'] ) ) {
- $query['tax_query'] = array();
- foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
- if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
- $query['tax_query'][] = array(
- 'taxonomy' => $taxonomy,
- 'terms' => array_filter( array_map( 'intval', $terms ) ),
- 'include_children' => false,
- );
- }
- }
- }
- if (
- isset( $block->context['query']['order'] ) &&
- in_array( strtoupper( $block->context['query']['order'] ), array( 'ASC', 'DESC' ), true )
- ) {
- $query['order'] = strtoupper( $block->context['query']['order'] );
- }
- if ( isset( $block->context['query']['orderBy'] ) ) {
- $query['orderby'] = $block->context['query']['orderBy'];
- }
- if (
- isset( $block->context['query']['author'] ) &&
- (int) $block->context['query']['author'] > 0
- ) {
- $query['author'] = (int) $block->context['query']['author'];
- }
- if ( ! empty( $block->context['query']['search'] ) ) {
- $query['s'] = $block->context['query']['search'];
- }
- if ( ! empty( $block->context['query']['parents'] ) && is_post_type_hierarchical( $query['post_type'] ) ) {
- $query['post_parent__in'] = array_filter( array_map( 'intval', $block->context['query']['parents'] ) );
- }
- }
-
- /**
- * Filters the arguments which will be passed to `WP_Query` for the Query Loop Block.
- *
- * Anything to this filter should be compatible with the `WP_Query` API to form
- * the query context which will be passed down to the Query Loop Block's children.
- * This can help, for example, to include additional settings or meta queries not
- * directly supported by the core Query Loop Block, and extend its capabilities.
- *
- * Please note that this will only influence the query that will be rendered on the
- * front-end. The editor preview is not affected by this filter. Also, worth noting
- * that the editor preview uses the REST API, so, ideally, one should aim to provide
- * attributes which are also compatible with the REST API, in order to be able to
- * implement identical queries on both sides.
- *
- * @since 6.1.0
- *
- * @param array $query Array containing parameters for `WP_Query` as parsed by the block context.
- * @param WP_Block $block Block instance.
- * @param int $page Current query's page.
- */
- return apply_filters( 'query_loop_block_query_vars', $query, $block, $page );
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php
deleted file mode 100644
index 3897a8945a5f95..00000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php
+++ /dev/null
@@ -1,84 +0,0 @@
-get_fields_for_response( $request );
- $keys = array( 'name', 'label', 'description' );
- $data = array();
- foreach ( $keys as $key ) {
- if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) {
- $data[ $key ] = $item[ $key ];
- }
- }
-
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
- $data = $this->add_additional_fields_to_object( $data, $request );
- $data = $this->filter_response_by_context( $data, $context );
- return rest_ensure_response( $data );
- }
-
- /**
- * Retrieves the block pattern category schema, conforming to JSON Schema.
- *
- * @since 6.0.0
- *
- * @return array Item schema data.
- */
- public function get_item_schema() {
- if ( $this->schema ) {
- return $this->add_additional_fields_schema( $this->schema );
- }
-
- $schema = array(
- '$schema' => 'http://json-schema.org/draft-04/schema#',
- 'title' => 'block-pattern-category',
- 'type' => 'object',
- 'properties' => array(
- 'name' => array(
- 'description' => __( 'The category name.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'label' => array(
- 'description' => __( 'The category label, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'description' => array(
- 'description' => __( 'The category description, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- ),
- );
-
- $this->schema = $schema;
-
- return $this->add_additional_fields_schema( $this->schema );
- }
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php
deleted file mode 100644
index 80de88aa31a816..00000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php
+++ /dev/null
@@ -1,235 +0,0 @@
- 'call-to-action',
- 'columns' => 'text',
- 'query' => 'posts',
- );
-
- /**
- * Prepare a raw block pattern before it gets output in a REST API response.
- *
- * @since 6.0.0
- *
- * @param array $item Raw pattern as registered, before any changes.
- * @param WP_REST_Request $request Request object.
- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
- */
- public function prepare_item_for_response( $item, $request ) {
- $fields = $this->get_fields_for_response( $request );
- $keys = array(
- 'name' => 'name',
- 'title' => 'title',
- 'description' => 'description',
- 'viewportWidth' => 'viewport_width',
- 'blockTypes' => 'block_types',
- 'postTypes' => 'post_types',
- 'categories' => 'categories',
- 'keywords' => 'keywords',
- 'content' => 'content',
- 'inserter' => 'inserter',
- 'templateTypes' => 'template_types',
- );
- $data = array();
- foreach ( $keys as $item_key => $rest_key ) {
- if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) {
- $data[ $rest_key ] = $item[ $item_key ];
- }
- }
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
- $data = $this->add_additional_fields_to_object( $data, $request );
- $data = $this->filter_response_by_context( $data, $context );
- return rest_ensure_response( $data );
- }
-
- /**
- * Retrieves the block pattern schema, conforming to JSON Schema.
- *
- * @since 6.0.0
- * @since 6.1.0 Added `post_types` property.
- *
- * @return array Item schema data.
- */
- public function get_item_schema() {
- if ( $this->schema ) {
- return $this->add_additional_fields_schema( $this->schema );
- }
-
- $schema = array(
- '$schema' => 'http://json-schema.org/draft-04/schema#',
- 'title' => 'block-pattern',
- 'type' => 'object',
- 'properties' => array(
- 'name' => array(
- 'description' => __( 'The pattern name.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'title' => array(
- 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'description' => array(
- 'description' => __( 'The pattern detailed description.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'viewport_width' => array(
- 'description' => __( 'The pattern viewport width for inserter preview.', 'gutenberg' ),
- 'type' => 'number',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'block_types' => array(
- 'description' => __( 'Block types that the pattern is intended to be used with.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'post_types' => array(
- 'description' => __( 'An array of post types that the pattern is restricted to be used with.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'categories' => array(
- 'description' => __( 'The pattern category slugs.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'keywords' => array(
- 'description' => __( 'The pattern keywords.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'template_types' => array(
- 'description' => __( 'An array of template types where the pattern fits.', 'gutenberg' ),
- 'type' => 'array',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'content' => array(
- 'description' => __( 'The pattern content.', 'gutenberg' ),
- 'type' => 'string',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- 'inserter' => array(
- 'description' => __( 'Determines whether the pattern is visible in inserter.', 'gutenberg' ),
- 'type' => 'boolean',
- 'readonly' => true,
- 'context' => array( 'view', 'edit', 'embed' ),
- ),
- ),
- );
-
- $this->schema = $schema;
-
- return $this->add_additional_fields_schema( $this->schema );
- }
-
- /**
- * Registers the routes for the objects of the controller.
- *
- * @since 6.0.0
- */
- public function register_routes() {
- register_rest_route(
- $this->namespace,
- '/' . $this->rest_base,
- array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_items' ),
- 'permission_callback' => array( $this, 'get_items_permissions_check' ),
- ),
- 'schema' => array( $this, 'get_public_item_schema' ),
- ),
- true
- );
- }
- /**
- * Retrieves all block patterns.
- *
- * @since 6.0.0
- * @since 6.2.0 Added migration for old core pattern categories to the new ones.
- *
- * @param WP_REST_Request $request Full details about the request.
- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
- */
- public function get_items( $request ) {
- if ( ! $this->remote_patterns_loaded ) {
- // Load block patterns from w.org.
- gutenberg_load_remote_block_patterns(); // Patterns with the `core` keyword.
- gutenberg_load_remote_featured_patterns(); // Patterns in the `featured` category.
- gutenberg_register_remote_theme_patterns(); // Patterns requested by current theme.
-
- $this->remote_patterns_loaded = true;
- }
-
- $response = array();
- $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
- foreach ( $patterns as $pattern ) {
- $migrated_pattern = $this->migrate_pattern_categories( $pattern );
- $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
- $response[] = $this->prepare_response_for_collection( $prepared_pattern );
- }
- return rest_ensure_response( $response );
- }
-
- /**
- * Migrates old core pattern categories to new ones.
- *
- * Core pattern categories are being revamped and we need to handle the migration
- * to the new ones and ensure backwards compatibility.
- *
- * @since 6.2.0
- *
- * @param array $pattern Raw pattern as registered, before applying any changes.
- * @return array Migrated pattern.
- */
- protected function migrate_pattern_categories( $pattern ) {
- if ( isset( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) {
- foreach ( $pattern['categories'] as $i => $category ) {
- if ( array_key_exists( $category, static::$categories_migration ) ) {
- $pattern['categories'][ $i ] = static::$categories_migration[ $category ];
- }
- }
- }
- return $pattern;
- }
-}
diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php
deleted file mode 100644
index 7043140c23fcff..00000000000000
--- a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php
+++ /dev/null
@@ -1,122 +0,0 @@
- true,
- 'order' => true,
- 'orderby' => true,
- 'page' => true,
- 'per_page' => true,
- 'search' => true,
- 'slug' => true,
- );
-
- $query_args = array_intersect_key( $request->get_params(), $valid_query_args );
-
- $query_args['locale'] = get_user_locale();
- $query_args['wp-version'] = $wp_version; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- it's defined in `version.php` above.
- $query_args['gutenberg-version'] = $gutenberg_data['Version'];
- $query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false;
- $query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false;
-
- $query_args = array_filter( $query_args );
-
- $transient_key = $this->get_transient_key( $query_args );
-
- /*
- * Use network-wide transient to improve performance. The locale is the only site
- * configuration that affects the response, and it's included in the transient key.
- */
- $raw_patterns = get_site_transient( $transient_key );
-
- if ( ! $raw_patterns ) {
- $api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args );
- if ( wp_http_supports( array( 'ssl' ) ) ) {
- $api_url = set_url_scheme( $api_url, 'https' );
- }
-
- /*
- * Default to a short TTL, to mitigate cache stampedes on high-traffic sites.
- * This assumes that most errors will be short-lived, e.g., packet loss that causes the
- * first request to fail, but a follow-up one will succeed. The value should be high
- * enough to avoid stampedes, but low enough to not interfere with users manually
- * re-trying a failed request.
- */
- $cache_ttl = 5;
- $wporg_response = wp_remote_get( $api_url );
- $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) );
-
- if ( is_wp_error( $wporg_response ) ) {
- $raw_patterns = $wporg_response;
-
- } elseif ( ! is_array( $raw_patterns ) ) {
- // HTTP request succeeded, but response data is invalid.
- $raw_patterns = new WP_Error(
- 'pattern_api_failed',
- sprintf(
- /* translators: %s: Support forums URL. */
- __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.', 'gutenberg' ),
- __( 'https://wordpress.org/support/forums/', 'gutenberg' )
- ),
- array(
- 'response' => wp_remote_retrieve_body( $wporg_response ),
- )
- );
-
- } else {
- // Response has valid data.
- $cache_ttl = HOUR_IN_SECONDS;
- }
-
- set_site_transient( $transient_key, $raw_patterns, $cache_ttl );
- }
-
- if ( is_wp_error( $raw_patterns ) ) {
- $raw_patterns->add_data( array( 'status' => 500 ) );
-
- return $raw_patterns;
- }
-
- $response = array();
-
- if ( $raw_patterns ) {
- foreach ( $raw_patterns as $pattern ) {
- $response[] = $this->prepare_response_for_collection(
- $this->prepare_item_for_response( $pattern, $request )
- );
- }
- }
-
- return new WP_REST_Response( $response );
- }
-}
diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php
deleted file mode 100644
index 32a7a33157a74a..00000000000000
--- a/lib/compat/wordpress-6.2/default-filters.php
+++ /dev/null
@@ -1,18 +0,0 @@
-name = $name;
- $this->value_starts_at = $value_start;
- $this->value_length = $value_length;
- $this->start = $start;
- $this->end = $end;
- $this->is_true = $is_true;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
deleted file mode 100644
index e38bc551923170..00000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
+++ /dev/null
@@ -1,56 +0,0 @@
-start = $start;
- $this->end = $end;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
deleted file mode 100644
index d61180074f608d..00000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
+++ /dev/null
@@ -1,2291 +0,0 @@
- "c" not " c".
- * This would increase the size of the changes for some operations but leave more
- * natural-looking output HTML.
- * - Decode HTML character references within class names when matching. E.g. match having
- * class `1<"2` needs to recognize `class="1<"2"`. Currently the Tag Processor
- * will fail to find the right tag if the class name is encoded as such.
- * - Properly decode HTML character references in `get_attribute()`. PHP's
- * `html_entity_decode()` is wrong in a couple ways: it doesn't account for the
- * no-ambiguous-ampersand rule, and it improperly handles the way semicolons may
- * or may not terminate a character reference.
- *
- * @package WordPress
- * @subpackage HTML-API
- * @since 6.2.0
- */
-
-if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
- return;
-}
-
-/**
- * Modifies attributes in an HTML document for tags matching a query.
- *
- * ## Usage
- *
- * Use of this class requires three steps:
- *
- * 1. Create a new class instance with your input HTML document.
- * 2. Find the tag(s) you are looking for.
- * 3. Request changes to the attributes in those tag(s).
- *
- * Example:
- *
- * $tags = new WP_HTML_Tag_Processor( $html );
- * if ( $tags->next_tag( 'option' ) ) {
- * $tags->set_attribute( 'selected', true );
- * }
- *
- * ### Finding tags
- *
- * The `next_tag()` function moves the internal cursor through
- * your input HTML document until it finds a tag meeting any of
- * the supplied restrictions in the optional query argument. If
- * no argument is provided then it will find the next HTML tag,
- * regardless of what kind it is.
- *
- * If you want to _find whatever the next tag is_:
- *
- * $tags->next_tag();
- *
- * | Goal | Query |
- * |-----------------------------------------------------------|---------------------------------------------------------------------------------|
- * | Find any tag. | `$tags->next_tag();` |
- * | Find next image tag. | `$tags->next_tag( array( 'tag_name' => 'img' ) );` |
- * | Find next image tag (without passing the array). | `$tags->next_tag( 'img' );` |
- * | Find next tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'class_name' => 'fullwidth' ) );` |
- * | Find next image tag containing the `fullwidth` CSS class. | `$tags->next_tag( array( 'tag_name' => 'img', 'class_name' => 'fullwidth' ) );` |
- *
- * If a tag was found meeting your criteria then `next_tag()`
- * will return `true` and you can proceed to modify it. If it
- * returns `false`, however, it failed to find the tag and
- * moved the cursor to the end of the file.
- *
- * Once the cursor reaches the end of the file the processor
- * is done and if you want to reach an earlier tag you will
- * need to recreate the processor and start over, as it's
- * unable to back up or move in reverse.
- *
- * See the section on bookmarks for an exception to this
- * no-backing-up rule.
- *
- * #### Custom queries
- *
- * Sometimes it's necessary to further inspect an HTML tag than
- * the query syntax here permits. In these cases one may further
- * inspect the search results using the read-only functions
- * provided by the processor or external state or variables.
- *
- * Example:
- *
- * // Paint up to the first five DIV or SPAN tags marked with the "jazzy" style.
- * $remaining_count = 5;
- * while ( $remaining_count > 0 && $tags->next_tag() ) {
- * if (
- * ( 'DIV' === $tags->get_tag() || 'SPAN' === $tags->get_tag() ) &&
- * 'jazzy' === $tags->get_attribute( 'data-style' )
- * ) {
- * $tags->add_class( 'theme-style-everest-jazz' );
- * $remaining_count--;
- * }
- * }
- *
- * `get_attribute()` will return `null` if the attribute wasn't present
- * on the tag when it was called. It may return `""` (the empty string)
- * in cases where the attribute was present but its value was empty.
- * For boolean attributes, those whose name is present but no value is
- * given, it will return `true` (the only way to set `false` for an
- * attribute is to remove it).
- *
- * ### Modifying HTML attributes for a found tag
- *
- * Once you've found the start of an opening tag you can modify
- * any number of the attributes on that tag. You can set a new
- * value for an attribute, remove the entire attribute, or do
- * nothing and move on to the next opening tag.
- *
- * Example:
- *
- * if ( $tags->next_tag( array( 'class' => 'wp-group-block' ) ) ) {
- * $tags->set_attribute( 'title', 'This groups the contained content.' );
- * $tags->remove_attribute( 'data-test-id' );
- * }
- *
- * If `set_attribute()` is called for an existing attribute it will
- * overwrite the existing value. Similarly, calling `remove_attribute()`
- * for a non-existing attribute has no effect on the document. Both
- * of these methods are safe to call without knowing if a given attribute
- * exists beforehand.
- *
- * ### Modifying CSS classes for a found tag
- *
- * The tag processor treats the `class` attribute as a special case.
- * Because it's a common operation to add or remove CSS classes, this
- * interface adds helper methods to make that easier.
- *
- * As with attribute values, adding or removing CSS classes is a safe
- * operation that doesn't require checking if the attribute or class
- * exists before making changes. If removing the only class then the
- * entire `class` attribute will be removed.
- *
- * Example:
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from `Yippee!`
- * // to `Yippee!`
- * $tags->add_class( 'is-active' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * // from ``
- * // to `
- * $tags->remove_class( 'rugby' );
- *
- * When class changes are enqueued but a direct change to `class` is made via
- * `set_attribute` then the changes to `set_attribute` (or `remove_attribute`)
- * will take precedence over those made through `add_class` and `remove_class`.
- *
- * ### Bookmarks
- *
- * While scanning through the input HTMl document it's possible to set
- * a named bookmark when a particular tag is found. Later on, after
- * continuing to scan other tags, it's possible to `seek` to one of
- * the set bookmarks and then proceed again from that point forward.
- *
- * Because bookmarks create processing overhead one should avoid
- * creating too many of them. As a rule, create only bookmarks
- * of known string literal names; avoid creating "mark_{$index}"
- * and so on. It's fine from a performance standpoint to create a
- * bookmark and update it frequently, such as within a loop.
- *
- * $total_todos = 0;
- * while ( $p->next_tag( array( 'tag_name' => 'UL', 'class_name' => 'todo' ) ) ) {
- * $p->set_bookmark( 'list-start' );
- * while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
- * if ( 'UL' === $p->get_tag() && $p->is_tag_closer() ) {
- * $p->set_bookmark( 'list-end' );
- * $p->seek( 'list-start' );
- * $p->set_attribute( 'data-contained-todos', (string) $total_todos );
- * $total_todos = 0;
- * $p->seek( 'list-end' );
- * break;
- * }
- *
- * if ( 'LI' === $p->get_tag() && ! $p->is_tag_closer() ) {
- * $total_todos++;
- * }
- * }
- * }
- *
- * ## Design and limitations
- *
- * The Tag Processor is designed to linearly scan HTML documents and tokenize
- * HTML tags and their attributes. It's designed to do this as efficiently as
- * possible without compromising parsing integrity. Therefore it will be
- * slower than some methods of modifying HTML, such as those incorporating
- * over-simplified PCRE patterns, but will not introduce the defects and
- * failures that those methods bring in, which lead to broken page renders
- * and often to security vulnerabilities. On the other hand, it will be faster
- * than full-blown HTML parsers such as DOMDocument and use considerably
- * less memory. It requires a negligible memory overhead, enough to consider
- * it a zero-overhead system.
- *
- * The performance characteristics are maintained by avoiding tree construction
- * and semantic cleanups which are specified in HTML5. Because of this, for
- * example, it's not possible for the Tag Processor to associate any given
- * opening tag with its corresponding closing tag, or to return the inner markup
- * inside an element. Systems may be built on top of the Tag Processor to do
- * this, but the Tag Processor is and should be constrained so it can remain an
- * efficient, low-level, and reliable HTML scanner.
- *
- * The Tag Processor's design incorporates a "garbage-in-garbage-out" philosophy.
- * HTML5 specifies that certain invalid content be transformed into different forms
- * for display, such as removing null bytes from an input document and replacing
- * invalid characters with the Unicode replacement character U+FFFD �. Where errors
- * or transformations exist within the HTML5 specification, the Tag Processor leaves
- * those invalid inputs untouched, passing them through to the final browser to handle.
- * While this implies that certain operations will be non-spec-compliant, such as
- * reading the value of an attribute with invalid content, it also preserves a
- * simplicity and efficiency for handling those error cases.
- *
- * Most operations within the Tag Processor are designed to minimize the difference
- * between an input and output document for any given change. For example, the
- * `add_class` and `remove_class` methods preserve whitespace and the class ordering
- * within the `class` attribute; and when encountering tags with duplicated attributes,
- * the Tag Processor will leave those invalid duplicate attributes where they are but
- * update the proper attribute which the browser will read for parsing its value. An
- * exception to this rule is that all attribute updates store their values as
- * double-quoted strings, meaning that attributes on input with single-quoted or
- * unquoted values will appear in the output with double-quotes.
- *
- * @since 6.2.0
- */
-class WP_HTML_Tag_Processor {
- /**
- * The maximum number of bookmarks allowed to exist at
- * any given time.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::set_bookmark()
- */
- const MAX_BOOKMARKS = 10;
-
- /**
- * Maximum number of times seek() can be called.
- * Prevents accidental infinite loops.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::seek()
- */
- const MAX_SEEK_OPS = 1000;
-
- /**
- * The HTML document to parse.
- *
- * @since 6.2.0
- * @var string
- */
- protected $html;
-
- /**
- * The last query passed to next_tag().
- *
- * @since 6.2.0
- * @var array|null
- */
- private $last_query;
-
- /**
- * The tag name this processor currently scans for.
- *
- * @since 6.2.0
- * @var string|null
- */
- private $sought_tag_name;
-
- /**
- * The CSS class name this processor currently scans for.
- *
- * @since 6.2.0
- * @var string|null
- */
- private $sought_class_name;
-
- /**
- * The match offset this processor currently scans for.
- *
- * @since 6.2.0
- * @var int|null
- */
- private $sought_match_offset;
-
- /**
- * Whether to visit tag closers, e.g. , when walking an input document.
- *
- * @since 6.2.0
- * @var bool
- */
- private $stop_on_tag_closers;
-
- /**
- * How many bytes from the original HTML document have been read and parsed.
- *
- * This value points to the latest byte offset in the input document which
- * has been already parsed. It is the internal cursor for the Tag Processor
- * and updates while scanning through the HTML tokens.
- *
- * @since 6.2.0
- * @var int
- */
- private $bytes_already_parsed = 0;
-
- /**
- * Byte offset in input document where current tag name starts.
- *
- * Example:
- *
- *
...
- * 01234
- * - tag name starts at 1
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_name_starts_at;
-
- /**
- * Byte length of current tag name.
- *
- * Example:
- *
- *
...
- * 01234
- * --- tag name length is 3
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_name_length;
-
- /**
- * Byte offset in input document where current tag token ends.
- *
- * Example:
- *
- *
...
- * 0 1 |
- * 01234567890123456
- * --- tag name ends at 14
- *
- * @since 6.2.0
- * @var int|null
- */
- private $tag_ends_at;
-
- /**
- * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
.
- *
- * @var bool
- */
- private $is_closing_tag;
-
- /**
- * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name.
- *
- * Example:
- *
- * // Supposing the parser is working through this content
- * // and stops after recognizing the `id` attribute.
- * //
- * // ^ parsing will continue from this point.
- * $this->attributes = array(
- * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 )
- * );
- *
- * // When picking up parsing again, or when asking to find the
- * // `class` attribute we will continue and add to this array.
- * $this->attributes = array(
- * 'id' => new WP_HTML_Attribute_Match( 'id', null, 6, 17 ),
- * 'class' => new WP_HTML_Attribute_Match( 'class', 'outline', 18, 32 )
- * );
- *
- * // Note that only the `class` attribute value is stored in the index.
- * // That's because it is the only value used by this class at the moment.
- *
- * @since 6.2.0
- * @var WP_HTML_Attribute_Token[]
- */
- private $attributes = array();
-
- /**
- * Which class names to add or remove from a tag.
- *
- * These are tracked separately from attribute updates because they are
- * semantically distinct, whereas this interface exists for the common
- * case of adding and removing class names while other attributes are
- * generally modified as with DOM `setAttribute` calls.
- *
- * When modifying an HTML document these will eventually be collapsed
- * into a single `set_attribute( 'class', $changes )` call.
- *
- * Example:
- *
- * // Add the `wp-block-group` class, remove the `wp-group` class.
- * $classname_updates = array(
- * // Indexed by a comparable class name.
- * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS,
- * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS
- * );
- *
- * @since 6.2.0
- * @var bool[]
- */
- private $classname_updates = array();
-
- /**
- * Tracks a semantic location in the original HTML which
- * shifts with updates as they are applied to the document.
- *
- * @since 6.2.0
- * @var WP_HTML_Span[]
- */
- protected $bookmarks = array();
-
- const ADD_CLASS = true;
- const REMOVE_CLASS = false;
- const SKIP_CLASS = null;
-
- /**
- * Lexical replacements to apply to input HTML document.
- *
- * "Lexical" in this class refers to the part of this class which
- * operates on pure text _as text_ and not as HTML. There's a line
- * between the public interface, with HTML-semantic methods like
- * `set_attribute` and `add_class`, and an internal state that tracks
- * text offsets in the input document.
- *
- * When higher-level HTML methods are called, those have to transform their
- * operations (such as setting an attribute's value) into text diffing
- * operations (such as replacing the sub-string from indices A to B with
- * some given new string). These text-diffing operations are the lexical
- * updates.
- *
- * As new higher-level methods are added they need to collapse their
- * operations into these lower-level lexical updates since that's the
- * Tag Processor's internal language of change. Any code which creates
- * these lexical updates must ensure that they do not cross HTML syntax
- * boundaries, however, so these should never be exposed outside of this
- * class or any classes which intentionally expand its functionality.
- *
- * These are enqueued while editing the document instead of being immediately
- * applied to avoid processing overhead, string allocations, and string
- * copies when applying many updates to a single document.
- *
- * Example:
- *
- * // Replace an attribute stored with a new value, indices
- * // sourced from the lazily-parsed HTML recognizer.
- * $start = $attributes['src']->start;
- * $end = $attributes['src']->end;
- * $modifications[] = new WP_HTML_Text_Replacement( $start, $end, $new_value );
- *
- * // Correspondingly, something like this will appear in this array.
- * $lexical_updates = array(
- * WP_HTML_Text_Replacement( 14, 28, 'https://my-site.my-domain/wp-content/uploads/2014/08/kittens.jpg' )
- * );
- *
- * @since 6.2.0
- * @var WP_HTML_Text_Replacement[]
- */
- protected $lexical_updates = array();
-
- /**
- * Tracks and limits `seek()` calls to prevent accidental infinite loops.
- *
- * @since 6.2.0
- * @var int
- *
- * @see WP_HTML_Tag_Processor::seek()
- */
- protected $seek_count = 0;
-
- /**
- * Constructor.
- *
- * @since 6.2.0
- *
- * @param string $html HTML to process.
- */
- public function __construct( $html ) {
- $this->html = $html;
- }
-
- /**
- * Finds the next tag matching the $query.
- *
- * @since 6.2.0
- *
- * @param array|string|null $query {
- * Optional. Which tag name to find, having which class, etc. Default is to find any tag.
- *
- * @type string|null $tag_name Which tag to find, or `null` for "any tag."
- * @type int|null $match_offset Find the Nth tag matching all search criteria.
- * 1 for "first" tag, 3 for "third," etc.
- * Defaults to first tag.
- * @type string|null $class_name Tag must contain this whole class name to match.
- * @type string|null $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
.
- * }
- * @return boolean Whether a tag was matched.
- */
- public function next_tag( $query = null ) {
- $this->parse_query( $query );
- $already_found = 0;
-
- do {
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- // Find the next tag if it exists.
- if ( false === $this->parse_next_tag() ) {
- $this->bytes_already_parsed = strlen( $this->html );
-
- return false;
- }
-
- // Parse all of its attributes.
- while ( $this->parse_next_attribute() ) {
- continue;
- }
-
- // Ensure that the tag closes before the end of the document.
- $tag_ends_at = strpos( $this->html, '>', $this->bytes_already_parsed );
- if ( false === $tag_ends_at ) {
- return false;
- }
- $this->tag_ends_at = $tag_ends_at;
- $this->bytes_already_parsed = $tag_ends_at;
-
- // Finally, check if the parsed tag and its attributes match the search query.
- if ( $this->matches() ) {
- ++$already_found;
- }
-
- /*
- * For non-DATA sections which might contain text that looks like HTML tags but
- * isn't, scan with the appropriate alternative mode. Looking at the first letter
- * of the tag name as a pre-check avoids a string allocation when it's not needed.
- */
- $t = $this->html[ $this->tag_name_starts_at ];
- if ( ! $this->is_closing_tag && ( 's' === $t || 'S' === $t || 't' === $t || 'T' === $t ) ) {
- $tag_name = $this->get_tag();
-
- if ( 'SCRIPT' === $tag_name && ! $this->skip_script_data() ) {
- $this->bytes_already_parsed = strlen( $this->html );
- return false;
- } elseif (
- ( 'TEXTAREA' === $tag_name || 'TITLE' === $tag_name ) &&
- ! $this->skip_rcdata( $tag_name )
- ) {
- $this->bytes_already_parsed = strlen( $this->html );
- return false;
- }
- }
- } while ( $already_found < $this->sought_match_offset );
-
- return true;
- }
-
-
- /**
- * Sets a bookmark in the HTML document.
- *
- * Bookmarks represent specific places or tokens in the HTML
- * document, such as a tag opener or closer. When applying
- * edits to a document, such as setting an attribute, the
- * text offsets of that token may shift; the bookmark is
- * kept updated with those shifts and remains stable unless
- * the entire span of text in which the token sits is removed.
- *
- * Release bookmarks when they are no longer needed.
- *
- * Example:
- *
- *
Surprising fact you may not know!
- * ^ ^
- * \-|-- this `H2` opener bookmark tracks the token
- *
- *
Surprising fact you may no…
- * ^ ^
- * \-|-- it shifts with edits
- *
- * Bookmarks provide the ability to seek to a previously-scanned
- * place in the HTML document. This avoids the need to re-scan
- * the entire document.
- *
- * Example:
- *
- *
One
Two
Three
- * ^^^^
- * want to note this last item
- *
- * $p = new WP_HTML_Tag_Processor( $html );
- * $in_list = false;
- * while ( $p->next_tag( array( 'tag_closers' => $in_list ? 'visit' : 'skip' ) ) ) {
- * if ( 'UL' === $p->get_tag() ) {
- * if ( $p->is_tag_closer() ) {
- * $in_list = false;
- * $p->set_bookmark( 'resume' );
- * if ( $p->seek( 'last-li' ) ) {
- * $p->add_class( 'last-li' );
- * }
- * $p->seek( 'resume' );
- * $p->release_bookmark( 'last-li' );
- * $p->release_bookmark( 'resume' );
- * } else {
- * $in_list = true;
- * }
- * }
- *
- * if ( 'LI' === $p->get_tag() ) {
- * $p->set_bookmark( 'last-li' );
- * }
- * }
- *
- * Bookmarks intentionally hide the internal string offsets
- * to which they refer. They are maintained internally as
- * updates are applied to the HTML document and therefore
- * retain their "position" - the location to which they
- * originally pointed. The inability to use bookmarks with
- * functions like `substr` is therefore intentional to guard
- * against accidentally breaking the HTML.
- *
- * Because bookmarks allocate memory and require processing
- * for every applied update, they are limited and require
- * a name. They should not be created with programmatically-made
- * names, such as "li_{$index}" with some loop. As a general
- * rule they should only be created with string-literal names
- * like "start-of-section" or "last-paragraph".
- *
- * Bookmarks are a powerful tool to enable complicated behavior.
- * Consider double-checking that you need this tool if you are
- * reaching for it, as inappropriate use could lead to broken
- * HTML structure or unwanted processing overhead.
- *
- * @since 6.2.0
- *
- * @param string $name Identifies this particular bookmark.
- * @return bool Whether the bookmark was successfully created.
- */
- public function set_bookmark( $name ) {
- if ( null === $this->tag_name_starts_at ) {
- return false;
- }
-
- if ( ! array_key_exists( $name, $this->bookmarks ) && count( $this->bookmarks ) >= self::MAX_BOOKMARKS ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Too many bookmarks: cannot create any more.' ),
- '6.2.0'
- );
- return false;
- }
-
- $this->bookmarks[ $name ] = new WP_HTML_Span(
- $this->tag_name_starts_at - ( $this->is_closing_tag ? 2 : 1 ),
- $this->tag_ends_at
- );
-
- return true;
- }
-
-
- /**
- * Removes a bookmark that is no longer needed.
- *
- * Releasing a bookmark frees up the small
- * performance overhead it requires.
- *
- * @param string $name Name of the bookmark to remove.
- * @return bool Whether the bookmark already existed before removal.
- */
- public function release_bookmark( $name ) {
- if ( ! array_key_exists( $name, $this->bookmarks ) ) {
- return false;
- }
-
- unset( $this->bookmarks[ $name ] );
-
- return true;
- }
-
-
- /**
- * Skips contents of title and textarea tags.
- *
- * @since 6.2.0
- *
- * @see https://html.spec.whatwg.org/multipage/parsing.html#rcdata-state
- *
- * @param string $tag_name The lowercase tag name which will close the RCDATA region.
- * @return bool Whether an end to the RCDATA region was found before the end of the document.
- */
- private function skip_rcdata( $tag_name ) {
- $html = $this->html;
- $doc_length = strlen( $html );
- $tag_length = strlen( $tag_name );
-
- $at = $this->bytes_already_parsed;
-
- while ( false !== $at && $at < $doc_length ) {
- $at = strpos( $this->html, '', $at );
-
- // If there is no possible tag closer then fail.
- if ( false === $at || ( $at + $tag_length ) >= $doc_length ) {
- $this->bytes_already_parsed = $doc_length;
- return false;
- }
-
- $closer_potentially_starts_at = $at;
- $at += 2;
-
- /*
- * Find a case-insensitive match to the tag name.
- *
- * Because tag names are limited to US-ASCII there is no
- * need to perform any kind of Unicode normalization when
- * comparing; any character which could be impacted by such
- * normalization could not be part of a tag name.
- */
- for ( $i = 0; $i < $tag_length; $i++ ) {
- $tag_char = $tag_name[ $i ];
- $html_char = $html[ $at + $i ];
-
- if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
- $at += $i;
- continue 2;
- }
- }
-
- $at += $tag_length;
- $this->bytes_already_parsed = $at;
-
- /*
- * Ensure that the tag name terminates to avoid matching on
- * substrings of a longer tag name. For example, the sequence
- * "' !== $c ) {
- continue;
- }
-
- while ( $this->parse_next_attribute() ) {
- continue;
- }
- $at = $this->bytes_already_parsed;
- if ( $at >= strlen( $this->html ) ) {
- return false;
- }
-
- if ( '>' === $html[ $at ] || '/' === $html[ $at ] ) {
- $this->bytes_already_parsed = $closer_potentially_starts_at;
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Skips contents of script tags.
- *
- * @since 6.2.0
- *
- * @return bool Whether the script tag was closed before the end of the document.
- */
- private function skip_script_data() {
- $state = 'unescaped';
- $html = $this->html;
- $doc_length = strlen( $html );
- $at = $this->bytes_already_parsed;
-
- while ( false !== $at && $at < $doc_length ) {
- $at += strcspn( $html, '-<', $at );
-
- /*
- * For all script states a "-->" transitions
- * back into the normal unescaped script mode,
- * even if that's the current state.
- */
- if (
- $at + 2 < $doc_length &&
- '-' === $html[ $at ] &&
- '-' === $html[ $at + 1 ] &&
- '>' === $html[ $at + 2 ]
- ) {
- $at += 3;
- $state = 'unescaped';
- continue;
- }
-
- // Everything of interest past here starts with "<".
- if ( $at + 1 >= $doc_length || '<' !== $html[ $at++ ] ) {
- continue;
- }
-
- /*
- * Unlike with "-->", the "
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $at + 3 &&
- '-' === $html[ $at + 2 ] &&
- '-' === $html[ $at + 3 ]
- ) {
- $closer_at = $at + 4;
- // If it's not possible to close the comment then there is nothing more to scan.
- if ( strlen( $html ) <= $closer_at ) {
- return false;
- }
-
- // Abruptly-closed empty comments are a sequence of dashes followed by `>`.
- $span_of_dashes = strspn( $html, '-', $closer_at );
- if ( '>' === $html[ $closer_at + $span_of_dashes ] ) {
- $at = $closer_at + $span_of_dashes + 1;
- continue;
- }
-
- /*
- * Comments may be closed by either a --> or an invalid --!>.
- * The first occurrence closes the comment.
- *
- * See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment
- */
- $closer_at--; // Pre-increment inside condition below reduces risk of accidental infinite looping.
- while ( ++$closer_at < strlen( $html ) ) {
- $closer_at = strpos( $html, '--', $closer_at );
- if ( false === $closer_at ) {
- return false;
- }
-
- if ( $closer_at + 2 < strlen( $html ) && '>' === $html[ $closer_at + 2 ] ) {
- $at = $closer_at + 3;
- continue 2;
- }
-
- if ( $closer_at + 3 < strlen( $html ) && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) {
- $at = $closer_at + 4;
- continue 2;
- }
- }
- }
-
- /*
- *
- * The CDATA is case-sensitive.
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $at + 8 &&
- '[' === $html[ $at + 2 ] &&
- 'C' === $html[ $at + 3 ] &&
- 'D' === $html[ $at + 4 ] &&
- 'A' === $html[ $at + 5 ] &&
- 'T' === $html[ $at + 6 ] &&
- 'A' === $html[ $at + 7 ] &&
- '[' === $html[ $at + 8 ]
- ) {
- $closer_at = strpos( $html, ']]>', $at + 9 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 3;
- continue;
- }
-
- /*
- *
- * These are ASCII-case-insensitive.
- * https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if (
- strlen( $html ) > $at + 8 &&
- ( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) &&
- ( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) &&
- ( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) &&
- ( 'T' === $html[ $at + 5 ] || 't' === $html[ $at + 5 ] ) &&
- ( 'Y' === $html[ $at + 6 ] || 'y' === $html[ $at + 6 ] ) &&
- ( 'P' === $html[ $at + 7 ] || 'p' === $html[ $at + 7 ] ) &&
- ( 'E' === $html[ $at + 8 ] || 'e' === $html[ $at + 8 ] )
- ) {
- $closer_at = strpos( $html, '>', $at + 9 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- /*
- * Anything else here is an incorrectly-opened comment and transitions
- * to the bogus comment state - skip to the nearest >.
- */
- $at = strpos( $html, '>', $at + 1 );
- continue;
- }
-
- /*
- * > is a missing end tag name, which is ignored.
- *
- * See https://html.spec.whatwg.org/#parse-error-missing-end-tag-name
- */
- if ( '>' === $html[ $at + 1 ] ) {
- $at++;
- continue;
- }
-
- /*
- * transitions to a bogus comment state – skip to the nearest >
- * See https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
- */
- if ( '?' === $html[ $at + 1 ] ) {
- $closer_at = strpos( $html, '>', $at + 2 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- /*
- * If a non-alpha starts the tag name in a tag closer it's a comment.
- * Find the first `>`, which closes the comment.
- *
- * See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name
- */
- if ( $this->is_closing_tag ) {
- $closer_at = strpos( $html, '>', $at + 3 );
- if ( false === $closer_at ) {
- return false;
- }
-
- $at = $closer_at + 1;
- continue;
- }
-
- ++$at;
- }
-
- return false;
- }
-
- /**
- * Parses the next attribute.
- *
- * @since 6.2.0
- *
- * @return bool Whether an attribute was found before the end of the document.
- */
- private function parse_next_attribute() {
- // Skip whitespace and slashes.
- $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n/", $this->bytes_already_parsed );
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- /*
- * Treat the equal sign as a part of the attribute
- * name if it is the first encountered byte.
- *
- * @see https://html.spec.whatwg.org/multipage/parsing.html#before-attribute-name-state
- */
- $name_length = '=' === $this->html[ $this->bytes_already_parsed ]
- ? 1 + strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed + 1 )
- : strcspn( $this->html, "=/> \t\f\r\n", $this->bytes_already_parsed );
-
- // No attribute, just tag closer.
- if ( 0 === $name_length || $this->bytes_already_parsed + $name_length >= strlen( $this->html ) ) {
- return false;
- }
-
- $attribute_start = $this->bytes_already_parsed;
- $attribute_name = substr( $this->html, $attribute_start, $name_length );
- $this->bytes_already_parsed += $name_length;
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- $this->skip_whitespace();
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- $has_value = '=' === $this->html[ $this->bytes_already_parsed ];
- if ( $has_value ) {
- ++$this->bytes_already_parsed;
- $this->skip_whitespace();
- if ( $this->bytes_already_parsed >= strlen( $this->html ) ) {
- return false;
- }
-
- switch ( $this->html[ $this->bytes_already_parsed ] ) {
- case "'":
- case '"':
- $quote = $this->html[ $this->bytes_already_parsed ];
- $value_start = $this->bytes_already_parsed + 1;
- $value_length = strcspn( $this->html, $quote, $value_start );
- $attribute_end = $value_start + $value_length + 1;
- $this->bytes_already_parsed = $attribute_end;
- break;
-
- default:
- $value_start = $this->bytes_already_parsed;
- $value_length = strcspn( $this->html, "> \t\f\r\n", $value_start );
- $attribute_end = $value_start + $value_length;
- $this->bytes_already_parsed = $attribute_end;
- }
- } else {
- $value_start = $this->bytes_already_parsed;
- $value_length = 0;
- $attribute_end = $attribute_start + $name_length;
- }
-
- if ( $attribute_end >= strlen( $this->html ) ) {
- return false;
- }
-
- if ( $this->is_closing_tag ) {
- return true;
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $comparable_name = strtolower( $attribute_name );
-
- // If an attribute is listed many times, only use the first declaration and ignore the rest.
- if ( ! array_key_exists( $comparable_name, $this->attributes ) ) {
- $this->attributes[ $comparable_name ] = new WP_HTML_Attribute_Token(
- $attribute_name,
- $value_start,
- $value_length,
- $attribute_start,
- $attribute_end,
- ! $has_value
- );
- }
-
- return true;
- }
-
- /**
- * Move the internal cursor past any immediate successive whitespace.
- *
- * @since 6.2.0
- *
- * @return void
- */
- private function skip_whitespace() {
- $this->bytes_already_parsed += strspn( $this->html, " \t\f\r\n", $this->bytes_already_parsed );
- }
-
- /**
- * Applies attribute updates and cleans up once a tag is fully parsed.
- *
- * @since 6.2.0
- *
- * @return void
- */
- private function after_tag() {
- $this->get_updated_html();
- $this->tag_name_starts_at = null;
- $this->tag_name_length = null;
- $this->tag_ends_at = null;
- $this->is_closing_tag = null;
- $this->attributes = array();
- }
-
- /**
- * Converts class name updates into tag attributes updates
- * (they are accumulated in different data formats for performance).
- *
- * @since 6.2.0
- *
- * @see WP_HTML_Tag_Processor::$lexical_updates
- * @see WP_HTML_Tag_Processor::$classname_updates
- *
- * @return void
- */
- private function class_name_updates_to_attributes_updates() {
- if ( count( $this->classname_updates ) === 0 ) {
- return;
- }
-
- $existing_class = $this->get_enqueued_attribute_value( 'class' );
- if ( null === $existing_class || true === $existing_class ) {
- $existing_class = '';
- }
-
- if ( false === $existing_class && isset( $this->attributes['class'] ) ) {
- $existing_class = substr(
- $this->html,
- $this->attributes['class']->value_starts_at,
- $this->attributes['class']->value_length
- );
- }
-
- if ( false === $existing_class ) {
- $existing_class = '';
- }
-
- /**
- * Updated "class" attribute value.
- *
- * This is incrementally built while scanning through the existing class
- * attribute, skipping removed classes on the way, and then appending
- * added classes at the end. Only when finished processing will the
- * value contain the final new value.
-
- * @var string $class
- */
- $class = '';
-
- /**
- * Tracks the cursor position in the existing
- * class attribute value while parsing.
- *
- * @var int $at
- */
- $at = 0;
-
- /**
- * Indicates if there's any need to modify the existing class attribute.
- *
- * If a call to `add_class()` and `remove_class()` wouldn't impact
- * the `class` attribute value then there's no need to rebuild it.
- * For example, when adding a class that's already present or
- * removing one that isn't.
- *
- * This flag enables a performance optimization when none of the enqueued
- * class updates would impact the `class` attribute; namely, that the
- * processor can continue without modifying the input document, as if
- * none of the `add_class()` or `remove_class()` calls had been made.
- *
- * This flag is set upon the first change that requires a string update.
- *
- * @var bool $modified
- */
- $modified = false;
-
- // Remove unwanted classes by only copying the new ones.
- $existing_class_length = strlen( $existing_class );
- while ( $at < $existing_class_length ) {
- // Skip to the first non-whitespace character.
- $ws_at = $at;
- $ws_length = strspn( $existing_class, " \t\f\r\n", $ws_at );
- $at += $ws_length;
-
- // Capture the class name – it's everything until the next whitespace.
- $name_length = strcspn( $existing_class, " \t\f\r\n", $at );
- if ( 0 === $name_length ) {
- // If no more class names are found then that's the end.
- break;
- }
-
- $name = substr( $existing_class, $at, $name_length );
- $at += $name_length;
-
- // If this class is marked for removal, start processing the next one.
- $remove_class = (
- isset( $this->classname_updates[ $name ] ) &&
- self::REMOVE_CLASS === $this->classname_updates[ $name ]
- );
-
- // If a class has already been seen then skip it; it should not be added twice.
- if ( ! $remove_class ) {
- $this->classname_updates[ $name ] = self::SKIP_CLASS;
- }
-
- if ( $remove_class ) {
- $modified = true;
- continue;
- }
-
- /*
- * Otherwise, append it to the new "class" attribute value.
- *
- * There are options for handling whitespace between tags.
- * Preserving the existing whitespace produces fewer changes
- * to the HTML content and should clarify the before/after
- * content when debugging the modified output.
- *
- * This approach contrasts normalizing the inter-class
- * whitespace to a single space, which might appear cleaner
- * in the output HTML but produce a noisier change.
- */
- $class .= substr( $existing_class, $ws_at, $ws_length );
- $class .= $name;
- }
-
- // Add new classes by appending those which haven't already been seen.
- foreach ( $this->classname_updates as $name => $operation ) {
- if ( self::ADD_CLASS === $operation ) {
- $modified = true;
-
- $class .= strlen( $class ) > 0 ? ' ' : '';
- $class .= $name;
- }
- }
-
- $this->classname_updates = array();
- if ( ! $modified ) {
- return;
- }
-
- if ( strlen( $class ) > 0 ) {
- $this->set_attribute( 'class', $class );
- } else {
- $this->remove_attribute( 'class' );
- }
- }
-
- /**
- * Applies attribute updates to HTML document.
- *
- * @since 6.2.0
- * @since 6.2.1 Accumulates shift for internal cursor and passed pointer.
- *
- * @param int $shift_this_point Accumulate and return shift for this position.
- * @return int How many bytes the given pointer moved in response to the updates.
- */
- private function apply_attributes_updates( $shift_this_point = 0 ) {
- if ( ! count( $this->lexical_updates ) ) {
- return 0;
- }
-
- $accumulated_shift_for_given_point = 0;
-
- /*
- * Attribute updates can be enqueued in any order but updates
- * to the document must occur in lexical order; that is, each
- * replacement must be made before all others which follow it
- * at later string indices in the input document.
- *
- * Sorting avoid making out-of-order replacements which
- * can lead to mangled output, partially-duplicated
- * attributes, and overwritten attributes.
- */
- usort( $this->lexical_updates, array( self::class, 'sort_start_ascending' ) );
-
- $bytes_already_copied = 0;
- $output_buffer = '';
- foreach ( $this->lexical_updates as $diff ) {
- $shift = strlen( $diff->text ) - ( $diff->end - $diff->start );
-
- // Adjust the cursor position by however much an update affects it.
- if ( $diff->start <= $this->bytes_already_parsed ) {
- $this->bytes_already_parsed += $shift;
- }
-
- // Accumulate shift of the given pointer within this function call.
- if ( $diff->start <= $shift_this_point ) {
- $accumulated_shift_for_given_point += $shift;
- }
-
- $output_buffer .= substr( $this->html, $bytes_already_copied, $diff->start - $bytes_already_copied );
- $output_buffer .= $diff->text;
- $bytes_already_copied = $diff->end;
- }
-
- $this->html = $output_buffer . substr( $this->html, $bytes_already_copied );
-
- /*
- * Adjust bookmark locations to account for how the text
- * replacements adjust offsets in the input document.
- */
- foreach ( $this->bookmarks as $bookmark ) {
- /*
- * Each lexical update which appears before the bookmark's endpoints
- * might shift the offsets for those endpoints. Loop through each change
- * and accumulate the total shift for each bookmark, then apply that
- * shift after tallying the full delta.
- */
- $head_delta = 0;
- $tail_delta = 0;
-
- foreach ( $this->lexical_updates as $diff ) {
- $update_head = $bookmark->start >= $diff->start;
- $update_tail = $bookmark->end >= $diff->start;
-
- if ( ! $update_head && ! $update_tail ) {
- break;
- }
-
- $delta = strlen( $diff->text ) - ( $diff->end - $diff->start );
-
- if ( $update_head ) {
- $head_delta += $delta;
- }
-
- if ( $update_tail ) {
- $tail_delta += $delta;
- }
- }
-
- $bookmark->start += $head_delta;
- $bookmark->end += $tail_delta;
- }
-
- $this->lexical_updates = array();
-
- return $accumulated_shift_for_given_point;
- }
-
- /**
- * Move the internal cursor in the Tag Processor to a given bookmark's location.
- *
- * In order to prevent accidental infinite loops, there's a
- * maximum limit on the number of times seek() can be called.
- *
- * @since 6.2.0
- *
- * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
- * @return bool Whether the internal cursor was successfully moved to the bookmark's location.
- */
- public function seek( $bookmark_name ) {
- if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Unknown bookmark name.' ),
- '6.2.0'
- );
- return false;
- }
-
- if ( ++$this->seek_count > self::MAX_SEEK_OPS ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Too many calls to seek() - this can lead to performance issues.' ),
- '6.2.0'
- );
- return false;
- }
-
- // Flush out any pending updates to the document.
- $this->get_updated_html();
-
- // Point this tag processor before the sought tag opener and consume it.
- $this->bytes_already_parsed = $this->bookmarks[ $bookmark_name ]->start;
- return $this->next_tag( array( 'tag_closers' => 'visit' ) );
- }
-
- /**
- * Compare two WP_HTML_Text_Replacement objects.
- *
- * @since 6.2.0
- *
- * @param WP_HTML_Text_Replacement $a First attribute update.
- * @param WP_HTML_Text_Replacement $b Second attribute update.
- * @return int Comparison value for string order.
- */
- private static function sort_start_ascending( $a, $b ) {
- $by_start = $a->start - $b->start;
- if ( 0 !== $by_start ) {
- return $by_start;
- }
-
- $by_text = isset( $a->text, $b->text ) ? strcmp( $a->text, $b->text ) : 0;
- if ( 0 !== $by_text ) {
- return $by_text;
- }
-
- /*
- * This code should be unreachable, because it implies the two replacements
- * start at the same location and contain the same text.
- */
- return $a->end - $b->end;
- }
-
- /**
- * Return the enqueued value for a given attribute, if one exists.
- *
- * Enqueued updates can take different data types:
- * - If an update is enqueued and is boolean, the return will be `true`
- * - If an update is otherwise enqueued, the return will be the string value of that update.
- * - If an attribute is enqueued to be removed, the return will be `null` to indicate that.
- * - If no updates are enqueued, the return will be `false` to differentiate from "removed."
- *
- * @since 6.2.0
- *
- * @param string $comparable_name The attribute name in its comparable form.
- * @return string|boolean|null Value of enqueued update if present, otherwise false.
- */
- private function get_enqueued_attribute_value( $comparable_name ) {
- if ( ! isset( $this->lexical_updates[ $comparable_name ] ) ) {
- return false;
- }
-
- $enqueued_text = $this->lexical_updates[ $comparable_name ]->text;
-
- // Removed attributes erase the entire span.
- if ( '' === $enqueued_text ) {
- return null;
- }
-
- /*
- * Boolean attribute updates are just the attribute name without a corresponding value.
- *
- * This value might differ from the given comparable name in that there could be leading
- * or trailing whitespace, and that the casing follows the name given in `set_attribute`.
- *
- * Example:
- *
- * $p->set_attribute( 'data-TEST-id', 'update' );
- * 'update' === $p->get_enqueued_attribute_value( 'data-test-id' );
- *
- * Detect this difference based on the absence of the `=`, which _must_ exist in any
- * attribute containing a value, e.g. ``.
- * ¹ ²
- * 1. Attribute with a string value.
- * 2. Boolean attribute whose value is `true`.
- */
- $equals_at = strpos( $enqueued_text, '=' );
- if ( false === $equals_at ) {
- return true;
- }
-
- /*
- * Finally, a normal update's value will appear after the `=` and
- * be double-quoted, as performed incidentally by `set_attribute`.
- *
- * e.g. `type="text"`
- * ¹² ³
- * 1. Equals is here.
- * 2. Double-quoting starts one after the equals sign.
- * 3. Double-quoting ends at the last character in the update.
- */
- $enqueued_value = substr( $enqueued_text, $equals_at + 2, -1 );
- return html_entity_decode( $enqueued_value );
- }
-
- /**
- * Returns the value of a requested attribute from a matched tag opener if that attribute exists.
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '
Test
' );
- * $p->next_tag( array( 'class_name' => 'test' ) ) === true;
- * $p->get_attribute( 'data-test-id' ) === '14';
- * $p->get_attribute( 'enabled' ) === true;
- * $p->get_attribute( 'aria-label' ) === null;
- *
- * $p->next_tag() === false;
- * $p->get_attribute( 'class' ) === null;
- *
- * @since 6.2.0
- *
- * @param string $name Name of attribute whose value is requested.
- * @return string|true|null Value of attribute or `null` if not available. Boolean attributes return `true`.
- */
- public function get_attribute( $name ) {
- if ( null === $this->tag_name_starts_at ) {
- return null;
- }
-
- $comparable = strtolower( $name );
-
- /*
- * For every attribute other than `class` it's possible to perform a quick check if
- * there's an enqueued lexical update whose value takes priority over what's found in
- * the input document.
- *
- * The `class` attribute is special though because of the exposed helpers `add_class`
- * and `remove_class`. These form a builder for the `class` attribute, so an additional
- * check for enqueued class changes is required in addition to the check for any enqueued
- * attribute values. If any exist, those enqueued class changes must first be flushed out
- * into an attribute value update.
- */
- if ( 'class' === $name ) {
- $this->class_name_updates_to_attributes_updates();
- }
-
- // Return any enqueued attribute value updates if they exist.
- $enqueued_value = $this->get_enqueued_attribute_value( $comparable );
- if ( false !== $enqueued_value ) {
- return $enqueued_value;
- }
-
- if ( ! isset( $this->attributes[ $comparable ] ) ) {
- return null;
- }
-
- $attribute = $this->attributes[ $comparable ];
-
- /*
- * This flag distinguishes an attribute with no value
- * from an attribute with an empty string value. For
- * unquoted attributes this could look very similar.
- * It refers to whether an `=` follows the name.
- *
- * e.g.
- * ¹ ²
- * 1. Attribute `boolean-attribute` is `true`.
- * 2. Attribute `empty-attribute` is `""`.
- */
- if ( true === $attribute->is_true ) {
- return true;
- }
-
- $raw_value = substr( $this->html, $attribute->value_starts_at, $attribute->value_length );
-
- return html_entity_decode( $raw_value );
- }
-
- /**
- * Gets lowercase names of all attributes matching a given prefix in the current tag.
- *
- * Note that matching is case-insensitive. This is in accordance with the spec:
- *
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '
' );
- * $p->next_tag() === true;
- * $p->get_tag() === 'DIV';
- *
- * $p->next_tag() === false;
- * $p->get_tag() === null;
- *
- * @since 6.2.0
- *
- * @return string|null Name of currently matched tag in input HTML, or `null` if none found.
- */
- public function get_tag() {
- if ( null === $this->tag_name_starts_at ) {
- return null;
- }
-
- $tag_name = substr( $this->html, $this->tag_name_starts_at, $this->tag_name_length );
-
- return strtoupper( $tag_name );
- }
-
- /**
- * Indicates if the current tag token is a tag closer.
- *
- * Example:
- *
- * $p = new WP_HTML_Tag_Processor( '' );
- * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
- * $p->is_tag_closer() === false;
- *
- * $p->next_tag( array( 'tag_name' => 'div', 'tag_closers' => 'visit' ) );
- * $p->is_tag_closer() === true;
- *
- * @since 6.2.0
- *
- * @return bool Whether the current tag is a tag closer.
- */
- public function is_tag_closer() {
- return $this->is_closing_tag;
- }
-
- /**
- * Updates or creates a new attribute on the currently matched tag with the passed value.
- *
- * For boolean attributes special handling is provided:
- * - When `true` is passed as the value, then only the attribute name is added to the tag.
- * - When `false` is passed, the attribute gets removed if it existed before.
- *
- * For string attributes, the value is escaped using the `esc_attr` function.
- *
- * @since 6.2.0
- * @since 6.2.1 Fix: Only create a single update for multiple calls with case-variant attribute names.
- *
- * @param string $name The attribute name to target.
- * @param string|bool $value The new attribute value.
- * @return bool Whether an attribute value was set.
- */
- public function set_attribute( $name, $value ) {
- if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
- return false;
- }
-
- /*
- * WordPress rejects more characters than are strictly forbidden
- * in HTML5. This is to prevent additional security risks deeper
- * in the WordPress and plugin stack. Specifically the
- * less-than (<) greater-than (>) and ampersand (&) aren't allowed.
- *
- * The use of a PCRE match enables looking for specific Unicode
- * code points without writing a UTF-8 decoder. Whereas scanning
- * for one-byte characters is trivial (with `strcspn`), scanning
- * for the longer byte sequences would be more complicated. Given
- * that this shouldn't be in the hot path for execution, it's a
- * reasonable compromise in efficiency without introducing a
- * noticeable impact on the overall system.
- *
- * @see https://html.spec.whatwg.org/#attributes-2
- *
- * @TODO as the only regex pattern maybe we should take it out? are
- * Unicode patterns available broadly in Core?
- */
- if ( preg_match(
- '~[' .
- // Syntax-like characters.
- '"\'>& =' .
- // Control characters.
- '\x{00}-\x{1F}' .
- // HTML noncharacters.
- '\x{FDD0}-\x{FDEF}' .
- '\x{FFFE}\x{FFFF}\x{1FFFE}\x{1FFFF}\x{2FFFE}\x{2FFFF}\x{3FFFE}\x{3FFFF}' .
- '\x{4FFFE}\x{4FFFF}\x{5FFFE}\x{5FFFF}\x{6FFFE}\x{6FFFF}\x{7FFFE}\x{7FFFF}' .
- '\x{8FFFE}\x{8FFFF}\x{9FFFE}\x{9FFFF}\x{AFFFE}\x{AFFFF}\x{BFFFE}\x{BFFFF}' .
- '\x{CFFFE}\x{CFFFF}\x{DFFFE}\x{DFFFF}\x{EFFFE}\x{EFFFF}\x{FFFFE}\x{FFFFF}' .
- '\x{10FFFE}\x{10FFFF}' .
- ']~Ssu',
- $name
- ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'Invalid attribute name.' ),
- '6.2.0'
- );
-
- return false;
- }
-
- /*
- * > The values "true" and "false" are not allowed on boolean attributes.
- * > To represent a false value, the attribute has to be omitted altogether.
- * - HTML5 spec, https://html.spec.whatwg.org/#boolean-attributes
- */
- if ( false === $value ) {
- return $this->remove_attribute( $name );
- }
-
- if ( true === $value ) {
- $updated_attribute = $name;
- } else {
- $escaped_new_value = esc_attr( $value );
- $updated_attribute = "{$name}=\"{$escaped_new_value}\"";
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $comparable_name = strtolower( $name );
-
- if ( isset( $this->attributes[ $comparable_name ] ) ) {
- /*
- * Update an existing attribute.
- *
- * Example – set attribute id to "new" in :
- *
- *
- * ^-------------^
- * start end
- * replacement: `id="new"`
- *
- * Result:
- */
- $existing_attribute = $this->attributes[ $comparable_name ];
- $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
- $existing_attribute->start,
- $existing_attribute->end,
- $updated_attribute
- );
- } else {
- /*
- * Create a new attribute at the tag's name end.
- *
- * Example – add attribute id="new" to :
- *
- *
- * ^
- * start and end
- * replacement: ` id="new"`
- *
- * Result:
- */
- $this->lexical_updates[ $comparable_name ] = new WP_HTML_Text_Replacement(
- $this->tag_name_starts_at + $this->tag_name_length,
- $this->tag_name_starts_at + $this->tag_name_length,
- ' ' . $updated_attribute
- );
- }
-
- /*
- * Any calls to update the `class` attribute directly should wipe out any
- * enqueued class changes from `add_class` and `remove_class`.
- */
- if ( 'class' === $comparable_name && ! empty( $this->classname_updates ) ) {
- $this->classname_updates = array();
- }
-
- return true;
- }
-
- /**
- * Remove an attribute from the currently-matched tag.
- *
- * @since 6.2.0
- *
- * @param string $name The attribute name to remove.
- * @return bool Whether an attribute was removed.
- */
- public function remove_attribute( $name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- /*
- * > There must never be two or more attributes on
- * > the same start tag whose names are an ASCII
- * > case-insensitive match for each other.
- * - HTML 5 spec
- *
- * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2:ascii-case-insensitive
- */
- $name = strtolower( $name );
-
- /*
- * Any calls to update the `class` attribute directly should wipe out any
- * enqueued class changes from `add_class` and `remove_class`.
- */
- if ( 'class' === $name && count( $this->classname_updates ) !== 0 ) {
- $this->classname_updates = array();
- }
-
- /*
- * If updating an attribute that didn't exist in the input
- * document, then remove the enqueued update and move on.
- *
- * For example, this might occur when calling `remove_attribute()`
- * after calling `set_attribute()` for the same attribute
- * and when that attribute wasn't originally present.
- */
- if ( ! isset( $this->attributes[ $name ] ) ) {
- if ( isset( $this->lexical_updates[ $name ] ) ) {
- unset( $this->lexical_updates[ $name ] );
- }
- return false;
- }
-
- /*
- * Removes an existing tag attribute.
- *
- * Example – remove the attribute id from :
- *
- * ^-------------^
- * start end
- * replacement: ``
- *
- * Result:
- */
- $this->lexical_updates[ $name ] = new WP_HTML_Text_Replacement(
- $this->attributes[ $name ]->start,
- $this->attributes[ $name ]->end,
- ''
- );
-
- return true;
- }
-
- /**
- * Adds a new class name to the currently matched tag.
- *
- * @since 6.2.0
- *
- * @param string $class_name The class name to add.
- * @return bool Whether the class was set to be added.
- */
- public function add_class( $class_name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- if ( null !== $this->tag_name_starts_at ) {
- $this->classname_updates[ $class_name ] = self::ADD_CLASS;
- }
-
- return true;
- }
-
- /**
- * Removes a class name from the currently matched tag.
- *
- * @since 6.2.0
- *
- * @param string $class_name The class name to remove.
- * @return bool Whether the class was set to be removed.
- */
- public function remove_class( $class_name ) {
- if ( $this->is_closing_tag ) {
- return false;
- }
-
- if ( null !== $this->tag_name_starts_at ) {
- $this->classname_updates[ $class_name ] = self::REMOVE_CLASS;
- }
-
- return true;
- }
-
- /**
- * Returns the string representation of the HTML Tag Processor.
- *
- * @since 6.2.0
- *
- * @see WP_HTML_Tag_Processor::get_updated_html()
- *
- * @return string The processed HTML.
- */
- public function __toString() {
- return $this->get_updated_html();
- }
-
- /**
- * Returns the string representation of the HTML Tag Processor.
- *
- * @since 6.2.0
- * @since 6.2.1 Shifts the internal cursor corresponding to the applied updates.
- *
- * @return string The processed HTML.
- */
- public function get_updated_html() {
- $requires_no_updating = 0 === count( $this->classname_updates ) && 0 === count( $this->lexical_updates );
-
- /*
- * When there is nothing more to update and nothing has already been
- * updated, return the original document and avoid a string copy.
- */
- if ( $requires_no_updating ) {
- return $this->html;
- }
-
- /*
- * Keep track of the position right before the current tag. This will
- * be necessary for reparsing the current tag after updating the HTML.
- */
- $before_current_tag = $this->tag_name_starts_at - 1;
-
- /*
- * 1. Apply the enqueued edits and update all the pointers to reflect those changes.
- */
- $this->class_name_updates_to_attributes_updates();
- $before_current_tag += $this->apply_attributes_updates( $before_current_tag );
-
- /*
- * 2. Rewind to before the current tag and reparse to get updated attributes.
- *
- * At this point the internal cursor points to the end of the tag name.
- * Rewind before the tag name starts so that it's as if the cursor didn't
- * move; a call to `next_tag()` will reparse the recently-updated attributes
- * and additional calls to modify the attributes will apply at this same
- * location.
- *
- *
Previous HTMLMore HTML
- * ^ | back up by the length of the tag name plus the opening <
- * \<-/ back up by strlen("em") + 1 ==> 3
- */
-
- // Store existing state so it can be restored after reparsing.
- $previous_parsed_byte_count = $this->bytes_already_parsed;
- $previous_query = $this->last_query;
-
- // Reparse attributes.
- $this->bytes_already_parsed = $before_current_tag;
- $this->next_tag();
-
- // Restore previous state.
- $this->bytes_already_parsed = $previous_parsed_byte_count;
- $this->parse_query( $previous_query );
-
- return $this->html;
- }
-
- /**
- * Parses tag query input into internal search criteria.
- *
- * @since 6.2.0
- *
- * @param array|string|null $query {
- * Optional. Which tag name to find, having which class, etc. Default is to find any tag.
- *
- * @type string|null $tag_name Which tag to find, or `null` for "any tag."
- * @type int|null $match_offset Find the Nth tag matching all search criteria.
- * 1 for "first" tag, 3 for "third," etc.
- * Defaults to first tag.
- * @type string|null $class_name Tag must contain this class name to match.
- * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
.
- * }
- * @return void
- */
- private function parse_query( $query ) {
- if ( null !== $query && $query === $this->last_query ) {
- return;
- }
-
- $this->last_query = $query;
- $this->sought_tag_name = null;
- $this->sought_class_name = null;
- $this->sought_match_offset = 1;
- $this->stop_on_tag_closers = false;
-
- // A single string value means "find the tag of this name".
- if ( is_string( $query ) ) {
- $this->sought_tag_name = $query;
- return;
- }
-
- // An empty query parameter applies no restrictions on the search.
- if ( null === $query ) {
- return;
- }
-
- // If not using the string interface, an associative array is required.
- if ( ! is_array( $query ) ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'The query argument must be an array or a tag name.' ),
- '6.2.0'
- );
- return;
- }
-
- if ( isset( $query['tag_name'] ) && is_string( $query['tag_name'] ) ) {
- $this->sought_tag_name = $query['tag_name'];
- }
-
- if ( isset( $query['class_name'] ) && is_string( $query['class_name'] ) ) {
- $this->sought_class_name = $query['class_name'];
- }
-
- if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) {
- $this->sought_match_offset = $query['match_offset'];
- }
-
- if ( isset( $query['tag_closers'] ) ) {
- $this->stop_on_tag_closers = 'visit' === $query['tag_closers'];
- }
- }
-
-
- /**
- * Checks whether a given tag and its attributes match the search criteria.
- *
- * @since 6.2.0
- *
- * @return boolean Whether the given tag and its attribute match the search criteria.
- */
- private function matches() {
- if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) {
- return false;
- }
-
- // Does the tag name match the requested tag name in a case-insensitive manner?
- if ( null !== $this->sought_tag_name ) {
- /*
- * String (byte) length lookup is fast. If they aren't the
- * same length then they can't be the same string values.
- */
- if ( strlen( $this->sought_tag_name ) !== $this->tag_name_length ) {
- return false;
- }
-
- /*
- * Check each character to determine if they are the same.
- * Defer calls to `strtoupper()` to avoid them when possible.
- * Calling `strcasecmp()` here tested slowed than comparing each
- * character, so unless benchmarks show otherwise, it should
- * not be used.
- *
- * It's expected that most of the time that this runs, a
- * lower-case tag name will be supplied and the input will
- * contain lower-case tag names, thus normally bypassing
- * the case comparison code.
- */
- for ( $i = 0; $i < $this->tag_name_length; $i++ ) {
- $html_char = $this->html[ $this->tag_name_starts_at + $i ];
- $tag_char = $this->sought_tag_name[ $i ];
-
- if ( $html_char !== $tag_char && strtoupper( $html_char ) !== $tag_char ) {
- return false;
- }
- }
- }
-
- $needs_class_name = null !== $this->sought_class_name;
-
- if ( $needs_class_name && ! isset( $this->attributes['class'] ) ) {
- return false;
- }
-
- /*
- * Match byte-for-byte (case-sensitive and encoding-form-sensitive) on the class name.
- *
- * This will overlook certain classes that exist in other lexical variations
- * than was supplied to the search query, but requires more complicated searching.
- */
- if ( $needs_class_name ) {
- $class_start = $this->attributes['class']->value_starts_at;
- $class_end = $class_start + $this->attributes['class']->value_length;
- $class_at = $class_start;
-
- /*
- * Ensure that boundaries surround the class name to avoid matching on
- * substrings of a longer name. For example, the sequence "not-odd"
- * should not match for the class "odd" even though "odd" is found
- * within the class attribute text.
- *
- * See https://html.spec.whatwg.org/#attributes-3
- * See https://html.spec.whatwg.org/#space-separated-tokens
- */
- while (
- // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
- false !== ( $class_at = strpos( $this->html, $this->sought_class_name, $class_at ) ) &&
- $class_at < $class_end
- ) {
- /*
- * Verify this class starts at a boundary.
- */
- if ( $class_at > $class_start ) {
- $character = $this->html[ $class_at - 1 ];
-
- if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
- $class_at += strlen( $this->sought_class_name );
- continue;
- }
- }
-
- /*
- * Verify this class ends at a boundary as well.
- */
- if ( $class_at + strlen( $this->sought_class_name ) < $class_end ) {
- $character = $this->html[ $class_at + strlen( $this->sought_class_name ) ];
-
- if ( ' ' !== $character && "\t" !== $character && "\f" !== $character && "\r" !== $character && "\n" !== $character ) {
- $class_at += strlen( $this->sought_class_name );
- continue;
- }
- }
-
- return true;
- }
-
- return false;
- }
-
- return true;
- }
-}
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
deleted file mode 100644
index b3f70c8e7c57f4..00000000000000
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
+++ /dev/null
@@ -1,63 +0,0 @@
-start = $start;
- $this->end = $end;
- $this->text = $text;
- }
-}
diff --git a/lib/compat/wordpress-6.2/menu.php b/lib/compat/wordpress-6.2/menu.php
deleted file mode 100644
index e0d582ad3dc988..00000000000000
--- a/lib/compat/wordpress-6.2/menu.php
+++ /dev/null
@@ -1,35 +0,0 @@
- $menu_item ) {
- if ( str_contains( $menu_item[2], 'site-editor.php?postType=wp_template_part' ) && ! str_contains( $menu_item[2], 'path=' ) ) {
- $submenu['themes.php'][ $index ][2] = 'site-editor.php?postType=wp_template_part&path=/wp_template_part/all';
- break;
- }
- }
-}
-add_action( 'admin_menu', 'gutenberg_update_template_parts_menu_url' );
diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php
deleted file mode 100644
index 97f7daecdff2f7..00000000000000
--- a/lib/compat/wordpress-6.2/rest-api.php
+++ /dev/null
@@ -1,145 +0,0 @@
-register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' );
-
-/**
- * Add extra collection params to pattern directory requests.
- *
- * @param array $query_params JSON Schema-formatted collection parameters.
- * @return array Updated parameters.
- */
-function gutenberg_pattern_directory_collection_params_6_2( $query_params ) {
- $query_params['page'] = array(
- 'description' => __( 'Current page of the collection.', 'gutenberg' ),
- 'type' => 'integer',
- 'default' => 1,
- 'sanitize_callback' => 'absint',
- 'validate_callback' => 'rest_validate_request_arg',
- 'minimum' => 1,
- );
-
- $query_params['per_page'] = array(
- 'description' => __( 'Maximum number of items to be returned in result set.', 'gutenberg' ),
- 'type' => 'integer',
- 'default' => 100,
- 'minimum' => 1,
- 'maximum' => 100,
- 'sanitize_callback' => 'absint',
- 'validate_callback' => 'rest_validate_request_arg',
- );
-
- $query_params['offset'] = array(
- 'description' => __( 'Offset the result set by a specific number of items.', 'gutenberg' ),
- 'type' => 'integer',
- );
-
- $query_params['order'] = array(
- 'description' => __( 'Order sort attribute ascending or descending.', 'gutenberg' ),
- 'type' => 'string',
- 'default' => 'desc',
- 'enum' => array( 'asc', 'desc' ),
- );
-
- $query_params['orderby'] = array(
- 'description' => __( 'Sort collection by post attribute.', 'gutenberg' ),
- 'type' => 'string',
- 'default' => 'date',
- 'enum' => array(
- 'author',
- 'date',
- 'id',
- 'include',
- 'modified',
- 'parent',
- 'relevance',
- 'slug',
- 'include_slugs',
- 'title',
- 'favorite_count',
- ),
- );
-
- return $query_params;
-}
-add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' );
-
-/**
- * Updates REST API response for the sidebars and marks them as 'inactive'.
- *
- * Note: This can be a part of the `prepare_item_for_response` in `class-wp-rest-sidebars-controller.php`.
- *
- * @param WP_REST_Response $response The sidebar response object.
- * @return WP_REST_Response $response Updated response object.
- */
-function gutenberg_modify_rest_sidebars_response( $response ) {
- if ( wp_is_block_theme() ) {
- $response->data['status'] = 'inactive';
- }
- return $response;
-}
-add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
-
-if ( ! function_exists( 'add_block_pattern_block_types_schema' ) ) {
- /**
- * Add the `block_types` value to the `pattern-directory-item` schema.
- *
- * @since 6.2.0 Added 'block_types' property.
- */
- function add_block_pattern_block_types_schema() {
- register_rest_field(
- 'pattern-directory-item',
- 'block_types',
- array(
- 'schema' => array(
- 'description' => __( 'The block types which can use this pattern.', 'gutenberg' ),
- 'type' => 'array',
- 'uniqueItems' => true,
- 'items' => array( 'type' => 'string' ),
- 'context' => array( 'view', 'embed' ),
- ),
- )
- );
- }
-}
-add_filter( 'rest_api_init', 'add_block_pattern_block_types_schema' );
-
-
-if ( ! function_exists( 'filter_block_pattern_response' ) ) {
- /**
- * Add the `block_types` value into the API response.
- *
- * @since 6.2.0 Added 'block_types' property.
- *
- * @param WP_REST_Response $response The response object.
- * @param object $raw_pattern The unprepared pattern.
- */
- function filter_block_pattern_response( $response, $raw_pattern ) {
- $data = $response->get_data();
- $data['block_types'] = array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types );
- $response->set_data( $data );
- return $response;
- }
-}
-add_filter( 'rest_prepare_block_pattern', 'filter_block_pattern_response', 10, 2 );
-
-
-/**
- * Registers the block pattern directory.
- */
-function gutenberg_register_rest_pattern_directory() {
- $pattern_directory_controller = new Gutenberg_REST_Pattern_Directory_Controller_6_2();
- $pattern_directory_controller->register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' );
diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php
deleted file mode 100644
index 37c1ced3c8cfcd..00000000000000
--- a/lib/compat/wordpress-6.2/script-loader.php
+++ /dev/null
@@ -1,75 +0,0 @@
-query( 'wp-inert-polyfill', 'registered' );
- if ( ! $script ) {
- $scripts->add( 'wp-inert-polyfill', gutenberg_url( 'build/vendors/inert-polyfill' . $extension ), array() );
- }
-
- $script = $scripts->query( 'wp-polyfill', 'registered' );
- $script->deps = array_merge( $script->deps, array( 'wp-inert-polyfill' ) );
-}
-add_action( 'wp_default_scripts', 'gutenberg_register_vendor_scripts_62' );
-
-/**
- * This function takes care of adding inline styles
- * in the proper place, depending on the theme in use.
- *
- * This method was added to core in 5.9.1, but with a single param ($style). The second param ($priority) was
- * added post 6.0, so the 6.1 release needs to have wp_enqueue_block_support_styles updated to include this param.
- *
- * For block themes, it's loaded in the head.
- * For classic ones, it's loaded in the body
- * because the wp_head action happens before
- * the render_block.
- *
- * @link https://core.trac.wordpress.org/ticket/53494.
- *
- * @deprecated 6.2 Block supports styles are now stored for enqueuing via the style engine API. See: packages/style-engine/README.md.
- *
- * @param string $style String containing the CSS styles to be added.
- * @param int $priority To set the priority for the add_action.
- */
-function gutenberg_enqueue_block_support_styles( $style, $priority = 10 ) {
- _deprecated_function( __FUNCTION__, '6.2' );
-
- $action_hook_name = 'wp_footer';
- if ( wp_is_block_theme() ) {
- $action_hook_name = 'wp_head';
- }
- add_action(
- $action_hook_name,
- static function () use ( $style ) {
- echo "\n";
- },
- $priority
- );
-}
-
-add_filter(
- 'block_editor_settings_all',
- static function( $settings ) {
- // We must override what core is passing now.
- $settings['__unstableIsBlockBasedTheme'] = wp_is_block_theme();
- return $settings;
- },
- 100
-);
diff --git a/lib/compat/wordpress-6.2/site-editor.php b/lib/compat/wordpress-6.2/site-editor.php
deleted file mode 100644
index b6246e49c6d113..00000000000000
--- a/lib/compat/wordpress-6.2/site-editor.php
+++ /dev/null
@@ -1,24 +0,0 @@
-post ) ) {
- return $settings;
- }
-
- unset( $settings['__unstableHomeTemplate'] );
-
- return $settings;
-}
-add_filter( 'block_editor_settings_all', 'gutenberg_site_editor_unset_homepage_setting', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/theme.php b/lib/compat/wordpress-6.2/theme.php
deleted file mode 100644
index 3e7b31109745a6..00000000000000
--- a/lib/compat/wordpress-6.2/theme.php
+++ /dev/null
@@ -1,23 +0,0 @@
-is_block_theme() ) {
- set_theme_mod( 'wp_classic_sidebars', $wp_registered_sidebars );
- }
-}
-add_action( 'switch_theme', 'gutenberg_set_classic_sidebars', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/widgets.php b/lib/compat/wordpress-6.2/widgets.php
deleted file mode 100644
index ce37f1bd9a34dc..00000000000000
--- a/lib/compat/wordpress-6.2/widgets.php
+++ /dev/null
@@ -1,35 +0,0 @@
-is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" );
@@ -154,7 +154,7 @@ function gutenberg_register_remote_theme_patterns() {
$patterns_registry = WP_Block_Patterns_Registry::get_instance();
foreach ( $patterns as $pattern ) {
$pattern['source'] = 'pattern-directory/theme'; // Added in 6.3.0.
- $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern );
+ $normalized_pattern = wp_normalize_remote_block_pattern( $pattern );
$pattern_name = sanitize_title( $normalized_pattern['title'] );
// Some patterns might be already registered as core patterns with the `core` prefix.
$is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" );
diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
index ac40e6b842f523..0a5b026cded3b9 100644
--- a/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
+++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php
@@ -13,7 +13,15 @@
*
* @see WP_REST_Controller
*/
-class Gutenberg_REST_Block_Patterns_Controller_6_3 extends Gutenberg_REST_Block_Patterns_Controller_6_2 {
+class Gutenberg_REST_Block_Patterns_Controller_6_3 extends WP_REST_Block_Patterns_Controller {
+ /**
+ * Defines whether remote patterns should be loaded.
+ *
+ * @since 6.0.0
+ * @var bool
+ */
+ private $remote_patterns_loaded;
+
/**
* Prepare a raw block pattern before it gets output in a REST API response.
*
@@ -158,4 +166,33 @@ public function get_item_schema() {
return $this->add_additional_fields_schema( $this->schema );
}
+
+ /**
+ * Retrieves all block patterns.
+ *
+ * @since 6.0.0
+ * @since 6.2.0 Added migration for old core pattern categories to the new ones.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function get_items( $request ) {
+ if ( ! $this->remote_patterns_loaded ) {
+ // Load block patterns from w.org.
+ gutenberg_load_remote_block_patterns(); // Patterns with the `core` keyword.
+ gutenberg_load_remote_featured_patterns(); // Patterns in the `featured` category.
+ gutenberg_register_remote_theme_patterns(); // Patterns requested by current theme.
+
+ $this->remote_patterns_loaded = true;
+ }
+
+ $response = array();
+ $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
+ foreach ( $patterns as $pattern ) {
+ $migrated_pattern = $this->migrate_pattern_categories( $pattern );
+ $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
+ $response[] = $this->prepare_response_for_collection( $prepared_pattern );
+ }
+ return rest_ensure_response( $response );
+ }
}
diff --git a/lib/load.php b/lib/load.php
index 72bc0ab66a99b4..ef8da334debe6e 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -35,13 +35,6 @@ function gutenberg_is_experiment_enabled( $name ) {
require_once __DIR__ . '/experimental/class-wp-rest-block-editor-settings-controller.php';
}
- // WordPress 6.2 compat.
- require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php';
- require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php';
- require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php';
- require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php';
- require_once __DIR__ . '/compat/wordpress-6.2/block-patterns.php';
-
// WordPress 6.3 compat.
require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php';
require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php';
@@ -81,25 +74,6 @@ function gutenberg_is_experiment_enabled( $name ) {
// Gutenberg plugin compat.
require __DIR__ . '/compat/plugin/edit-site-routes-backwards-compat.php';
-// WordPress 6.2 compat.
-require __DIR__ . '/compat/wordpress-6.2/blocks.php';
-require __DIR__ . '/compat/wordpress-6.2/script-loader.php';
-require __DIR__ . '/compat/wordpress-6.2/block-template-utils.php';
-require __DIR__ . '/compat/wordpress-6.2/get-global-styles-and-settings.php';
-require __DIR__ . '/compat/wordpress-6.2/default-filters.php';
-require __DIR__ . '/compat/wordpress-6.2/site-editor.php';
-require __DIR__ . '/compat/wordpress-6.2/block-editor.php';
-require __DIR__ . '/compat/wordpress-6.2/theme.php';
-require __DIR__ . '/compat/wordpress-6.2/widgets.php';
-require __DIR__ . '/compat/wordpress-6.2/menu.php';
-
-if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
- require __DIR__ . '/compat/wordpress-6.2/html-api/class-wp-html-attribute-token.php';
- require __DIR__ . '/compat/wordpress-6.2/html-api/class-wp-html-span.php';
- require __DIR__ . '/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php';
- require __DIR__ . '/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php';
-}
-
if ( ! class_exists( 'WP_HTML_Processor' ) ) {
require __DIR__ . '/compat/wordpress-6.4/html-api/class-wp-html-active-formatting-elements.php';
require __DIR__ . '/compat/wordpress-6.4/html-api/class-wp-html-open-elements.php';
diff --git a/phpunit/block-template-utils-test.php b/phpunit/block-template-utils-test.php
index 08fb0c2c2c1e3b..4b10e33deee93f 100644
--- a/phpunit/block-template-utils-test.php
+++ b/phpunit/block-template-utils-test.php
@@ -26,23 +26,23 @@ public function tear_down() {
}
public function test_get_template_hierarchy() {
- $hierarchy = gutenberg_get_template_hierarchy( 'front-page' );
+ $hierarchy = get_template_hierarchy( 'front-page' );
$this->assertEquals( array( 'front-page', 'home', 'index' ), $hierarchy );
// Custom templates.
- $hierarchy = gutenberg_get_template_hierarchy( 'whatever-slug', true );
+ $hierarchy = get_template_hierarchy( 'whatever-slug', true );
$this->assertEquals( array( 'page', 'singular', 'index' ), $hierarchy );
// Single slug templates(ex. page, tag, author, etc..
- $hierarchy = gutenberg_get_template_hierarchy( 'page' );
+ $hierarchy = get_template_hierarchy( 'page' );
$this->assertEquals( array( 'page', 'singular', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'tag' );
+ $hierarchy = get_template_hierarchy( 'tag' );
$this->assertEquals( array( 'tag', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'author' );
+ $hierarchy = get_template_hierarchy( 'author' );
$this->assertEquals( array( 'author', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'date' );
+ $hierarchy = get_template_hierarchy( 'date' );
$this->assertEquals( array( 'date', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy' );
+ $hierarchy = get_template_hierarchy( 'taxonomy' );
$this->assertEquals( array( 'taxonomy', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'attachment' );
+ $hierarchy = get_template_hierarchy( 'attachment' );
$this->assertEquals(
array(
'attachment',
@@ -52,23 +52,23 @@ public function test_get_template_hierarchy() {
),
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'singular' );
+ $hierarchy = get_template_hierarchy( 'singular' );
$this->assertEquals( array( 'singular', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'single' );
+ $hierarchy = get_template_hierarchy( 'single' );
$this->assertEquals( array( 'single', 'singular', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'archive' );
+ $hierarchy = get_template_hierarchy( 'archive' );
$this->assertEquals( array( 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'index' );
+ $hierarchy = get_template_hierarchy( 'index' );
$this->assertEquals( array( 'index' ), $hierarchy );
// Taxonomies.
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-book_type', false );
+ $hierarchy = get_template_hierarchy( 'taxonomy-book_type', false );
$this->assertEquals( array( 'taxonomy-book_type', 'taxonomy', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-books', false, 'taxonomy-books' );
+ $hierarchy = get_template_hierarchy( 'taxonomy-books', false, 'taxonomy-books' );
$this->assertEquals( array( 'taxonomy-books', 'taxonomy', 'archive', 'index' ), $hierarchy );
// Single word category.
- $hierarchy = gutenberg_get_template_hierarchy( 'category-fruits', false );
+ $hierarchy = get_template_hierarchy( 'category-fruits', false );
$this->assertEquals(
array(
'category-fruits',
@@ -79,7 +79,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'category-fruits', false, 'category' );
+ $hierarchy = get_template_hierarchy( 'category-fruits', false, 'category' );
$this->assertEquals(
array(
'category-fruits',
@@ -90,7 +90,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Multi word category.
- $hierarchy = gutenberg_get_template_hierarchy( 'category-fruits-yellow', false );
+ $hierarchy = get_template_hierarchy( 'category-fruits-yellow', false );
$this->assertEquals(
array(
'category-fruits-yellow',
@@ -101,7 +101,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'category-fruits-yellow', false, 'category' );
+ $hierarchy = get_template_hierarchy( 'category-fruits-yellow', false, 'category' );
$this->assertEquals(
array(
'category-fruits-yellow',
@@ -112,7 +112,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Single word taxonomy.
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-books-action', false, 'taxonomy-books' );
+ $hierarchy = get_template_hierarchy( 'taxonomy-books-action', false, 'taxonomy-books' );
$this->assertEquals(
array(
'taxonomy-books-action',
@@ -124,10 +124,10 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-book_type-adventure', false );
+ $hierarchy = get_template_hierarchy( 'taxonomy-book_type-adventure', false );
$this->assertEquals( array( 'taxonomy-book_type-adventure', 'taxonomy-book_type', 'taxonomy', 'archive', 'index' ), $hierarchy );
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-books-action-adventure', false, 'taxonomy-books' );
+ $hierarchy = get_template_hierarchy( 'taxonomy-books-action-adventure', false, 'taxonomy-books' );
$this->assertEquals(
array(
'taxonomy-books-action-adventure',
@@ -139,7 +139,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Multi word taxonomy/terms.
- $hierarchy = gutenberg_get_template_hierarchy( 'taxonomy-greek-books-action-adventure', false, 'taxonomy-greek-books' );
+ $hierarchy = get_template_hierarchy( 'taxonomy-greek-books-action-adventure', false, 'taxonomy-greek-books' );
$this->assertEquals(
array(
'taxonomy-greek-books-action-adventure',
@@ -151,7 +151,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Post types.
- $hierarchy = gutenberg_get_template_hierarchy( 'single-book', false, 'single-book' );
+ $hierarchy = get_template_hierarchy( 'single-book', false, 'single-book' );
$this->assertEquals(
array(
'single-book',
@@ -161,7 +161,7 @@ public function test_get_template_hierarchy() {
),
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'single-art-project', false, 'single-art-project' );
+ $hierarchy = get_template_hierarchy( 'single-art-project', false, 'single-art-project' );
$this->assertEquals(
array(
'single-art-project',
@@ -171,7 +171,7 @@ public function test_get_template_hierarchy() {
),
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'single-art-project-imagine', false, 'single-art-project' );
+ $hierarchy = get_template_hierarchy( 'single-art-project-imagine', false, 'single-art-project' );
$this->assertEquals(
array(
'single-art-project-imagine',
@@ -183,7 +183,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'single-custom_book', false );
+ $hierarchy = get_template_hierarchy( 'single-custom_book', false );
$this->assertEquals(
array(
'single-custom_book',
@@ -194,7 +194,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'single-custom_book-book-1', false );
+ $hierarchy = get_template_hierarchy( 'single-custom_book-book-1', false );
$this->assertEquals(
array(
'single-custom_book-book-1',
@@ -206,7 +206,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'page-hi', false, 'page' );
+ $hierarchy = get_template_hierarchy( 'page-hi', false, 'page' );
$this->assertEquals(
array(
'page-hi',
@@ -216,7 +216,7 @@ public function test_get_template_hierarchy() {
),
$hierarchy
);
- $hierarchy = gutenberg_get_template_hierarchy( 'page-hi', false );
+ $hierarchy = get_template_hierarchy( 'page-hi', false );
$this->assertEquals(
array(
'page-hi',
@@ -227,7 +227,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Authors.
- $hierarchy = gutenberg_get_template_hierarchy( 'author-rigas', false, 'author' );
+ $hierarchy = get_template_hierarchy( 'author-rigas', false, 'author' );
$this->assertEquals(
array(
'author-rigas',
@@ -238,7 +238,7 @@ public function test_get_template_hierarchy() {
$hierarchy
);
// Archive post types.
- $hierarchy = gutenberg_get_template_hierarchy( 'archive-book', false );
+ $hierarchy = get_template_hierarchy( 'archive-book', false );
$this->assertEquals(
array(
'archive-book',
diff --git a/phpunit/class-wp-rest-pattern-directory-controller-test.php b/phpunit/class-wp-rest-pattern-directory-controller-test.php
deleted file mode 100644
index fd8e5246116f98..00000000000000
--- a/phpunit/class-wp-rest-pattern-directory-controller-test.php
+++ /dev/null
@@ -1,289 +0,0 @@
-user->create(
- array(
- 'role' => 'contributor',
- )
- );
-
- self::$http_request_urls = array();
-
- static::$controller = new Gutenberg_REST_Pattern_Directory_Controller_6_2();
- }
-
- public static function wpTearDownAfterClass() {
- self::delete_user( self::$contributor_id );
- }
-
- /**
- * Clear the captured request URLs after each test.
- */
- public function tear_down() {
- self::$http_request_urls = array();
- parent::tear_down();
- }
-
- /**
- * @covers WP_REST_Pattern_Directory_Controller::register_routes
- *
- * @since 5.8.0
- * @since 6.2.0 Added pattern directory categories endpoint.
- */
- public function test_register_routes() {
- $routes = rest_get_server()->get_routes();
-
- $this->assertArrayHasKey( '/wp/v2/pattern-directory/patterns', $routes );
- }
-
- /**
- * Tests if the provided query args are passed through to the wp.org API.
- *
- * @dataProvider data_get_items_query_args
- *
- * @covers WP_REST_Pattern_Directory_Controller::get_items
- *
- * @since 6.2.0
- *
- * @param string $param Query parameter name (ex, page).
- * @param mixed $value Query value to test.
- * @param bool $is_error Whether this value should error or not.
- * @param mixed $expected Expected value (or expected error code).
- */
- public function test_get_items_query_args( $param, $value, $is_error, $expected ) {
- wp_set_current_user( self::$contributor_id );
- self::capture_http_urls();
-
- $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
- if ( $value ) {
- $request->set_query_params( array( $param => $value ) );
- }
-
- $response = rest_do_request( $request );
- $data = $response->get_data();
- if ( $is_error ) {
- $this->assertSame( $expected, $data['code'] );
- $this->assertStringContainsString( $param, $data['message'] );
- } else {
- $this->assertCount( 1, self::$http_request_urls );
- $this->assertStringContainsString( $param . '=' . $expected, self::$http_request_urls[0] );
- }
- }
-
- /**
- * @since 6.2.0
- */
- public function data_get_items_query_args() {
- return array(
- 'per_page default' => array( 'per_page', false, false, 100 ),
- 'per_page custom-1' => array( 'per_page', 5, false, 5 ),
- 'per_page custom-2' => array( 'per_page', 50, false, 50 ),
- 'per_page invalid-1' => array( 'per_page', 200, true, 'rest_invalid_param' ),
- 'per_page invalid-2' => array( 'per_page', 'abc', true, 'rest_invalid_param' ),
-
- 'page default' => array( 'page', false, false, 1 ),
- 'page custom' => array( 'page', 5, false, 5 ),
- 'page invalid' => array( 'page', 'abc', true, 'rest_invalid_param' ),
-
- 'offset custom' => array( 'offset', 5, false, 5 ),
- 'offset invalid-1' => array( 'offset', 'abc', true, 'rest_invalid_param' ),
-
- 'order default' => array( 'order', false, false, 'desc' ),
- 'order custom' => array( 'order', 'asc', false, 'asc' ),
- 'order invalid-1' => array( 'order', 10, true, 'rest_invalid_param' ),
- 'order invalid-2' => array( 'order', 'fake', true, 'rest_invalid_param' ),
-
- 'orderby default' => array( 'orderby', false, false, 'date' ),
- 'orderby custom-1' => array( 'orderby', 'title', false, 'title' ),
- 'orderby custom-2' => array( 'orderby', 'date', false, 'date' ),
- 'orderby custom-3' => array( 'orderby', 'favorite_count', false, 'favorite_count' ),
- 'orderby invalid-1' => array( 'orderby', 10, true, 'rest_invalid_param' ),
- 'orderby invalid-2' => array( 'orderby', 'fake', true, 'rest_invalid_param' ),
- );
- }
-
- /**
- * Attach a filter to capture requested wp.org URL.
- *
- * @since 6.2.0
- */
- private static function capture_http_urls() {
- add_filter(
- 'pre_http_request',
- static function ( $preempt, $args, $url ) {
- if ( 'api.wordpress.org' !== wp_parse_url( $url, PHP_URL_HOST ) ) {
- return $preempt;
- }
-
- self::$http_request_urls[] = $url;
-
- // Return a response to prevent external API request.
- $response = array(
- 'headers' => array(),
- 'response' => array(
- 'code' => 200,
- 'message' => 'OK',
- ),
- 'body' => '[]',
- 'cookies' => array(),
- 'filename' => null,
- );
-
- return $response;
- },
- 10,
- 3
- );
- }
-
- /**
- * @covers WP_REST_Pattern_Directory_Controller::prepare_item_for_response
- *
- * @since 5.8.0
- * @since 6.2.0 Added `block_types` property.
- */
- public function test_prepare_item() {
- $raw_patterns = json_decode( self::get_raw_response( 'browse-all' ) );
- $raw_patterns[0]->extra_field = 'this should be removed';
-
- $prepared_pattern = static::$controller->prepare_response_for_collection(
- static::$controller->prepare_item_for_response( $raw_patterns[0], new WP_REST_Request() )
- );
-
- $this->assertPatternMatchesSchema( $prepared_pattern );
- $this->assertArrayNotHasKey( 'extra_field', $prepared_pattern );
- }
-
- /**
- * Asserts that the pattern matches the expected response schema.
- *
- * @param WP_REST_Response[] $pattern An individual pattern from the REST API response.
- */
- public function assertPatternMatchesSchema( $pattern ) {
- $schema = static::$controller->get_item_schema();
- $pattern_id = isset( $pattern->id ) ? $pattern->id : '{pattern ID is missing}';
-
- $this->assertTrue(
- rest_validate_value_from_schema( $pattern, $schema ),
- "Pattern ID `$pattern_id` doesn't match the response schema."
- );
-
- $this->assertSame(
- array_keys( $schema['properties'] ),
- array_keys( $pattern ),
- "Pattern ID `$pattern_id` doesn't contain all of the fields expected from the schema."
- );
- }
-
- /**
- * Get a mocked raw response from api.wordpress.org.
- *
- * @return string
- */
- private static function get_raw_response( $action ) {
- $fixtures_dir = __DIR__ . '/fixtures/pattern-directory';
-
- switch ( $action ) {
- default:
- case 'browse-all':
- // Response from https://api.wordpress.org/patterns/1.0/.
- $response = file_get_contents( $fixtures_dir . '/browse-all.json' );
- break;
- }
-
- return $response;
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_context_param() {
- // Covered by the core test.
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_get_items() {
- // Covered by the core test.
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_get_item() {
- // Controller does not implement get_item().
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_create_item() {
- // Controller does not implement create_item().
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_update_item() {
- // Controller does not implement update_item().
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_delete_item() {
- // Controller does not implement delete_item().
- }
-
- /**
- * @doesNotPerformAssertions
- */
- public function test_get_item_schema() {
- // The controller's schema is hardcoded, so tests would not be meaningful.
- }
-}
diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js
index 88acc23da59413..4104a791f29c6b 100644
--- a/tools/webpack/blocks.js
+++ b/tools/webpack/blocks.js
@@ -28,7 +28,6 @@ const blockViewRegex = new RegExp(
* the block will still call the core function when updates are back ported.
*/
const prefixFunctions = [
- 'build_query_vars_from_query_block',
'wp_apply_colors_support',
'wp_enqueue_block_support_styles',
'wp_get_typography_font_size_value',