- ',
- ),
- // Initial block pattern to be used with block transformations with patterns.
- 'social-links-shared-background-color' => array(
- 'title' => _x( 'Social links with a shared background color', 'Block pattern title', 'gutenberg' ),
- 'categories' => array( 'buttons' ),
- 'blockTypes' => array( 'core/social-links' ),
- 'viewportWidth' => 500,
- 'content' => '
-
-
-
- ',
- ),
- );
-
- foreach ( $patterns as $name => $pattern ) {
- $pattern_name = 'core/' . $name;
- if ( ! WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_name ) ) {
- register_block_pattern( $pattern_name, $pattern );
- }
- }
-}
-
-/**
- * Deactivate the legacy patterns bundled with WordPress.
- */
-function gutenberg_remove_core_patterns() {
- $core_block_patterns = array(
- 'text-two-columns',
- 'two-buttons',
- 'two-images',
- 'text-two-columns-with-images',
- 'text-three-columns-buttons',
- 'large-header',
- 'large-header-button',
- 'three-buttons',
- 'heading-paragraph',
- 'quote',
- 'query-standard-posts',
- 'query-medium-posts',
- 'query-small-posts',
- 'query-grid-posts',
- 'query-large-title-posts',
- 'query-offset-posts',
- 'social-links-shared-background-color',
- );
-
- foreach ( $core_block_patterns as $core_block_pattern ) {
- $name = 'core/' . $core_block_pattern;
- if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $name ) ) {
- unregister_block_pattern( $name );
- }
- }
-}
-
-add_action(
- 'init',
- function() {
- if ( ! get_theme_support( 'core-block-patterns' ) || ! function_exists( 'unregister_block_pattern' ) ) {
- return;
- }
- gutenberg_remove_core_patterns();
- gutenberg_register_gutenberg_patterns();
- }
-);
diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php
deleted file mode 100644
index c42ec73152219a..00000000000000
--- a/lib/compat/wordpress-6.0/block-patterns.php
+++ /dev/null
@@ -1,44 +0,0 @@
-get_patterns();
- if ( empty( $pattern_settings ) ) {
- return;
- }
-
- $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
- $request['slug'] = $pattern_settings;
- $response = rest_do_request( $request );
- if ( $response->is_error() ) {
- return;
- }
- $patterns = $response->get_data();
- $patterns_registry = WP_Block_Patterns_Registry::get_instance();
- foreach ( $patterns as $pattern ) {
- $pattern_name = sanitize_title( $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" );
- if ( ! $is_registered ) {
- register_block_pattern( $pattern_name, (array) $pattern );
- }
- }
- }
-}
diff --git a/lib/compat/wordpress-6.0/block-template-utils.php b/lib/compat/wordpress-6.0/block-template-utils.php
deleted file mode 100644
index 1fcd3dae83e594..00000000000000
--- a/lib/compat/wordpress-6.0/block-template-utils.php
+++ /dev/null
@@ -1,132 +0,0 @@
-get( 'TextDomain' );
- $filename = get_temp_dir() . $theme_name . $obscura . '.zip';
-
- $zip = new ZipArchive();
- if ( true !== $zip->open( $filename, ZipArchive::CREATE | ZipArchive::OVERWRITE ) ) {
- return new WP_Error( 'unable_to_create_zip', __( 'Unable to open export file (archive) for writing.', 'gutenberg' ) );
- }
-
- $zip->addEmptyDir( 'templates' );
- $zip->addEmptyDir( 'parts' );
-
- // Get path of the theme.
- $theme_path = wp_normalize_path( get_stylesheet_directory() );
-
- // Create recursive directory iterator.
- $theme_files = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator( $theme_path ),
- RecursiveIteratorIterator::LEAVES_ONLY
- );
-
- // Make a copy of the current theme.
- foreach ( $theme_files as $file ) {
- // Skip directories as they are added automatically.
- if ( ! $file->isDir() ) {
- // Get real and relative path for current file.
- $file_path = wp_normalize_path( $file );
- $relative_path = substr( $file_path, strlen( $theme_path ) + 1 );
-
- if ( ! gutenberg_is_theme_directory_ignored( $relative_path ) ) {
- $zip->addFile( $file_path, $relative_path );
- }
- }
- }
-
- // Load templates into the zip file.
- $templates = gutenberg_get_block_templates();
- foreach ( $templates as $template ) {
- $template->content = _remove_theme_attribute_in_block_template_content( $template->content );
-
- $zip->addFromString(
- 'templates/' . $template->slug . '.html',
- $template->content
- );
- }
-
- // Load template parts into the zip file.
- $template_parts = gutenberg_get_block_templates( array(), 'wp_template_part' );
- foreach ( $template_parts as $template_part ) {
- $zip->addFromString(
- 'parts/' . $template_part->slug . '.html',
- $template_part->content
- );
- }
-
- // Load theme.json into the zip file.
- $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) );
- // Merge with user data.
- $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() );
-
- $theme_json_raw = $tree->get_data();
- // If a version is defined, add a schema.
- if ( $theme_json_raw['version'] ) {
- global $wp_version;
- $theme_json_version = 'wp/' . substr( $wp_version, 0, 3 );
- if ( defined( 'IS_GUTENBERG_PLUGIN' ) ) {
- $theme_json_version = 'trunk';
- }
- $schema = array( '$schema' => 'https://schemas.wp.org/' . $theme_json_version . '/theme.json' );
- $theme_json_raw = array_merge( $schema, $theme_json_raw );
- }
-
- // Convert to a string.
- $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
-
- // Replace 4 spaces with a tab.
- $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded );
-
- // Add the theme.json file to the zip.
- $zip->addFromString(
- 'theme.json',
- $theme_json_tabbed
- );
-
- // Save changes to the zip file.
- $zip->close();
-
- return $filename;
-}
diff --git a/lib/compat/wordpress-6.0/blocks.php b/lib/compat/wordpress-6.0/blocks.php
deleted file mode 100644
index ed83f62f76d420..00000000000000
--- a/lib/compat/wordpress-6.0/blocks.php
+++ /dev/null
@@ -1,194 +0,0 @@
- 'comment_date_gmt',
- 'order' => 'ASC',
- 'status' => 'approve',
- 'no_found_rows' => false,
- );
-
- if ( is_user_logged_in() ) {
- $comment_args['include_unapproved'] = array( get_current_user_id() );
- } else {
- $unapproved_email = wp_get_unapproved_comment_author_email();
-
- if ( $unapproved_email ) {
- $comment_args['include_unapproved'] = array( $unapproved_email );
- }
- }
-
- if ( ! empty( $block->context['postId'] ) ) {
- $comment_args['post_id'] = (int) $block->context['postId'];
- }
-
- if ( get_option( 'thread_comments' ) ) {
- $comment_args['hierarchical'] = 'threaded';
- } else {
- $comment_args['hierarchical'] = false;
- }
-
- if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) {
- $per_page = get_option( 'comments_per_page' );
- $default_page = get_option( 'default_comments_page' );
- if ( $per_page > 0 ) {
- $comment_args['number'] = $per_page;
-
- $page = (int) get_query_var( 'cpage' );
- if ( $page ) {
- $comment_args['paged'] = $page;
- } elseif ( 'oldest' === $default_page ) {
- $comment_args['paged'] = 1;
- } elseif ( 'newest' === $default_page ) {
- $max_num_pages = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages;
- if ( 0 !== $max_num_pages ) {
- $comment_args['paged'] = $max_num_pages;
- }
- }
- // Set the `cpage` query var to ensure the previous and next pagination links are correct
- // when inheriting the Discussion Settings.
- if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) {
- set_query_var( 'cpage', $comment_args['paged'] );
- }
- }
- }
-
- return $comment_args;
- }
-}
-
-if ( ! function_exists( 'get_comments_pagination_arrow' ) ) {
- /**
- * Helper function that returns the proper pagination arrow html for
- * `CommentsPaginationNext` and `CommentsPaginationPrevious` blocks based
- * on the provided `paginationArrow` from `CommentsPagination` context.
- *
- * It's used in CommentsPaginationNext and CommentsPaginationPrevious blocks.
- *
- * @since 6.0.0
- *
- * @param WP_Block $block Block instance.
- * @param string $pagination_type Type of the arrow we will be rendering. Default 'next'. Accepts 'next' or 'previous'.
- *
- * @return string|null Returns the constructed WP_Query arguments.
- */
- function get_comments_pagination_arrow( $block, $pagination_type = 'next' ) {
- $arrow_map = array(
- 'none' => '',
- 'arrow' => array(
- 'next' => '→',
- 'previous' => '←',
- ),
- 'chevron' => array(
- 'next' => '»',
- 'previous' => '«',
- ),
- );
- if ( ! empty( $block->context['comments/paginationArrow'] ) && ! empty( $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ] ) ) {
- $arrow_attribute = $block->context['comments/paginationArrow'];
- $arrow = $arrow_map[ $block->context['comments/paginationArrow'] ][ $pagination_type ];
- $arrow_classes = "wp-block-comments-pagination-$pagination_type-arrow is-arrow-$arrow_attribute";
- return "$arrow";
- }
- return null;
- }
-}
-
-/**
- * Workaround for getting discussion settings as block editor settings
- * so any user can access to them without needing to be an admin.
- *
- * @param array $settings Default editor settings.
- *
- * @return array Filtered editor settings.
- */
-function gutenberg_extend_block_editor_settings_with_discussion_settings( $settings ) {
-
- $settings['__experimentalDiscussionSettings'] = array(
- 'commentOrder' => get_option( 'comment_order' ),
- 'commentsPerPage' => get_option( 'comments_per_page' ),
- 'defaultCommentsPage' => get_option( 'default_comments_page' ),
- 'pageComments' => get_option( 'page_comments' ),
- 'threadComments' => get_option( 'thread_comments' ),
- 'threadCommentsDepth' => get_option( 'thread_comments_depth' ),
- 'defaultCommentStatus' => get_option( 'default_comment_status' ),
- 'avatarURL' => get_avatar_url(
- '',
- array(
- 'size' => 96,
- 'force_default' => true,
- 'default' => get_option( 'avatar_default' ),
- )
- ),
- );
-
- return $settings;
-}
-add_filter( 'block_editor_settings_all', 'gutenberg_extend_block_editor_settings_with_discussion_settings' );
-
-/**
- * Mark the `children` attr of comments as embeddable so they can be included in
- * REST API responses without additional requests.
- *
- * @return void
- */
-function gutenberg_rest_comment_set_children_as_embeddable() {
- add_filter(
- 'rest_prepare_comment',
- function ( $response ) {
- $links = $response->get_links();
- if ( isset( $links['children'] ) ) {
- $href = $links['children'][0]['href'];
- $response->remove_link( 'children', $href );
- $response->add_link( 'children', $href, array( 'embeddable' => true ) );
- }
- return $response;
- }
- );
-}
-add_action( 'rest_api_init', 'gutenberg_rest_comment_set_children_as_embeddable' );
-
-/**
- * Registers the lock block attribute for block types.
- *
- * Once 6.0 is the minimum supported WordPress version for the Gutenberg
- * plugin, this shim can be removed
- *
- * Doesn't need to be backported into Core.
- *
- * @param array $args Array of arguments for registering a block type.
- * @return array $args
- */
-function gutenberg_register_lock_attribute( $args ) {
- // Setup attributes if needed.
- if ( ! isset( $args['attributes'] ) || ! is_array( $args['attributes'] ) ) {
- $args['attributes'] = array();
- }
-
- if ( ! array_key_exists( 'lock', $args['attributes'] ) ) {
- $args['attributes']['lock'] = array(
- 'type' => 'object',
- );
- }
-
- return $args;
-}
-add_filter( 'register_block_type_args', 'gutenberg_register_lock_attribute' );
diff --git a/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php b/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php
deleted file mode 100644
index e4cffb2bff268a..00000000000000
--- a/lib/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php
+++ /dev/null
@@ -1,89 +0,0 @@
-namespace = 'wp-block-editor/v1';
- $this->rest_base = 'export';
- }
-
- /**
- * Registers the necessary REST API routes.
- */
- public function register_routes() {
- register_rest_route(
- $this->namespace,
- '/' . $this->rest_base,
- array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'export' ),
- 'permission_callback' => array( $this, 'permissions_check' ),
- ),
- ),
- true // Override core route if already exists (WP 5.9).
- );
- }
-
- /**
- * Checks whether a given request has permission to export.
- *
- * @return WP_Error|bool True if the request has access, or WP_Error object.
- */
- public function permissions_check() {
- if ( current_user_can( 'edit_theme_options' ) ) {
- return true;
- }
-
- return new WP_Error(
- 'rest_cannot_export_templates',
- __( 'Sorry, you are not allowed to export templates and template parts.', 'gutenberg' ),
- array( 'status' => rest_authorization_required_code() )
- );
- }
-
- /**
- * Output a ZIP file with an export of the current templates
- * template parts, theme.json and index.php from the site editor,
- * and close the connection.
- *
- * @return WP_Error|void
- */
- public function export() {
- // Generate the export file.
- $filename = gutenberg_generate_block_templates_export_file();
-
- if ( is_wp_error( $filename ) ) {
- $filename->add_data( array( 'status' => 500 ) );
-
- return $filename;
- }
-
- $theme_name = basename( get_stylesheet() );
-
- header( 'Content-Type: application/zip' );
- header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' );
- header( 'Content-Length: ' . filesize( $filename ) );
- flush();
- readfile( $filename );
- unlink( $filename );
- exit;
- }
-}
diff --git a/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php b/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php
deleted file mode 100644
index 331e6015edb0cd..00000000000000
--- a/lib/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php
+++ /dev/null
@@ -1,48 +0,0 @@
-= 6.0.
- *
- * @param array $query_args Query arguments to generate a transient key from.
- * @return string Transient key.
- */
- protected function get_transient_key( $query_args ) {
- if ( method_exists( get_parent_class( $this ), __FUNCTION__ ) ) {
- return parent::get_transient_key( $query_args );
- }
-
- if ( isset( $query_args['slug'] ) ) {
- // This is an additional precaution because the "sort" function expects an array.
- $query_args['slug'] = wp_parse_list( $query_args['slug'] );
-
- // Empty arrays should not affect the transient key.
- if ( empty( $query_args['slug'] ) ) {
- unset( $query_args['slug'] );
- } else {
- // Sort the array so that the transient key doesn't depend on the order of slugs.
- sort( $query_args['slug'] );
- }
- }
-
- return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
- }
-}
diff --git a/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php
deleted file mode 100644
index 0f60781dae106c..00000000000000
--- a/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php
+++ /dev/null
@@ -1,152 +0,0 @@
-namespace = 'wp/v2';
- $this->rest_base = 'block-patterns/categories';
- }
-
- /**
- * Registers the routes for the objects of the controller.
- *
- * @see register_rest_route()
- *
- * @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' ),
- )
- );
- }
-
- /**
- * Checks whether a given request has permission to read block patterns.
- *
- * @since 6.0.0
- *
- * @param WP_REST_Request $request Full details about the request.
- *
- * @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
- */
- public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- if ( current_user_can( 'edit_posts' ) ) {
- return true;
- }
-
- foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
- if ( current_user_can( $post_type->cap->edit_posts ) ) {
- return true;
- }
- }
-
- return new WP_Error(
- 'rest_cannot_view',
- __( 'Sorry, you are not allowed to view the registered block pattern categories.', 'gutenberg' ),
- array( 'status' => rest_authorization_required_code() )
- );
- }
-
- /**
- * Retrieves all block pattern categories.
- *
- * @since 6.0.0
- *
- * @param WP_REST_Request $request Full details about the request.
- *
- * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
- */
- public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- $response = array();
- $categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
- foreach ( $categories as $category ) {
- $prepared_category = $this->prepare_item_for_response( $category, $request );
- $response[] = $this->prepare_response_for_collection( $prepared_category );
- }
- return rest_ensure_response( $response );
- }
-
- /**
- * Prepare a raw block pattern category before it gets output in a REST API response.
- *
- * @since 6.0.0
- *
- * @param object $item Raw category as registered, before any changes.
- * @param WP_REST_Request $request Request object.
- * @return WP_REST_Response
- */
- public function prepare_item_for_response( $item, $request ) {
- $fields = $this->get_fields_for_response( $request );
- $keys = array( 'name', 'label' );
- $data = array();
- foreach ( $keys as $key ) {
- if ( 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() {
- $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' ),
- ),
- ),
- );
-
- return $this->add_additional_fields_schema( $schema );
- }
-}
diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php
deleted file mode 100644
index 07820ccbd1dbe2..00000000000000
--- a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php
+++ /dev/null
@@ -1,680 +0,0 @@
- array( 'color', 'gradient' ),
- 'background-color' => array( 'color', 'background' ),
- 'border-radius' => array( 'border', 'radius' ),
- 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
- 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
- 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ),
- 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ),
- 'border-color' => array( 'border', 'color' ),
- 'border-width' => array( 'border', 'width' ),
- 'border-style' => array( 'border', 'style' ),
- 'border-top-color' => array( 'border', 'top', 'color' ),
- 'border-top-width' => array( 'border', 'top', 'width' ),
- 'border-top-style' => array( 'border', 'top', 'style' ),
- 'border-right-color' => array( 'border', 'right', 'color' ),
- 'border-right-width' => array( 'border', 'right', 'width' ),
- 'border-right-style' => array( 'border', 'right', 'style' ),
- 'border-bottom-color' => array( 'border', 'bottom', 'color' ),
- 'border-bottom-width' => array( 'border', 'bottom', 'width' ),
- 'border-bottom-style' => array( 'border', 'bottom', 'style' ),
- 'border-left-color' => array( 'border', 'left', 'color' ),
- 'border-left-width' => array( 'border', 'left', 'width' ),
- 'border-left-style' => array( 'border', 'left', 'style' ),
- 'color' => array( 'color', 'text' ),
- 'font-family' => array( 'typography', 'fontFamily' ),
- 'font-size' => array( 'typography', 'fontSize' ),
- 'font-style' => array( 'typography', 'fontStyle' ),
- 'font-weight' => array( 'typography', 'fontWeight' ),
- 'letter-spacing' => array( 'typography', 'letterSpacing' ),
- 'line-height' => array( 'typography', 'lineHeight' ),
- 'margin' => array( 'spacing', 'margin' ),
- 'margin-top' => array( 'spacing', 'margin', 'top' ),
- 'margin-right' => array( 'spacing', 'margin', 'right' ),
- 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ),
- 'margin-left' => array( 'spacing', 'margin', 'left' ),
- 'padding' => array( 'spacing', 'padding' ),
- 'padding-top' => array( 'spacing', 'padding', 'top' ),
- 'padding-right' => array( 'spacing', 'padding', 'right' ),
- 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
- 'padding-left' => array( 'spacing', 'padding', 'left' ),
- '--wp--style--block-gap' => array( 'spacing', 'blockGap' ),
- 'text-decoration' => array( 'typography', 'textDecoration' ),
- 'text-transform' => array( 'typography', 'textTransform' ),
- 'filter' => array( 'filter', 'duotone' ),
- );
-
- /**
- * Presets are a set of values that serve
- * to bootstrap some styles: colors, font sizes, etc.
- *
- * They are a unkeyed array of values such as:
- *
- * ```php
- * array(
- * array(
- * 'slug' => 'unique-name-within-the-set',
- * 'name' => 'Name for the UI',
- * => 'value'
- * ),
- * )
- * ```
- *
- * This contains the necessary metadata to process them:
- *
- * - path => Where to find the preset within the settings section.
- * - prevent_override => Disables override of default presets by theme presets.
- * The relationship between whether to override the defaults
- * and whether the defaults are enabled is inverse:
- * - If defaults are enabled => theme presets should not be overriden
- * - If defaults are disabled => theme presets should be overriden
- * For example, a theme sets defaultPalette to false,
- * making the default palette hidden from the user.
- * In that case, we want all the theme presets to be present,
- * so they should override the defaults by setting this false.
- * - use_default_names => whether to use the default names
- * - value_key => the key that represents the value
- * - value_func => optionally, instead of value_key, a function to generate
- * the value that takes a preset as an argument
- * (either value_key or value_func should be present)
- * - css_vars => template string to use in generating the CSS Custom Property.
- * Example output: "--wp--preset--duotone--blue: " will generate as many CSS Custom Properties as presets defined
- * substituting the $slug for the slug's value for each preset value.
- * - classes => array containing a structure with the classes to
- * generate for the presets, where for each array item
- * the key is the class name and the value the property name.
- * The "$slug" substring will be replaced by the slug of each preset.
- * For example:
- * 'classes' => array(
- * '.has-$slug-color' => 'color',
- * '.has-$slug-background-color' => 'background-color',
- * '.has-$slug-border-color' => 'border-color',
- * )
- * - properties => array of CSS properties to be used by kses to
- * validate the content of each preset
- * by means of the remove_insecure_properties method.
- */
- const PRESETS_METADATA = array(
- array(
- 'path' => array( 'color', 'palette' ),
- 'prevent_override' => array( 'color', 'defaultPalette' ),
- 'use_default_names' => false,
- 'value_key' => 'color',
- 'css_vars' => '--wp--preset--color--$slug',
- 'classes' => array(
- '.has-$slug-color' => 'color',
- '.has-$slug-background-color' => 'background-color',
- '.has-$slug-border-color' => 'border-color',
- ),
- 'properties' => array( 'color', 'background-color', 'border-color' ),
- ),
- array(
- 'path' => array( 'color', 'gradients' ),
- 'prevent_override' => array( 'color', 'defaultGradients' ),
- 'use_default_names' => false,
- 'value_key' => 'gradient',
- 'css_vars' => '--wp--preset--gradient--$slug',
- 'classes' => array( '.has-$slug-gradient-background' => 'background' ),
- 'properties' => array( 'background' ),
- ),
- array(
- 'path' => array( 'color', 'duotone' ),
- 'prevent_override' => array( 'color', 'defaultDuotone' ),
- 'use_default_names' => false,
- 'value_func' => 'gutenberg_get_duotone_filter_property',
- 'css_vars' => '--wp--preset--duotone--$slug',
- 'classes' => array(),
- 'properties' => array( 'filter' ),
- ),
- array(
- 'path' => array( 'typography', 'fontSizes' ),
- 'prevent_override' => false,
- 'use_default_names' => true,
- 'value_key' => 'size',
- 'css_vars' => '--wp--preset--font-size--$slug',
- 'classes' => array( '.has-$slug-font-size' => 'font-size' ),
- 'properties' => array( 'font-size' ),
- ),
- array(
- 'path' => array( 'typography', 'fontFamilies' ),
- 'prevent_override' => false,
- 'use_default_names' => false,
- 'value_key' => 'fontFamily',
- 'css_vars' => '--wp--preset--font-family--$slug',
- 'classes' => array( '.has-$slug-font-family' => 'font-family' ),
- 'properties' => array( 'font-family' ),
- ),
- );
-
- /**
- * The top-level keys a theme.json can have.
- *
- * @var string[]
- */
- const VALID_TOP_LEVEL_KEYS = array(
- 'customTemplates',
- 'patterns',
- 'settings',
- 'styles',
- 'templateParts',
- 'version',
- 'title',
- );
-
- const APPEARANCE_TOOLS_OPT_INS = array(
- array( 'border', 'color' ),
- array( 'border', 'radius' ),
- array( 'border', 'style' ),
- array( 'border', 'width' ),
- array( 'color', 'link' ),
- array( 'spacing', 'blockGap' ),
- array( 'spacing', 'margin' ),
- array( 'spacing', 'padding' ),
- array( 'typography', 'lineHeight' ),
- );
-
- /**
- * The valid properties under the settings key.
- *
- * @var array
- */
- const VALID_SETTINGS = array(
- 'appearanceTools' => null,
- 'border' => array(
- 'color' => null,
- 'radius' => null,
- 'style' => null,
- 'width' => null,
- ),
- 'color' => array(
- 'background' => null,
- 'custom' => null,
- 'customDuotone' => null,
- 'customGradient' => null,
- 'defaultDuotone' => null,
- 'defaultGradients' => null,
- 'defaultPalette' => null,
- 'duotone' => null,
- 'gradients' => null,
- 'link' => null,
- 'palette' => null,
- 'text' => null,
- ),
- 'custom' => null,
- 'layout' => array(
- 'contentSize' => null,
- 'wideSize' => null,
- ),
- 'spacing' => array(
- 'blockGap' => null,
- 'margin' => null,
- 'padding' => null,
- 'units' => null,
- ),
- 'typography' => array(
- 'customFontSize' => null,
- 'dropCap' => null,
- 'fontFamilies' => null,
- 'fontSizes' => null,
- 'fontStyle' => null,
- 'fontWeight' => null,
- 'letterSpacing' => null,
- 'lineHeight' => null,
- 'textDecoration' => null,
- 'textTransform' => null,
- ),
- );
-
- /**
- * The valid properties under the styles key.
- *
- * @var array
- */
- const VALID_STYLES = array(
- 'border' => array(
- 'color' => null,
- 'radius' => null,
- 'style' => null,
- 'width' => null,
- 'top' => null,
- 'right' => null,
- 'bottom' => null,
- 'left' => null,
- ),
- 'color' => array(
- 'background' => null,
- 'gradient' => null,
- 'text' => null,
- ),
- 'filter' => array(
- 'duotone' => null,
- ),
- 'spacing' => array(
- 'margin' => null,
- 'padding' => null,
- 'blockGap' => 'top',
- ),
- 'typography' => array(
- 'fontFamily' => null,
- 'fontSize' => null,
- 'fontStyle' => null,
- 'fontWeight' => null,
- 'letterSpacing' => null,
- 'lineHeight' => null,
- 'textDecoration' => null,
- 'textTransform' => null,
- ),
- );
-
- /**
- * Returns the current theme's wanted patterns(slugs) to be
- * registered from Pattern Directory.
- *
- * @return array
- */
- public function get_patterns() {
- if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) {
- return $this->theme_json['patterns'];
- }
- return array();
- }
-
- /**
- * Converts each style section into a list of rulesets
- * containing the block styles to be appended to the stylesheet.
- *
- * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax
- *
- * For each section this creates a new ruleset such as:
- *
- * block-selector {
- * style-property-one: value;
- * }
- *
- * @param array $style_nodes Nodes with styles.
- * @return string The new stylesheet.
- */
- protected function get_block_classes( $style_nodes ) {
- $block_rules = '';
-
- foreach ( $style_nodes as $metadata ) {
- if ( null === $metadata['selector'] ) {
- continue;
- }
-
- $node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
- $selector = $metadata['selector'];
- $settings = _wp_array_get( $this->theme_json, array( 'settings' ) );
- $declarations = static::compute_style_properties( $node, $settings );
-
- // 1. Separate the ones who use the general selector
- // and the ones who use the duotone selector.
- $declarations_duotone = array();
- foreach ( $declarations as $index => $declaration ) {
- if ( 'filter' === $declaration['name'] ) {
- unset( $declarations[ $index ] );
- $declarations_duotone[] = $declaration;
- }
- }
-
- /*
- * Reset default browser margin on the root body element.
- * This is set on the root selector **before** generating the ruleset
- * from the `theme.json`. This is to ensure that if the `theme.json` declares
- * `margin` in its `spacing` declaration for the `body` element then these
- * user-generated values take precedence in the CSS cascade.
- * @link https://github.com/WordPress/gutenberg/issues/36147.
- */
- if ( static::ROOT_BLOCK_SELECTOR === $selector ) {
- $block_rules .= 'body { margin: 0; }';
- }
-
- // 2. Generate the rules that use the general selector.
- $block_rules .= static::to_ruleset( $selector, $declarations );
-
- // 3. Generate the rules that use the duotone selector.
- if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) {
- $selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] );
- $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone );
- }
-
- if ( static::ROOT_BLOCK_SELECTOR === $selector ) {
- $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
- $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
- $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
-
- $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null;
- if ( $has_block_gap_support ) {
- $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }';
- $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }';
- }
- }
- }
-
- return $block_rules;
- }
-
- /**
- * Merge new incoming data.
- *
- * @param WP_Theme_JSON $incoming Data to merge.
- */
- public function merge( $incoming ) {
- $incoming_data = $incoming->get_raw_data();
- $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data );
-
- /*
- * The array_replace_recursive algorithm merges at the leaf level,
- * but we don't want leaf arrays to be merged, so we overwrite it.
- *
- * For leaf values that are sequential arrays it will use the numeric indexes for replacement.
- * We rather replace the existing with the incoming value, if it exists.
- * This is the case of spacing.units.
- *
- * For leaf values that are associative arrays it will merge them as expected.
- * This is also not the behavior we want for the current associative arrays (presets).
- * We rather replace the existing with the incoming value, if it exists.
- * This happens, for example, when we merge data from theme.json upon existing
- * theme supports or when we merge anything coming from the same source twice.
- * This is the case of color.palette, color.gradients, color.duotone,
- * typography.fontSizes, or typography.fontFamilies.
- *
- * Additionally, for some preset types, we also want to make sure the
- * values they introduce don't conflict with default values. We do so
- * by checking the incoming slugs for theme presets and compare them
- * with the equivalent default presets: if a slug is present as a default
- * we remove it from the theme presets.
- */
- $nodes = static::get_setting_nodes( $incoming_data );
- $slugs_global = static::get_default_slugs( $this->theme_json, array( 'settings' ) );
- foreach ( $nodes as $node ) {
- $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] );
- $slugs = array_merge_recursive( $slugs_global, $slugs_node );
-
- // Replace the spacing.units.
- $path = array_merge( $node['path'], array( 'spacing', 'units' ) );
- $content = _wp_array_get( $incoming_data, $path, null );
- if ( isset( $content ) ) {
- _wp_array_set( $this->theme_json, $path, $content );
- }
-
- // Replace the presets.
- foreach ( static::PRESETS_METADATA as $preset ) {
- $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true );
-
- foreach ( static::VALID_ORIGINS as $origin ) {
- $base_path = array_merge( $node['path'], $preset['path'] );
- $path = array_merge( $base_path, array( $origin ) );
- $content = _wp_array_get( $incoming_data, $path, null );
- if ( ! isset( $content ) ) {
- continue;
- }
-
- if ( 'theme' === $origin && $preset['use_default_names'] ) {
- foreach ( $content as &$item ) {
- if ( ! array_key_exists( 'name', $item ) ) {
- $name = static::get_name_from_defaults( $item['slug'], $base_path );
- if ( null !== $name ) {
- $item['name'] = $name;
- }
- }
- }
- }
-
- if (
- ( 'theme' !== $origin ) ||
- ( 'theme' === $origin && $override_preset )
- ) {
- _wp_array_set( $this->theme_json, $path, $content );
- } else {
- $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() );
- $content = static::filter_slugs( $content, $slugs_for_preset );
- _wp_array_set( $this->theme_json, $path, $content );
- }
- }
- }
- }
- }
-
- /**
- * Converts all filter (duotone) presets into SVGs.
- *
- * @param array $origins List of origins to process.
- *
- * @return string SVG filters.
- */
- public function get_svg_filters( $origins ) {
- $blocks_metadata = static::get_blocks_metadata();
- $setting_nodes = static::get_setting_nodes( $this->theme_json, $blocks_metadata );
-
- $filters = '';
- foreach ( $setting_nodes as $metadata ) {
- $node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
- if ( empty( $node['color']['duotone'] ) ) {
- continue;
- }
-
- $duotone_presets = $node['color']['duotone'];
-
- foreach ( $origins as $origin ) {
- if ( ! isset( $duotone_presets[ $origin ] ) ) {
- continue;
- }
- foreach ( $duotone_presets[ $origin ] as $duotone_preset ) {
- $filters .= gutenberg_get_duotone_filter_svg( $duotone_preset );
- }
- }
- }
-
- return $filters;
- }
-
- /**
- * For metadata values that can either be booleans or paths to booleans, gets the value.
- *
- * ```php
- * $data = array(
- * 'color' => array(
- * 'defaultPalette' => true
- * )
- * );
- *
- * static::get_metadata_boolean( $data, false );
- * // => false
- *
- * static::get_metadata_boolean( $data, array( 'color', 'defaultPalette' ) );
- * // => true
- * ```
- *
- * @param array $data The data to inspect.
- * @param bool|array $path Boolean or path to a boolean.
- * @param bool $default Default value if the referenced path is missing.
- * @return boolean
- */
- protected static function get_metadata_boolean( $data, $path, $default = false ) {
- if ( is_bool( $path ) ) {
- return $path;
- }
-
- if ( is_array( $path ) ) {
- $value = _wp_array_get( $data, $path );
- if ( null !== $value ) {
- return $value;
- }
- }
-
- return $default;
- }
-
- /**
- * Returns a valid theme.json as provided by a theme.
- *
- * Unlike get_raw_data() this returns the presets flattened, as provided by a theme.
- * This also uses appearanceTools instead of their opt-ins if all of them are true.
- *
- * @return string[]
- */
- public function get_data() {
- $output = $this->theme_json;
- $nodes = static::get_setting_nodes( $output );
-
- /**
- * Flatten the theme & custom origins into a single one.
- *
- * For example, the following:
- *
- * {
- * "settings": {
- * "color": {
- * "palette": {
- * "theme": [ {} ],
- * "custom": [ {} ]
- * }
- * }
- * }
- * }
- *
- * will be converted to:
- *
- * {
- * "settings": {
- * "color": {
- * "palette": [ {} ]
- * }
- * }
- * }
- */
- foreach ( $nodes as $node ) {
- foreach ( static::PRESETS_METADATA as $preset_metadata ) {
- $path = array_merge( $node['path'], $preset_metadata['path'] );
- $preset = _wp_array_get( $output, $path, null );
- if ( null === $preset ) {
- continue;
- }
-
- $items = array();
- if ( isset( $preset['theme'] ) ) {
- foreach ( $preset['theme'] as $item ) {
- $slug = $item['slug'];
- unset( $item['slug'] );
- $items[ $slug ] = $item;
- }
- }
- if ( isset( $preset['custom'] ) ) {
- foreach ( $preset['custom'] as $item ) {
- $slug = $item['slug'];
- unset( $item['slug'] );
- $items[ $slug ] = $item;
- }
- }
- $flattened_preset = array();
- foreach ( $items as $slug => $value ) {
- $flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value );
- }
- _wp_array_set( $output, $path, $flattened_preset );
- }
- }
-
- // If all of the static::APPEARANCE_TOOLS_OPT_INS are true,
- // this code unsets them and sets 'appearanceTools' instead.
- foreach ( $nodes as $node ) {
- $all_opt_ins_are_set = true;
- foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
- $full_path = array_merge( $node['path'], $opt_in_path );
- // Use "unset prop" as a marker instead of "null" because
- // "null" can be a valid value for some props (e.g. blockGap).
- $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
- if ( 'unset prop' === $opt_in_value ) {
- $all_opt_ins_are_set = false;
- break;
- }
- }
-
- if ( $all_opt_ins_are_set ) {
- _wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true );
- foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
- $full_path = array_merge( $node['path'], $opt_in_path );
- // Use "unset prop" as a marker instead of "null" because
- // "null" can be a valid value for some props (e.g. blockGap).
- $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
- if ( true !== $opt_in_value ) {
- continue;
- }
-
- // The following could be improved to be path independent.
- // At the moment it relies on a couple of assumptions:
- //
- // - all opt-ins having a path of size 2.
- // - there's two sources of settings: the top-level and the block-level.
- if (
- ( 1 === count( $node['path'] ) ) &&
- ( 'settings' === $node['path'][0] )
- ) {
- // Top-level settings.
- unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] );
- if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) {
- unset( $output['settings'][ $opt_in_path[0] ] );
- }
- } elseif (
- ( 3 === count( $node['path'] ) ) &&
- ( 'settings' === $node['path'][0] ) &&
- ( 'blocks' === $node['path'][1] )
- ) {
- // Block-level settings.
- $block_name = $node['path'][2];
- unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] );
- if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) {
- unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] );
- }
- }
- }
- }
- }
-
- wp_recursive_ksort( $output );
-
- return $output;
- }
-
- /**
- * Enables some settings.
- *
- * @since 5.9.0
- *
- * @param array $context The context to which the settings belong.
- */
- protected static function do_opt_in_into_settings( &$context ) {
- foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) {
- // Use "unset prop" as a marker instead of "null" because
- // "null" can be a valid value for some props (e.g. blockGap).
- if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) {
- _wp_array_set( $context, $path, true );
- }
- }
-
- unset( $context['appearanceTools'] );
- }
-}
diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php
deleted file mode 100644
index 07b83049cfeaad..00000000000000
--- a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php
+++ /dev/null
@@ -1,223 +0,0 @@
- true ) );
-
- if ( null === static::$theme ) {
- $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) );
- $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) );
- static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data );
-
- if ( wp_get_theme()->parent() ) {
- // Get parent theme.json.
- $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
- $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
- $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );
-
- // Merge the child theme.json into the parent theme.json.
- // The child theme takes precedence over the parent.
- $parent_theme->merge( static::$theme );
- static::$theme = $parent_theme;
- }
- }
-
- if ( ! $options['with_supports'] ) {
- return static::$theme;
- }
-
- /*
- * We want the presets and settings declared in theme.json
- * to override the ones declared via theme supports.
- * So we take theme supports, transform it to theme.json shape
- * and merge the static::$theme upon that.
- */
- $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() );
- if ( ! wp_theme_has_theme_json() ) {
- if ( ! isset( $theme_support_data['settings']['color'] ) ) {
- $theme_support_data['settings']['color'] = array();
- }
-
- $default_palette = false;
- if ( current_theme_supports( 'default-color-palette' ) ) {
- $default_palette = true;
- }
- if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) {
- // If the theme does not have any palette, we still want to show the core one.
- $default_palette = true;
- }
- $theme_support_data['settings']['color']['defaultPalette'] = $default_palette;
-
- $default_gradients = false;
- if ( current_theme_supports( 'default-gradient-presets' ) ) {
- $default_gradients = true;
- }
- if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) {
- // If the theme does not have any gradients, we still want to show the core ones.
- $default_gradients = true;
- }
- $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients;
-
- // Classic themes without a theme.json don't support global duotone.
- $theme_support_data['settings']['color']['defaultDuotone'] = false;
- }
- $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data );
- $with_theme_supports->merge( static::$theme );
-
- return $with_theme_supports;
- }
- /**
- * Returns the style variations defined by the theme.
- *
- * @return array
- */
- public static function get_style_variations() {
- $variations = array();
- $base_directory = get_stylesheet_directory() . '/styles';
- if ( is_dir( $base_directory ) ) {
- $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
- $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) );
- ksort( $nested_html_files );
- foreach ( $nested_html_files as $path => $file ) {
- $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) );
- if ( is_array( $decoded_file ) ) {
- $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) );
- $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data();
- if ( empty( $variation['title'] ) ) {
- $variation['title'] = basename( $path, '.json' );
- }
- $variations[] = $variation;
- }
- }
- }
- return $variations;
- }
-
- /**
- * Returns the user's origin config.
- *
- * @return WP_Theme_JSON_Gutenberg Entity that holds styles for user data.
- */
- public static function get_user_data() {
- if ( null !== static::$user ) {
- return static::$user;
- }
-
- $config = array();
- $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() );
-
- if ( array_key_exists( 'post_content', $user_cpt ) ) {
- $decoded_data = json_decode( $user_cpt['post_content'], true );
-
- $json_decoding_error = json_last_error();
- if ( JSON_ERROR_NONE !== $json_decoding_error ) {
- trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() );
- return new WP_Theme_JSON_Gutenberg( $config, 'custom' );
- }
-
- // Very important to verify if the flag isGlobalStylesUserThemeJSON is true.
- // If is not true the content was not escaped and is not safe.
- if (
- is_array( $decoded_data ) &&
- isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
- $decoded_data['isGlobalStylesUserThemeJSON']
- ) {
- unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
- $config = $decoded_data;
- }
- }
- static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' );
-
- return static::$user;
- }
-
- /**
- * There are three sources of data (origins) for a site:
- * default, theme, and custom. The custom's has higher priority
- * than the theme's, and the theme's higher than defaults's.
- *
- * Unlike the getters {@link get_core_data},
- * {@link get_theme_data}, and {@link get_user_data},
- * this method returns data after it has been merged
- * with the previous origins. This means that if the same piece of data
- * is declared in different origins (user, theme, and core),
- * the last origin overrides the previous.
- *
- * For example, if the user has set a background color
- * for the paragraph block, and the theme has done it as well,
- * the user preference wins.
- *
- * @param string $origin Optional. To what level should we merge data.
- * Valid values are 'theme' or 'custom'.
- * Default is 'custom'.
- * @return WP_Theme_JSON_Gutenberg
- */
- public static function get_merged_data( $origin = 'custom' ) {
- if ( is_array( $origin ) ) {
- _deprecated_argument( __FUNCTION__, '5.9' );
- }
-
- $result = new WP_Theme_JSON_Gutenberg();
- $result->merge( static::get_core_data() );
- $result->merge( static::get_theme_data() );
-
- if ( 'custom' === $origin ) {
- $result->merge( static::get_user_data() );
- }
-
- return $result;
- }
-}
diff --git a/lib/compat/wordpress-6.0/client-assets.php b/lib/compat/wordpress-6.0/client-assets.php
deleted file mode 100644
index f9affb31af4b52..00000000000000
--- a/lib/compat/wordpress-6.0/client-assets.php
+++ /dev/null
@@ -1,117 +0,0 @@
-get_all_registered() as $block_type ) {
- if ( ! empty( $block_type->style ) ) {
- $style_handles[] = $block_type->style;
- }
-
- if ( ! empty( $block_type->editor_style ) ) {
- $style_handles[] = $block_type->editor_style;
- }
-
- if ( ! empty( $block_type->script ) ) {
- $script_handles[] = $block_type->script;
- }
- }
-
- $style_handles = array_unique( $style_handles );
- $done = wp_styles()->done;
-
- ob_start();
-
- // We do not need reset styles for the iframed editor.
- wp_styles()->done = array( 'wp-reset-editor-styles' );
- wp_styles()->do_items( $style_handles );
- wp_styles()->done = $done;
-
- $styles = ob_get_clean();
-
- $script_handles = array_unique( $script_handles );
- $done = wp_scripts()->done;
-
- ob_start();
-
- wp_scripts()->done = array();
- wp_scripts()->do_items( $script_handles );
- wp_scripts()->done = $done;
-
- $scripts = ob_get_clean();
-
- return array(
- 'styles' => $styles,
- 'scripts' => $scripts,
- );
-}
-
-add_filter(
- 'block_editor_settings_all',
- function( $settings ) {
- // The `__unstableResolvedAssets` are generated by `_wp_get_iframed_editor_assets` for WP >= 6.0.
- if ( function_exists( '_wp_get_iframed_editor_assets' ) ) {
- return $settings;
- }
-
- // In the future we can allow WP Dependency handles to be passed.
- $settings['__unstableResolvedAssets'] = gutenberg_resolve_assets();
- return $settings;
- }
-);
diff --git a/lib/compat/wordpress-6.0/edit-form-blocks.php b/lib/compat/wordpress-6.0/edit-form-blocks.php
deleted file mode 100644
index b11851222b0baf..00000000000000
--- a/lib/compat/wordpress-6.0/edit-form-blocks.php
+++ /dev/null
@@ -1,66 +0,0 @@
- $user_path ) {
- if ( is_string( $user_path ) && str_starts_with( $user_path, '/wp/v2/users/me' ) ) {
- $preload_paths[ $user_index ] = '/wp/v2/users/me';
- break;
- }
- }
-
- return $preload_paths;
-}
-add_filter( 'block_editor_rest_api_preload_paths', 'gutenberg_optimize_preload_paths' );
-
-/**
- * Disables loading remote block patterns from REST while initializing the editor.
- * Nowadays these loads are done in the `block-patterns/patterns` REST endpoint, and
- * are undesired when initializing the block editor page, both in post and site editor.
- *
- * @param WP_Screen $current_screen WordPress current screen object.
- */
-function gutenberg_disable_load_remote_patterns( $current_screen ) {
- $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) );
- if ( $is_site_editor || $current_screen->is_block_editor() ) {
- add_filter( 'should_load_remote_block_patterns', '__return_false' );
- }
-}
-add_action( 'current_screen', 'gutenberg_disable_load_remote_patterns' );
diff --git a/lib/compat/wordpress-6.0/functions.php b/lib/compat/wordpress-6.0/functions.php
deleted file mode 100644
index 9368b211b05990..00000000000000
--- a/lib/compat/wordpress-6.0/functions.php
+++ /dev/null
@@ -1,27 +0,0 @@
-get_settings();
- return _wp_array_get( $settings, $path, $settings );
-}
-
-/**
- * Function to get the styles resulting of merging core, theme, and user data.
- *
- * @param array $path Path to the specific style to retrieve. Optional.
- * If empty, will return all styles.
- * @param array $context {
- * Metadata to know where to retrieve the $path from. Optional.
- *
- * @type string $block_name Which block to retrieve the styles from.
- * If empty, it'll return the styles for the global context.
- * @type string $origin Which origin to take data from.
- * Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
- * If empty or unknown, 'all' is used.
- * }
- *
- * @return array The styles to retrieve.
- */
-function gutenberg_get_global_styles( $path = array(), $context = array() ) {
- if ( ! empty( $context['block_name'] ) ) {
- $path = array_merge( array( 'blocks', $context['block_name'] ), $path );
- }
- $origin = 'custom';
- if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
- $origin = 'theme';
- }
- $styles = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_raw_data()['styles'];
- return _wp_array_get( $styles, $path, $styles );
-}
-
-/**
- * Returns a string containing the SVGs to be referenced as filters (duotone).
- *
- * @return string
- */
-function gutenberg_get_global_styles_svg_filters() {
- // Return cached value if it can be used and exists.
- // It's cached by theme to make sure that theme switching clears the cache.
- $transient_name = 'gutenberg_global_styles_svg_filters_' . get_stylesheet();
- $can_use_cached = (
- ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
- ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
- ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
- ! is_admin()
- );
- if ( $can_use_cached ) {
- $cached = get_transient( $transient_name );
- if ( $cached ) {
- return $cached;
- }
- }
-
- $supports_theme_json = wp_theme_has_theme_json();
-
- $origins = array( 'default', 'theme', 'custom' );
- if ( ! $supports_theme_json ) {
- $origins = array( 'default' );
- }
-
- $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
- $svgs = $tree->get_svg_filters( $origins );
-
- if ( $can_use_cached ) {
- // Cache for a minute, same as gutenberg_get_global_stylesheet.
- set_transient( $transient_name, $svgs, MINUTE_IN_SECONDS );
- }
-
- return $svgs;
-}
diff --git a/lib/compat/wordpress-6.0/post-lock.php b/lib/compat/wordpress-6.0/post-lock.php
deleted file mode 100644
index e36cdff3e7a221..00000000000000
--- a/lib/compat/wordpress-6.0/post-lock.php
+++ /dev/null
@@ -1,67 +0,0 @@
-display_name;
- }
- }
-
- return $response;
-}
-add_filter( 'heartbeat_received', 'gutenberg_refresh_post_lock', 20, 2 );
-
-/**
- * Updates post editor settings and adds avatar to the `postLock` user details.
- *
- * @param array $settings Default editor settings.
- * @param WP_Block_Editor_Context $block_editor_context The current block editor context.
- *
- * @return array Filtered editor settings.
- */
-function gutenberg_update_post_lock_details( $settings, $block_editor_context ) {
- if ( empty( $block_editor_context->post ) ) {
- return $settings;
- }
-
- if ( ! isset( $settings['postLock']['user'] ) ) {
- return $settings;
- }
-
- $user_id = wp_check_post_lock( $block_editor_context->post->ID );
- if ( $user_id ) {
- $settings['postLock']['user']['avatar'] = get_avatar_url( $user_id, array( 'size' => 128 ) );
- }
-
- return $settings;
-}
-add_filter( 'block_editor_settings_all', 'gutenberg_update_post_lock_details', 10, 2 );
diff --git a/lib/compat/wordpress-6.0/render-svg-filters.php b/lib/compat/wordpress-6.0/render-svg-filters.php
deleted file mode 100644
index f13502b867fe95..00000000000000
--- a/lib/compat/wordpress-6.0/render-svg-filters.php
+++ /dev/null
@@ -1,38 +0,0 @@
-is_block_editor()
- ) {
- return;
- }
-
- $filters = gutenberg_get_global_styles_svg_filters();
- if ( ! empty( $filters ) ) {
- echo $filters;
- }
-}
-
-// Override actions introduced in 5.9.1 if they exist.
-remove_action( 'wp_body_open', 'wp_global_styles_render_svg_filters' );
-remove_action( 'in_admin_header', 'wp_global_styles_render_svg_filters' );
-add_action( 'wp_body_open', 'gutenberg_global_styles_render_svg_filters' );
-add_action( 'in_admin_header', 'gutenberg_global_styles_render_svg_filters' );
diff --git a/lib/compat/wordpress-6.0/rest-api.php b/lib/compat/wordpress-6.0/rest-api.php
deleted file mode 100644
index 0fa1852b18813b..00000000000000
--- a/lib/compat/wordpress-6.0/rest-api.php
+++ /dev/null
@@ -1,65 +0,0 @@
-register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );
-
-/**
- * Registers the Edit Site's Export REST API routes.
- *
- * @return void
- */
-function gutenberg_register_edit_site_export_endpoint() {
- $editor_settings = new Gutenberg_REST_Edit_Site_Export_Controller();
- $editor_settings->register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_endpoint' );
-
-
-/**
- * Register a core site settings.
- *
- * Note: Needs to be backported into the `register_initial_settings` function.
- */
-function gutenberg_register_site_settings() {
- register_setting(
- 'reading',
- 'show_on_front',
- array(
- 'show_in_rest' => true,
- 'type' => 'string',
- 'description' => __( 'What to show on the front page', 'gutenberg' ),
- )
- );
-
- register_setting(
- 'reading',
- 'page_on_front',
- array(
- 'show_in_rest' => true,
- 'type' => 'number',
- 'description' => __( 'The ID of the page that should be displayed on the front page', 'gutenberg' ),
- )
- );
-
- register_setting(
- 'reading',
- 'page_for_posts',
- array(
- 'show_in_rest' => true,
- 'type' => 'number',
- 'description' => __( 'The ID of the page that should display the latest posts', 'gutenberg' ),
- )
- );
-}
-add_action( 'rest_api_init', 'gutenberg_register_site_settings', 10 );
diff --git a/lib/compat/wordpress-6.0/site-editor.php b/lib/compat/wordpress-6.0/site-editor.php
deleted file mode 100644
index 081657d0061fe9..00000000000000
--- a/lib/compat/wordpress-6.0/site-editor.php
+++ /dev/null
@@ -1,95 +0,0 @@
- 'page',
- 'postId' => $front_page_id,
- );
- }
-
- $hierarchy = array( 'front-page', 'home', 'index' );
- $template = resolve_block_template( 'home', $hierarchy, '' );
-
- if ( ! $template ) {
- return null;
- }
-
- return array(
- 'postType' => 'wp_template',
- 'postId' => $template->id,
- );
-}
-
-/**
- * Do a server-side redirection if missing `postType` and `postId`
- * query args when visiting site editor.
- *
- * Note: This is a backward compatibility redirect for WP 5.9.
- *
- * @return void
- */
-function gutenberg_site_editor_maybe_redirect() {
- // Skip redirection for WP 6.0 and later.
- if ( function_exists( '_resolve_home_block_template' ) ) {
- return;
- }
-
- // Check theme support. The action runs before checks in the Site Editor.
- if ( ! wp_is_block_theme() ) {
- return;
- }
-
- if ( empty( $_GET['postType'] ) && empty( $_GET['postId'] ) ) {
- $template = gutenberg_resolve_home_template();
- if ( ! empty( $_GET['styles'] ) ) {
- $template['styles'] = sanitize_key( $_GET['styles'] );
- }
-
- $redirect_url = add_query_arg(
- $template,
- admin_url( 'site-editor.php' )
- );
- wp_safe_redirect( $redirect_url );
- exit;
- }
-}
-add_action( 'load-site-editor.php', 'gutenberg_site_editor_maybe_redirect' );
-
-/**
- * Add home template settings for WP 5.9.
- *
- * @param array $settings Existing block editor settings.
- * @param WP_Block_Editor_Context $context The current block editor context.
- * @return array
- */
-function gutenberg_site_editor_homepage_setting( $settings, $context ) {
- if ( isset( $context->post ) ) {
- return $settings;
- }
-
- if ( ! isset( $settings['__unstableHomeTemplate'] ) ) {
- $settings['__unstableHomeTemplate'] = gutenberg_resolve_home_template();
- }
-
- return $settings;
-}
-add_filter( 'block_editor_settings_all', 'gutenberg_site_editor_homepage_setting', 10, 2 );
diff --git a/lib/compat/wordpress-6.0/theme-i18n.json b/lib/compat/wordpress-6.0/theme-i18n.json
deleted file mode 100644
index 282e520c338b42..00000000000000
--- a/lib/compat/wordpress-6.0/theme-i18n.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "title": "Style variation name",
- "settings": {
- "typography": {
- "fontSizes": [
- {
- "name": "Font size name"
- }
- ],
- "fontFamilies": [
- {
- "name": "Font family name"
- }
- ]
- },
- "color": {
- "palette": [
- {
- "name": "Color name"
- }
- ],
- "gradients": [
- {
- "name": "Gradient name"
- }
- ],
- "duotone": [
- {
- "name": "Duotone name"
- }
- ]
- },
- "blocks": {
- "*": {
- "typography": {
- "fontSizes": [
- {
- "name": "Font size name"
- }
- ],
- "fontFamilies": [
- {
- "name": "Font family name"
- }
- ]
- },
- "color": {
- "palette": [
- {
- "name": "Color name"
- }
- ],
- "gradients": [
- {
- "name": "Gradient name"
- }
- ]
- }
- }
- }
- },
- "customTemplates": [
- {
- "title": "Custom template name"
- }
- ],
- "templateParts": [
- {
- "title": "Template part name"
- }
- ]
-}
diff --git a/lib/compat/wordpress-6.0/theme.json b/lib/compat/wordpress-6.0/theme.json
deleted file mode 100644
index 7691aa4a64e6a9..00000000000000
--- a/lib/compat/wordpress-6.0/theme.json
+++ /dev/null
@@ -1,245 +0,0 @@
-{
- "version": 2,
- "settings": {
- "appearanceTools": false,
- "border": {
- "color": false,
- "radius": false,
- "style": false,
- "width": false
- },
- "color": {
- "background": true,
- "custom": true,
- "customDuotone": true,
- "customGradient": true,
- "defaultDuotone": true,
- "defaultGradients": true,
- "defaultPalette": true,
- "duotone": [
- {
- "name": "Dark grayscale",
- "colors": [ "#000000", "#7f7f7f" ],
- "slug": "dark-grayscale"
- },
- {
- "name": "Grayscale",
- "colors": [ "#000000", "#ffffff" ],
- "slug": "grayscale"
- },
- {
- "name": "Purple and yellow",
- "colors": [ "#8c00b7", "#fcff41" ],
- "slug": "purple-yellow"
- },
- {
- "name": "Blue and red",
- "colors": [ "#000097", "#ff4747" ],
- "slug": "blue-red"
- },
- {
- "name": "Midnight",
- "colors": [ "#000000", "#00a5ff" ],
- "slug": "midnight"
- },
- {
- "name": "Magenta and yellow",
- "colors": [ "#c7005a", "#fff278" ],
- "slug": "magenta-yellow"
- },
- {
- "name": "Purple and green",
- "colors": [ "#a60072", "#67ff66" ],
- "slug": "purple-green"
- },
- {
- "name": "Blue and orange",
- "colors": [ "#1900d8", "#ffa96b" ],
- "slug": "blue-orange"
- }
- ],
- "gradients": [
- {
- "name": "Vivid cyan blue to vivid purple",
- "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)",
- "slug": "vivid-cyan-blue-to-vivid-purple"
- },
- {
- "name": "Light green cyan to vivid green cyan",
- "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)",
- "slug": "light-green-cyan-to-vivid-green-cyan"
- },
- {
- "name": "Luminous vivid amber to luminous vivid orange",
- "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)",
- "slug": "luminous-vivid-amber-to-luminous-vivid-orange"
- },
- {
- "name": "Luminous vivid orange to vivid red",
- "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)",
- "slug": "luminous-vivid-orange-to-vivid-red"
- },
- {
- "name": "Very light gray to cyan bluish gray",
- "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)",
- "slug": "very-light-gray-to-cyan-bluish-gray"
- },
- {
- "name": "Cool to warm spectrum",
- "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)",
- "slug": "cool-to-warm-spectrum"
- },
- {
- "name": "Blush light purple",
- "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)",
- "slug": "blush-light-purple"
- },
- {
- "name": "Blush bordeaux",
- "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)",
- "slug": "blush-bordeaux"
- },
- {
- "name": "Luminous dusk",
- "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)",
- "slug": "luminous-dusk"
- },
- {
- "name": "Pale ocean",
- "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)",
- "slug": "pale-ocean"
- },
- {
- "name": "Electric grass",
- "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)",
- "slug": "electric-grass"
- },
- {
- "name": "Midnight",
- "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)",
- "slug": "midnight"
- }
- ],
- "link": false,
- "palette": [
- {
- "name": "Black",
- "slug": "black",
- "color": "#000000"
- },
- {
- "name": "Cyan bluish gray",
- "slug": "cyan-bluish-gray",
- "color": "#abb8c3"
- },
- {
- "name": "White",
- "slug": "white",
- "color": "#ffffff"
- },
- {
- "name": "Pale pink",
- "slug": "pale-pink",
- "color": "#f78da7"
- },
- {
- "name": "Vivid red",
- "slug": "vivid-red",
- "color": "#cf2e2e"
- },
- {
- "name": "Luminous vivid orange",
- "slug": "luminous-vivid-orange",
- "color": "#ff6900"
- },
- {
- "name": "Luminous vivid amber",
- "slug": "luminous-vivid-amber",
- "color": "#fcb900"
- },
- {
- "name": "Light green cyan",
- "slug": "light-green-cyan",
- "color": "#7bdcb5"
- },
- {
- "name": "Vivid green cyan",
- "slug": "vivid-green-cyan",
- "color": "#00d084"
- },
- {
- "name": "Pale cyan blue",
- "slug": "pale-cyan-blue",
- "color": "#8ed1fc"
- },
- {
- "name": "Vivid cyan blue",
- "slug": "vivid-cyan-blue",
- "color": "#0693e3"
- },
- {
- "name": "Vivid purple",
- "slug": "vivid-purple",
- "color": "#9b51e0"
- }
- ],
- "text": true
- },
- "spacing": {
- "blockGap": null,
- "margin": false,
- "padding": false,
- "units": [ "px", "em", "rem", "vh", "vw", "%" ]
- },
- "typography": {
- "customFontSize": true,
- "dropCap": true,
- "fontSizes": [
- {
- "name": "Small",
- "slug": "small",
- "size": "13px"
- },
- {
- "name": "Medium",
- "slug": "medium",
- "size": "20px"
- },
- {
- "name": "Large",
- "slug": "large",
- "size": "36px"
- },
- {
- "name": "Extra Large",
- "slug": "x-large",
- "size": "42px"
- }
- ],
- "fontStyle": true,
- "fontWeight": true,
- "letterSpacing": true,
- "lineHeight": false,
- "textDecoration": true,
- "textTransform": true
- },
- "blocks": {
- "core/button": {
- "border": {
- "radius": true
- }
- },
- "core/pullquote": {
- "border": {
- "color": true,
- "radius": true,
- "style": true,
- "width": true
- }
- }
- }
- },
- "styles": {
- "spacing": { "blockGap": "24px" }
- }
-}
diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php
deleted file mode 100644
index 7860aa6a1dd661..00000000000000
--- a/lib/compat/wordpress-6.1/block-patterns.php
+++ /dev/null
@@ -1,177 +0,0 @@
-
- *
- * 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',
- );
-
- /*
- * 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();
- $stylesheet = get_stylesheet();
- $template = get_template();
- if ( $stylesheet !== $template ) {
- $themes[] = wp_get_theme( $stylesheet );
- }
- $themes[] = wp_get_theme( $template );
-
- 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' ) 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' );
diff --git a/lib/compat/wordpress-6.1/block-template-utils.php b/lib/compat/wordpress-6.1/block-template-utils.php
index 088333990a0d5f..928291b500084e 100644
--- a/lib/compat/wordpress-6.1/block-template-utils.php
+++ b/lib/compat/wordpress-6.1/block-template-utils.php
@@ -84,7 +84,7 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t
array(
'taxonomy' => 'wp_theme',
'field' => 'name',
- 'terms' => wp_get_theme()->get_stylesheet(),
+ 'terms' => get_stylesheet(),
),
),
);
@@ -429,7 +429,7 @@ function gutenberg_build_block_template_result_from_post( $post ) {
$theme = $terms[0]->name;
$template_file = _get_block_template_file( $post->post_type, $post->post_name );
- $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && null !== $template_file;
+ $has_theme_file = get_stylesheet() === $theme && null !== $template_file;
$template = new WP_Block_Template();
$template->wp_id = $post->ID;
diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php
similarity index 60%
rename from lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php
rename to lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php
index c053a678083da8..b40f3aa2497f16 100644
--- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php
+++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php
@@ -1,6 +1,6 @@
namespace = 'wp/v2';
- $this->rest_base = 'block-patterns/patterns';
- }
-
- /**
- * 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
- );
- }
-
- /**
- * Checks whether a given request has permission to read block patterns.
- *
- * @since 6.0.0
- *
- * @param WP_REST_Request $request Full details about the request.
- * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
- */
- public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- if ( current_user_can( 'edit_posts' ) ) {
- return true;
- }
-
- foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
- if ( current_user_can( $post_type->cap->edit_posts ) ) {
- return true;
- }
- }
-
- return new WP_Error(
- 'rest_cannot_view',
- __( 'Sorry, you are not allowed to view the registered block patterns.', 'gutenberg' ),
- array( 'status' => rest_authorization_required_code() )
- );
- }
-
- /**
- * Retrieves all block patterns.
- *
- * @since 6.0.0
- *
- * @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.
- _load_remote_block_patterns(); // Patterns with the `core` keyword.
- _load_remote_featured_patterns(); // Patterns in the `featured` category.
- _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 ) {
- $prepared_pattern = $this->prepare_item_for_response( $pattern, $request );
- $response[] = $this->prepare_response_for_collection( $prepared_pattern );
- }
- return rest_ensure_response( $response );
- }
-
+class Gutenberg_REST_Block_Patterns_Controller_6_1 extends WP_REST_Block_Patterns_Controller {
/**
* Prepare a raw block pattern before it gets output in a REST API response.
*
* @since 6.0.0
+ * @since 6.1.0 Added `postTypes` property.
*
* @param array $item Raw pattern as registered, before any changes.
* @param WP_REST_Request $request Request object.
@@ -147,6 +55,7 @@ public function prepare_item_for_response( $item, $request ) {
* 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.
*/
diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php
index 879316c1c649e3..e911b0f1e4d203 100644
--- a/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php
+++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php
@@ -228,7 +228,7 @@ protected function prepare_item_for_database( $request ) {
$changes->post_type = $this->post_type;
$changes->post_status = 'publish';
$changes->tax_input = array(
- 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
+ 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(),
);
} elseif ( 'custom' !== $template->source ) {
$changes->post_name = $template->slug;
diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php
index 73e012f33d1c7e..1d4a83e3406fbe 100644
--- a/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php
+++ b/lib/compat/wordpress-6.1/class-wp-theme-json-resolver-6-1.php
@@ -15,7 +15,7 @@
*
* @access private
*/
-class WP_Theme_JSON_Resolver_6_1 extends WP_Theme_JSON_Resolver_6_0 {
+class WP_Theme_JSON_Resolver_6_1 extends WP_Theme_JSON_Resolver {
/**
* Container for data coming from core.
diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php
index 35c540ce1c57a6..0829a09d084c70 100644
--- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php
+++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php
@@ -56,86 +56,36 @@ function ( $item ) {
}
/**
- * Returns the stylesheet resulting of merging core, theme, and user data.
+ * Repeated logic from `get_default_block_editor_settings` function. When implemented into core,
+ * remove logic from `get_default_block_editor_settings` and simple call this function instead.
*
- * @param array $types Types of styles to load. Optional.
- * It accepts 'variables', 'styles', 'presets' as values.
- * If empty, it'll load all for themes with theme.json support
- * and only [ 'variables', 'presets' ] for themes without theme.json support.
- *
- * @return string Stylesheet.
+ * @return array
*/
-function gutenberg_get_global_stylesheet( $types = array() ) {
- // Return cached value if it can be used and exists.
- // It's cached by theme to make sure that theme switching clears the cache.
- $can_use_cached = (
- ( empty( $types ) ) &&
- ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
- ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
- ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
- ! is_admin()
+function gutenberg_get_legacy_theme_supports_for_theme_json() {
+ $theme_settings = array(
+ 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ),
+ 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
+ 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ),
+ 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ),
+ 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ),
+ 'enableCustomUnits' => get_theme_support( 'custom-units' ),
);
- $transient_name = 'gutenberg_global_styles_' . get_stylesheet();
- if ( $can_use_cached ) {
- $cached = get_transient( $transient_name );
- if ( $cached ) {
- return $cached;
- }
- }
- $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
- $supports_theme_json = wp_theme_has_theme_json();
- if ( empty( $types ) && ! $supports_theme_json ) {
- $types = array( 'variables', 'presets', 'base-layout-styles' );
- } elseif ( empty( $types ) ) {
- $types = array( 'variables', 'styles', 'presets' );
- }
- /*
- * If variables are part of the stylesheet,
- * we add them.
- *
- * This is so themes without a theme.json still work as before 5.9:
- * they can override the default presets.
- * See https://core.trac.wordpress.org/ticket/54782
- */
- $styles_variables = '';
- if ( in_array( 'variables', $types, true ) ) {
- /*
- * We only use the default, theme, and custom origins.
- * This is because styles for blocks origin are added
- * at a later phase (render cycle) so we only render the ones in use.
- * @see wp_add_global_styles_for_blocks
- */
- $origins = array( 'default', 'theme', 'custom' );
- $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins );
- $types = array_diff( $types, array( 'variables' ) );
+ // Theme settings.
+ $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) );
+ if ( false !== $color_palette ) {
+ $theme_settings['colors'] = $color_palette;
}
- /*
- * For the remaining types (presets, styles), we do consider origins:
- *
- * - themes without theme.json: only the classes for the presets defined by core
- * - themes with theme.json: the presets and styles classes, both from core and the theme
- */
- $styles_rest = '';
- if ( ! empty( $types ) ) {
- /*
- * We only use the default, theme, and custom origins.
- * This is because styles for blocks origin are added
- * at a later phase (render cycle) so we only render the ones in use.
- * @see wp_add_global_styles_for_blocks
- */
- $origins = array( 'default', 'theme', 'custom' );
- if ( ! $supports_theme_json ) {
- $origins = array( 'default' );
- }
- $styles_rest = $tree->get_stylesheet( $types, $origins );
+ $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) );
+ if ( false !== $font_sizes ) {
+ $theme_settings['fontSizes'] = $font_sizes;
}
- $stylesheet = $styles_variables . $styles_rest;
- if ( $can_use_cached ) {
- // Cache for a minute.
- // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites.
- set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS );
+
+ $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) );
+ if ( false !== $gradient_presets ) {
+ $theme_settings['gradients'] = $gradient_presets;
}
- return $stylesheet;
+
+ return $theme_settings;
}
diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php
index f3391389d9e38a..5dd06d3aad855b 100644
--- a/lib/compat/wordpress-6.1/rest-api.php
+++ b/lib/compat/wordpress-6.1/rest-api.php
@@ -35,15 +35,6 @@ function gutenberg_update_post_types_rest_response( $response, $post_type ) {
}
add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 );
-/**
- * Registers the block patterns REST API routes.
- */
-function gutenberg_register_gutenberg_rest_block_patterns() {
- $block_patterns = new Gutenberg_REST_Block_Patterns_Controller();
- $block_patterns->register_routes();
-}
-add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 );
-
/**
* Exposes the site logo URL through the WordPress REST API.
*
diff --git a/lib/compat/wordpress-6.1/template-parts-screen.php b/lib/compat/wordpress-6.1/template-parts-screen.php
index c8b5958bedcd4d..1555b6ab1e250b 100644
--- a/lib/compat/wordpress-6.1/template-parts-screen.php
+++ b/lib/compat/wordpress-6.1/template-parts-screen.php
@@ -118,17 +118,8 @@ static function( $classes ) {
'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(),
'supportsLayout' => wp_theme_has_theme_json(),
'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ),
- '__unstableHomeTemplate' => gutenberg_resolve_home_template(),
);
- /**
- * We don't need home template resolution when block template parts are supported.
- * Set the value to true to satisfy the editor initialization guard clause.
- */
- if ( $custom_settings['supportsTemplatePartsMode'] ) {
- $custom_settings['__unstableHomeTemplate'] = true;
- }
-
// Add additional back-compat patterns registered by `current_screen` et al.
$custom_settings['__experimentalAdditionalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered( true );
$custom_settings['__experimentalAdditionalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered( true );
@@ -143,7 +134,7 @@ static function( $classes ) {
}
$active_global_styles_id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
- $active_theme = wp_get_theme()->get_stylesheet();
+ $active_theme = get_stylesheet();
$preload_paths = array(
array( '/wp/v2/media', 'OPTIONS' ),
'/wp/v2/types?context=view',
diff --git a/lib/compat/wordpress-6.2/block-editor-settings.php b/lib/compat/wordpress-6.2/block-editor-settings.php
new file mode 100644
index 00000000000000..7323d34eb667ce
--- /dev/null
+++ b/lib/compat/wordpress-6.2/block-editor-settings.php
@@ -0,0 +1,28 @@
+ gutenberg_get_global_stylesheet( array( 'custom-css' ) ),
+ '__unstableType' => 'user',
+ 'isGlobalStyles' => true,
+ );
+ }
+
+ return $settings;
+}
+
+add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings_6_2', PHP_INT_MAX );
diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php
index a28566d441e7a8..1e529faf963210 100644
--- a/lib/compat/wordpress-6.2/block-patterns.php
+++ b/lib/compat/wordpress-6.2/block-patterns.php
@@ -6,9 +6,9 @@
*/
/**
- * Registers the block pattern categories REST API routes.
+ * Registers the block pattern categories.
*/
-function gutenberg_register_core_block_patterns_and_categories() {
+function gutenberg_register_core_block_patterns_categories() {
register_block_pattern_category(
'banner',
array(
@@ -18,54 +18,127 @@ function gutenberg_register_core_block_patterns_and_categories() {
register_block_pattern_category(
'buttons',
array(
- 'label' => _x( 'Buttons', 'Block pattern category', 'gutenberg' ),
+ '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' ),
+ 'label' => _x( 'Columns', 'Block pattern category', 'gutenberg' ),
+ 'description' => __( 'Multi-column patterns with more complex layouts.', 'gutenberg' ),
)
);
register_block_pattern_category(
- 'footer',
+ 'text',
array(
- 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ),
+ 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ),
+ 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ),
)
);
register_block_pattern_category(
- 'gallery',
+ 'query',
array(
- 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ),
+ 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ),
+ 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
)
);
register_block_pattern_category(
- 'header',
+ 'featured',
array(
- 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ),
+ '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(
- 'text',
+ 'call-to-action',
array(
- 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ),
+ 'label' => _x( 'Call to Action', 'Block pattern category', 'gutenberg' ),
+ 'description' => __( 'Sections whose purpose is to trigger a specific action.', 'gutenberg' ),
)
);
register_block_pattern_category(
- 'query',
+ '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 post summaries in lists, grids, and other layouts.', 'gutenberg' ),
+ 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ),
)
);
+ // Site building pattern categories.
register_block_pattern_category(
- 'featured',
+ 'footer',
array(
- 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ),
+ '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_and_categories' );
+add_action( 'init', 'gutenberg_register_core_block_patterns_categories' );
/**
* Registers Gutenberg-bundled patterns, with a focus on headers and footers
@@ -111,3 +184,173 @@ function gutenberg_register_core_block_patterns() {
}
}
add_action( 'init', 'gutenberg_register_core_block_patterns' );
+
+/**
+ * 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',
+ );
+
+ /*
+ * 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' ) 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' );
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
new file mode 100644
index 00000000000000..d34160edb2af0b
--- /dev/null
+++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php
@@ -0,0 +1,107 @@
+ 'call-to-action',
+ 'columns' => 'text',
+ 'query' => 'posts',
+ );
+
+ /**
+ * 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.
+ _load_remote_block_patterns(); // Patterns with the `core` keyword.
+ _load_remote_featured_patterns(); // Patterns in the `featured` category.
+ _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.0/class-gutenberg-rest-global-styles-controller.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php
similarity index 54%
rename from lib/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php
rename to lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php
index 2a50e9684c0938..684786ef22d762 100644
--- a/lib/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php
+++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php
@@ -9,111 +9,23 @@
/**
* Base Global Styles REST API Controller.
*/
-class Gutenberg_REST_Global_Styles_Controller extends WP_REST_Global_Styles_Controller {
+class Gutenberg_REST_Global_Styles_Controller_6_2 extends WP_REST_Global_Styles_Controller {
/**
* Registers the controllers routes.
*
* @return void
*/
public function register_routes() {
- // List themes global styles.
- register_rest_route(
- $this->namespace,
- '/' . $this->rest_base . '/themes/(?P[\/\s%\w\.\(\)\[\]\@_\-]+)/variations',
- array(
- array(
- 'methods' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'get_theme_items' ),
- 'permission_callback' => array( $this, 'get_theme_items_permissions_check' ),
- 'args' => array(
- 'stylesheet' => array(
- 'description' => __( 'The theme identifier', 'gutenberg' ),
- 'type' => 'string',
- ),
- ),
- ),
- )
- );
-
parent::register_routes();
}
- /**
- * Checks if a given request has access to read a single theme global styles config.
- *
- * @param WP_REST_Request $request Full details about the request.
- * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
- */
- public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
- // Verify if the current user has edit_theme_options capability.
- // This capability is required to edit/view/delete templates.
- if ( ! current_user_can( 'edit_theme_options' ) ) {
- return new WP_Error(
- 'rest_cannot_manage_global_styles',
- __( 'Sorry, you are not allowed to access the global styles on this site.', 'gutenberg' ),
- array(
- 'status' => rest_authorization_required_code(),
- )
- );
- }
-
- return true;
- }
-
- /**
- * Prepares a single global styles config for update.
- *
- * @since 5.9.0
- *
- * @param WP_REST_Request $request Request object.
- * @return stdClass Changes to pass to wp_update_post.
- */
- protected function prepare_item_for_database( $request ) {
- $changes = new stdClass();
- $changes->ID = $request['id'];
- $post = get_post( $request['id'] );
- $existing_config = array();
- if ( $post ) {
- $existing_config = json_decode( $post->post_content, true );
- $json_decoding_error = json_last_error();
- if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
- ! $existing_config['isGlobalStylesUserThemeJSON'] ) {
- $existing_config = array();
- }
- }
- if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
- $config = array();
- if ( isset( $request['styles'] ) ) {
- $config['styles'] = $request['styles'];
- } elseif ( isset( $existing_config['styles'] ) ) {
- $config['styles'] = $existing_config['styles'];
- }
- if ( isset( $request['settings'] ) ) {
- $config['settings'] = $request['settings'];
- } elseif ( isset( $existing_config['settings'] ) ) {
- $config['settings'] = $existing_config['settings'];
- }
- $config['isGlobalStylesUserThemeJSON'] = true;
- $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA;
- $changes->post_content = wp_json_encode( $config );
- }
- // Post title.
- if ( isset( $request['title'] ) ) {
- if ( is_string( $request['title'] ) ) {
- $changes->post_title = $request['title'];
- } elseif ( ! empty( $request['title']['raw'] ) ) {
- $changes->post_title = $request['title']['raw'];
- }
- }
- return $changes;
- }
-
/**
* Prepare a global styles config output for response.
*
* @since 5.9.0
+ * @since 6.2 Handling of style.css was added to WP_Theme_JSON.
*
- * @param WP_Post $post Global Styles post object.
+ * @param WP_Post $post Global Styles post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
@@ -124,12 +36,15 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V
if ( $is_global_styles_user_theme_json ) {
$config = ( new WP_Theme_JSON_Gutenberg( $raw_config, 'custom' ) )->get_raw_data();
}
+
// Base fields for every post.
$data = array();
$fields = $this->get_fields_for_response( $request );
+
if ( rest_is_field_included( 'id', $fields ) ) {
$data['id'] = $post->ID;
}
+
if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
@@ -138,94 +53,152 @@ public function prepare_item_for_response( $post, $request ) { // phpcs:ignore V
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
+
$data['title']['rendered'] = get_the_title( $post->ID );
+
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
}
+
if ( rest_is_field_included( 'settings', $fields ) ) {
$data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
}
+
if ( rest_is_field_included( 'styles', $fields ) ) {
$data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
}
+
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
+
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
- $links = $this->prepare_links( $post->ID );
- $response->add_links( $links );
- if ( ! empty( $links['self']['href'] ) ) {
- $actions = $this->get_available_actions();
- $self = $links['self']['href'];
- foreach ( $actions as $rel ) {
- $response->add_link( $rel, $self );
+
+ if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
+ $links = $this->prepare_links( $post->ID );
+ $response->add_links( $links );
+ if ( ! empty( $links['self']['href'] ) ) {
+ $actions = $this->get_available_actions();
+ $self = $links['self']['href'];
+ foreach ( $actions as $rel ) {
+ $response->add_link( $rel, $self );
+ }
}
}
+
return $response;
}
/**
- * Returns the given theme global styles config.
+ * Updates a single global style config.
*
* @since 5.9.0
+ * @since 6.2.0 Added validation of styles.css property.
*
- * @param WP_REST_Request $request The request instance.
- * @return WP_REST_Response|WP_Error
+ * @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_theme_item( $request ) {
- if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) {
- // This endpoint only supports the active theme for now.
- return new WP_Error(
- 'rest_theme_not_found',
- __( 'Theme not found.', 'gutenberg' ),
- array( 'status' => 404 )
- );
+ public function update_item( $request ) {
+ $post_before = $this->get_post( $request['id'] );
+ if ( is_wp_error( $post_before ) ) {
+ return $post_before;
}
- $theme = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( 'theme' );
- $data = array();
- $fields = $this->get_fields_for_response( $request );
- if ( rest_is_field_included( 'settings', $fields ) ) {
- $data['settings'] = $theme->get_settings();
+
+ $changes = $this->prepare_item_for_database( $request );
+ if ( is_wp_error( $changes ) ) {
+ return $changes;
}
- if ( rest_is_field_included( 'styles', $fields ) ) {
- $raw_data = $theme->get_raw_data();
- $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array();
+
+ $result = wp_update_post( wp_slash( (array) $changes ), true, false );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $post = get_post( $request['id'] );
+ $fields_update = $this->update_additional_fields_for_object( $post, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
}
- $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
- $data = $this->add_additional_fields_to_object( $data, $request );
- $data = $this->filter_response_by_context( $data, $context );
- $response = rest_ensure_response( $data );
- $links = array(
- 'self' => array(
- 'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ),
- ),
- );
- $response->add_links( $links );
- return $response;
- }
+ wp_after_insert_post( $post, true, $post_before );
+
+ $response = $this->prepare_item_for_response( $post, $request );
+
+ return rest_ensure_response( $response );
+ }
+ /**
+ * Prepares a single global styles config for update.
+ *
+ * @since 5.9.0
+ * @since 6.2.0 Added validation of styles.css property.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return stdClass Changes to pass to wp_update_post.
+ */
+ protected function prepare_item_for_database( $request ) {
+ $changes = new stdClass();
+ $changes->ID = $request['id'];
+ $post = get_post( $request['id'] );
+ $existing_config = array();
+ if ( $post ) {
+ $existing_config = json_decode( $post->post_content, true );
+ $json_decoding_error = json_last_error();
+ if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
+ ! $existing_config['isGlobalStylesUserThemeJSON'] ) {
+ $existing_config = array();
+ }
+ }
+ if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
+ $config = array();
+ if ( isset( $request['styles'] ) ) {
+ $config['styles'] = $request['styles'];
+ if ( isset( $request['styles']['css'] ) ) {
+ $validate_custom_css = $this->validate_custom_css( $request['styles']['css'] );
+ if ( is_wp_error( $validate_custom_css ) ) {
+ return $validate_custom_css;
+ }
+ }
+ } elseif ( isset( $existing_config['styles'] ) ) {
+ $config['styles'] = $existing_config['styles'];
+ }
+ if ( isset( $request['settings'] ) ) {
+ $config['settings'] = $request['settings'];
+ } elseif ( isset( $existing_config['settings'] ) ) {
+ $config['settings'] = $existing_config['settings'];
+ }
+ $config['isGlobalStylesUserThemeJSON'] = true;
+ $config['version'] = WP_Theme_JSON_Gutenberg::LATEST_SCHEMA;
+ $changes->post_content = wp_json_encode( $config );
+ }
+ // Post title.
+ if ( isset( $request['title'] ) ) {
+ if ( is_string( $request['title'] ) ) {
+ $changes->post_title = $request['title'];
+ } elseif ( ! empty( $request['title']['raw'] ) ) {
+ $changes->post_title = $request['title']['raw'];
+ }
+ }
+ return $changes;
+ }
/**
- * Returns the given theme global styles variations.
+ * Validate style.css as valid CSS.
*
- * @param WP_REST_Request $request The request instance.
+ * Currently just checks for invalid markup.
*
- * @return WP_REST_Response|WP_Error
+ * @since 6.2.0
+ *
+ * @param string $css CSS to validate.
+ * @return true|WP_Error True if the input was validated, otherwise WP_Error.
*/
- public function get_theme_items( $request ) {
- if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) {
- // This endpoint only supports the active theme for now.
+ private function validate_custom_css( $css ) {
+ if ( preg_match( '#?\w+#', $css ) ) {
return new WP_Error(
- 'rest_theme_not_found',
- __( 'Theme not found.', 'gutenberg' ),
- array( 'status' => 404 )
+ 'rest_custom_css_illegal_markup',
+ __( 'Markup is not allowed in CSS.', 'gutenberg' ),
+ array( 'status' => 400 )
);
}
-
- $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations();
- $response = rest_ensure_response( $variations );
-
- return $response;
+ return true;
}
-
}
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
index ab845f3c38de15..e8e456676e9749 100644
--- 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
@@ -9,7 +9,7 @@
/**
* Controller which provides REST endpoint for block patterns from wordpress.org/patterns.
*/
-class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends Gutenberg_REST_Pattern_Directory_Controller_6_0 {
+class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends WP_REST_Pattern_Directory_Controller {
/**
* Registers the necessary REST API routes.
*
diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php
deleted file mode 100644
index 4c6a1f50612a84..00000000000000
--- a/lib/compat/wordpress-6.2/class-wp-theme-json-6-2.php
+++ /dev/null
@@ -1,219 +0,0 @@
- array( 'color', 'gradient' ),
- 'background-color' => array( 'color', 'background' ),
- 'border-radius' => array( 'border', 'radius' ),
- 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
- 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
- 'border-bottom-left-radius' => array( 'border', 'radius', 'bottomLeft' ),
- 'border-bottom-right-radius' => array( 'border', 'radius', 'bottomRight' ),
- 'border-color' => array( 'border', 'color' ),
- 'border-width' => array( 'border', 'width' ),
- 'border-style' => array( 'border', 'style' ),
- 'border-top-color' => array( 'border', 'top', 'color' ),
- 'border-top-width' => array( 'border', 'top', 'width' ),
- 'border-top-style' => array( 'border', 'top', 'style' ),
- 'border-right-color' => array( 'border', 'right', 'color' ),
- 'border-right-width' => array( 'border', 'right', 'width' ),
- 'border-right-style' => array( 'border', 'right', 'style' ),
- 'border-bottom-color' => array( 'border', 'bottom', 'color' ),
- 'border-bottom-width' => array( 'border', 'bottom', 'width' ),
- 'border-bottom-style' => array( 'border', 'bottom', 'style' ),
- 'border-left-color' => array( 'border', 'left', 'color' ),
- 'border-left-width' => array( 'border', 'left', 'width' ),
- 'border-left-style' => array( 'border', 'left', 'style' ),
- 'color' => array( 'color', 'text' ),
- 'font-family' => array( 'typography', 'fontFamily' ),
- 'font-size' => array( 'typography', 'fontSize' ),
- 'font-style' => array( 'typography', 'fontStyle' ),
- 'font-weight' => array( 'typography', 'fontWeight' ),
- 'letter-spacing' => array( 'typography', 'letterSpacing' ),
- 'line-height' => array( 'typography', 'lineHeight' ),
- 'margin' => array( 'spacing', 'margin' ),
- 'margin-top' => array( 'spacing', 'margin', 'top' ),
- 'margin-right' => array( 'spacing', 'margin', 'right' ),
- 'margin-bottom' => array( 'spacing', 'margin', 'bottom' ),
- 'margin-left' => array( 'spacing', 'margin', 'left' ),
- 'min-height' => array( 'dimensions', 'minHeight' ),
- 'padding' => array( 'spacing', 'padding' ),
- 'padding-top' => array( 'spacing', 'padding', 'top' ),
- 'padding-right' => array( 'spacing', 'padding', 'right' ),
- 'padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
- 'padding-left' => array( 'spacing', 'padding', 'left' ),
- '--wp--style--root--padding' => array( 'spacing', 'padding' ),
- '--wp--style--root--padding-top' => array( 'spacing', 'padding', 'top' ),
- '--wp--style--root--padding-right' => array( 'spacing', 'padding', 'right' ),
- '--wp--style--root--padding-bottom' => array( 'spacing', 'padding', 'bottom' ),
- '--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ),
- 'text-decoration' => array( 'typography', 'textDecoration' ),
- 'text-transform' => array( 'typography', 'textTransform' ),
- 'filter' => array( 'filter', 'duotone' ),
- 'box-shadow' => array( 'shadow' ),
- );
-
- /**
- * The valid properties under the settings key.
- *
- * @since 5.8.0 As `ALLOWED_SETTINGS`.
- * @since 5.9.0 Renamed from `ALLOWED_SETTINGS` to `VALID_SETTINGS`,
- * added new properties for `border`, `color`, `spacing`,
- * and `typography`, and renamed others according to the new schema.
- * @since 6.0.0 Added `color.defaultDuotone`.
- * @since 6.1.0 Added `layout.definitions` and `useRootPaddingAwareAlignments`.
- * @since 6.2.0 Added `dimensions.minHeight`.
- * @var array
- */
- const VALID_SETTINGS = array(
- 'appearanceTools' => null,
- 'useRootPaddingAwareAlignments' => null,
- 'border' => array(
- 'color' => null,
- 'radius' => null,
- 'style' => null,
- 'width' => null,
- ),
- 'color' => array(
- 'background' => null,
- 'custom' => null,
- 'customDuotone' => null,
- 'customGradient' => null,
- 'defaultDuotone' => null,
- 'defaultGradients' => null,
- 'defaultPalette' => null,
- 'duotone' => null,
- 'gradients' => null,
- 'link' => null,
- 'palette' => null,
- 'text' => null,
- ),
- 'custom' => null,
- 'dimensions' => array(
- 'minHeight' => null,
- ),
- 'layout' => array(
- 'contentSize' => null,
- 'definitions' => null,
- 'wideSize' => null,
- ),
- 'spacing' => array(
- 'customSpacingSize' => null,
- 'spacingSizes' => null,
- 'spacingScale' => null,
- 'blockGap' => null,
- 'margin' => null,
- 'padding' => null,
- 'units' => null,
- ),
- 'typography' => array(
- 'fluid' => null,
- 'customFontSize' => null,
- 'dropCap' => null,
- 'fontFamilies' => null,
- 'fontSizes' => null,
- 'fontStyle' => null,
- 'fontWeight' => null,
- 'letterSpacing' => null,
- 'lineHeight' => null,
- 'textDecoration' => null,
- 'textTransform' => null,
- ),
- );
-
- /**
- * The valid properties under the styles key.
- *
- * @since 5.8.0 As `ALLOWED_STYLES`.
- * @since 5.9.0 Renamed from `ALLOWED_STYLES` to `VALID_STYLES`,
- * added new properties for `border`, `filter`, `spacing`,
- * and `typography`.
- * @since 6.1.0 Added new side properties for `border`,
- * added new property `shadow`,
- * updated `blockGap` to be allowed at any level.
- * @var array
- */
- const VALID_STYLES = array(
- 'border' => array(
- 'color' => null,
- 'radius' => null,
- 'style' => null,
- 'width' => null,
- 'top' => null,
- 'right' => null,
- 'bottom' => null,
- 'left' => null,
- ),
- 'color' => array(
- 'background' => null,
- 'gradient' => null,
- 'text' => null,
- ),
- 'dimensions' => array(
- 'minHeight' => null,
- ),
- 'filter' => array(
- 'duotone' => null,
- ),
- 'shadow' => null,
- 'spacing' => array(
- 'margin' => null,
- 'padding' => null,
- 'blockGap' => null,
- ),
- 'typography' => array(
- 'fontFamily' => null,
- 'fontSize' => null,
- 'fontStyle' => null,
- 'fontWeight' => null,
- 'letterSpacing' => null,
- 'lineHeight' => null,
- 'textDecoration' => null,
- 'textTransform' => null,
- ),
- );
-}
diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php
index e10710e0f4709f..110c8bac7b147c 100644
--- a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php
+++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php
@@ -17,6 +17,86 @@
*/
class WP_Theme_JSON_Resolver_6_2 extends WP_Theme_JSON_Resolver_6_1 {
+ /**
+ * Returns the custom post type that contains the user's origin config
+ * for the active theme or a void array if none are found.
+ *
+ * This can also create and return a new draft custom post type.
+ *
+ * @param WP_Theme $theme The theme object. If empty, it
+ * defaults to the active theme.
+ * @param bool $create_post Optional. Whether a new custom post
+ * type should be created if none are
+ * found. Default false.
+ * @param array $post_status_filter Optional. Filter custom post type by
+ * post status. Default `array( 'publish' )`,
+ * so it only fetches published posts.
+ * @return array Custom Post Type for the user's origin config.
+ */
+ public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) {
+ if ( ! $theme instanceof WP_Theme ) {
+ $theme = wp_get_theme();
+ }
+
+ /*
+ * Bail early if the theme does not support a theme.json.
+ *
+ * Since wp_theme_has_theme_json only supports the active
+ * theme, the extra condition for whether $theme is the active theme is
+ * present here.
+ */
+ if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) {
+ return array();
+ }
+
+ $user_cpt = array();
+ $post_type_filter = 'wp_global_styles';
+ $stylesheet = $theme->get_stylesheet();
+ $args = array(
+ 'posts_per_page' => 1,
+ 'orderby' => 'date',
+ 'order' => 'desc',
+ 'post_type' => $post_type_filter,
+ 'post_status' => $post_status_filter,
+ 'ignore_sticky_posts' => true,
+ 'no_found_rows' => true,
+ 'update_post_meta_cache' => false,
+ 'update_post_term_cache' => false,
+ 'tax_query' => array(
+ array(
+ 'taxonomy' => 'wp_theme',
+ 'field' => 'name',
+ 'terms' => $stylesheet,
+ ),
+ ),
+ );
+
+ $global_style_query = new WP_Query();
+ $recent_posts = $global_style_query->query( $args );
+ if ( count( $recent_posts ) === 1 ) {
+ $user_cpt = get_object_vars( $recent_posts[0] );
+ } elseif ( $create_post ) {
+ $cpt_post_id = wp_insert_post(
+ array(
+ 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
+ 'post_status' => 'publish',
+ 'post_title' => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518.
+ 'post_type' => $post_type_filter,
+ 'post_name' => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ),
+ 'tax_input' => array(
+ 'wp_theme' => array( $stylesheet ),
+ ),
+ ),
+ true
+ );
+ if ( ! is_wp_error( $cpt_post_id ) ) {
+ $user_cpt = get_object_vars( get_post( $cpt_post_id ) );
+ }
+ }
+
+ return $user_cpt;
+ }
+
/**
* Determines whether the active theme has a theme.json file.
*
@@ -32,4 +112,119 @@ public static function theme_has_support() {
return wp_theme_has_theme_json();
}
+ /**
+ * Returns the data merged from multiple origins.
+ *
+ * There are four sources of data (origins) for a site:
+ *
+ * - default => WordPress
+ * - blocks => each one of the blocks provides data for itself
+ * - theme => the active theme
+ * - custom => data provided by the user
+ *
+ * The custom's has higher priority than the theme's, the theme's higher than blocks',
+ * and block's higher than default's.
+ *
+ * Unlike the getters
+ * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data},
+ * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data},
+ * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data},
+ * this method returns data after it has been merged with the previous origins.
+ * This means that if the same piece of data is declared in different origins
+ * (default, blocks, theme, custom), the last origin overrides the previous.
+ *
+ * For example, if the user has set a background color
+ * for the paragraph block, and the theme has done it as well,
+ * the user preference wins.
+ *
+ * @param string $origin Optional. To what level should we merge data:'default', 'blocks', 'theme' or 'custom'.
+ * 'custom' is used as default value as well as fallback value if the origin is unknown.
+ *
+ * @return WP_Theme_JSON
+ */
+ public static function get_merged_data( $origin = 'custom' ) {
+ if ( is_array( $origin ) ) {
+ _deprecated_argument( __FUNCTION__, '5.9.0' );
+ }
+
+ $result = static::get_core_data();
+ if ( 'default' === $origin ) {
+ $result->set_spacing_sizes();
+ return $result;
+ }
+
+ $result->merge( static::get_block_data() );
+ if ( 'blocks' === $origin ) {
+ return $result;
+ }
+
+ $result->merge( static::get_theme_data() );
+ if ( 'theme' === $origin ) {
+ $result->set_spacing_sizes();
+ return $result;
+ }
+
+ $result->merge( static::get_user_data() );
+ $result->set_spacing_sizes();
+ return $result;
+ }
+
+ /**
+ * Returns the user's origin config.
+ *
+ * @since 6.2 Added check for the WP_Theme_JSON_Gutenberg class to prevent $user
+ * values set in core fron overriding the new custom css values added to VALID_STYLES.
+ * This does not need to be backported to core as the new VALID_STYLES[css] value will
+ * be added to core with 6.2.
+ *
+ * @return WP_Theme_JSON_Gutenberg Entity that holds styles for user data.
+ */
+ public static function get_user_data() {
+ if ( null !== static::$user && static::$user instanceof WP_Theme_JSON_Gutenberg ) {
+ return static::$user;
+ }
+
+ $config = array();
+ $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() );
+
+ if ( array_key_exists( 'post_content', $user_cpt ) ) {
+ $decoded_data = json_decode( $user_cpt['post_content'], true );
+
+ $json_decoding_error = json_last_error();
+ if ( JSON_ERROR_NONE !== $json_decoding_error ) {
+ trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() );
+ /**
+ * Filters the data provided by the user for global styles & settings.
+ *
+ * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data.
+ */
+ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) );
+ $config = $theme_json->get_data();
+ return new WP_Theme_JSON_Gutenberg( $config, 'custom' );
+ }
+
+ // Very important to verify if the flag isGlobalStylesUserThemeJSON is true.
+ // If is not true the content was not escaped and is not safe.
+ if (
+ is_array( $decoded_data ) &&
+ isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
+ $decoded_data['isGlobalStylesUserThemeJSON']
+ ) {
+ unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
+ $config = $decoded_data;
+ }
+ }
+
+ /**
+ * Filters the data provided by the user for global styles & settings.
+ *
+ * @param WP_Theme_JSON_Data_Gutenberg Class to access and update the underlying data.
+ */
+ $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data_Gutenberg( $config, 'custom' ) );
+ $config = $theme_json->get_data();
+
+ static::$user = new WP_Theme_JSON_Gutenberg( $config, 'custom' );
+
+ return static::$user;
+ }
}
diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php
index 860cf9a8bf412a..4698ead7f9bc30 100644
--- a/lib/compat/wordpress-6.2/default-filters.php
+++ b/lib/compat/wordpress-6.2/default-filters.php
@@ -17,6 +17,9 @@
* @package gutenberg
*/
-add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' );
-add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' );
-add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' );
+/**
+ * When backporting to core, the existing filters hooked to WP_Theme_JSON_Resolver::clean_cached_data()
+ * need to be removed.
+ */
+add_action( 'start_previewing_theme', '_gutenberg_clean_theme_json_caches' );
+add_action( 'switch_theme', '_gutenberg_clean_theme_json_caches' );
diff --git a/lib/compat/wordpress-6.2/edit-form-blocks.php b/lib/compat/wordpress-6.2/edit-form-blocks.php
new file mode 100644
index 00000000000000..5031550abc1541
--- /dev/null
+++ b/lib/compat/wordpress-6.2/edit-form-blocks.php
@@ -0,0 +1,24 @@
+get_stylesheet( array( 'variables' ), $origins );
+ $types = array_diff( $types, array( 'variables' ) );
+ }
+
+ /*
+ * For the remaining types (presets, styles), we do consider origins:
*
- * @param WP_Upgrader $upgrader Instance of WP_Upgrader class.
- * @param array $options Metadata that identifies the data that is updated.
+ * - themes without theme.json: only the classes for the presets defined by core
+ * - themes with theme.json: the presets and styles classes, both from core and the theme
*/
- function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgrader, $options ) {
- // The cache only needs cleaning when the active theme was updated.
- if (
- 'update' === $options['action'] &&
- 'theme' === $options['type'] &&
- ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) )
- ) {
- wp_theme_has_theme_json_clean_cache();
+ $styles_rest = '';
+ if ( ! empty( $types ) ) {
+ /*
+ * We only use the default, theme, and custom origins.
+ * This is because styles for blocks origin are added
+ * at a later phase (render cycle) so we only render the ones in use.
+ * @see wp_add_global_styles_for_blocks
+ */
+ $origins = array( 'default', 'theme', 'custom' );
+ if ( ! $supports_theme_json ) {
+ $origins = array( 'default' );
}
+ $styles_rest = $tree->get_stylesheet( $types, $origins );
}
+ $stylesheet = $styles_variables . $styles_rest;
+ if ( $can_use_cached ) {
+ wp_cache_set( $cache_key, $stylesheet, $cache_group );
+ }
+ return $stylesheet;
+}
+
+/**
+ * Function to get the settings resulting of merging core, theme, and user data.
+ *
+ * @param array $path Path to the specific setting to retrieve. Optional.
+ * If empty, will return all settings.
+ * @param array $context {
+ * Metadata to know where to retrieve the $path from. Optional.
+ *
+ * @type string $block_name Which block to retrieve the settings from.
+ * If empty, it'll return the settings for the global context.
+ * @type string $origin Which origin to take data from.
+ * Valid values are 'all' (core, theme, and user) or 'base' (core and theme).
+ * If empty or unknown, 'all' is used.
+ * }
+ *
+ * @return array The settings to retrieve.
+ */
+function gutenberg_get_global_settings( $path = array(), $context = array() ) {
+ if ( ! empty( $context['block_name'] ) ) {
+ $new_path = array( 'blocks', $context['block_name'] );
+ foreach ( $path as $subpath ) {
+ $new_path[] = $subpath;
+ }
+ $path = $new_path;
+ }
+
+ // This is the default value when no origin is provided or when it is 'all'.
+ $origin = 'custom';
+ if (
+ ! wp_theme_has_theme_json() ||
+ ( isset( $context['origin'] ) && 'base' === $context['origin'] )
+ ) {
+ $origin = 'theme';
+ }
+
+ $cache_group = 'theme_json';
+ $cache_key = 'gutenberg_get_global_settings_' . $origin;
+ $settings = wp_cache_get( $cache_key, $cache_group );
+
+ if ( false === $settings || WP_DEBUG ) {
+ $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings();
+ wp_cache_set( $cache_key, $settings, $cache_group );
+ }
+
+ return _wp_array_get( $settings, $path, $settings );
+}
+
+/**
+ * Private function to clean the caches used by gutenberg_get_global_settings method.
+ *
+ * @access private
+ */
+function _gutenberg_clean_theme_json_caches() {
+ wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' );
+ wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' );
+ wp_cache_delete( 'gutenberg_get_global_settings_custom', 'theme_json' );
+ wp_cache_delete( 'gutenberg_get_global_settings_theme', 'theme_json' );
+ WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data();
+}
+
+/**
+ * Tell the cache mechanisms not to persist theme.json data across requests.
+ * The data stored under this cache group:
+ *
+ * - wp_theme_has_theme_json
+ * - gutenberg_get_global_settings
+ * - gutenberg_get_global_stylesheet
+ *
+ * There is some hooks consumers can use to modify parts
+ * of the theme.json logic.
+ * See https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
+ *
+ * The rationale to make this cache group non persistent is to make sure derived data
+ * from theme.json is always fresh from the potential modifications done via hooks
+ * that can use dynamic data (modify the stylesheet depending on some option,
+ * or settings depending on user permissions, etc.).
+ *
+ * A different alternative considered was to invalidate the cache upon certain
+ * events such as options add/update/delete, user meta, etc.
+ * It was judged not enough, hence this approach.
+ * See https://github.com/WordPress/gutenberg/pull/45372
+ */
+function _gutenberg_add_non_persistent_theme_json_cache_group() {
+ wp_cache_add_non_persistent_groups( 'theme_json' );
}
+add_action( 'plugins_loaded', '_gutenberg_add_non_persistent_theme_json_cache_group' );
diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php
index 023a79e3db95fb..12f7afda3b4d5d 100644
--- a/lib/compat/wordpress-6.2/rest-api.php
+++ b/lib/compat/wordpress-6.2/rest-api.php
@@ -23,6 +23,15 @@ function gutenberg_register_rest_pattern_directory() {
}
add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' );
+/**
+ * Registers the block patterns REST API routes.
+ */
+function gutenberg_register_rest_block_patterns() {
+ $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_2();
+ $block_patterns->register_routes();
+}
+add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' );
+
/**
* Add extra collection params to pattern directory requests.
*
@@ -83,3 +92,27 @@ function gutenberg_pattern_directory_collection_params_6_2( $query_params ) {
return $query_params;
}
add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' );
+
+/**
+ * Registers the Global Styles REST API routes.
+ */
+function gutenberg_register_global_styles_endpoints() {
+ $editor_settings = new Gutenberg_REST_Global_Styles_Controller_6_2();
+ $editor_settings->register_routes();
+}
+add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );
+
+/**
+ * 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 ) {
+ $response->data['status'] = wp_is_block_theme() ? 'inactive' : 'active';
+
+ return $response;
+}
+add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
diff --git a/lib/compat/wordpress-6.2/script-loader.php b/lib/compat/wordpress-6.2/script-loader.php
index 9d67cbd3ef1ad8..e08bfa6e46ca8e 100644
--- a/lib/compat/wordpress-6.2/script-loader.php
+++ b/lib/compat/wordpress-6.2/script-loader.php
@@ -63,3 +63,82 @@ static function () use ( $style ) {
$priority
);
}
+
+/**
+ * Sets the content assets for the block editor.
+ *
+ * Note for core merge: see inline comment on what's been updated.
+ */
+function gutenberg_resolve_assets_override() {
+ global $pagenow;
+
+ $script_handles = array();
+ // Note for core merge: only 'wp-edit-blocks' should be in this array.
+ $style_handles = array(
+ 'wp-edit-blocks',
+ );
+
+ if ( current_theme_supports( 'wp-block-styles' ) ) {
+ $style_handles[] = 'wp-block-library-theme';
+ }
+
+ if ( 'widgets.php' === $pagenow || 'customize.php' === $pagenow ) {
+ $style_handles[] = 'wp-widgets';
+ $style_handles[] = 'wp-edit-widgets';
+ }
+
+ $block_registry = WP_Block_Type_Registry::get_instance();
+
+ foreach ( $block_registry->get_all_registered() as $block_type ) {
+ // In older WordPress versions, like 6.0, these properties are not defined.
+ if ( isset( $block_type->style_handles ) && is_array( $block_type->style_handles ) ) {
+ $style_handles = array_merge( $style_handles, $block_type->style_handles );
+ }
+
+ if ( isset( $block_type->editor_style_handles ) && is_array( $block_type->editor_style_handles ) ) {
+ $style_handles = array_merge( $style_handles, $block_type->editor_style_handles );
+ }
+
+ if ( isset( $block_type->script_handles ) && is_array( $block_type->script_handles ) ) {
+ $script_handles = array_merge( $script_handles, $block_type->script_handles );
+ }
+ }
+
+ $style_handles = array_unique( $style_handles );
+ $done = wp_styles()->done;
+
+ ob_start();
+
+ // We do not need reset styles for the iframed editor.
+ wp_styles()->done = array( 'wp-reset-editor-styles' );
+ wp_styles()->do_items( $style_handles );
+ wp_styles()->done = $done;
+
+ $styles = ob_get_clean();
+
+ $script_handles = array_unique( $script_handles );
+ $done = wp_scripts()->done;
+
+ ob_start();
+
+ wp_scripts()->done = array();
+ wp_scripts()->do_items( $script_handles );
+ wp_scripts()->done = $done;
+
+ $scripts = ob_get_clean();
+
+ return array(
+ 'styles' => $styles,
+ 'scripts' => $scripts,
+ );
+}
+
+add_filter(
+ 'block_editor_settings_all',
+ function( $settings ) {
+ // We must override what core is passing now.
+ $settings['__unstableResolvedAssets'] = gutenberg_resolve_assets_override();
+ return $settings;
+ },
+ 100
+);
diff --git a/lib/compat/wordpress-6.2/site-editor.php b/lib/compat/wordpress-6.2/site-editor.php
new file mode 100644
index 00000000000000..b6246e49c6d113
--- /dev/null
+++ b/lib/compat/wordpress-6.2/site-editor.php
@@ -0,0 +1,24 @@
+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
new file mode 100644
index 00000000000000..79d55206449472
--- /dev/null
+++ b/lib/compat/wordpress-6.2/theme.php
@@ -0,0 +1,23 @@
+is_block_theme() ) {
+ set_theme_mod( 'wp_legacy_sidebars', $wp_registered_sidebars );
+ }
+}
+add_action( 'switch_theme', 'gutenberg_set_legacy_sidebars', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/widgets.php b/lib/compat/wordpress-6.2/widgets.php
new file mode 100644
index 00000000000000..19591ae64607e3
--- /dev/null
+++ b/lib/compat/wordpress-6.2/widgets.php
@@ -0,0 +1,32 @@
+ array( 'mobile' ),
),
+ '__experimentalBlockInspectorTabs' => array(
+ 'description' => __( 'Block inspector tab display overrides.', 'gutenberg' ),
+ 'type' => 'object',
+ 'context' => array( 'post-editor', 'site-editor', 'widgets-editor' ),
+ ),
+
+ '__experimentalBlockInspectorAnimation' => array(
+ 'description' => __( 'Whether to enable animation when showing and hiding the block inspector.', 'gutenberg' ),
+ 'type' => 'object',
+ 'context' => array( 'site-editor' ),
+ ),
+
'alignWide' => array(
'description' => __( 'Enable/Disable Wide/Full Alignments.', 'gutenberg' ),
'type' => 'boolean',
diff --git a/lib/experimental/class-wp-theme-json-gutenberg.php b/lib/experimental/class-wp-theme-json-gutenberg.php
deleted file mode 100644
index a16697dff07d07..00000000000000
--- a/lib/experimental/class-wp-theme-json-gutenberg.php
+++ /dev/null
@@ -1,31 +0,0 @@
-get( 'TextDomain' ) );
+ if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) {
+ $theme_json_file = static::get_file_path_from_theme( 'theme.json' );
+ $wp_theme = wp_get_theme();
+ if ( '' !== $theme_json_file ) {
+ $theme_json_data = static::read_json_file( $theme_json_file );
+ $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) );
+ } else {
+ $theme_json_data = array();
+ }
$theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $theme_json_data );
/**
@@ -48,17 +54,21 @@ public static function get_theme_data( $deprecated = array(), $settings = array(
$theme_json_data = $theme_json->get_data();
static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data );
- if ( wp_get_theme()->parent() ) {
+ if ( $wp_theme->parent() ) {
// Get parent theme.json.
- $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) );
- $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) );
- $parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data );
- $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );
-
- // Merge the child theme.json into the parent theme.json.
- // The child theme takes precedence over the parent.
- $parent_theme->merge( static::$theme );
- static::$theme = $parent_theme;
+ $parent_theme_json_file = static::get_file_path_from_theme( 'theme.json', true );
+ if ( '' !== $parent_theme_json_file ) {
+ $parent_theme_json_data = static::read_json_file( $parent_theme_json_file );
+ $parent_theme_json_data = gutenberg_add_registered_webfonts_to_theme_json( $parent_theme_json_data );
+ $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data );
+
+ /*
+ * Merge the child theme.json into the parent theme.json.
+ * The child theme takes precedence over the parent.
+ */
+ $parent_theme->merge( static::$theme );
+ static::$theme = $parent_theme;
+ }
}
}
@@ -72,7 +82,7 @@ public static function get_theme_data( $deprecated = array(), $settings = array(
* So we take theme supports, transform it to theme.json shape
* and merge the static::$theme upon that.
*/
- $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() );
+ $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( gutenberg_get_legacy_theme_supports_for_theme_json() );
if ( ! wp_theme_has_theme_json() ) {
if ( ! isset( $theme_support_data['settings']['color'] ) ) {
$theme_support_data['settings']['color'] = array();
@@ -164,47 +174,4 @@ private static function remove_JSON_comments( $array ) {
return $array;
}
- /**
- * Returns the data merged from multiple origins.
- *
- * There are three sources of data (origins) for a site:
- * default, theme, and custom. The custom's has higher priority
- * than the theme's, and the theme's higher than default's.
- *
- * Unlike the getters {@link get_core_data},
- * {@link get_theme_data}, and {@link get_user_data},
- * this method returns data after it has been merged
- * with the previous origins. This means that if the same piece of data
- * is declared in different origins (user, theme, and core),
- * the last origin overrides the previous.
- *
- * For example, if the user has set a background color
- * for the paragraph block, and the theme has done it as well,
- * the user preference wins.
- *
- * @since 5.8.0
- * @since 5.9.0 Added user data, removed the `$settings` parameter,
- * added the `$origin` parameter.
- *
- * @param string $origin Optional. To what level should we merge data.
- * Valid values are 'theme' or 'custom'. Default 'custom'.
- * @return WP_Theme_JSON
- */
- public static function get_merged_data( $origin = 'custom' ) {
- if ( is_array( $origin ) ) {
- _deprecated_argument( __FUNCTION__, '5.9' );
- }
-
- $result = new WP_Theme_JSON_Gutenberg();
- $result->merge( static::get_core_data() );
- $result->merge( static::get_block_data() );
- $result->merge( static::get_theme_data() );
- if ( 'custom' === $origin ) {
- $result->merge( static::get_user_data() );
- }
- // Generate the default spacing sizes presets.
- $result->set_spacing_sizes();
-
- return $result;
- }
}
diff --git a/lib/experimental/class-wp-webfonts.php b/lib/experimental/class-wp-webfonts.php
index e40aca9e8671f8..0c0cb06a27ffb3 100644
--- a/lib/experimental/class-wp-webfonts.php
+++ b/lib/experimental/class-wp-webfonts.php
@@ -178,7 +178,9 @@ public function enqueue_webfont( $font_family_name ) {
*/
public static function get_font_slug( $to_convert ) {
if ( is_array( $to_convert ) ) {
- if ( isset( $to_convert['font-family'] ) ) {
+ if ( isset( $to_convert['slug'] ) ) {
+ return $to_convert['slug'];
+ } elseif ( isset( $to_convert['font-family'] ) ) {
$to_convert = $to_convert['font-family'];
} elseif ( isset( $to_convert['fontFamily'] ) ) {
$to_convert = $to_convert['fontFamily'];
@@ -188,6 +190,11 @@ public static function get_font_slug( $to_convert ) {
}
}
+ // If the font-family is a comma-separated list (example: "Inter, sans-serif" ), use just the first font.
+ if ( strpos( $to_convert, ',' ) !== false ) {
+ $to_convert = explode( ',', $to_convert )[0];
+ }
+
return sanitize_title( $to_convert );
}
diff --git a/lib/experimental/html/class-wp-html-span.php b/lib/experimental/html/class-wp-html-span.php
new file mode 100644
index 00000000000000..39e603662b17b9
--- /dev/null
+++ b/lib/experimental/html/class-wp-html-span.php
@@ -0,0 +1,52 @@
+start = $start;
+ $this->end = $end;
+ }
+}
diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php
index 6e72fc38ff4118..affbb6fb27b5c1 100644
--- a/lib/experimental/html/class-wp-html-tag-processor.php
+++ b/lib/experimental/html/class-wp-html-tag-processor.php
@@ -180,6 +180,25 @@
* @since 6.2.0
*/
class WP_HTML_Tag_Processor {
+ /**
+ * The maximum number of bookmarks allowed to exist at
+ * any given time.
+ *
+ * @see set_bookmark();
+ * @since 6.2.0
+ * @var int
+ */
+ const MAX_BOOKMARKS = 10;
+
+ /**
+ * Maximum number of times seek() can be called.
+ * Prevents accidental infinite loops.
+ *
+ * @see seek()
+ * @since 6.2.0
+ * @var int
+ */
+ const MAX_SEEK_OPS = 1000;
/**
* The HTML document to parse.
@@ -221,6 +240,14 @@ class WP_HTML_Tag_Processor {
*/
private $sought_match_offset;
+ /**
+ * Whether to visit tag closers, e.g. , when walking an input document.
+ *
+ * @since 6.2.0
+ * @var boolean
+ */
+ private $stop_on_tag_closers;
+
/**
* The updated HTML document.
*
@@ -276,6 +303,29 @@ class WP_HTML_Tag_Processor {
*/
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
+ */
+ private $tag_ends_at;
+
+ /**
+ * Whether the current tag is an opening tag, e.g.
, or a closing tag, e.g.
.
+ *
+ * @var boolean
+ */
+ private $is_closing_tag;
+
/**
* Lazily-built index of attributes found within an HTML tag, keyed by the attribute name.
*
@@ -318,11 +368,11 @@ class WP_HTML_Tag_Processor {
*
* Example:
*
- * // Add the `WP-block-group` class, remove the `WP-group` class.
- * $class_changes = [
+ * // Add the `wp-block-group` class, remove the `wp-group` class.
+ * $classname_updates = [
* // Indexed by a comparable class name
- * 'wp-block-group' => new WP_Class_Name_Operation( 'WP-block-group', WP_Class_Name_Operation::ADD ),
- * 'wp-group' => new WP_Class_Name_Operation( 'WP-group', WP_Class_Name_Operation::REMOVE )
+ * 'wp-block-group' => WP_HTML_Tag_Processor::ADD_CLASS,
+ * 'wp-group' => WP_HTML_Tag_Processor::REMOVE_CLASS
* ];
*
*
@@ -331,6 +381,15 @@ class WP_HTML_Tag_Processor {
*/
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[]
+ */
+ private $bookmarks = array();
+
const ADD_CLASS = true;
const REMOVE_CLASS = false;
const SKIP_CLASS = null;
@@ -365,6 +424,16 @@ class WP_HTML_Tag_Processor {
*/
private $attribute_updates = array();
+ /**
+ * Tracks how many times we've performed a `seek()`
+ * so that we can prevent accidental infinite loops.
+ *
+ * @see seek
+ * @since 6.2.0
+ * @var int
+ */
+ private $seek_count = 0;
+
/**
* Constructor.
*
@@ -412,7 +481,16 @@ public function next_tag( $query = null ) {
return false;
}
- $this->parse_tag_opener_attributes();
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
+
+ $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes );
+ if ( false === $tag_ends_at ) {
+ return false;
+ }
+ $this->tag_ends_at = $tag_ends_at;
+ $this->parsed_bytes = $tag_ends_at;
if ( $this->matches() ) {
++$already_found;
@@ -439,6 +517,123 @@ public function next_tag( $query = null ) {
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 thing.
+ *
+ * Example:
+ * ```
+ *
One
Two
Three
+ * ^^^^
+ * want to note this last item
+ *
+ * $p = new WP_HTML_Tag_Processor( $html );
+ * $in_list = false;
+ * while ( $p->next_tag( [ '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' );
+ * }
+ * }
+ * ```
+ *
+ * Because bookmarks maintain their position they don't
+ * expose any internal offsets for the HTML document
+ * and can't be used with normal string functions.
+ *
+ * Because bookmarks allocate memory and require processing
+ * for every applied update they are limited and require
+ * a name. They should not be created inside a loop.
+ *
+ * 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.
+ *
+ * @param string $name Identifies this particular bookmark.
+ * @return false|void
+ * @throws Exception Throws on invalid bookmark name if WP_DEBUG set.
+ */
+ 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 ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( "Tried to jump to a non-existent HTML bookmark {$name}." );
+ }
+ return false;
+ }
+
+ $this->bookmarks[ $name ] = new WP_HTML_Span(
+ $this->tag_name_starts_at - 1,
+ $this->tag_ends_at
+ );
+
+ return true;
+ }
+
+
+ /**
+ * Removes a bookmark if you no longer need to use it.
+ *
+ * Releasing a bookmark frees up the small performance
+ * overhead they require, mainly in the form of compute
+ * costs when modifying the document.
+ *
+ * @param string $name Name of the bookmark to remove.
+ * @return bool
+ */
+ public function release_bookmark( $name ) {
+ if ( ! array_key_exists( $name, $this->bookmarks ) ) {
+ return false;
+ }
+
+ unset( $this->bookmarks[ $name ] );
+
+ return true;
+ }
+
+
/**
* Skips the contents of the title and textarea tags until an appropriate
* tag closer is found.
@@ -495,7 +690,9 @@ private function skip_rcdata( $tag_name ) {
continue;
}
- $this->skip_tag_closer_attributes();
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
$at = $this->parsed_bytes;
if ( $at >= strlen( $this->html ) ) {
return false;
@@ -620,12 +817,14 @@ private function skip_script_data() {
if ( $is_closing ) {
$this->parsed_bytes = $at;
- $this->skip_tag_closer_attributes();
-
if ( $this->parsed_bytes >= $doc_length ) {
return false;
}
+ while ( $this->parse_next_attribute() ) {
+ continue;
+ }
+
if ( '>' === $html[ $this->parsed_bytes ] ) {
++$this->parsed_bytes;
return true;
@@ -656,6 +855,13 @@ private function parse_next_tag() {
return false;
}
+ if ( '/' === $this->html[ $at + 1 ] ) {
+ $this->is_closing_tag = true;
+ $at++;
+ } else {
+ $this->is_closing_tag = false;
+ }
+
/*
* HTML tag names must start with [a-zA-Z] otherwise they are not tags.
* For example, "<3" is rendered as text, not a tag opener. This means
@@ -777,35 +983,12 @@ private function parse_next_tag() {
return false;
}
- /**
- * Parses all attributes of the current tag.
- *
- * @since 6.2.0
- */
- private function parse_tag_opener_attributes() {
- while ( $this->parse_next_attribute() ) {
- continue;
- }
- }
-
- /**
- * Skips all attributes of the current tag.
- *
- * @since 6.2.0
- */
- private function skip_tag_closer_attributes() {
- while ( $this->parse_next_attribute( 'tag-closer' ) ) {
- continue;
- }
- }
-
/**
* Parses the next attribute.
*
- * @param string $context tag-opener or tag-closer.
* @since 6.2.0
*/
- private function parse_next_attribute( $context = 'tag-opener' ) {
+ private function parse_next_attribute() {
// Skip whitespace and slashes.
$this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes );
if ( $this->parsed_bytes >= strlen( $this->html ) ) {
@@ -872,7 +1055,7 @@ private function parse_next_attribute( $context = 'tag-opener' ) {
return false;
}
- if ( 'tag-opener' !== $context ) {
+ if ( $this->is_closing_tag ) {
return true;
}
@@ -914,6 +1097,8 @@ private function after_tag() {
$this->apply_attributes_updates();
$this->tag_name_starts_at = null;
$this->tag_name_length = null;
+ $this->tag_ends_at = null;
+ $this->is_closing_tag = null;
$this->attributes = array();
}
@@ -1074,9 +1259,77 @@ private function apply_attributes_updates() {
$this->updated_bytes = $diff->end;
}
+ foreach ( $this->bookmarks as $bookmark ) {
+ /**
+ * As we loop through $this->attribute_updates, we keep comparing
+ * $bookmark->start and $bookmark->end to $diff->start. We can't
+ * change it and still expect the correct result, so let's accumulate
+ * the deltas separately and apply them all at once after the loop.
+ */
+ $head_delta = 0;
+ $tail_delta = 0;
+
+ foreach ( $this->attribute_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->attribute_updates = array();
}
+ /**
+ * Move the current pointer 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.
+ *
+ * @param string $bookmark_name Jump to the place in the document identified by this bookmark name.
+ * @return bool
+ * @throws Exception Throws on invalid bookmark name if WP_DEBUG set.
+ */
+ public function seek( $bookmark_name ) {
+ if ( ! array_key_exists( $bookmark_name, $this->bookmarks ) ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( 'Invalid bookmark name' );
+ }
+ return false;
+ }
+
+ if ( ++$this->seek_count > self::MAX_SEEK_OPS ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ throw new Exception( 'Too many calls to seek() - this can lead to performance issues.' );
+ }
+ 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->parsed_bytes = $this->bookmarks[ $bookmark_name ]->start;
+ $this->updated_bytes = $this->parsed_bytes;
+ $this->updated_html = substr( $this->html, 0, $this->updated_bytes );
+ return $this->next_tag();
+ }
+
/**
* Sort function to arrange objects with a start property in ascending order.
*
@@ -1159,6 +1412,25 @@ public function get_tag() {
return strtoupper( $tag_name );
}
+ /**
+ * Indicates if the current tag token is a tag closer.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( '' );
+ * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] );
+ * $p->is_tag_closer() === false;
+ *
+ * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] );
+ * $p->is_tag_closer() === true;
+ *
+ *
+ * @return bool
+ */
+ public function is_tag_closer() {
+ return $this->is_closing_tag;
+ }
+
/**
* Updates or creates a new attribute on the currently matched tag with the value passed.
*
@@ -1175,8 +1447,8 @@ public function get_tag() {
* @throws Exception When WP_DEBUG is true and the attribute name is invalid.
*/
public function set_attribute( $name, $value ) {
- if ( null === $this->tag_name_starts_at ) {
- return;
+ if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
+ return false;
}
/*
@@ -1216,7 +1488,7 @@ public function set_attribute( $name, $value ) {
']~Ssu',
$name
) ) {
- if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+ if ( WP_DEBUG ) {
throw new Exception( 'Invalid attribute name' );
}
@@ -1286,8 +1558,8 @@ public function set_attribute( $name, $value ) {
* @param string $name The attribute name to remove.
*/
public function remove_attribute( $name ) {
- if ( ! isset( $this->attributes[ $name ] ) ) {
- return;
+ if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) {
+ return false;
}
/*
@@ -1316,6 +1588,10 @@ public function remove_attribute( $name ) {
* @param string $class_name The class name to add.
*/
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;
}
@@ -1329,6 +1605,10 @@ public function add_class( $class_name ) {
* @param string $class_name The class name to remove.
*/
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;
}
@@ -1354,45 +1634,31 @@ public function __toString() {
* @return string The processed HTML.
*/
public function get_updated_html() {
- // Short-circuit if there are no updates to apply.
+ // Short-circuit if there are no new updates to apply.
if ( ! count( $this->classname_updates ) && ! count( $this->attribute_updates ) ) {
return $this->updated_html . substr( $this->html, $this->updated_bytes );
}
- /*
- * Parsing is in progress – let's apply the attribute updates without moving on to the next tag.
- *
- * In practice:
- * 1. Apply the attributes updates to the original HTML
- * 2. Replace the original HTML with the updated HTML
- * 3. Point this tag processor to the current tag name's end in that updated HTML
- */
-
- // Find tag name's end in the updated markup.
- $markup_updated_up_to_a_tag_name_end = $this->updated_html . substr( $this->html, $this->updated_bytes, $this->tag_name_starts_at + $this->tag_name_length - $this->updated_bytes );
- $updated_tag_name_ends_at = strlen( $markup_updated_up_to_a_tag_name_end );
- $updated_tag_name_starts_at = $updated_tag_name_ends_at - $this->tag_name_length;
+ // Otherwise: apply the updates, rewind before the current tag, and parse it again.
+ $delta_between_updated_html_end_and_current_tag_end = substr(
+ $this->html,
+ $this->updated_bytes,
+ $this->tag_name_starts_at + $this->tag_name_length - $this->updated_bytes
+ );
+ $updated_html_up_to_current_tag_name_end = $this->updated_html . $delta_between_updated_html_end_and_current_tag_end;
- // Apply attributes updates.
- $this->updated_html = $markup_updated_up_to_a_tag_name_end;
- $this->updated_bytes = $this->tag_name_starts_at + $this->tag_name_length;
+ // 1. Apply the attributes updates to the original HTML
$this->class_name_updates_to_attributes_updates();
$this->apply_attributes_updates();
- // Replace $this->html with the updated markup.
- $this->html = $this->updated_html . substr( $this->html, $this->updated_bytes );
-
- // Rewind this processor to the tag name's end.
- $this->tag_name_starts_at = $updated_tag_name_starts_at;
- $this->parsed_bytes = $updated_tag_name_ends_at;
-
- // Restore the previous version of the updated_html as we are not finished with the current_tag yet.
- $this->updated_html = $markup_updated_up_to_a_tag_name_end;
- $this->updated_bytes = $updated_tag_name_ends_at;
+ // 2. Replace the original HTML with the updated HTML
+ $this->html = $this->updated_html . substr( $this->html, $this->updated_bytes );
+ $this->updated_html = $updated_html_up_to_current_tag_name_end;
+ $this->updated_bytes = strlen( $this->updated_html );
- // Parse the attributes in the updated markup.
- $this->attributes = array();
- $this->parse_tag_opener_attributes();
+ // 3. Point this tag processor at the original tag opener and consume it
+ $this->parsed_bytes = strlen( $updated_html_up_to_current_tag_name_end ) - $this->tag_name_length - 2;
+ $this->next_tag();
return $this->html;
}
@@ -1407,6 +1673,7 @@ public function get_updated_html() {
*
* @type string|null $tag_name Which tag to find, or `null` for "any 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.