' . $matches[3];
},
- $block_content
+ $content_without_layout_classes
);
+
+ // Add layout classes to inner wrapper.
+ if ( ! empty( $layout_classes ) ) {
+ $processor = new WP_HTML_Tag_Processor( $updated_content );
+ if ( $processor->next_tag( array( 'class_name' => 'wp-block-group__inner-container' ) ) ) {
+ foreach ( $layout_classes as $class_name ) {
+ $processor->add_class( $class_name );
+ }
+ }
+ $updated_content = $processor->get_updated_html();
+ }
return $updated_content;
}
diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php
index f47c649fff081..e2cd7c1587b9f 100644
--- a/src/wp-includes/block-template-utils.php
+++ b/src/wp-includes/block-template-utils.php
@@ -125,11 +125,11 @@ function get_default_block_template_types() {
),
'single' => array(
'title' => _x( 'Single Posts', 'Template name' ),
- 'description' => __( 'Displays single posts on your website unless a custom template has been applied to that post or a dedicated template exists.' ),
+ 'description' => __( 'Displays a single post on your website unless a custom template has been applied to that post or a dedicated template exists.' ),
),
'page' => array(
'title' => _x( 'Pages', 'Template name' ),
- 'description' => __( 'Display all static pages unless a custom template has been applied or a dedicated template exists.' ),
+ 'description' => __( 'Displays a static page unless a custom template has been applied to that page or a dedicated template exists.' ),
),
'archive' => array(
'title' => _x( 'All Archives', 'Template name' ),
diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php
index 431e2b015332c..c9b3d51c32b60 100644
--- a/src/wp-includes/blocks.php
+++ b/src/wp-includes/blocks.php
@@ -59,6 +59,7 @@ function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) {
}
$field_mappings = array(
+ 'viewModule' => 'view-module',
'editorScript' => 'editor-script',
'script' => 'script',
'viewScript' => 'view-script',
@@ -121,6 +122,82 @@ function get_block_asset_url( $path ) {
return plugins_url( basename( $path ), $path );
}
+/**
+ * Finds a script handle for the selected block metadata field. It detects
+ * when a path to file was provided and finds a corresponding asset file
+ * with details necessary to register the script under automatically
+ * generated handle name. It returns unprocessed script handle otherwise.
+ *
+ * @since 5.5.0
+ * @since 6.1.0 Added `$index` parameter.
+ *
+ * @param array $metadata Block metadata.
+ * @param string $field_name Field name to pick from metadata.
+ * @param int $index Optional. Index of the script to register when multiple items passed.
+ * Default 0.
+ * @return string|false Script handle provided directly or created through
+ * script's registration, or false on failure.
+ */
+function register_block_module_handle( $metadata, $field_name, $index = 0 ) {
+ if ( empty( $metadata[ $field_name ] ) ) {
+ return false;
+ }
+
+ $module_handle = $metadata[ $field_name ];
+ if ( is_array( $module_handle ) ) {
+ if ( empty( $module_handle[ $index ] ) ) {
+ return false;
+ }
+ $module_handle = $module_handle[ $index ];
+ }
+
+ $module_path = remove_block_asset_path_prefix( $module_handle );
+ if ( $module_handle === $module_path ) {
+ return $module_handle;
+ }
+
+ $path = dirname( $metadata['file'] );
+ $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) );
+ $module_handle = generate_block_asset_handle( $metadata['name'], $field_name, $index );
+ $module_asset_path = wp_normalize_path(
+ realpath( $module_asset_raw_path )
+ );
+
+ if ( empty( $module_asset_path ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: 1: Asset file location, 2: Field name, 3: Block name. */
+ __( 'The asset file (%1$s) for the "%2$s" defined in "%3$s" block definition is missing.' ),
+ $module_asset_raw_path,
+ $field_name,
+ $metadata['name']
+ ),
+ '6.5.0'
+ );
+ return false;
+ }
+
+ $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) );
+ $module_uri = get_block_asset_url( $module_path_norm );
+
+ $module_asset = require $module_asset_path;
+ $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array();
+ $result = wp_register_module(
+ $module_handle,
+ $module_uri,
+ $module_dependencies,
+ isset( $module_asset['version'] ) ? $module_asset['version'] : false,
+ );
+
+ if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $module_dependencies, true ) ) {
+ // script translations?
+ wp_set_script_translations( $module_handle, $metadata['textdomain'] );
+ }
+
+ return $module_handle;
+}
+
/**
* Finds a script handle for the selected block metadata field. It detects
* when a path to file was provided and finds a corresponding asset file
@@ -500,6 +577,41 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
}
}
+ $module_fields = array(
+ 'viewModule' => 'view_module_handles',
+ );
+ foreach ( $module_fields as $metadata_field_name => $settings_field_name ) {
+
+ if ( ! empty( $settings[ $metadata_field_name ] ) ) {
+ $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ];
+ }
+ if ( ! empty( $metadata[ $metadata_field_name ] ) ) {
+ $modules = $metadata[ $metadata_field_name ];
+ $processed_modules = array();
+ if ( is_array( $modules ) ) {
+ for ( $index = 0; $index < count( $modules ); $index++ ) {
+ $result = register_block_module_handle(
+ $metadata,
+ $metadata_field_name,
+ $index
+ );
+ if ( $result ) {
+ $processed_modules[] = $result;
+ }
+ }
+ } else {
+ $result = register_block_module_handle(
+ $metadata,
+ $metadata_field_name
+ );
+ if ( $result ) {
+ $processed_modules[] = $result;
+ }
+ }
+ $settings[ $settings_field_name ] = $processed_modules;
+ }
+ }
+
$style_fields = array(
'editorStyle' => 'editor_style_handles',
'style' => 'style_handles',
diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php
index 9caa366032a75..f2de01b9d3db3 100644
--- a/src/wp-includes/class-wp-block-type.php
+++ b/src/wp-includes/class-wp-block-type.php
@@ -209,6 +209,30 @@ class WP_Block_Type {
*/
public $view_script_handles = array();
+ /**
+ * Block type editor only module handles.
+ *
+ * @since 6.5.0
+ * @var string[]
+ */
+ public $editor_module_handles = array();
+
+ /**
+ * Block type front end and editor module handles.
+ *
+ * @since 6.5.0
+ * @var string[]
+ */
+ public $module_handles = array();
+
+ /**
+ * Block type front end only module handles.
+ *
+ * @since 6.5.0
+ * @var string[]
+ */
+ public $view_module_handles = array();
+
/**
* Block type editor only style handles.
*
diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php
index 65d3af6a12f1a..b02f415e631d0 100644
--- a/src/wp-includes/class-wp-block.php
+++ b/src/wp-includes/class-wp-block.php
@@ -274,6 +274,18 @@ public function render( $options = array() ) {
}
}
+ if ( ( ! empty( $this->block_type->module_handles ) ) ) {
+ foreach ( $this->block_type->module_handles as $module_handle ) {
+ wp_enqueue_module( $module_handle );
+ }
+ }
+
+ if ( ! empty( $this->block_type->view_module_handles ) ) {
+ foreach ( $this->block_type->view_module_handles as $view_module_handle ) {
+ wp_enqueue_module( $view_module_handle );
+ }
+ }
+
if ( ( ! empty( $this->block_type->style_handles ) ) ) {
foreach ( $this->block_type->style_handles as $style_handle ) {
wp_enqueue_style( $style_handle );
diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php
index 24f82510bd782..d12eea0e5f326 100644
--- a/src/wp-includes/class-wp-customize-manager.php
+++ b/src/wp-includes/class-wp-customize-manager.php
@@ -3079,25 +3079,26 @@ public function trash_changeset_post( $post ) {
return false;
}
+ $previous_status = $post->post_status;
+
/** This filter is documented in wp-includes/post.php */
- $check = apply_filters( 'pre_trash_post', null, $post );
+ $check = apply_filters( 'pre_trash_post', null, $post, $previous_status );
if ( null !== $check ) {
return $check;
}
/** This action is documented in wp-includes/post.php */
- do_action( 'wp_trash_post', $post_id );
+ do_action( 'wp_trash_post', $post_id, $previous_status );
- add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
+ add_post_meta( $post_id, '_wp_trash_meta_status', $previous_status );
add_post_meta( $post_id, '_wp_trash_meta_time', time() );
- $old_status = $post->post_status;
$new_status = 'trash';
$wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
clean_post_cache( $post->ID );
$post->post_status = $new_status;
- wp_transition_post_status( $new_status, $old_status, $post );
+ wp_transition_post_status( $new_status, $previous_status, $post );
/** This action is documented in wp-includes/post.php */
do_action( "edit_post_{$post->post_type}", $post->ID, $post );
@@ -3119,7 +3120,7 @@ public function trash_changeset_post( $post ) {
wp_trash_post_comments( $post_id );
/** This action is documented in wp-includes/post.php */
- do_action( 'trashed_post', $post_id );
+ do_action( 'trashed_post', $post_id, $previous_status );
return $post;
}
diff --git a/src/wp-includes/class-wp-script-modules.php b/src/wp-includes/class-wp-script-modules.php
new file mode 100644
index 0000000000000..4c542211b86bf
--- /dev/null
+++ b/src/wp-includes/class-wp-script-modules.php
@@ -0,0 +1,352 @@
+ $deps Optional. An array of module
+ * identifiers of the dependencies of
+ * this module. The dependencies can
+ * be strings or arrays. If they are
+ * arrays, they need an `id` key with
+ * the module identifier, and can
+ * contain an `import` key with either
+ * `static` or `dynamic`. By default,
+ * dependencies that don't contain an
+ * `import` key are considered static.
+ * @param string|false|null $version Optional. String specifying the
+ * module version number. Defaults to
+ * false. It is added to the URL as a
+ * query string for cache busting
+ * purposes. If $version is set to
+ * false, the version number is the
+ * currently installed WordPress
+ * version. If $version is set to
+ * null, no version is added.
+ */
+ public function register( $module_id, $src, $deps = array(), $version = false ) {
+ if ( ! isset( $this->registered[ $module_id ] ) ) {
+ $dependencies = array();
+ foreach ( $deps as $dependency ) {
+ if ( is_array( $dependency ) ) {
+ if ( ! isset( $dependency['id'] ) ) {
+ _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' );
+ continue;
+ }
+ $dependencies[] = array(
+ 'id' => $dependency['id'],
+ 'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static',
+ );
+ } elseif ( is_string( $dependency ) ) {
+ $dependencies[] = array(
+ 'id' => $dependency,
+ 'import' => 'static',
+ );
+ } else {
+ _doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' );
+ }
+ }
+
+ $this->registered[ $module_id ] = array(
+ 'src' => $src,
+ 'version' => $version,
+ 'enqueue' => isset( $this->enqueued_before_registered[ $module_id ] ),
+ 'dependencies' => $dependencies,
+ 'enqueued' => false,
+ 'preloaded' => false,
+ );
+ }
+ }
+
+ /**
+ * Marks the module to be enqueued in the page the next time
+ * `prints_enqueued_modules` is called.
+ *
+ * If a src is provided and the module has not been registered yet, it will be
+ * registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $module_id The identifier of the module.
+ * Should be unique. It will be used
+ * in the final import map.
+ * @param string $src Optional. Full URL of the module,
+ * or path of the module relative to
+ * the WordPress root directory. If
+ * it is provided and the module has
+ * not been registered yet, it will be
+ * registered.
+ * @param array $deps Optional. An array of module
+ * identifiers of the dependencies of
+ * this module. The dependencies can
+ * be strings or arrays. If they are
+ * arrays, they need an `id` key with
+ * the module identifier, and can
+ * contain an `import` key with either
+ * `static` or `dynamic`. By default,
+ * dependencies that don't contain an
+ * `import` key are considered static.
+ * @param string|false|null $version Optional. String specifying the
+ * module version number. Defaults to
+ * false. It is added to the URL as a
+ * query string for cache busting
+ * purposes. If $version is set to
+ * false, the version number is the
+ * currently installed WordPress
+ * version. If $version is set to
+ * null, no version is added.
+ */
+ public function enqueue( $module_id, $src = '', $deps = array(), $version = false ) {
+ if ( isset( $this->registered[ $module_id ] ) ) {
+ $this->registered[ $module_id ]['enqueue'] = true;
+ } elseif ( $src ) {
+ $this->register( $module_id, $src, $deps, $version );
+ $this->registered[ $module_id ]['enqueue'] = true;
+ } else {
+ $this->enqueued_before_registered[ $module_id ] = true;
+ }
+ }
+
+ /**
+ * Unmarks the module so it will no longer be enqueued in the page.
+ *
+ * @since 6.5.0
+ *
+ * @param string $module_id The identifier of the module.
+ */
+ public function dequeue( $module_id ) {
+ if ( isset( $this->registered[ $module_id ] ) ) {
+ $this->registered[ $module_id ]['enqueue'] = false;
+ }
+ unset( $this->enqueued_before_registered[ $module_id ] );
+ }
+
+ /**
+ * Adds the hooks to print the import map, enqueued modules and module
+ * preloads.
+ *
+ * It adds the actions to print the enqueued modules and module preloads to
+ * both `wp_head` and `wp_footer` because in classic themes, the modules
+ * used by the theme and plugins will likely be able to be printed in the
+ * `head`, but the ones used by the blocks will need to be enqueued in the
+ * `footer`.
+ *
+ * As all modules are deferred and dependencies are handled by the browser,
+ * the order of the modules is not important, but it's still better to print
+ * the ones that are available when the `wp_head` is rendered, so the browser
+ * starts downloading those as soon as possible.
+ *
+ * The import map is also printed in the footer to be able to include the
+ * dependencies of all the modules, including the ones printed in the footer.
+ *
+ * @since 6.5.0
+ */
+ public function add_hooks() {
+ add_action( 'wp_head', array( $this, 'print_enqueued_modules' ) );
+ add_action( 'wp_head', array( $this, 'print_module_preloads' ) );
+ add_action( 'wp_footer', array( $this, 'print_enqueued_modules' ) );
+ add_action( 'wp_footer', array( $this, 'print_module_preloads' ) );
+ add_action( 'wp_footer', array( $this, 'print_import_map' ) );
+ }
+
+ /**
+ * Prints the enqueued modules using script tags with type="module"
+ * attributes.
+ *
+ * If a enqueued module has already been printed, it will not be printed again
+ * on subsequent calls to this function.
+ *
+ * @since 6.5.0
+ */
+ public function print_enqueued_modules() {
+ foreach ( $this->get_marked_for_enqueue() as $module_id => $module ) {
+ if ( false === $module['enqueued'] ) {
+ // Mark it as enqueued so it doesn't get enqueued again.
+ $this->registered[ $module_id ]['enqueued'] = true;
+
+ wp_print_script_tag(
+ array(
+ 'type' => 'module',
+ 'src' => $this->get_versioned_src( $module ),
+ 'id' => $module_id . '-js-module',
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Prints the the static dependencies of the enqueued modules using link tags
+ * with rel="modulepreload" attributes.
+ *
+ * If a module is marked for enqueue, it will not be preloaded. If a preloaded
+ * module has already been printed, it will not be printed again on subsequent
+ * calls to this function.
+ *
+ * @since 6.5.0
+ */
+ public function print_module_preloads() {
+ foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ), array( 'static' ) ) as $module_id => $module ) {
+ // Don't preload if it's marked for enqueue or has already been preloaded.
+ if ( true !== $module['enqueue'] && false === $module['preloaded'] ) {
+ // Mark it as preloaded so it doesn't get preloaded again.
+ $this->registered[ $module_id ]['preloaded'] = true;
+
+ echo sprintf(
+ '',
+ esc_url( $this->get_versioned_src( $module ) ),
+ esc_attr( $module_id . '-js-modulepreload' )
+ );
+ }
+ }
+ }
+
+ /**
+ * Prints the import map using a script tag with a type="importmap" attribute.
+ *
+ * @since 6.5.0
+ */
+ public function print_import_map() {
+ $import_map = $this->get_import_map();
+ if ( ! empty( $import_map['imports'] ) ) {
+ wp_print_inline_script_tag(
+ wp_json_encode( $import_map, JSON_HEX_TAG | JSON_HEX_AMP ),
+ array(
+ 'type' => 'importmap',
+ 'id' => 'wp-importmap',
+ )
+ );
+ }
+ }
+
+ /**
+ * Returns the import map array.
+ *
+ * @since 6.5.0
+ *
+ * @return array Array with an `imports` key mapping to an array of module identifiers and their respective URLs,
+ * including the version query.
+ */
+ private function get_import_map() {
+ $imports = array();
+ foreach ( $this->get_dependencies( array_keys( $this->get_marked_for_enqueue() ) ) as $module_id => $module ) {
+ $imports[ $module_id ] = $this->get_versioned_src( $module );
+ }
+ return array( 'imports' => $imports );
+ }
+
+ /**
+ * Retrieves the list of modules marked for enqueue.
+ *
+ * @since 6.5.0
+ *
+ * @return array Modules marked for enqueue, keyed by module identifier.
+ */
+ private function get_marked_for_enqueue() {
+ $enqueued = array();
+ foreach ( $this->registered as $module_id => $module ) {
+ if ( true === $module['enqueue'] ) {
+ $enqueued[ $module_id ] = $module;
+ }
+ }
+ return $enqueued;
+ }
+
+ /**
+ * Retrieves all the dependencies for the given module identifiers, filtered
+ * by import types.
+ *
+ * It will consolidate an array containing a set of unique dependencies based
+ * on the requested import types: 'static', 'dynamic', or both. This method is
+ * recursive and also retrieves dependencies of the dependencies.
+ *
+ * @since 6.5.0
+ *
+ * @param array $module_ids The identifiers of the modules for which to gather dependencies.
+ * @param array $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both.
+ * Default is both.
+ * @return array List of dependencies, keyed by module identifier.
+ */
+ private function get_dependencies( $module_ids, $import_types = array( 'static', 'dynamic' ) ) {
+ return array_reduce(
+ $module_ids,
+ function ( $dependency_modules, $module_id ) use ( $import_types ) {
+ $dependencies = array();
+ foreach ( $this->registered[ $module_id ]['dependencies'] as $dependency ) {
+ if (
+ in_array( $dependency['import'], $import_types, true ) &&
+ isset( $this->registered[ $dependency['id'] ] ) &&
+ ! isset( $dependency_modules[ $dependency['id'] ] )
+ ) {
+ $dependencies[ $dependency['id'] ] = $this->registered[ $dependency['id'] ];
+ }
+ }
+ return array_merge( $dependency_modules, $dependencies, $this->get_dependencies( array_keys( $dependencies ), $import_types ) );
+ },
+ array()
+ );
+ }
+
+ /**
+ * Gets the versioned URL for a module src.
+ *
+ * If $version is set to false, the version number is the currently installed
+ * WordPress version. If $version is set to null, no version is added.
+ * Otherwise, the string passed in $version is used.
+ *
+ * @since 6.5.0
+ *
+ * @param array $module The module.
+ * @return string The module src with a version if relevant.
+ */
+ private function get_versioned_src( array $module ) {
+ $args = array();
+ if ( false === $module['version'] ) {
+ $args['ver'] = get_bloginfo( 'version' );
+ } elseif ( null !== $module['version'] ) {
+ $args['ver'] = $module['version'];
+ }
+ if ( $args ) {
+ return add_query_arg( $args, $module['src'] );
+ }
+ return $module['src'];
+ }
+}
diff --git a/src/wp-includes/class-wp-tax-query.php b/src/wp-includes/class-wp-tax-query.php
index 38841c42b06de..58e53ea4a2c75 100644
--- a/src/wp-includes/class-wp-tax-query.php
+++ b/src/wp-includes/class-wp-tax-query.php
@@ -505,7 +505,7 @@ public function get_sql_for_clause( &$clause, $parent_query ) {
protected function find_compatible_table_alias( $clause, $parent_query ) {
$alias = false;
- // Sanity check. Only IN queries use the JOIN syntax.
+ // Confidence check. Only IN queries use the JOIN syntax.
if ( ! isset( $clause['operator'] ) || 'IN' !== $clause['operator'] ) {
return $alias;
}
diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php
index 90710a5c1b2f4..44db8364f1092 100644
--- a/src/wp-includes/class-wp-theme-json-resolver.php
+++ b/src/wp-includes/class-wp-theme-json-resolver.php
@@ -312,9 +312,6 @@ public static function get_theme_data( $deprecated = array(), $options = array()
}
$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;
-
// Allow themes to enable link color setting via theme_support.
if ( current_theme_supports( 'link-color' ) ) {
$theme_support_data['settings']['color']['link'] = true;
@@ -327,6 +324,11 @@ public static function get_theme_data( $deprecated = array(), $options = array()
$theme_support_data['settings']['border']['style'] = true;
$theme_support_data['settings']['border']['width'] = true;
}
+
+ // Allow themes to enable appearance tools via theme_support.
+ if ( current_theme_supports( 'appearance-tools' ) ) {
+ $theme_support_data['settings']['appearanceTools'] = true;
+ }
}
$with_theme_supports = new WP_Theme_JSON( $theme_support_data );
$with_theme_supports->merge( static::$theme );
diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php
index 9fea12a8d3e99..37f4d11ce9fb4 100644
--- a/src/wp-includes/class-wp-theme-json.php
+++ b/src/wp-includes/class-wp-theme-json.php
@@ -344,6 +344,8 @@ class WP_Theme_JSON {
* @since 6.3.0 Added support for `typography.textColumns`, removed `layout.definitions`.
* @since 6.4.0 Added support for `layout.allowEditing`, `background.backgroundImage`,
* `typography.writingMode`, `lightbox.enabled` and `lightbox.allowEditing`.
+ * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize` and
+ * `background.backgroundSize`.
* @var array
*/
const VALID_SETTINGS = array(
@@ -351,6 +353,7 @@ class WP_Theme_JSON {
'useRootPaddingAwareAlignments' => null,
'background' => array(
'backgroundImage' => null,
+ 'backgroundSize' => null,
),
'border' => array(
'color' => null,
@@ -380,9 +383,10 @@ class WP_Theme_JSON {
'minHeight' => null,
),
'layout' => array(
- 'contentSize' => null,
- 'wideSize' => null,
- 'allowEditing' => null,
+ 'contentSize' => null,
+ 'wideSize' => null,
+ 'allowEditing' => null,
+ 'allowCustomContentAndWideSize' => null,
),
'lightbox' => array(
'enabled' => null,
@@ -571,10 +575,12 @@ public static function get_element_class_name( $element ) {
* @since 6.0.0
* @since 6.2.0 Added `dimensions.minHeight` and `position.sticky`.
* @since 6.4.0 Added `background.backgroundImage`.
+ * @since 6.5.0 Added `background.backgroundSize`.
* @var array
*/
const APPEARANCE_TOOLS_OPT_INS = array(
array( 'background', 'backgroundImage' ),
+ array( 'background', 'backgroundSize' ),
array( 'border', 'color' ),
array( 'border', 'radius' ),
array( 'border', 'style' ),
diff --git a/src/wp-includes/class-wpdb.php b/src/wp-includes/class-wpdb.php
index baba39d91f4d9..d9186b91f463f 100644
--- a/src/wp-includes/class-wpdb.php
+++ b/src/wp-includes/class-wpdb.php
@@ -154,7 +154,7 @@ class wpdb {
protected $result;
/**
- * Cached column info, for sanity checking data before inserting.
+ * Cached column info, for confidence checking data before inserting.
*
* @since 4.2.0
*
@@ -172,7 +172,7 @@ class wpdb {
protected $table_charset = array();
/**
- * Whether text fields in the current query need to be sanity checked.
+ * Whether text fields in the current query need to be confidence checked.
*
* @since 4.2.0
*
@@ -1927,7 +1927,7 @@ public function flush() {
mysqli_free_result( $this->result );
$this->result = null;
- // Sanity check before using the handle.
+ // Confidence check before using the handle.
if ( empty( $this->dbh ) || ! ( $this->dbh instanceof mysqli ) ) {
return;
}
@@ -3516,7 +3516,7 @@ protected function check_safe_collation( $query ) {
return false;
}
- // If any of the columns don't have one of these collations, it needs more sanity checking.
+ // If any of the columns don't have one of these collations, it needs more confidence checking.
$safe_collations = array(
'utf8_bin',
'utf8_general_ci',
diff --git a/src/wp-includes/cron.php b/src/wp-includes/cron.php
index c1837e6051c1e..aadc22b7eb17d 100644
--- a/src/wp-includes/cron.php
+++ b/src/wp-includes/cron.php
@@ -874,7 +874,7 @@ function spawn_cron( $gmt_time = 0 ) {
return false;
}
- // Sanity check.
+ // Confidence check.
$crons = wp_get_ready_cron_jobs();
if ( empty( $crons ) ) {
return false;
diff --git a/src/wp-includes/fonts.php b/src/wp-includes/fonts.php
index 306364bdc8099..87503c275f390 100644
--- a/src/wp-includes/fonts.php
+++ b/src/wp-includes/fonts.php
@@ -22,7 +22,7 @@
* @type array $font_variation {
* @type string $font-family The font-family property.
* @type string|string[] $src The URL(s) to each resource containing the font data.
- * @type string $font_style Optional. The font-style property. Default 'normal'.
+ * @type string $font-style Optional. The font-style property. Default 'normal'.
* @type string $font-weight Optional. The font-weight property. Default '400'.
* @type string $font-display Optional. The font-display property. Default 'fallback'.
* @type string $ascent-override Optional. The ascent-override property.
diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php
index bc150c8a537cb..83938306b0774 100644
--- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -3506,7 +3506,7 @@ function convert_smilies( $text ) {
$textarr = preg_split( '/(<.*>)/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); // Capture the tags as well as in between.
$stop = count( $textarr ); // Loop stuff.
- // Ignore proessing of specific tags.
+ // Ignore processing of specific tags.
$tags_to_ignore = 'code|pre|style|script|textarea';
$ignore_block_element = '';
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
index d270157d1f886..c4ecb81f66160 100644
--- a/src/wp-includes/functions.php
+++ b/src/wp-includes/functions.php
@@ -4284,7 +4284,7 @@ function _wp_die_process_input( $message, $title = '', $args = array() ) {
}
/**
- * Encodes a variable into JSON, with some sanity checks.
+ * Encodes a variable into JSON, with some confidence checks.
*
* @since 4.1.0
* @since 5.3.0 No longer handles support for PHP < 5.6.
@@ -4300,7 +4300,7 @@ function _wp_die_process_input( $message, $title = '', $args = array() ) {
function wp_json_encode( $value, $flags = 0, $depth = 512 ) {
$json = json_encode( $value, $flags, $depth );
- // If json_encode() was successful, no need to do more sanity checking.
+ // If json_encode() was successful, no need to do more confidence checking.
if ( false !== $json ) {
return $json;
}
@@ -4315,7 +4315,7 @@ function wp_json_encode( $value, $flags = 0, $depth = 512 ) {
}
/**
- * Performs sanity checks on data that shall be encoded to JSON.
+ * Performs confidence checks on data that shall be encoded to JSON.
*
* @ignore
* @since 4.1.0
diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php
index 1258f1f4e37f1..acca33be1e844 100644
--- a/src/wp-includes/global-styles-and-settings.php
+++ b/src/wp-includes/global-styles-and-settings.php
@@ -222,7 +222,13 @@ function wp_get_global_stylesheet( $types = array() ) {
* @see wp_add_global_styles_for_blocks
*/
$origins = array( 'default', 'theme', 'custom' );
- if ( ! $supports_theme_json ) {
+ /*
+ * If the theme doesn't have theme.json but supports both appearance tools and color palette,
+ * the 'theme' origin should be included so color palette presets are also output.
+ */
+ if ( ! $supports_theme_json && ( current_theme_supports( 'appearance-tools' ) || current_theme_supports( 'border' ) ) && current_theme_supports( 'editor-color-palette' ) ) {
+ $origins = array( 'default', 'theme' );
+ } elseif ( ! $supports_theme_json ) {
$origins = array( 'default' );
}
$styles_rest = $tree->get_stylesheet( $types, $origins );
diff --git a/src/wp-includes/html-api/class-wp-html-open-elements.php b/src/wp-includes/html-api/class-wp-html-open-elements.php
index 55c4d3a663c5b..1234abcb9dfe4 100644
--- a/src/wp-includes/html-api/class-wp-html-open-elements.php
+++ b/src/wp-includes/html-api/class-wp-html-open-elements.php
@@ -129,7 +129,7 @@ public function has_element_in_specific_scope( $tag_name, $termination_list ) {
}
if ( in_array( $node->node_name, $termination_list, true ) ) {
- return true;
+ return false;
}
}
@@ -166,18 +166,22 @@ public function has_element_in_scope( $tag_name ) {
* Returns whether a particular element is in list item scope.
*
* @since 6.4.0
+ * @since 6.5.0 Implemented: no longer throws on every invocation.
*
* @see https://html.spec.whatwg.org/#has-an-element-in-list-item-scope
*
- * @throws WP_HTML_Unsupported_Exception Always until this function is implemented.
- *
* @param string $tag_name Name of tag to check.
* @return bool Whether given element is in scope.
*/
public function has_element_in_list_item_scope( $tag_name ) {
- throw new WP_HTML_Unsupported_Exception( 'Cannot process elements depending on list item scope.' );
-
- return false; // The linter requires this unreachable code until the function is implemented and can return.
+ return $this->has_element_in_specific_scope(
+ $tag_name,
+ array(
+ // There are more elements that belong here which aren't currently supported.
+ 'OL',
+ 'UL',
+ )
+ );
}
/**
@@ -375,10 +379,22 @@ public function walk_down() {
* see WP_HTML_Open_Elements::walk_down().
*
* @since 6.4.0
+ * @since 6.5.0 Accepts $above_this_node to start traversal above a given node, if it exists.
+ *
+ * @param ?WP_HTML_Token $above_this_node Start traversing above this node, if provided and if the node exists.
*/
- public function walk_up() {
+ public function walk_up( $above_this_node = null ) {
+ $has_found_node = null === $above_this_node;
+
for ( $i = count( $this->stack ) - 1; $i >= 0; $i-- ) {
- yield $this->stack[ $i ];
+ $node = $this->stack[ $i ];
+
+ if ( ! $has_found_node ) {
+ $has_found_node = $node === $above_this_node;
+ continue;
+ }
+
+ yield $node;
}
}
diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php
index e46c368c702d4..cce26a60c5350 100644
--- a/src/wp-includes/html-api/class-wp-html-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-processor.php
@@ -100,15 +100,19 @@
* The following list specifies the HTML tags that _are_ supported:
*
* - Containers: ADDRESS, BLOCKQUOTE, DETAILS, DIALOG, DIV, FOOTER, HEADER, MAIN, MENU, SPAN, SUMMARY.
- * - Form elements: BUTTON, FIELDSET, SEARCH.
+ * - Custom elements: All custom elements are supported. :)
+ * - Form elements: BUTTON, DATALIST, FIELDSET, LABEL, LEGEND, METER, PROGRESS, SEARCH.
* - Formatting elements: B, BIG, CODE, EM, FONT, I, SMALL, STRIKE, STRONG, TT, U.
* - Heading elements: H1, H2, H3, H4, H5, H6, HGROUP.
* - Links: A.
- * - Lists: DL.
- * - Media elements: FIGCAPTION, FIGURE, IMG.
+ * - Lists: DD, DL, DT, LI, OL, LI.
+ * - Media elements: AUDIO, CANVAS, FIGCAPTION, FIGURE, IMG, MAP, PICTURE, VIDEO.
* - Paragraph: P.
- * - Sectioning elements: ARTICLE, ASIDE, NAV, SECTION
- * - Deprecated elements: CENTER, DIR
+ * - Phrasing elements: ABBR, BDI, BDO, CITE, DATA, DEL, DFN, INS, MARK, OUTPUT, Q, SAMP, SUB, SUP, TIME, VAR.
+ * - Sectioning elements: ARTICLE, ASIDE, NAV, SECTION.
+ * - Templating elements: SLOT.
+ * - Text decoration: RUBY.
+ * - Deprecated elements: ACRONYM, BLINK, CENTER, DIR, ISINDEX, MULTICOL, NEXTID, SPACER.
*
* ### Supported markup
*
@@ -644,10 +648,12 @@ private function step_in_body() {
case '+MAIN':
case '+MENU':
case '+NAV':
+ case '+OL':
case '+P':
case '+SEARCH':
case '+SECTION':
case '+SUMMARY':
+ case '+UL':
if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
$this->close_a_p_element();
}
@@ -681,9 +687,11 @@ private function step_in_body() {
case '-MAIN':
case '-MENU':
case '-NAV':
+ case '-OL':
case '-SEARCH':
case '-SECTION':
case '-SUMMARY':
+ case '-UL':
if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name ) ) {
// @todo Report parse error.
// Ignore the token.
@@ -751,6 +759,109 @@ private function step_in_body() {
$this->state->stack_of_open_elements->pop_until( '(internal: H1 through H6 - do not use)' );
return true;
+ /*
+ * > A start tag whose tag name is "li"
+ * > A start tag whose tag name is one of: "dd", "dt"
+ */
+ case '+DD':
+ case '+DT':
+ case '+LI':
+ $this->state->frameset_ok = false;
+ $node = $this->state->stack_of_open_elements->current_node();
+ $is_li = 'LI' === $tag_name;
+
+ in_body_list_loop:
+ /*
+ * The logic for LI and DT/DD is the same except for one point: LI elements _only_
+ * close other LI elements, but a DT or DD element closes _any_ open DT or DD element.
+ */
+ if ( $is_li ? 'LI' === $node->node_name : ( 'DD' === $node->node_name || 'DT' === $node->node_name ) ) {
+ $node_name = $is_li ? 'LI' : $node->node_name;
+ $this->generate_implied_end_tags( $node_name );
+ if ( $node_name !== $this->state->stack_of_open_elements->current_node()->node_name ) {
+ // @todo Indicate a parse error once it's possible. This error does not impact the logic here.
+ }
+
+ $this->state->stack_of_open_elements->pop_until( $node_name );
+ goto in_body_list_done;
+ }
+
+ if (
+ 'ADDRESS' !== $node->node_name &&
+ 'DIV' !== $node->node_name &&
+ 'P' !== $node->node_name &&
+ $this->is_special( $node->node_name )
+ ) {
+ /*
+ * > If node is in the special category, but is not an address, div,
+ * > or p element, then jump to the step labeled done below.
+ */
+ goto in_body_list_done;
+ } else {
+ /*
+ * > Otherwise, set node to the previous entry in the stack of open elements
+ * > and return to the step labeled loop.
+ */
+ foreach ( $this->state->stack_of_open_elements->walk_up( $node ) as $item ) {
+ $node = $item;
+ break;
+ }
+ goto in_body_list_loop;
+ }
+
+ in_body_list_done:
+ if ( $this->state->stack_of_open_elements->has_p_in_button_scope() ) {
+ $this->close_a_p_element();
+ }
+
+ $this->insert_html_element( $this->state->current_token );
+ return true;
+
+ /*
+ * > An end tag whose tag name is "li"
+ * > An end tag whose tag name is one of: "dd", "dt"
+ */
+ case '-DD':
+ case '-DT':
+ case '-LI':
+ if (
+ /*
+ * An end tag whose tag name is "li":
+ * If the stack of open elements does not have an li element in list item scope,
+ * then this is a parse error; ignore the token.
+ */
+ (
+ 'LI' === $tag_name &&
+ ! $this->state->stack_of_open_elements->has_element_in_list_item_scope( 'LI' )
+ ) ||
+ /*
+ * An end tag whose tag name is one of: "dd", "dt":
+ * If the stack of open elements does not have an element in scope that is an
+ * HTML element with the same tag name as that of the token, then this is a
+ * parse error; ignore the token.
+ */
+ (
+ 'LI' !== $tag_name &&
+ ! $this->state->stack_of_open_elements->has_element_in_scope( $tag_name )
+ )
+ ) {
+ /*
+ * This is a parse error, ignore the token.
+ *
+ * @todo Indicate a parse error once it's possible.
+ */
+ return $this->step();
+ }
+
+ $this->generate_implied_end_tags( $tag_name );
+
+ if ( $tag_name !== $this->state->stack_of_open_elements->current_node()->node_name ) {
+ // @todo Indicate a parse error once it's possible. This error does not impact the logic here.
+ }
+
+ $this->state->stack_of_open_elements->pop_until( $tag_name );
+ return true;
+
/*
* > An end tag whose tag name is "p"
*/
@@ -830,41 +941,132 @@ private function step_in_body() {
$this->reconstruct_active_formatting_elements();
$this->insert_html_element( $this->state->current_token );
return true;
+ }
+ /*
+ * These tags require special handling in the 'in body' insertion mode
+ * but that handling hasn't yet been implemented.
+ *
+ * As the rules for each tag are implemented, the corresponding tag
+ * name should be removed from this list. An accompanying test should
+ * help ensure this list is maintained.
+ *
+ * @see Tests_HtmlApi_WpHtmlProcessor::test_step_in_body_fails_on_unsupported_tags
+ *
+ * Since this switch structure throws a WP_HTML_Unsupported_Exception, it's
+ * possible to handle "any other start tag" and "any other end tag" below,
+ * as that guarantees execution doesn't proceed for the unimplemented tags.
+ *
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
+ */
+ switch ( $tag_name ) {
+ case 'APPLET':
+ case 'AREA':
+ case 'BASE':
+ case 'BASEFONT':
+ case 'BGSOUND':
+ case 'BODY':
+ case 'BR':
+ case 'CAPTION':
+ case 'COL':
+ case 'COLGROUP':
+ case 'DD':
+ case 'DT':
+ case 'EMBED':
+ case 'FORM':
+ case 'FRAME':
+ case 'FRAMESET':
+ case 'HEAD':
+ case 'HR':
+ case 'HTML':
+ case 'IFRAME':
+ case 'INPUT':
+ case 'KEYGEN':
+ case 'LI':
+ case 'LINK':
+ case 'LISTING':
+ case 'MARQUEE':
+ case 'MATH':
+ case 'META':
+ case 'NOBR':
+ case 'NOEMBED':
+ case 'NOFRAMES':
+ case 'NOSCRIPT':
+ case 'OBJECT':
+ case 'OL':
+ case 'OPTGROUP':
+ case 'OPTION':
+ case 'PARAM':
+ case 'PLAINTEXT':
+ case 'PRE':
+ case 'RB':
+ case 'RP':
+ case 'RT':
+ case 'RTC':
+ case 'SARCASM':
+ case 'SCRIPT':
+ case 'SELECT':
+ case 'SOURCE':
+ case 'STYLE':
+ case 'SVG':
+ case 'TABLE':
+ case 'TBODY':
+ case 'TD':
+ case 'TEMPLATE':
+ case 'TEXTAREA':
+ case 'TFOOT':
+ case 'TH':
+ case 'THEAD':
+ case 'TITLE':
+ case 'TR':
+ case 'TRACK':
+ case 'UL':
+ case 'WBR':
+ case 'XMP':
+ $this->last_error = self::ERROR_UNSUPPORTED;
+ throw new WP_HTML_Unsupported_Exception( "Cannot process {$tag_name} element." );
+ }
+
+ if ( ! $this->is_tag_closer() ) {
/*
* > Any other start tag
*/
- case '+SPAN':
- $this->reconstruct_active_formatting_elements();
- $this->insert_html_element( $this->state->current_token );
- return true;
+ $this->reconstruct_active_formatting_elements();
+ $this->insert_html_element( $this->state->current_token );
+ return true;
+ } else {
+ /*
+ * > Any other end tag
+ */
/*
- * Any other end tag
+ * Find the corresponding tag opener in the stack of open elements, if
+ * it exists before reaching a special element, which provides a kind
+ * of boundary in the stack. For example, a `` should not
+ * close anything beyond its containing `P` or `DIV` element.
*/
- case '-SPAN':
- foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
- // > If node is an HTML element with the same tag name as the token, then:
- if ( $item->node_name === $tag_name ) {
- $this->generate_implied_end_tags( $tag_name );
+ foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) {
+ if ( $tag_name === $node->node_name ) {
+ break;
+ }
- // > If node is not the current node, then this is a parse error.
+ if ( self::is_special( $node->node_name ) ) {
+ // This is a parse error, ignore the token.
+ return $this->step();
+ }
+ }
- $this->state->stack_of_open_elements->pop_until( $tag_name );
- return true;
- }
+ $this->generate_implied_end_tags( $tag_name );
+ if ( $node !== $this->state->stack_of_open_elements->current_node() ) {
+ // @todo Record parse error: this error doesn't impact parsing.
+ }
- // > Otherwise, if node is in the special category, then this is a parse error; ignore the token, and return.
- if ( self::is_special( $item->node_name ) ) {
- return $this->step();
- }
+ foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) {
+ $this->state->stack_of_open_elements->pop();
+ if ( $node === $item ) {
+ return true;
}
- // Execution should not reach here; if it does then something went wrong.
- return false;
-
- default:
- $this->last_error = self::ERROR_UNSUPPORTED;
- throw new WP_HTML_Unsupported_Exception( "Cannot process {$tag_name} element." );
+ }
}
}
@@ -1128,6 +1330,9 @@ private function close_a_p_element() {
*/
private function generate_implied_end_tags( $except_for_this_element = null ) {
$elements_with_implied_end_tags = array(
+ 'DD',
+ 'DT',
+ 'LI',
'P',
);
@@ -1153,6 +1358,9 @@ private function generate_implied_end_tags( $except_for_this_element = null ) {
*/
private function generate_implied_end_tags_thoroughly() {
$elements_with_implied_end_tags = array(
+ 'DD',
+ 'DT',
+ 'LI',
'P',
);
@@ -1264,7 +1472,7 @@ private function run_adoption_agency_algorithm() {
// > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return.
if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) {
- $this->state->active_formatting_elements->remove_node( $formatting_element->bookmark_name );
+ $this->state->active_formatting_elements->remove_node( $formatting_element );
return;
}
diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php
index 224d8a18caf98..9cf301e95c11a 100644
--- a/src/wp-includes/media.php
+++ b/src/wp-includes/media.php
@@ -4133,7 +4133,7 @@ function _wp_image_editor_choose( $args = array() ) {
! call_user_func( array( $implementation, 'supports_mime_type' ), $args['output_mime_type'] )
) {
/*
- * This implementation supports the imput type but not the output type.
+ * This implementation supports the input type but not the output type.
* Keep looking to see if we can find an implementation that supports both.
*/
$supports_input = $implementation;
diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php
index d6b6bc6649cc9..25fefbb6211cb 100644
--- a/src/wp-includes/post.php
+++ b/src/wp-includes/post.php
@@ -3861,7 +3861,7 @@ function wp_untrash_post_comments( $post = null ) {
}
foreach ( $group_by_status as $status => $comments ) {
- // Sanity check. This shouldn't happen.
+ // Confidence check. This shouldn't happen.
if ( 'post-trashed' === $status ) {
$status = '0';
}
diff --git a/src/wp-includes/script-modules.php b/src/wp-includes/script-modules.php
new file mode 100644
index 0000000000000..52552317e4aa5
--- /dev/null
+++ b/src/wp-includes/script-modules.php
@@ -0,0 +1,116 @@
+add_hooks();
+ }
+ return $instance;
+}
+
+/**
+ * Registers the module if no module with that module identifier has already
+ * been registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $module_id The identifier of the module.
+ * Should be unique. It will be used
+ * in the final import map.
+ * @param string $src Full URL of the module, or path of
+ * the module relative to the
+ * WordPress root directory.
+ * @param array $deps Optional. An array of module
+ * identifiers of the dependencies of
+ * this module. The dependencies can
+ * be strings or arrays. If they are
+ * arrays, they need an `id` key with
+ * the module identifier, and can
+ * contain an `import` key with either
+ * `static` or `dynamic`. By default,
+ * dependencies that don't contain an
+ * `import` key are considered static.
+ * @param string|false|null $version Optional. String specifying the
+ * module version number. Defaults to
+ * false. It is added to the URL as a
+ * query string for cache busting
+ * purposes. If $version is set to
+ * false, the version number is the
+ * currently installed WordPress
+ * version. If $version is set to
+ * null, no version is added.
+ */
+function wp_register_module( $module_id, $src, $deps = array(), $version = false ) {
+ wp_modules()->register( $module_id, $src, $deps, $version );
+}
+
+/**
+ * Marks the module to be enqueued in the page.
+ *
+ * If a src is provided and the module has not been registered yet, it will be
+ * registered.
+ *
+ * @since 6.5.0
+ *
+ * @param string $module_id The identifier of the module.
+ * Should be unique. It will be used
+ * in the final import map.
+ * @param string $src Optional. Full URL of the module,
+ * or path of the module relative to
+ * the WordPress root directory. If
+ * it is provided and the module has
+ * not been registered yet, it will be
+ * registered.
+ * @param array $deps Optional. An array of module
+ * identifiers of the dependencies of
+ * this module. The dependencies can
+ * be strings or arrays. If they are
+ * arrays, they need an `id` key with
+ * the module identifier, and can
+ * contain an `import` key with either
+ * `static` or `dynamic`. By default,
+ * dependencies that don't contain an
+ * `import` key are considered static.
+ * @param string|false|null $version Optional. String specifying the
+ * module version number. Defaults to
+ * false. It is added to the URL as a
+ * query string for cache busting
+ * purposes. If $version is set to
+ * false, the version number is the
+ * currently installed WordPress
+ * version. If $version is set to
+ * null, no version is added.
+ */
+function wp_enqueue_module( $module_id, $src = '', $deps = array(), $version = false ) {
+ wp_modules()->enqueue( $module_id, $src, $deps, $version );
+}
+
+/**
+ * Unmarks the module so it is no longer enqueued in the page.
+ *
+ * @since 6.5.0
+ *
+ * @param string $module_id The identifier of the module.
+ */
+function wp_dequeue_module( $module_id ) {
+ wp_modules()->dequeue( $module_id );
+}
diff --git a/src/wp-includes/shortcodes.php b/src/wp-includes/shortcodes.php
index 24df21d4da11e..98cd8f71ddd02 100644
--- a/src/wp-includes/shortcodes.php
+++ b/src/wp-includes/shortcodes.php
@@ -504,7 +504,7 @@ function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) {
$element = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $element );
}
- // Looks like we found some crazy unfiltered HTML. Skipping it for sanity.
+ // Looks like we found some crazy unfiltered HTML. Skipping it for confidence.
$element = strtr( $element, $trans );
continue;
}
diff --git a/src/wp-includes/style-engine/class-wp-style-engine.php b/src/wp-includes/style-engine/class-wp-style-engine.php
index 121bac2d927ba..790e33f38c0e3 100644
--- a/src/wp-includes/style-engine/class-wp-style-engine.php
+++ b/src/wp-includes/style-engine/class-wp-style-engine.php
@@ -23,6 +23,7 @@
* @since 6.1.0
* @since 6.3.0 Added support for text-columns.
* @since 6.4.0 Added support for background.backgroundImage.
+ * @since 6.5.0 Added support for background.backgroundPosition and background.backgroundRepeat.
*/
#[AllowDynamicProperties]
final class WP_Style_Engine {
@@ -48,14 +49,26 @@ final class WP_Style_Engine {
*/
const BLOCK_STYLE_DEFINITIONS_METADATA = array(
'background' => array(
- 'backgroundImage' => array(
+ 'backgroundImage' => array(
'property_keys' => array(
'default' => 'background-image',
),
'value_func' => array( self::class, 'get_url_or_value_css_declaration' ),
'path' => array( 'background', 'backgroundImage' ),
),
- 'backgroundSize' => array(
+ 'backgroundPosition' => array(
+ 'property_keys' => array(
+ 'default' => 'background-position',
+ ),
+ 'path' => array( 'background', 'backgroundPosition' ),
+ ),
+ 'backgroundRepeat' => array(
+ 'property_keys' => array(
+ 'default' => 'background-repeat',
+ ),
+ 'path' => array( 'background', 'backgroundRepeat' ),
+ ),
+ 'backgroundSize' => array(
'property_keys' => array(
'default' => 'background-size',
),
@@ -215,6 +228,9 @@ final class WP_Style_Engine {
'default' => 'font-size',
),
'path' => array( 'typography', 'fontSize' ),
+ 'css_vars' => array(
+ 'font-size' => '--wp--preset--font-size--$slug',
+ ),
'classnames' => array(
'has-$slug-font-size' => 'font-size',
),
@@ -223,6 +239,9 @@ final class WP_Style_Engine {
'property_keys' => array(
'default' => 'font-family',
),
+ 'css_vars' => array(
+ 'font-family' => '--wp--preset--font-family--$slug',
+ ),
'path' => array( 'typography', 'fontFamily' ),
'classnames' => array(
'has-$slug-font-family' => 'font-family',
diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php
index 59ec5345fe0cd..b64a2f08db3ee 100644
--- a/src/wp-includes/taxonomy.php
+++ b/src/wp-includes/taxonomy.php
@@ -2434,6 +2434,11 @@ function wp_insert_term( $term, $taxonomy, $args = array() ) {
$description = wp_unslash( $args['description'] );
$parent = (int) $args['parent'];
+ // Sanitization could clean the name to an empty string that must be checked again.
+ if ( '' === $name ) {
+ return new WP_Error( 'invalid_term_name', __( 'Invalid term name.' ) );
+ }
+
$slug_provided = ! empty( $args['slug'] );
if ( ! $slug_provided ) {
$slug = sanitize_title( $name );
@@ -2573,7 +2578,7 @@ function wp_insert_term( $term, $taxonomy, $args = array() ) {
$tt_id = (int) $wpdb->insert_id;
/*
- * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
+ * Confidence check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
* an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
* and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
* are not fired.
diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php
index 1638128b4cf26..040275e0f8377 100644
--- a/src/wp-includes/theme.php
+++ b/src/wp-includes/theme.php
@@ -2617,12 +2617,15 @@ function get_theme_starter_content() {
* @since 5.6.0 The `post-formats` feature warns if no array is passed as the second parameter.
* @since 5.8.0 The `widgets-block-editor` feature enables the Widgets block editor.
* @since 6.0.0 The `html5` feature warns if no array is passed as the second parameter.
+ * @since 6.5.0 The `appearance-tools` feature enables a few design tools for blocks,
+ * see `WP_Theme_JSON::APPEARANCE_TOOLS_OPT_INS` for a complete list.
*
* @global array $_wp_theme_features
*
* @param string $feature The feature being added. Likely core values include:
* - 'admin-bar'
* - 'align-wide'
+ * - 'appearance-tools'
* - 'automatic-feed-links'
* - 'core-block-patterns'
* - 'custom-background'
diff --git a/src/wp-settings.php b/src/wp-settings.php
index 38b03ecf7268f..c354747f32148 100644
--- a/src/wp-settings.php
+++ b/src/wp-settings.php
@@ -365,6 +365,8 @@
require ABSPATH . WPINC . '/fonts/class-wp-font-face-resolver.php';
require ABSPATH . WPINC . '/fonts/class-wp-font-face.php';
require ABSPATH . WPINC . '/fonts.php';
+require ABSPATH . WPINC . '/class-wp-script-modules.php';
+require ABSPATH . WPINC . '/script-modules.php';
$GLOBALS['wp_embed'] = new WP_Embed();
diff --git a/tests/phpunit/tests/actions.php b/tests/phpunit/tests/actions.php
index 8b57382b9c545..e25183f75913d 100644
--- a/tests/phpunit/tests/actions.php
+++ b/tests/phpunit/tests/actions.php
@@ -229,35 +229,133 @@ public function test_action_args_with_php4_syntax() {
$this->assertSame( array( $val ), array_pop( $argsvar ) );
}
- public function test_action_priority() {
- $a = new MockAction();
+ /**
+ * @ticket 60193
+ *
+ * @dataProvider data_priority_callback_order_with_integers
+ * @dataProvider data_priority_callback_order_with_unhappy_path_nonintegers
+ *
+ * @covers ::do_action
+ *
+ * @param array $priorities {
+ * Indexed array of the priorities for the MockAction callbacks.
+ *
+ * @type mixed $0 Priority for 'action' callback.
+ * @type mixed $1 Priority for 'action2' callback.
+ * }
+ * @param array $expected_call_order An array of callback names in expected call order.
+ * @param string $expected_deprecation Optional. Deprecation message. Default ''.
+ */
+ public function test_priority_callback_order( $priorities, $expected_call_order, $expected_deprecation = '' ) {
+ $mock = new MockAction();
$hook_name = __FUNCTION__;
- add_action( $hook_name, array( &$a, 'action' ), 10 );
- add_action( $hook_name, array( &$a, 'action2' ), 9 );
+ if ( $expected_deprecation && PHP_VERSION_ID >= 80100 ) {
+ $this->expectDeprecation();
+ $this->expectDeprecationMessage( $expected_deprecation );
+ }
+
+ add_action( $hook_name, array( $mock, 'action' ), $priorities[0] );
+ add_action( $hook_name, array( $mock, 'action2' ), $priorities[1] );
do_action( $hook_name );
- // Two events, one per action.
- $this->assertSame( 2, $a->get_call_count() );
+ $this->assertSame( 2, $mock->get_call_count(), 'The number of call counts does not match' );
+
+ $actual_call_order = wp_list_pluck( $mock->get_events(), 'action' );
+ $this->assertSame( $expected_call_order, $actual_call_order, 'The action callback order does not match the expected order' );
+ }
- $expected = array(
- // 'action2' is called first because it has priority 9.
- array(
- 'action' => 'action2',
- 'hook_name' => $hook_name,
- 'tag' => $hook_name, // Back compat.
- 'args' => array( '' ),
+ /**
+ * Happy path data provider.
+ *
+ * @return array[]
+ */
+ public function data_priority_callback_order_with_integers() {
+ return array(
+ 'int DESC' => array(
+ 'priorities' => array( 10, 9 ),
+ 'expected_call_order' => array( 'action2', 'action' ),
),
- // 'action' is called second.
- array(
- 'action' => 'action',
- 'hook_name' => $hook_name,
- 'tag' => $hook_name, // Back compat.
- 'args' => array( '' ),
+ 'int ASC' => array(
+ 'priorities' => array( 9, 10 ),
+ 'expected_call_order' => array( 'action', 'action2' ),
),
);
+ }
+
+ /**
+ * Unhappy path data provider.
+ *
+ * @return array[]
+ */
+ public function data_priority_callback_order_with_unhappy_path_nonintegers() {
+ return array(
+ // Numbers as strings and floats.
+ 'int as string DESC' => array(
+ 'priorities' => array( '10', '9' ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ ),
+ 'int as string ASC' => array(
+ 'priorities' => array( '9', '10' ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'float DESC' => array(
+ 'priorities' => array( 10.0, 9.5 ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ 'expected_deprecation' => 'Implicit conversion from float 9.5 to int loses precision',
+ ),
+ 'float ASC' => array(
+ 'priorities' => array( 9.5, 10.0 ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ 'expected_deprecation' => 'Implicit conversion from float 9.5 to int loses precision',
+ ),
+ 'float as string DESC' => array(
+ 'priorities' => array( '10.0', '9.5' ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ ),
+ 'float as string ASC' => array(
+ 'priorities' => array( '9.5', '10.0' ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
- $this->assertSame( $expected, $a->get_events() );
+ // Non-numeric.
+ 'null' => array(
+ 'priorities' => array( null, null ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'bool DESC' => array(
+ 'priorities' => array( true, false ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ ),
+ 'bool ASC' => array(
+ 'priorities' => array( false, true ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'non-numerical string DESC' => array(
+ 'priorities' => array( 'test1', 'test2' ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'non-numerical string ASC' => array(
+ 'priorities' => array( 'test1', 'test2' ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'int, non-numerical string DESC' => array(
+ 'priorities' => array( 10, 'test' ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ ),
+ 'int, non-numerical string ASC' => array(
+ 'priorities' => array( 'test', 10 ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ 'float, non-numerical string DESC' => array(
+ 'priorities' => array( 10.0, 'test' ),
+ 'expected_call_order' => array( 'action2', 'action' ),
+ ),
+ 'float, non-numerical string ASC' => array(
+ 'priorities' => array( 'test', 10.0 ),
+ 'expected_call_order' => array( 'action', 'action2' ),
+ ),
+ );
}
/**
diff --git a/tests/phpunit/tests/ajax/wpAjaxImageEditor.php b/tests/phpunit/tests/ajax/wpAjaxImageEditor.php
index ac761c3520341..89745d645883c 100644
--- a/tests/phpunit/tests/ajax/wpAjaxImageEditor.php
+++ b/tests/phpunit/tests/ajax/wpAjaxImageEditor.php
@@ -52,7 +52,7 @@ public function testCropImageIntoLargerOne() {
$ret = wp_save_image( $id );
$this->assertObjectHasProperty( 'error', $ret );
- $this->assertEquals( 'Images cannot be scaled to a size larger than the original.', $ret->error );
+ $this->assertSame( 'Images cannot be scaled to a size larger than the original.', $ret->error );
}
/**
diff --git a/tests/phpunit/tests/ajax/wpAjaxInlineSave.php b/tests/phpunit/tests/ajax/wpAjaxInlineSave.php
index 2edd630de8c64..afb73e6dcff61 100644
--- a/tests/phpunit/tests/ajax/wpAjaxInlineSave.php
+++ b/tests/phpunit/tests/ajax/wpAjaxInlineSave.php
@@ -110,7 +110,7 @@ public function test_quick_edit_draft_should_not_set_publish_date() {
$this->assertSame( 'draft', $post->post_status );
- $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt );
+ $this->assertSame( '0000-00-00 00:00:00', $post->post_date_gmt );
// Set up a request.
$_POST['_inline_edit'] = wp_create_nonce( 'inlineeditnonce' );
@@ -142,7 +142,7 @@ public function test_quick_edit_draft_should_not_set_publish_date() {
$post_date = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $_POST['aa'], $_POST['mm'], $_POST['jj'], $_POST['hh'], $_POST['mn'], $_POST['ss'] );
- $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt );
+ $this->assertSame( '0000-00-00 00:00:00', $post->post_date_gmt );
}
/**
@@ -167,7 +167,7 @@ public function test_quick_edit_draft_should_set_publish_date() {
$this->assertSame( 'draft', $post->post_status );
- $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt );
+ $this->assertSame( '0000-00-00 00:00:00', $post->post_date_gmt );
// Set up a request.
$_POST['_inline_edit'] = wp_create_nonce( 'inlineeditnonce' );
@@ -197,6 +197,6 @@ public function test_quick_edit_draft_should_set_publish_date() {
$post = get_post( $post->ID );
- $this->assertEquals( '2020-09-11 19:20:11', $post->post_date_gmt );
+ $this->assertSame( '2020-09-11 19:20:11', $post->post_date_gmt );
}
}
diff --git a/tests/phpunit/tests/block-supports/layout.php b/tests/phpunit/tests/block-supports/layout.php
index df0abf9b49922..379c10fe2b566 100644
--- a/tests/phpunit/tests/block-supports/layout.php
+++ b/tests/phpunit/tests/block-supports/layout.php
@@ -252,4 +252,94 @@ public function data_layout_support_flag_renders_classnames_on_wrapper() {
),
);
}
+
+ /**
+ * Check that wp_restore_group_inner_container() restores the legacy inner container on the Group block.
+ *
+ * @ticket 60130
+ *
+ * @covers ::wp_restore_group_inner_container
+ *
+ * @dataProvider data_restore_group_inner_container
+ *
+ * @param array $args Dataset to test.
+ * @param string $expected_output The expected output.
+ */
+ public function test_restore_group_inner_container( $args, $expected_output ) {
+ $actual_output = wp_restore_group_inner_container( $args['block_content'], $args['block'] );
+ $this->assertEquals( $expected_output, $actual_output );
+ }
+
+ /**
+ * Data provider for test_restore_group_inner_container.
+ *
+ * @return array
+ */
+ public function data_restore_group_inner_container() {
+ return array(
+ 'group block with existing inner container' => array(
+ 'args' => array(
+ 'block_content' => '
', array( 'HTML', 'BODY', 'DD' ), 3 ),
+ 'DD and DT mutually close, LI self-closes (li 1)' => array( '
', array( 'HTML', 'BODY', 'DD', 'LI' ), 1 ),
+ 'DD and DT mutually close, LI self-closes (li 2)' => array( '
', array( 'HTML', 'BODY', 'DD', 'LI' ), 2 ),
// H1 - H6 close out _any_ H1 - H6 when encountering _any_ of H1 - H6, making this section surprising.
'EM inside H3 after unclosed P' => array( '
', array( 'HTML', 'BODY', 'DIV', 'IMG' ), 1 ),
);
}
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
index 7bd243d8dce9a..c1adf9a71a3f8 100644
--- a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
@@ -35,7 +35,7 @@ public function test_in_body_article_group_closes_open_p_element( $tag_name ) {
continue;
}
- $this->assertEquals(
+ $this->assertSame(
$tag_name,
$processor->get_tag(),
"Expected to find {$tag_name} but found {$processor->get_tag()} instead."
@@ -125,7 +125,7 @@ public function test_in_body_skips_unexpected_button_closer() {
$p = WP_HTML_Processor::create_fragment( '
Test
' );
$p->step();
- $this->assertEquals( 'DIV', $p->get_tag(), 'Did not stop at initial DIV tag.' );
+ $this->assertSame( 'DIV', $p->get_tag(), 'Did not stop at initial DIV tag.' );
$this->assertFalse( $p->is_tag_closer(), 'Did not find that initial DIV tag is an opener.' );
/*
@@ -133,7 +133,7 @@ public function test_in_body_skips_unexpected_button_closer() {
* It should be ignored as there's no BUTTON to close.
*/
$this->assertTrue( $p->step(), 'Found no further tags when it should have found the closing DIV' );
- $this->assertEquals( 'DIV', $p->get_tag(), "Did not skip unexpected BUTTON; stopped at {$p->get_tag()}." );
+ $this->assertSame( 'DIV', $p->get_tag(), "Did not skip unexpected BUTTON; stopped at {$p->get_tag()}." );
$this->assertTrue( $p->is_tag_closer(), 'Did not find that the terminal DIV tag is a closer.' );
}
@@ -224,6 +224,109 @@ public function test_in_body_button_with_button_in_scope_as_ancestor() {
$this->assertSame( array( 'HTML', 'BODY', 'BUTTON' ), $p->get_breadcrumbs(), 'Failed to produce expected DOM nesting for third button.' );
}
+ /**
+ * Verifies that H1 through H6 elements close an open P element.
+ *
+ * @ticket 60215
+ *
+ * @dataProvider data_heading_elements
+ *
+ * @param string $tag_name Name of H1 - H6 element under test.
+ */
+ public function test_in_body_heading_element_closes_open_p_tag( $tag_name ) {
+ $processor = WP_HTML_Processor::create_fragment(
+ "
Open<{$tag_name}>Closed P{$tag_name}>
"
+ );
+
+ $processor->next_tag( $tag_name );
+ $this->assertSame(
+ array( 'HTML', 'BODY', $tag_name ),
+ $processor->get_breadcrumbs(),
+ "Expected {$tag_name} to be a direct child of the BODY, having closed the open P element."
+ );
+
+ $processor->next_tag( 'IMG' );
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'IMG' ),
+ $processor->get_breadcrumbs(),
+ 'Expected IMG to be a direct child of BODY, having closed the open P element.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[].
+ */
+ public function data_heading_elements() {
+ return array(
+ 'H1' => array( 'H1' ),
+ 'H2' => array( 'H2' ),
+ 'H3' => array( 'H3' ),
+ 'H4' => array( 'H4' ),
+ 'H5' => array( 'H5' ),
+ 'H6' => array( 'H5' ),
+ );
+ }
+
+ /**
+ * Verifies that H1 through H6 elements close an open H1 through H6 element.
+ *
+ * @ticket 60215
+ *
+ * @dataProvider data_heading_combinations
+ *
+ * @param string $first_heading H1 - H6 element appearing (unclosed) before the second.
+ * @param string $second_heading H1 - H6 element appearing after the first.
+ */
+ public function test_in_body_heading_element_closes_other_heading_elements( $first_heading, $second_heading ) {
+ $processor = WP_HTML_Processor::create_fragment(
+ "
<{$first_heading} first> then <{$second_heading} second> and end {$second_heading}>{$first_heading}>
"
+ );
+
+ while ( $processor->next_tag() && null === $processor->get_attribute( 'second' ) ) {
+ continue;
+ }
+
+ $this->assertTrue(
+ $processor->get_attribute( 'second' ),
+ "Failed to find expected {$second_heading} tag."
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'DIV', $second_heading ),
+ $processor->get_breadcrumbs(),
+ "Expected {$second_heading} to be a direct child of the DIV, having closed the open {$first_heading} element."
+ );
+
+ $processor->next_tag( 'IMG' );
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'DIV', 'IMG' ),
+ $processor->get_breadcrumbs(),
+ "Expected IMG to be a direct child of DIV, having closed the open {$first_heading} element."
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_heading_combinations() {
+ $headings = array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' );
+
+ $combinations = array();
+
+ // Create all unique pairs of H1 - H6 elements.
+ foreach ( $headings as $first_tag ) {
+ foreach ( $headings as $second_tag ) {
+ $combinations[ "{$first_tag} then {$second_tag}" ] = array( $first_tag, $second_tag );
+ }
+ }
+
+ return $combinations;
+ }
+
/**
* Verifies that when "in body" and encountering "any other end tag"
* that the HTML processor ignores the end tag if there's a special
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php
new file mode 100644
index 0000000000000..0c7e3422f09fc
--- /dev/null
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRulesListElements.php
@@ -0,0 +1,431 @@
+
' );
+
+ while (
+ null === $processor->get_attribute( 'target' ) &&
+ $processor->next_tag()
+ ) {
+ continue;
+ }
+
+ $this->assertTrue(
+ $processor->get_attribute( 'target' ),
+ 'Failed to find target node.'
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'LI' ),
+ $processor->get_breadcrumbs(),
+ "LI should have closed open LI, but didn't."
+ );
+ }
+
+ /**
+ * Ensures that an opening LI element implicitly closes other open elements with optional closing tags.
+ *
+ * @ticket 60215
+ */
+ public function test_in_body_li_generates_implied_end_tags_inside_open_li() {
+ $processor = WP_HTML_Processor::create_fragment( '
' );
+
+ while (
+ null === $processor->get_attribute( 'target' ) &&
+ $processor->next_tag()
+ ) {
+ continue;
+ }
+
+ $this->assertTrue(
+ $processor->get_attribute( 'target' ),
+ 'Failed to find target node.'
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'LI' ),
+ $processor->get_breadcrumbs(),
+ "LI should have closed open LI, but didn't."
+ );
+ }
+
+ /**
+ * Ensures that when closing tags with optional tag closers, an opening LI tag doesn't close beyond a special boundary.
+ *
+ * @ticket 60215
+ */
+ public function test_in_body_li_generates_implied_end_tags_inside_open_li_but_stopping_at_special_tags() {
+ $processor = WP_HTML_Processor::create_fragment( '
' );
+
+ while (
+ null === $processor->get_attribute( 'target' ) &&
+ $processor->next_tag()
+ ) {
+ continue;
+ }
+
+ $this->assertTrue(
+ $processor->get_attribute( 'target' ),
+ 'Failed to find target node.'
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'LI', 'BLOCKQUOTE', 'LI' ),
+ $processor->get_breadcrumbs(),
+ 'LI should have left the BLOCKQOUTE open, but closed it.'
+ );
+ }
+
+ /**
+ * Ensures that an opening LI closes an open P in button scope.
+ *
+ * @ticket 60215
+ */
+ public function test_in_body_li_in_li_closes_p_in_button_scope() {
+ $processor = WP_HTML_Processor::create_fragment( '