From d130c92041cb79146d4eeb0097141b553a786595 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 5 Feb 2026 12:54:33 +0100 Subject: [PATCH 001/145] Fix newlines disappearing inside PRE,TEXTAREA elements Add serialize + normalize tests Add more PRE tests Fix newlines disappearing inside PRE,TEXTAREA elements Handline PRE,LISTING lints Simpler fix Remove overly-specific tests Add LISTING tests Add explanatory comment lints Remove accidental import --- .../html-api/class-wp-html-processor.php | 26 +++++++++ .../html-api/wpHtmlProcessor-serialize.php | 57 +++++++++++++++++++ 2 files changed, 83 insertions(+) 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 55f955f2c1a9a..d5b073f65cddf 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1412,6 +1412,32 @@ public function serialize_token(): string { $html .= '>'; + /* + * The HTML parser strips a leading newline immediately after the start + * tag of TEXTAREA, PRE, and LISTING elements. When serializing, prepend + * a leading newline to ensure the semantic HTML content is preserved. + * + * For example, `
\n\nX
` must not become `
\nX
` because its content + * has changed. However, `
X
` and `
\nX
` are _equivalent_. + * + * > A start tag whose tag name is "textarea" + * > … + * > If the next token is a U+000A LINE FEED (LF) character token, then ignore + * > that token and move on to the next one. (Newlines at the start of textarea + * > elements are ignored as an authoring convenience.) + * + * > A start tag whose tag name is one of: "pre", "listing" + * > … + * > If the next token is a U+000A LINE FEED (LF) character token, then ignore + * > that token and move on to the next one. (Newlines at the start of pre blocks + * > are ignored as an authoring convenience.) + * + * @see https://html.spec.whatwg.org/multipage/parsing.html + */ + if ( $tag_name === 'TEXTAREA' || $tag_name === 'PRE' || $tag_name === 'LISTING' ) { + $html .= "\n"; + } + // Flush out self-contained elements. if ( $in_html && in_array( $tag_name, array( 'IFRAME', 'NOEMBED', 'NOFRAMES', 'SCRIPT', 'STYLE', 'TEXTAREA', 'TITLE', 'XMP' ), true ) ) { $text = $this->get_modifiable_text(); diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index e2b5a79c2de2f..cb9f4abce9535 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -321,4 +321,61 @@ public static function data_provider_serialize_doctype() { 'Double quotes in system ID' => array( '', '' ), ); } + + /** + * @ticket TBD + * + * @dataProvider data_provider_normalize_special_leading_newline_cases + */ + public function test_normalize_special_leading_newline_handling( string $input, string $expected ) { + $normalized = WP_HTML_Processor::normalize( $input ); + $this->assertEqualHTML( $expected, $normalized ); + $normalized_twice = WP_HTML_Processor::normalize( $normalized ); + $this->assertEqualHTML( $expected, $normalized_twice ); + } + + public static function data_provider_normalize_special_leading_newline_cases() { + return array( + 'Leading newline in PRE' => array( + "
\nline 1\nline 2
", + "
line 1\nline 2
", + ), + 'Double leading newline in PRE' => array( + "
\n\nline 2\nline 3
", + "
\n\nline 2\nline 3
", + ), + 'Multiple text nodes inside PRE' => array( + "
\nline 1 still line 1
", + '
line 1 still line 1
', + ), + 'Multiple text nodes inside PRE with leading newlines' => array( + "
\n\nline 2 still line 2
", + "
\n\nline 2 still line 2
", + ), + 'Leading newline in LISTING' => array( + "\nline 1\nline 2", + "line 1\nline 2", + ), + 'Double leading newline in LISTING' => array( + "\n\nline 2\nline 3", + "\n\nline 2\nline 3", + ), + 'Multiple text nodes inside LISTING' => array( + "\nline 1 still line 1", + 'line 1 still line 1', + ), + 'Multiple text nodes inside LISTING with leading newlines' => array( + "\n\nline 2 still line 2", + "\n\nline 2 still line 2", + ), + 'Leading newline in TEXTAREA' => array( + "", + "", + ), + 'Double leading newline in TEXTAREA' => array( + "", + "", + ), + ); + } } From 21b48eac066a43a79c868c3d319308882b96158c Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 5 Feb 2026 17:57:12 +0100 Subject: [PATCH 002/145] Ticket number, data provider --- tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index cb9f4abce9535..cef916436ead6 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -323,7 +323,7 @@ public static function data_provider_serialize_doctype() { } /** - * @ticket TBD + * @ticket 64607 * * @dataProvider data_provider_normalize_special_leading_newline_cases */ @@ -334,6 +334,11 @@ public function test_normalize_special_leading_newline_handling( string $input, $this->assertEqualHTML( $expected, $normalized_twice ); } + /** + * Data provider. + * + * @return array[] + */ public static function data_provider_normalize_special_leading_newline_cases() { return array( 'Leading newline in PRE' => array( From 03fb7f01c710c873f449497d5ec09f09d90e539a Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 5 Feb 2026 19:50:25 +0100 Subject: [PATCH 003/145] YODA --- src/wp-includes/html-api/class-wp-html-processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d5b073f65cddf..502cc2ed3ee69 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1434,7 +1434,7 @@ public function serialize_token(): string { * * @see https://html.spec.whatwg.org/multipage/parsing.html */ - if ( $tag_name === 'TEXTAREA' || $tag_name === 'PRE' || $tag_name === 'LISTING' ) { + if ( 'TEXTAREA' === $tag_name || 'PRE' === $tag_name || 'LISTING' === $tag_name ) { $html .= "\n"; } From 0acab11958604124e56543931ffbfc8849e87304 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 6 Feb 2026 13:53:36 +0100 Subject: [PATCH 004/145] Apply suggestion from @mukeshpanchal27 Co-authored-by: Mukesh Panchal --- tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index cef916436ead6..175bb3845d554 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -323,9 +323,15 @@ public static function data_provider_serialize_doctype() { } /** + * Ensures that leading newlines in PRE, LISTING, and TEXTAREA elements are preserved upon normalization, + * and that normalization is idempotent in these cases. + * * @ticket 64607 * * @dataProvider data_provider_normalize_special_leading_newline_cases + * + * @param string $input HTML input containing leading newlines in PRE, LISTING, or TEXTAREA elements. + * @param string $expected Expected output after normalization, which should preserve leading newlines. */ public function test_normalize_special_leading_newline_handling( string $input, string $expected ) { $normalized = WP_HTML_Processor::normalize( $input ); From bdfd4332904d557e553cc6fc6a55840f92404376 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 9 Feb 2026 16:43:35 +0000 Subject: [PATCH 005/145] Abilities API: Add core/get-settings ability. Introduce a new `core/get-settings` ability to the WordPress Abilities API that dynamically discovers and exposes WordPress settings. Settings with `show_in_abilities` enabled are exposed through this ability. Props jorgefilipecosta, jason_the_adams, mukeshpanchal27, justlevine, ovidiu-galatan. Fixes #64605. git-svn-id: https://develop.svn.wordpress.org/trunk@61600 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/abilities.php | 4 + .../abilities/class-wp-settings-abilities.php | 343 +++++++++++++++++ src/wp-includes/option.php | 167 ++++---- .../wpRestAbilitiesSettingsController.php | 356 ++++++++++++++++++ 4 files changed, 797 insertions(+), 73 deletions(-) create mode 100644 src/wp-includes/abilities/class-wp-settings-abilities.php create mode 100644 tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php diff --git a/src/wp-includes/abilities.php b/src/wp-includes/abilities.php index 4c6db1ed830e0..132ca6fc673a3 100644 --- a/src/wp-includes/abilities.php +++ b/src/wp-includes/abilities.php @@ -9,6 +9,8 @@ declare( strict_types = 1 ); +require_once __DIR__ . '/abilities/class-wp-settings-abilities.php'; + /** * Registers the core ability categories. * @@ -257,4 +259,6 @@ function wp_register_core_abilities(): void { ), ) ); + + WP_Settings_Abilities::register(); } diff --git a/src/wp-includes/abilities/class-wp-settings-abilities.php b/src/wp-includes/abilities/class-wp-settings-abilities.php new file mode 100644 index 0000000000000..5af7fa48450ee --- /dev/null +++ b/src/wp-includes/abilities/class-wp-settings-abilities.php @@ -0,0 +1,343 @@ + args for allowed settings. + */ + private static function get_allowed_settings(): array { + $settings = array(); + + foreach ( get_registered_settings() as $option_name => $args ) { + if ( ! empty( $args['show_in_abilities'] ) ) { + $settings[ $option_name ] = $args; + } + } + + return $settings; + } + + /** + * Gets unique setting groups that have show_in_abilities enabled. + * + * @since 7.0.0 + * + * @return string[] List of unique group names. + */ + private static function get_available_groups(): array { + $groups = array(); + + foreach ( self::get_allowed_settings() as $args ) { + $group = $args['group'] ?? 'general'; + if ( ! in_array( $group, $groups, true ) ) { + $groups[] = $group; + } + } + + sort( $groups ); + + return $groups; + } + + /** + * Gets unique setting slugs that have show_in_abilities enabled. + * + * @since 7.0.0 + * + * @return string[] List of unique setting slugs. + */ + private static function get_available_slugs(): array { + $slugs = array(); + + foreach ( self::get_allowed_settings() as $option_name => $args ) { + $slugs[] = $option_name; + } + + sort( $slugs ); + + return $slugs; + } + + /** + * Builds a rich output schema from registered settings metadata. + * + * Creates a JSON Schema that documents each setting group and its settings + * with their types, titles, descriptions, defaults, and any additional + * schema properties from show_in_rest. + * + * @since 7.0.0 + * + * @return array JSON Schema for the output. + */ + private static function build_output_schema(): array { + $group_properties = array(); + + foreach ( self::get_allowed_settings() as $option_name => $args ) { + $group = $args['group'] ?? 'general'; + + $setting_schema = array( + 'type' => $args['type'] ?? 'string', + ); + + if ( ! empty( $args['label'] ) ) { + $setting_schema['title'] = $args['label']; + } + + if ( ! empty( $args['description'] ) ) { + $setting_schema['description'] = $args['description']; + } elseif ( ! empty( $args['label'] ) ) { + $setting_schema['description'] = $args['label']; + } + + if ( ! isset( $group_properties[ $group ] ) ) { + $group_properties[ $group ] = array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => false, + ); + } + + $group_properties[ $group ]['properties'][ $option_name ] = $setting_schema; + } + + ksort( $group_properties ); + + return array( + 'type' => 'object', + 'description' => __( 'Settings grouped by registration group. Each group contains settings with their current values.' ), + 'properties' => $group_properties, + 'additionalProperties' => false, + ); + } + + /** + * Registers the core/get-settings ability. + * + * @since 7.0.0 + * + * @return void + */ + private static function register_get_settings(): void { + wp_register_ability( + 'core/get-settings', + array( + 'label' => __( 'Get Settings' ), + 'description' => __( 'Returns registered WordPress settings grouped by their registration group. Returns key-value pairs per setting.' ), + 'category' => 'site', + 'input_schema' => array( + 'default' => (object) array(), + 'oneOf' => array( + // Branch 1: No filter (empty object). + array( + 'type' => 'object', + 'additionalProperties' => false, + 'maxProperties' => 0, + ), + // Branch 2: Filter by group only. + array( + 'type' => 'object', + 'properties' => array( + 'group' => array( + 'type' => 'string', + 'description' => __( 'Filter settings by group name.' ), + 'enum' => self::$available_groups, + ), + ), + 'required' => array( 'group' ), + 'additionalProperties' => false, + ), + // Branch 3: Filter by slugs only. + array( + 'type' => 'object', + 'properties' => array( + 'slugs' => array( + 'type' => 'array', + 'description' => __( 'Filter settings by specific setting slugs.' ), + 'items' => array( + 'type' => 'string', + 'enum' => self::$available_slugs, + ), + ), + ), + 'required' => array( 'slugs' ), + 'additionalProperties' => false, + ), + ), + ), + 'output_schema' => self::$output_schema, + 'execute_callback' => array( __CLASS__, 'execute_get_settings' ), + 'permission_callback' => array( __CLASS__, 'check_manage_options' ), + 'meta' => array( + 'annotations' => array( + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, + ), + 'show_in_rest' => true, + ), + ) + ); + } + + /** + * Permission callback for settings abilities. + * + * @since 7.0.0 + * + * @return bool True if the current user can manage options, false otherwise. + */ + public static function check_manage_options(): bool { + return current_user_can( 'manage_options' ); + } + + /** + * Execute callback for core/get-settings ability. + * + * Retrieves all registered settings that are exposed through the Abilities API, + * grouped by their registration group. + * + * @since 7.0.0 + * + * @param array $input { + * Optional. Input parameters. + * + * @type string $group Optional. Filter settings by group name. Cannot be used with slugs. + * @type string[] $slugs Optional. Filter settings by specific setting slugs. Cannot be used with group. + * } + * @return array Settings grouped by registration group. + */ + public static function execute_get_settings( $input = array() ): array { + $input = is_array( $input ) ? $input : array(); + $filter_group = ! empty( $input['group'] ) ? $input['group'] : null; + $filter_slugs = ! empty( $input['slugs'] ) ? $input['slugs'] : null; + + $settings_by_group = array(); + + foreach ( self::get_allowed_settings() as $option_name => $args ) { + $group = $args['group'] ?? 'general'; + + if ( $filter_group && $group !== $filter_group ) { + continue; + } + + if ( $filter_slugs && ! in_array( $option_name, $filter_slugs, true ) ) { + continue; + } + + $default = $args['default'] ?? null; + + $value = get_option( $option_name, $default ); + $value = self::cast_value( $value, $args['type'] ?? 'string' ); + + if ( ! isset( $settings_by_group[ $group ] ) ) { + $settings_by_group[ $group ] = array(); + } + + $settings_by_group[ $group ][ $option_name ] = $value; + } + + ksort( $settings_by_group ); + + return $settings_by_group; + } + + /** + * Casts a value to the appropriate type based on the setting's registered type. + * + * @since 7.0.0 + * + * @param mixed $value The value to cast. + * @param string $type The registered type (string, boolean, integer, number, array, object). + * @return string|bool|int|float|array The cast value. + */ + private static function cast_value( $value, string $type ) { + switch ( $type ) { + case 'boolean': + return (bool) $value; + case 'integer': + return (int) $value; + case 'number': + return (float) $value; + case 'array': + case 'object': + return is_array( $value ) ? $value : array(); + case 'string': + default: + return (string) $value; + } + } +} diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 7979c119a986f..8a9a2c3c89ece 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -2743,12 +2743,13 @@ function register_initial_settings() { 'general', 'blogname', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'title', ), - 'type' => 'string', - 'label' => __( 'Title' ), - 'description' => __( 'Site title.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'label' => __( 'Title' ), + 'description' => __( 'Site title.' ), ) ); @@ -2756,12 +2757,13 @@ function register_initial_settings() { 'general', 'blogdescription', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'description', ), - 'type' => 'string', - 'label' => __( 'Tagline' ), - 'description' => __( 'Site tagline.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'label' => __( 'Tagline' ), + 'description' => __( 'Site tagline.' ), ) ); @@ -2770,14 +2772,15 @@ function register_initial_settings() { 'general', 'siteurl', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'url', 'schema' => array( 'format' => 'uri', ), ), - 'type' => 'string', - 'description' => __( 'Site URL.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'Site URL.' ), ) ); } @@ -2787,14 +2790,15 @@ function register_initial_settings() { 'general', 'admin_email', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'email', 'schema' => array( 'format' => 'email', ), ), - 'type' => 'string', - 'description' => __( 'This address is used for admin purposes, like new user notification.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'This address is used for admin purposes, like new user notification.' ), ) ); } @@ -2803,11 +2807,12 @@ function register_initial_settings() { 'general', 'timezone_string', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'timezone', ), - 'type' => 'string', - 'description' => __( 'A city in the same timezone as you.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'A city in the same timezone as you.' ), ) ); @@ -2815,9 +2820,10 @@ function register_initial_settings() { 'general', 'date_format', array( - 'show_in_rest' => true, - 'type' => 'string', - 'description' => __( 'A date format for all date strings.' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'A date format for all date strings.' ), ) ); @@ -2825,9 +2831,10 @@ function register_initial_settings() { 'general', 'time_format', array( - 'show_in_rest' => true, - 'type' => 'string', - 'description' => __( 'A time format for all time strings.' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'A time format for all time strings.' ), ) ); @@ -2835,9 +2842,10 @@ function register_initial_settings() { 'general', 'start_of_week', array( - 'show_in_rest' => true, - 'type' => 'integer', - 'description' => __( 'A day number of the week that the week should start on.' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'integer', + 'description' => __( 'A day number of the week that the week should start on.' ), ) ); @@ -2845,12 +2853,13 @@ function register_initial_settings() { 'general', 'WPLANG', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'name' => 'language', ), - 'type' => 'string', - 'description' => __( 'WordPress locale code.' ), - 'default' => 'en_US', + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'WordPress locale code.' ), + 'default' => 'en_US', ) ); @@ -2858,10 +2867,11 @@ function register_initial_settings() { 'writing', 'use_smilies', array( - 'show_in_rest' => true, - 'type' => 'boolean', - 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ), - 'default' => true, + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'boolean', + 'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ), + 'default' => true, ) ); @@ -2869,9 +2879,10 @@ function register_initial_settings() { 'writing', 'default_category', array( - 'show_in_rest' => true, - 'type' => 'integer', - 'description' => __( 'Default post category.' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'integer', + 'description' => __( 'Default post category.' ), ) ); @@ -2879,9 +2890,10 @@ function register_initial_settings() { 'writing', 'default_post_format', array( - 'show_in_rest' => true, - 'type' => 'string', - 'description' => __( 'Default post format.' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'Default post format.' ), ) ); @@ -2889,11 +2901,12 @@ function register_initial_settings() { 'reading', 'posts_per_page', array( - 'show_in_rest' => true, - 'type' => 'integer', - 'label' => __( 'Maximum posts per page' ), - 'description' => __( 'Blog pages show at most.' ), - 'default' => 10, + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'integer', + 'label' => __( 'Maximum posts per page' ), + 'description' => __( 'Blog pages show at most.' ), + 'default' => 10, ) ); @@ -2901,10 +2914,11 @@ function register_initial_settings() { 'reading', 'show_on_front', array( - 'show_in_rest' => true, - 'type' => 'string', - 'label' => __( 'Show on front' ), - 'description' => __( 'What to show on the front page' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'string', + 'label' => __( 'Show on front' ), + 'description' => __( 'What to show on the front page' ), ) ); @@ -2912,10 +2926,11 @@ function register_initial_settings() { 'reading', 'page_on_front', array( - 'show_in_rest' => true, - 'type' => 'integer', - 'label' => __( 'Page on front' ), - 'description' => __( 'The ID of the page that should be displayed on the front page' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'integer', + 'label' => __( 'Page on front' ), + 'description' => __( 'The ID of the page that should be displayed on the front page' ), ) ); @@ -2923,9 +2938,10 @@ function register_initial_settings() { 'reading', 'page_for_posts', array( - 'show_in_rest' => true, - 'type' => 'integer', - 'description' => __( 'The ID of the page that should display the latest posts' ), + 'show_in_rest' => true, + 'show_in_abilities' => true, + 'type' => 'integer', + 'description' => __( 'The ID of the page that should display the latest posts' ), ) ); @@ -2933,13 +2949,14 @@ function register_initial_settings() { 'discussion', 'default_ping_status', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'schema' => array( 'enum' => array( 'open', 'closed' ), ), ), - 'type' => 'string', - 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'description' => __( 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.' ), ) ); @@ -2947,14 +2964,15 @@ function register_initial_settings() { 'discussion', 'default_comment_status', array( - 'show_in_rest' => array( + 'show_in_rest' => array( 'schema' => array( 'enum' => array( 'open', 'closed' ), ), ), - 'type' => 'string', - 'label' => __( 'Allow comments on new posts' ), - 'description' => __( 'Allow people to submit comments on new posts.' ), + 'show_in_abilities' => true, + 'type' => 'string', + 'label' => __( 'Allow comments on new posts' ), + 'description' => __( 'Allow people to submit comments on new posts.' ), ) ); } @@ -2985,10 +3003,12 @@ function register_initial_settings() { * @type string $label A label of the data attached to this setting. * @type string $description A description of the data attached to this setting. * @type callable $sanitize_callback A callback function that sanitizes the option's value. - * @type bool|array $show_in_rest Whether data associated with this setting should be included in the REST API. - * When registering complex settings, this argument may optionally be an - * array with a 'schema' key. - * @type mixed $default Default value when calling `get_option()`. + * @type bool|array $show_in_rest Whether data associated with this setting should be included in the REST API. + * When registering complex settings, this argument may optionally be an + * array with a 'schema' key. + * @type bool $show_in_abilities Whether this setting should be exposed through the Abilities API. + * Default false. + * @type mixed $default Default value when calling `get_option()`. * } */ function register_setting( $option_group, $option_name, $args = array() ) { @@ -3001,12 +3021,13 @@ function register_setting( $option_group, $option_name, $args = array() ) { $GLOBALS['new_whitelist_options'] = &$new_allowed_options; $defaults = array( - 'type' => 'string', - 'group' => $option_group, - 'label' => '', - 'description' => '', - 'sanitize_callback' => null, - 'show_in_rest' => false, + 'type' => 'string', + 'group' => $option_group, + 'label' => '', + 'description' => '', + 'sanitize_callback' => null, + 'show_in_rest' => false, + 'show_in_abilities' => false, ); // Back-compat: old sanitize callback is added. diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php new file mode 100644 index 0000000000000..198c0c3b8bc69 --- /dev/null +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesSettingsController.php @@ -0,0 +1,356 @@ +user->create( array( 'role' => 'administrator' ) ); + self::$subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + + // Register initial settings first so abilities can build schemas. + register_initial_settings(); + + // Ensure core abilities are registered for these tests. + remove_action( 'wp_abilities_api_categories_init', '_unhook_core_ability_categories_registration', 1 ); + remove_action( 'wp_abilities_api_init', '_unhook_core_abilities_registration', 1 ); + + add_action( 'wp_abilities_api_categories_init', 'wp_register_core_ability_categories' ); + add_action( 'wp_abilities_api_init', 'wp_register_core_abilities' ); + do_action( 'wp_abilities_api_categories_init' ); + do_action( 'wp_abilities_api_init' ); + } + + /** + * Tear down after class. + */ + public static function tear_down_after_class(): void { + // Re-add the unhook functions for subsequent tests. + add_action( 'wp_abilities_api_categories_init', '_unhook_core_ability_categories_registration', 1 ); + add_action( 'wp_abilities_api_init', '_unhook_core_abilities_registration', 1 ); + + // Remove the core abilities and their categories. + foreach ( wp_get_abilities() as $ability ) { + wp_unregister_ability( $ability->get_name() ); + } + foreach ( wp_get_ability_categories() as $ability_category ) { + wp_unregister_ability_category( $ability_category->get_slug() ); + } + + parent::tear_down_after_class(); + } + + /** + * Set up before each test. + */ + public function set_up(): void { + parent::set_up(); + + global $wp_rest_server; + $wp_rest_server = new WP_REST_Server(); + $this->server = $wp_rest_server; + + do_action( 'rest_api_init' ); + + wp_set_current_user( self::$admin_id ); + } + + /** + * Tear down after each test. + */ + public function tear_down(): void { + global $wp_rest_server; + $wp_rest_server = null; + + parent::tear_down(); + } + + /** + * Tests that unauthenticated users cannot access the get-settings ability. + * + * @ticket 64605 + */ + public function test_core_get_settings_requires_authentication(): void { + wp_set_current_user( 0 ); + + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 401, $response->get_status() ); + } + + /** + * Tests that subscribers cannot access the get-settings ability. + * + * @ticket 64605 + */ + public function test_core_get_settings_requires_manage_options_capability(): void { + wp_set_current_user( self::$subscriber_id ); + + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 403, $response->get_status() ); + } + + /** + * Tests that administrators can access the get-settings ability. + * + * @ticket 64605 + */ + public function test_core_get_settings_allows_administrators(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + } + + /** + * Tests that the get-settings ability returns settings grouped by registration group. + * + * @ticket 64605 + */ + public function test_core_get_settings_returns_grouped_settings(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertIsArray( $data ); + $this->assertArrayHasKey( 'general', $data ); + $this->assertArrayHasKey( 'blogname', $data['general'] ); + $this->assertArrayHasKey( 'blogdescription', $data['general'] ); + } + + /** + * Tests that the get-settings ability can filter by group. + * + * @ticket 64605 + */ + public function test_core_get_settings_filters_by_group(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'group' => 'general', + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertIsArray( $data ); + $this->assertCount( 1, $data ); + $this->assertArrayHasKey( 'general', $data ); + } + + /** + * Tests that the get-settings ability can filter by specific slugs. + * + * @ticket 64605 + */ + public function test_core_get_settings_filters_by_slugs(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'slugs' => array( 'blogname', 'blogdescription' ), + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertIsArray( $data ); + $this->assertArrayHasKey( 'general', $data ); + $this->assertCount( 2, $data['general'] ); + $this->assertArrayHasKey( 'blogname', $data['general'] ); + $this->assertArrayHasKey( 'blogdescription', $data['general'] ); + } + + /** + * Tests that settings without show_in_abilities are excluded. + * + * @ticket 64605 + */ + public function test_core_get_settings_excludes_settings_without_show_in_abilities(): void { + register_setting( + 'general', + 'test_setting_excluded', + array( + 'type' => 'string', + 'default' => 'test_value', + 'show_in_abilities' => false, + ) + ); + update_option( 'test_setting_excluded', 'test_value' ); + + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertArrayNotHasKey( 'test_setting_excluded', $data['general'] ?? array() ); + + unregister_setting( 'general', 'test_setting_excluded' ); + delete_option( 'test_setting_excluded' ); + } + + /** + * Tests that core settings with show_in_abilities are included. + * + * @ticket 64605 + */ + public function test_core_get_settings_includes_settings_with_show_in_abilities(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + // blogname has show_in_abilities => true in register_initial_settings(). + $this->assertArrayHasKey( 'general', $data ); + $this->assertArrayHasKey( 'blogname', $data['general'] ); + + // use_smilies has show_in_abilities => true. + $this->assertArrayHasKey( 'writing', $data ); + $this->assertArrayHasKey( 'use_smilies', $data['writing'] ); + } + + /** + * Tests that boolean settings are cast to actual booleans. + * + * @ticket 64605 + */ + public function test_core_get_settings_casts_boolean_values(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'slugs' => array( 'use_smilies' ), + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertArrayHasKey( 'writing', $data ); + $this->assertArrayHasKey( 'use_smilies', $data['writing'] ); + $this->assertIsBool( $data['writing']['use_smilies'] ); + } + + /** + * Tests that integer settings are cast to actual integers. + * + * @ticket 64605 + */ + public function test_core_get_settings_casts_integer_values(): void { + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'slugs' => array( 'start_of_week' ), + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertArrayHasKey( 'general', $data ); + $this->assertArrayHasKey( 'start_of_week', $data['general'] ); + $this->assertIsInt( $data['general']['start_of_week'] ); + } + + /** + * Tests that the get-settings ability requires GET method (read-only). + * + * @ticket 64605 + */ + public function test_core_get_settings_requires_get_method(): void { + $request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_header( 'Content-Type', 'application/json' ); + $request->set_body( wp_json_encode( array( 'input' => array() ) ) ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 405, $response->get_status() ); + + $data = $response->get_data(); + $this->assertSame( 'rest_ability_invalid_method', $data['code'] ); + } + + /** + * Tests that the get-settings ability returns correct values. + * + * @ticket 64605 + */ + public function test_core_get_settings_returns_correct_values(): void { + update_option( 'blogname', 'Test Site Name' ); + + $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/core/get-settings/run' ); + $request->set_query_params( + array( + 'input' => array( + 'slugs' => array( 'blogname' ), + ), + ) + ); + $response = $this->server->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + + $data = $response->get_data(); + + $this->assertSame( 'Test Site Name', $data['general']['blogname'] ); + } +} From edbdcbe35e7ce19cd9cde1166b0b6e2d52729570 Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Mon, 9 Feb 2026 16:53:07 +0000 Subject: [PATCH 006/145] Filesystem API: Avoid `chmod()` warnings if the permissions already match. This prevents spurious warnings from `WP_Filesystem_Direct::chmod()` when the web process doesn't have ownership over the files, and thus cannot change the permissions, even if only to reassert the existing mode. Follow-up to [6779], [11667], [12998]. Props redsweater, mukesh27, SergeyBiryukov. Fixes #64610. git-svn-id: https://develop.svn.wordpress.org/trunk@61601 602fd350-edb4-49c9-b593-d223f7449a82 --- .../includes/class-wp-filesystem-direct.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/wp-admin/includes/class-wp-filesystem-direct.php b/src/wp-admin/includes/class-wp-filesystem-direct.php index ed22a821a14b0..a4b197c15229f 100644 --- a/src/wp-admin/includes/class-wp-filesystem-direct.php +++ b/src/wp-admin/includes/class-wp-filesystem-direct.php @@ -170,6 +170,22 @@ public function chmod( $file, $mode = false, $recursive = false ) { } if ( ! $recursive || ! $this->is_dir( $file ) ) { + $current_mode = fileperms( $file ) & 0777 | 0644; + + /* + * fileperms() populates the stat cache, so have to clear it + * to maintain parity with the previous behavior. + */ + clearstatcache( true, $file ); + + /* + * Avoid calling chmod() if the requested mode is already set, + * to prevent throwing a warning when we aren't the owner. + */ + if ( $current_mode === $mode ) { + return true; + } + return chmod( $file, $mode ); } From 55227eb2d58ffa1ca3d90a061d1611932498243b Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 9 Feb 2026 16:59:52 +0000 Subject: [PATCH 007/145] Abilities API: Allow nested namespace ability names (2-4 segments). Expand ability name validation from exactly 2 segments (`namespace/ability`) to 2-4 segments, enabling names like `my-plugin/resource/find` and `my-plugin/resource/sub/find`. This allows plugins to organize abilities into logical resource groups. The validation regex changes from `/^[a-z0-9-]+\/[a-z0-9-]+$/` to `/^[a-z0-9-]+(?:\/[a-z0-9-]+){1,3}$/`, which accepts the first segment plus 1-3 additional slash-delimited segments. Updates the validation regex, error messages, docblocks, and adds corresponding unit and REST API tests. Props jorgefilipecosta, justlevine, jorbin. Fixes #64596. git-svn-id: https://develop.svn.wordpress.org/trunk@61602 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/abilities-api.php | 14 ++-- .../class-wp-abilities-registry.php | 9 ++- .../abilities-api/class-wp-ability.php | 4 +- .../abilities-api/wpAbilitiesRegistry.php | 68 +++++++++++++++++++ .../wpRestAbilitiesV1RunController.php | 62 +++++++++++++++++ 5 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/abilities-api.php b/src/wp-includes/abilities-api.php index 73ba658f3f10d..835bd535d2487 100644 --- a/src/wp-includes/abilities-api.php +++ b/src/wp-includes/abilities-api.php @@ -132,7 +132,8 @@ * * Ability names must follow these rules: * - * - Include a namespace prefix (e.g., `my-plugin/my-ability`). + * - Contain 2 to 4 segments separated by forward slashes + * (e.g., `my-plugin/my-ability`, `my-plugin/resource/find`, `my-plugin/resource/sub/find`). * - Use only lowercase alphanumeric characters, dashes, and forward slashes. * - Use descriptive, action-oriented names (e.g., `process-payment`, `generate-report`). * @@ -225,9 +226,8 @@ * @see wp_register_ability_category() * @see wp_unregister_ability() * - * @param string $name The name of the ability. Must be a namespaced string containing - * a prefix, e.g., `my-plugin/my-ability`. Can only contain lowercase - * alphanumeric characters, dashes, and forward slashes. + * @param string $name The name of the ability. Must be the fully-namespaced + * string identifier, e.g. `my-plugin/my-ability` or `my-plugin/resource/my-ability`. * @param array $args { * An associative array of arguments for configuring the ability. * @@ -318,7 +318,7 @@ function wp_register_ability( string $name, array $args ): ?WP_Ability { * @see wp_register_ability() * * @param string $name The name of the ability to unregister, including namespace prefix - * (e.g., 'my-plugin/my-ability'). + * (e.g., 'my-plugin/my-ability' or 'my-plugin/resource/find'). * @return WP_Ability|null The unregistered ability instance on success, `null` on failure. */ function wp_unregister_ability( string $name ): ?WP_Ability { @@ -351,7 +351,7 @@ function wp_unregister_ability( string $name ): ?WP_Ability { * @see wp_get_ability() * * @param string $name The name of the ability to check, including namespace prefix - * (e.g., 'my-plugin/my-ability'). + * (e.g., 'my-plugin/my-ability' or 'my-plugin/resource/find'). * @return bool `true` if the ability is registered, `false` otherwise. */ function wp_has_ability( string $name ): bool { @@ -383,7 +383,7 @@ function wp_has_ability( string $name ): bool { * @see wp_has_ability() * * @param string $name The name of the ability, including namespace prefix - * (e.g., 'my-plugin/my-ability'). + * (e.g., 'my-plugin/my-ability' or 'my-plugin/resource/find'). * @return WP_Ability|null The registered ability instance, or `null` if not registered. */ function wp_get_ability( string $name ): ?WP_Ability { diff --git a/src/wp-includes/abilities-api/class-wp-abilities-registry.php b/src/wp-includes/abilities-api/class-wp-abilities-registry.php index ecd6dc2785e70..758dd2c2524df 100644 --- a/src/wp-includes/abilities-api/class-wp-abilities-registry.php +++ b/src/wp-includes/abilities-api/class-wp-abilities-registry.php @@ -43,9 +43,8 @@ final class WP_Abilities_Registry { * * @see wp_register_ability() * - * @param string $name The name of the ability. The name must be a string containing a namespace - * prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase - * alphanumeric characters, dashes and the forward slash. + * @param string $name The name of the ability. Must be the fully-namespaced + * string identifier, e.g. `my-plugin/my-ability` or `my-plugin/resource/my-ability`. * @param array $args { * An associative array of arguments for the ability. * @@ -78,11 +77,11 @@ final class WP_Abilities_Registry { * @return WP_Ability|null The registered ability instance on success, null on failure. */ public function register( string $name, array $args ): ?WP_Ability { - if ( ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $name ) ) { + if ( ! preg_match( '/^[a-z0-9-]+(?:\/[a-z0-9-]+){1,3}$/', $name ) ) { _doing_it_wrong( __METHOD__, __( - 'Ability name must be a string containing a namespace prefix, i.e. "my-plugin/my-ability". It can only contain lowercase alphanumeric characters, dashes and the forward slash.' + 'Ability name must contain 2 to 4 segments separated by forward slashes, e.g. "my-plugin/my-ability" or "my-plugin/resource/my-ability". It can only contain lowercase alphanumeric characters, dashes, and forward slashes.' ), '6.9.0' ); diff --git a/src/wp-includes/abilities-api/class-wp-ability.php b/src/wp-includes/abilities-api/class-wp-ability.php index 967f1641156b0..bdcb8c0bd017a 100644 --- a/src/wp-includes/abilities-api/class-wp-ability.php +++ b/src/wp-includes/abilities-api/class-wp-ability.php @@ -52,7 +52,7 @@ class WP_Ability { /** * The name of the ability, with its namespace. - * Example: `my-plugin/my-ability`. + * Examples: `my-plugin/my-ability`, `my-plugin/resource/find`. * * @since 6.9.0 * @var string @@ -340,7 +340,7 @@ protected function prepare_properties( array $args ): array { /** * Retrieves the name of the ability, with its namespace. - * Example: `my-plugin/my-ability`. + * Examples: `my-plugin/my-ability`, `my-plugin/resource/find`. * * @since 6.9.0 * diff --git a/tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php b/tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php index 32479d69e2f8c..b9cc58279c118 100644 --- a/tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php +++ b/tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php @@ -136,6 +136,74 @@ public function test_register_invalid_uppercase_characters_in_name() { $this->assertNull( $result ); } + /** + * Should accept ability name with 3 segments (2 slashes). + * + * @ticket 64098 + * + * @covers WP_Abilities_Registry::register + */ + public function test_register_valid_name_with_three_segments() { + $result = $this->registry->register( 'test/sub/add-numbers', self::$test_ability_args ); + $this->assertInstanceOf( WP_Ability::class, $result ); + $this->assertSame( 'test/sub/add-numbers', $result->get_name() ); + } + + /** + * Should accept ability name with 4 segments (3 slashes). + * + * @ticket 64098 + * + * @covers WP_Abilities_Registry::register + */ + public function test_register_valid_name_with_four_segments() { + $result = $this->registry->register( 'test/sub/deep/add-numbers', self::$test_ability_args ); + $this->assertInstanceOf( WP_Ability::class, $result ); + $this->assertSame( 'test/sub/deep/add-numbers', $result->get_name() ); + } + + /** + * Should reject ability name with 5 segments (exceeds maximum of 4). + * + * @ticket 64098 + * + * @covers WP_Abilities_Registry::register + * + * @expectedIncorrectUsage WP_Abilities_Registry::register + */ + public function test_register_invalid_name_with_five_segments() { + $result = $this->registry->register( 'test/a/b/c/too-deep', self::$test_ability_args ); + $this->assertNull( $result ); + } + + /** + * Should reject ability name with empty segments (double slashes). + * + * @ticket 64098 + * + * @covers WP_Abilities_Registry::register + * + * @expectedIncorrectUsage WP_Abilities_Registry::register + */ + public function test_register_invalid_name_with_empty_segment() { + $result = $this->registry->register( 'test//add-numbers', self::$test_ability_args ); + $this->assertNull( $result ); + } + + /** + * Should reject ability name with trailing slash. + * + * @ticket 64098 + * + * @covers WP_Abilities_Registry::register + * + * @expectedIncorrectUsage WP_Abilities_Registry::register + */ + public function test_register_invalid_name_with_trailing_slash() { + $result = $this->registry->register( 'test/add-numbers/', self::$test_ability_args ); + $this->assertNull( $result ); + } + /** * Should reject ability registration without a label. * diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php index bccc30c2f2e94..0c03d72dab8a5 100644 --- a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php @@ -379,6 +379,43 @@ private function register_test_abilities(): void { ) ); + // Ability with nested namespace (3 segments). + $this->register_test_ability( + 'test/math/add', + array( + 'label' => 'Nested Add', + 'description' => 'Adds numbers with nested namespace', + 'category' => 'math', + 'input_schema' => array( + 'type' => 'object', + 'properties' => array( + 'a' => array( + 'type' => 'number', + 'description' => 'First number', + ), + 'b' => array( + 'type' => 'number', + 'description' => 'Second number', + ), + ), + 'required' => array( 'a', 'b' ), + 'additionalProperties' => false, + ), + 'output_schema' => array( + 'type' => 'number', + ), + 'execute_callback' => static function ( array $input ) { + return $input['a'] + $input['b']; + }, + 'permission_callback' => static function () { + return current_user_can( 'edit_posts' ); + }, + 'meta' => array( + 'show_in_rest' => true, + ), + ) + ); + // Read-only ability for query params testing. $this->register_test_ability( 'test/query-params', @@ -432,6 +469,31 @@ public function test_execute_regular_ability_post(): void { $this->assertEquals( 8, $response->get_data() ); } + /** + * Test executing an ability with a nested namespace (3 segments) via REST. + * + * @ticket 64098 + */ + public function test_execute_nested_namespace_ability(): void { + $request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/test/math/add/run' ); + $request->set_header( 'Content-Type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'input' => array( + 'a' => 10, + 'b' => 7, + ), + ) + ) + ); + + $response = $this->server->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 17, $response->get_data() ); + } + /** * Test executing a read-only ability with GET. * From edd68853d46446c07255e79b9fab4fec563d4bbb Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 9 Feb 2026 17:08:13 +0000 Subject: [PATCH 008/145] Block Supports: Prevent fatal error in `WP_Duotone` when the duotone attribute is an array. Adds type checks to `get_slug_from_attribute()`, `is_preset()`, and `get_all_global_style_block_names()` to handle cases where the duotone attribute is an array of custom colors instead of a preset reference string. This prevents an error when `preg_match()` receives an array instead of a string. Props jorgefilipecosta, westonruter, xavilc. Fixes #64612. git-svn-id: https://develop.svn.wordpress.org/trunk@61603 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-duotone.php | 19 ++++++++++++++++--- .../phpunit/tests/block-supports/duotone.php | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/class-wp-duotone.php b/src/wp-includes/class-wp-duotone.php index 69b56e090c5d4..8666f85a0f8e4 100644 --- a/src/wp-includes/class-wp-duotone.php +++ b/src/wp-includes/class-wp-duotone.php @@ -546,10 +546,14 @@ private static function colord_parse( $input ) { * * @since 6.3.0 * - * @param string $duotone_attr The duotone attribute from a block. - * @return string The slug of the duotone preset or an empty string if no slug is found. + * @param string|string[] $duotone_attr The duotone attribute from a block. + * @return string The slug of the duotone preset or an empty string if no slug is found (including when an array was passed). */ private static function get_slug_from_attribute( $duotone_attr ) { + if ( ! is_string( $duotone_attr ) ) { + return ''; + } + // Uses Branch Reset Groups `(?|…)` to return one capture group. preg_match( '/(?|var:preset\|duotone\|(\S+)|var\(--wp--preset--duotone--(\S+)\))/', $duotone_attr, $matches ); @@ -566,9 +570,13 @@ private static function get_slug_from_attribute( $duotone_attr ) { * @since 6.3.0 * * @param string $duotone_attr The duotone attribute from a block. - * @return bool True if the duotone preset present and valid. + * @param string|string[] $duotone_attr The duotone attribute from a block. */ private static function is_preset( $duotone_attr ) { + if ( ! is_string( $duotone_attr ) ) { + return false; + } + $slug = self::get_slug_from_attribute( $duotone_attr ); $filter_id = self::get_filter_id( $slug ); @@ -1050,6 +1058,11 @@ private static function get_all_global_style_block_names() { continue; } // If it has a duotone filter preset, save the block name and the preset slug. + // Only process if it's a string (preset reference), not an array (custom colors). + if ( ! is_string( $duotone_attr ) ) { + continue; + } + $slug = self::get_slug_from_attribute( $duotone_attr ); if ( $slug && $slug !== $duotone_attr ) { diff --git a/tests/phpunit/tests/block-supports/duotone.php b/tests/phpunit/tests/block-supports/duotone.php index 1f60a8247d4c4..808903a072452 100644 --- a/tests/phpunit/tests/block-supports/duotone.php +++ b/tests/phpunit/tests/block-supports/duotone.php @@ -93,6 +93,8 @@ public function data_get_slug_from_attribute() { 'pipe-slug-no-value' => array( 'var:preset|duotone|', '' ), 'css-var-spaces' => array( 'var(--wp--preset--duotone-- ', '' ), 'pipe-slug-spaces' => array( 'var:preset|duotone| ', '' ), + 'array-of-colors' => array( array( '#000000', '#ffffff' ), '' ), + 'empty-array' => array( array(), '' ), ); } @@ -164,6 +166,8 @@ public function data_is_preset() { 'css-var-invalid-slug-chars' => array( 'var(--wp--preset--duotone--.)', false ), 'css-var-missing-end-parenthesis' => array( 'var(--wp--preset--duotone--blue-orange', false ), 'invalid' => array( 'not a valid attribute', false ), + 'array-of-colors' => array( array( '#000000', '#ffffff' ), false ), + 'empty-array' => array( array(), false ), ); } From 4cad33af1d6b3ababddba904f99bd09494039f88 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 9 Feb 2026 18:42:51 +0000 Subject: [PATCH 009/145] Docs: Improve `@global` annotations in abstract-testcase.php Developed in https://github.com/WordPress/wordpress-develop/pull/10841 Follow-up to [61589], [61584]. Props noruzzaman, mukesh27, westonruter, shailu25, huzaifaalmesbah. See #64224. git-svn-id: https://develop.svn.wordpress.org/trunk@61604 602fd350-edb4-49c9-b593-d223f7449a82 --- tests/phpunit/includes/abstract-testcase.php | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index e4eefabfebf69..3a5d52b0706a9 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -160,10 +160,10 @@ public function wp_hash_password_options( array $options, string $algorithm ): a /** * After a test method runs, resets any state in WordPress the test method might have changed. * - * @global wpdb $wpdb WordPress database abstraction object. - * @global WP_Query $wp_the_query WordPress Query object. - * @global WP_Query $wp_query WordPress Query object. - * @global WP $wp WordPress environment object. + * @global wpdb $wpdb WordPress database abstraction object. + * @global WP_Query $wp_the_query Main WordPress query object. + * @global WP_Query $wp_query WordPress query object. + * @global WP $wp WordPress environment object. */ public function tear_down() { global $wpdb, $wp_the_query, $wp_query, $wp; @@ -370,10 +370,10 @@ protected function reset__SERVER() { * Stores $wp_filter, $wp_actions, $wp_filters, and $wp_current_filter * on a class variable so they can be restored on tear_down() using _restore_hooks(). * - * @global array $wp_filter Stores all of the filters and actions. - * @global array $wp_actions Stores the number of times each action was triggered. - * @global array $wp_filters Stores the number of times each filter was triggered. - * @global array $wp_current_filter Stores the list of current filters with the current one last. + * @global array $wp_filter All of the filters and actions. + * @global array $wp_actions The number of times each action was triggered. + * @global array $wp_filters The number of times each filter was triggered. + * @global array $wp_current_filter The list of current filters with the current one last. */ protected function _backup_hooks() { self::$hooks_saved['wp_filter'] = array(); @@ -393,10 +393,10 @@ protected function _backup_hooks() { * Restores the hook-related globals to their state at set_up() * so that future tests aren't affected by hooks set during this last test. * - * @global array $wp_filter Stores all of the filters and actions. - * @global array $wp_actions Stores the number of times each action was triggered. - * @global array $wp_filters Stores the number of times each filter was triggered. - * @global array $wp_current_filter Stores the list of current filters with the current one last. + * @global array $wp_filter All of the filters and actions. + * @global array $wp_actions The number of times each action was triggered. + * @global array $wp_filters The number of times each filter was triggered. + * @global array $wp_current_filter The list of current filters with the current one last. */ protected function _restore_hooks() { if ( isset( self::$hooks_saved['wp_filter'] ) ) { From f8b22857f4194b05876c5923d19b251b2f9cc8ee Mon Sep 17 00:00:00 2001 From: Ella Van Durpe Date: Mon, 9 Feb 2026 19:47:58 +0000 Subject: [PATCH 010/145] Gutenberg ref update. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates unit tests to account for: - "Dynamically add CSS class to Paragraph block" (https://github.com/WordPress/gutenberg/pull/71207) - New block server-side block registrations. Updates the REST API posts controller's excerpt filter to account for "Post Excerpt Block: Fix length limits for both Editor and Front and fix ellipsis consistency" (https://github.com/WordPress/gutenberg/pull/74140/changes#r2783014013). Developed in https://github.com/WordPress/wordpress-develop/pull/10865. Props ellatrix, scruffian, desrosj. See #64595. --- I've included a log of the Gutenberg changes with the following command: git log --reverse --format="- %s" 7bf80ea84eb8b62eceb1bb3fe82e42163673ca79..59a08c5496008ca88f4b6b86f38838c3612d88c8 | sed 's|#\([0-9][0-9]*\)|https://github.com/WordPress/gutenberg/pull/\1|g; /github\.com\/WordPress\/gutenberg\/pull/!d' | pbcopy - Editor: Cleanup active post as needed (https://github.com/WordPress/gutenberg/pull/74118) - Build: fully resolve import paths in transpiled files (https://github.com/WordPress/gutenberg/pull/73822) - Extensible Site Editor: The Canvas should share the same ThemeProvider as all the surfaces (https://github.com/WordPress/gutenberg/pull/74125) - Add Badge component to UI package (https://github.com/WordPress/gutenberg/pull/73875) - Theme_JSON_Resolver: defensively cover against situations where the post is null (https://github.com/WordPress/gutenberg/pull/74124) - Site Editor: Add extensible site editor experiment (https://github.com/WordPress/gutenberg/pull/74123) - Components: Fix DateTimePicker timezone handling for non-string values (https://github.com/WordPress/gutenberg/pull/73887) - Global Fonts: Convert relative font URLs to absolute theme URLs in font-face styles (https://github.com/WordPress/gutenberg/pull/74115) - Global Fonts: Correctly convert relative font URLs to absolute theme URLs in font-face styles (https://github.com/WordPress/gutenberg/pull/74137) - Add Line Indent support (https://github.com/WordPress/gutenberg/pull/73114) - Update report-flaky-tests action to use CommonJS module format (https://github.com/WordPress/gutenberg/pull/74152) - Media Modal experiment: Always show thumbnail field (https://github.com/WordPress/gutenberg/pull/74147) - Refactor isBlockHidden selector to simplify block support check (https://github.com/WordPress/gutenberg/pull/74151) - Apply `post_type_archive_title` on post type archive title in Breadcrumbs (https://github.com/WordPress/gutenberg/pull/73966) - DataView: update free-composition story (https://github.com/WordPress/gutenberg/pull/74146) - Add checkerboard pattern for background in featured image preview (https://github.com/WordPress/gutenberg/pull/74091) - Fix Post Date Block: Semantic use of `date` tag inside link (https://github.com/WordPress/gutenberg/pull/73788) - Terms Query Block: Fix Max terms for non-hierarchical taxonomies (https://github.com/WordPress/gutenberg/pull/74130) - Fields: Add MediaEdit component (https://github.com/WordPress/gutenberg/pull/73537) - Docs: Enhance documentation for Interactivity API and iAPI Router (https://github.com/WordPress/gutenberg/pull/73766) - DataViews: Add groupBy.showLabel config option to control group header label visibility (https://github.com/WordPress/gutenberg/pull/74161) - Theme_JSON_Resolver: check for `WP_Post` instance (https://github.com/WordPress/gutenberg/pull/74172) - Breadcrumbs: Stabilize block (https://github.com/WordPress/gutenberg/pull/74166) - Menu, CustomSelectControl (v1 & 2): Update animation (https://github.com/WordPress/gutenberg/pull/74111) - Add RTL support for drop caps in paragraph block styles in the editor (https://github.com/WordPress/gutenberg/pull/74058) - Font Library: fix help text position in Upload tab (https://github.com/WordPress/gutenberg/pull/74157) - Media Modal experiment: Tweak padding of the modal for consistency (https://github.com/WordPress/gutenberg/pull/74155) - Block visibility based on screen size: add backend block support (https://github.com/WordPress/gutenberg/pull/73994) - Accordion Header: Fix potential undo trap (https://github.com/WordPress/gutenberg/pull/74182) - Classic Block: Always use modal and display block placeholder (https://github.com/WordPress/gutenberg/pull/74162) - Update ToggleGroupControl visual design (https://github.com/WordPress/gutenberg/pull/74036) - Comment Author Name: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74068) - Query Loop: Hide `change design` or `choose pattern` when is locked (https://github.com/WordPress/gutenberg/pull/74160) - Fix: Prevent `accordion-heading` submitting/sending forms (button `type="button"`) (https://github.com/WordPress/gutenberg/pull/74177) - Button: Improve the label of the button block in list view (https://github.com/WordPress/gutenberg/pull/74163) - Add list view tab to the buttons, list and social icons blocks (https://github.com/WordPress/gutenberg/pull/74120) - improve `resolveSelect` type definition (https://github.com/WordPress/gutenberg/pull/73973) - Add label to MediaEdit component (https://github.com/WordPress/gutenberg/pull/74176) - Update LayoutCard story in DataForm to use card layout (https://github.com/WordPress/gutenberg/pull/73695) - `wordpress/dataviews`: migrate to Stack (https://github.com/WordPress/gutenberg/pull/74174) - `wordpress/dataviews`: reorganize code (https://github.com/WordPress/gutenberg/pull/74188) - Tests: Add unit tests for Button block __experimentalLabel functionality (https://github.com/WordPress/gutenberg/pull/74186) - Add `block_core_breadcrumbs_items` filter to Breadcrumbs allowing to filter final items array (https://github.com/WordPress/gutenberg/pull/74169) - `wordpress/dataviews`: improve stories and tests (https://github.com/WordPress/gutenberg/pull/74192) - Block Card: Make the parent block navigation generic, supports any block with list view support (https://github.com/WordPress/gutenberg/pull/74164) - Accordion: Passthrough 'openByDefault' value via context (https://github.com/WordPress/gutenberg/pull/74191) - Improve DataForm stories (https://github.com/WordPress/gutenberg/pull/74196) - DataViews: display a separate `—` for each level (https://github.com/WordPress/gutenberg/pull/74199) - Build: Support pnpm (https://github.com/WordPress/gutenberg/pull/74194) - Accordion: Remove 'isSelected' attribute (https://github.com/WordPress/gutenberg/pull/74198) - Update package changelogs (https://github.com/WordPress/gutenberg/pull/74202) - Docs: Clarify that `npm publishing` requires team approval during the RC1 launch (https://github.com/WordPress/gutenberg/pull/74204) - Extensible Site Editor: Lift template activation restriction (https://github.com/WordPress/gutenberg/pull/74197) - Block support: Add anchor support for dynamic blocks (https://github.com/WordPress/gutenberg/pull/74183) - Template Activation: Try fixing still flaky test (https://github.com/WordPress/gutenberg/pull/74216) - Build: Fix the default base url used when generating php files (https://github.com/WordPress/gutenberg/pull/74220) - Cleanup the dependencies in the root package.json (https://github.com/WordPress/gutenberg/pull/74212) - Remove outdated vendor prefix properties in CSS (https://github.com/WordPress/gutenberg/pull/74213) - Build: Rename extensible site editor page to avoid conflicts (https://github.com/WordPress/gutenberg/pull/74221) - Menu: Clean up popover wrappers (https://github.com/WordPress/gutenberg/pull/74207) - Use a stable npm version on static checks job (https://github.com/WordPress/gutenberg/pull/74222) - Block Editor: Make TextIndentControl component internal (https://github.com/WordPress/gutenberg/pull/74219) - Image Block: Add content tab and reorganize inspector controls (https://github.com/WordPress/gutenberg/pull/74201) - Extensible Site Editor: Fix the dashboard link (https://github.com/WordPress/gutenberg/pull/74231) - Command Palette: Fix in the font library page and site editor experiment (https://github.com/WordPress/gutenberg/pull/74232) - Block Inspector: Update the design of the style variation to use ToolsPanel (https://github.com/WordPress/gutenberg/pull/74224) - Add block transforms between Verse and Quote blocks (https://github.com/WordPress/gutenberg/pull/73068) - Docs: Fix `Get started with create-block` handbook link (https://github.com/WordPress/gutenberg/pull/74237) - tsconfig: Replace skipDefaultLibCheck with skipLibCheck (https://github.com/WordPress/gutenberg/pull/74239) - Docs: Fix `Gutenberg Release Process` handbook link (https://github.com/WordPress/gutenberg/pull/74240) - Schemas: Add breadcrumbs block schema (https://github.com/WordPress/gutenberg/pull/74227) - Tag Cloud: Use new HtmlRenderer component to remove extra div wrapper (https://github.com/WordPress/gutenberg/pull/74228) - Env: Strip version suffix for non-wp-org zip sources (https://github.com/WordPress/gutenberg/pull/74195) - DataViewsPicker Table Layout: Ensure checkbox column is always 48px wide (https://github.com/WordPress/gutenberg/pull/74181) - Docs: fix broken release process links (https://github.com/WordPress/gutenberg/pull/74250) - Add visibility badge for hidden blocks in the block inspector. (https://github.com/WordPress/gutenberg/pull/74180) - Docs: fix callout notices layout and clarify handbook link usage (https://github.com/WordPress/gutenberg/pull/74252) - Tag Cloud: Make error message prefix text translatable (https://github.com/WordPress/gutenberg/pull/74256) - Block variation transformation: change position and threshold (https://github.com/WordPress/gutenberg/pull/74251) - Tabs: Reset focus styles to avoid visual glitch (https://github.com/WordPress/gutenberg/pull/74225) - PHP-only blocks: use `HtmlRenderer` to ensure fontend & editor consistency (https://github.com/WordPress/gutenberg/pull/74261) - Add new `VisuallyHidden` component (https://github.com/WordPress/gutenberg/pull/74189) - Revert "Add Line Indent support (https://github.com/WordPress/gutenberg/pull/73114)" (https://github.com/WordPress/gutenberg/pull/74266) - Fix typos and improve clarity in documentation across multiple files (https://github.com/WordPress/gutenberg/pull/74270) - Archives Block: Use new HtmlRenderer component to remove extra div wrapper and remove editor styles (https://github.com/WordPress/gutenberg/pull/74255) - disable anchor more block (https://github.com/WordPress/gutenberg/pull/74267) - Comment Content: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74269) - Stylelint: Add design token linting (https://github.com/WordPress/gutenberg/pull/74226) - Storybook: Include design tokens styles automatically (https://github.com/WordPress/gutenberg/pull/73938) - Tabs: Adding border radius styling options (https://github.com/WordPress/gutenberg/pull/74103) - Storybook: Show props from component libraries (https://github.com/WordPress/gutenberg/pull/74279) - Theme: Fix design-tokens.js entrypoint to specify types and CJS variants (https://github.com/WordPress/gutenberg/pull/74129) - Add `Field` primitives (https://github.com/WordPress/gutenberg/pull/74190) - Validated form controls: Add stories for validation in popovers (https://github.com/WordPress/gutenberg/pull/71282) - Theme: Refine typography tokens (https://github.com/WordPress/gutenberg/pull/73931) - Packages: Avoid bumping the major version on prerelease packages (https://github.com/WordPress/gutenberg/pull/74285) - Components: Enhance Notice actions to allow more props like disabled and onClick with url (https://github.com/WordPress/gutenberg/pull/74094) - Update color ramp generation snapshots (https://github.com/WordPress/gutenberg/pull/74281) - Upgrade storybook to v9 (https://github.com/WordPress/gutenberg/pull/74143) - Footnotes Block: Fixing various Code Quality and Coding Standard issues (https://github.com/WordPress/gutenberg/pull/74243) - Fix: menu_order validation to allow zero and negative values (https://github.com/WordPress/gutenberg/pull/74282) - Fix: use WP_Theme_JSON_Gutenberg instead of WP_Theme_JSON class (https://github.com/WordPress/gutenberg/pull/74294) - PHP-only blocks: Generate inspector controls from attributes (https://github.com/WordPress/gutenberg/pull/74102) - Update the copyright license to 2026 (https://github.com/WordPress/gutenberg/pull/74306) - Update browsers list data (https://github.com/WordPress/gutenberg/pull/74312) - Storybook: Fix Sass warnings (https://github.com/WordPress/gutenberg/pull/74298) - Update eslint to 8.57.1 (https://github.com/WordPress/gutenberg/pull/74316) - Update eslint-plugin-storybook to 10.1.11 (https://github.com/WordPress/gutenberg/pull/74317) - Tag Cloud, Archives: Fix sidebar flash when changing settings (https://github.com/WordPress/gutenberg/pull/74291) - Tag Cloud, Archives: Restore missing block wrapper div (https://github.com/WordPress/gutenberg/pull/74321) - RSS Block: Use HtmlRenderer to remove extra div from editor and remove editor styles (https://github.com/WordPress/gutenberg/pull/74272) - Breadcrumbs Block: Use HtmlRenderer to remove extra div from editor (https://github.com/WordPress/gutenberg/pull/74273) - Latest Comments: Remove wrapper div and use HtmlRenderer for dynamic content rendering (https://github.com/WordPress/gutenberg/pull/74277) - DataForm: Fix panel field inaccessible when empty with labelPosition none or top (https://github.com/WordPress/gutenberg/pull/74264) - Storybook: Remove outdated story matchers (https://github.com/WordPress/gutenberg/pull/74299) - UI: Exclude package from `jsdoc/require-param` rule (https://github.com/WordPress/gutenberg/pull/74315) - Calender Block: Use HtmlRenderer to remove extra div from editor (https://github.com/WordPress/gutenberg/pull/74271) - Theme: Include Figma scopes extension in design tokens (https://github.com/WordPress/gutenberg/pull/73897) - UI: Remove redundant renderElement utility (https://github.com/WordPress/gutenberg/pull/74284) - Form Field Blocks: Replace dashicon with SVG icons (https://github.com/WordPress/gutenberg/pull/73996) - ContentOnlyControls: Polish header style (https://github.com/WordPress/gutenberg/pull/74260) - Footnotes: prevent inserting footnotes within a footnotes block (https://github.com/WordPress/gutenberg/pull/74287) - Block visibility based on screen size: basic clientside state (https://github.com/WordPress/gutenberg/pull/74025) - Block Support: Fix horizontal overflow in Manage allowed blocks modal (https://github.com/WordPress/gutenberg/pull/74337) - Block support: Backport anchor support changes in core (https://github.com/WordPress/gutenberg/pull/74341) - Dynamically add CSS class to Paragraph block (https://github.com/WordPress/gutenberg/pull/71207) - Test: Update URLs in tests to use example.org instead of test.com (https://github.com/WordPress/gutenberg/pull/74246) - Bump Node.js requirement to 20.19 (https://github.com/WordPress/gutenberg/pull/74342) - `@wordpress/theme`: update `colorjs.io` to version `0.6.0` (https://github.com/WordPress/gutenberg/pull/74278) - HtmlRenderer: Merge style props (https://github.com/WordPress/gutenberg/pull/74344) - @wordpress/theme: disable color ramp unit tests (https://github.com/WordPress/gutenberg/pull/74347) - Update the useCommandLoader example to fix the syntax error and add missing imports. (https://github.com/WordPress/gutenberg/pull/73660) - Code Modernization: Use null coalescing operator in place of `isset()` in ternaries. (https://github.com/WordPress/gutenberg/pull/74335) - Preview drop down: align preview editing widths with common breakpoints (https://github.com/WordPress/gutenberg/pull/74339) - Media mime type field: Disable sorting for now (https://github.com/WordPress/gutenberg/pull/74373) - Remove commented-out note regarding redundant settings OPTIONS requests in preload tests. (https://github.com/WordPress/gutenberg/pull/74375) - Core Merge: Deduplicate Font Library page and routes (https://github.com/WordPress/gutenberg/pull/74381) - Build: Build minified and non minified CSS in both npm run dev and npm run build (https://github.com/WordPress/gutenberg/pull/74380) - Fix TypeScript error output in check-build-type-declaration-files script (https://github.com/WordPress/gutenberg/pull/74346) - Revert bump of Node.js to 20.19 (https://github.com/WordPress/gutenberg/pull/74385) - Packages: Add support for publishing stable release of pre-release package (https://github.com/WordPress/gutenberg/pull/74332) - Forms Block: Switch from dashicons to SVG (https://github.com/WordPress/gutenberg/pull/74297) - Fit-text: Refactor control hook for readability (https://github.com/WordPress/gutenberg/pull/74350) - Pattern Overrides: Infer partial syncing supported blocks from the server (https://github.com/WordPress/gutenberg/pull/73889) - Categories Block: Fix CSS collision with labels (https://github.com/WordPress/gutenberg/pull/73862) - Fix parent popover not closing on click outside (https://github.com/WordPress/gutenberg/pull/74340) - List View Panel: Fix circular dependency issue that was breaking some Storybook stories (https://github.com/WordPress/gutenberg/pull/74399) - Block: memoize canOverrideBlocks (https://github.com/WordPress/gutenberg/pull/74400) - Fix storybook:dev race condition with dev script (https://github.com/WordPress/gutenberg/pull/74290) - Image Cropper package: Add react peer dependencies (https://github.com/WordPress/gutenberg/pull/74402) - Build: use .mjs extensions for build-module files (https://github.com/WordPress/gutenberg/pull/74348) - MediaEdit: expanded view (https://github.com/WordPress/gutenberg/pull/74336) - Inspector Fields: Show DataForm driven Content tab for all blocks that support content fields (+ support block bindings) (https://github.com/WordPress/gutenberg/pull/73863) - Build: Faster repo building in CI (https://github.com/WordPress/gutenberg/pull/74406) - `@wordpress/keycodes`: add `ariaKeyShortcut` and `shortcutFormats ` exports (https://github.com/WordPress/gutenberg/pull/74205) - Create default Core Navigation Overlay patterns (https://github.com/WordPress/gutenberg/pull/74047) - Enhance Block Bindings Documentation as per WP 6.9 updates: Customizing supported attributes an `getFieldsList` (https://github.com/WordPress/gutenberg/pull/73763) - Patterns: Improve memoization in the overrides panel (https://github.com/WordPress/gutenberg/pull/74407) - Docs: Remove "Customizing supported attributes filter" section from Block Bindings docs (https://github.com/WordPress/gutenberg/pull/74410) - fix script module IDs to use configured packageNamespace (https://github.com/WordPress/gutenberg/pull/74411) - Update package version after an unfinished publish (https://github.com/WordPress/gutenberg/pull/74413) - UI: Add `Fieldset` primitives (https://github.com/WordPress/gutenberg/pull/74296) - DataViews: add density picker to list layout (https://github.com/WordPress/gutenberg/pull/71050) - UI: Add `Icon` component (https://github.com/WordPress/gutenberg/pull/74311) - Separator, Code: don't require Enter for shortcut (https://github.com/WordPress/gutenberg/pull/63654) - Theme: Update semibold font weight to apply workaround at CSS (https://github.com/WordPress/gutenberg/pull/74392) - Block visibility based on screen size: add rules to hide on viewport size (https://github.com/WordPress/gutenberg/pull/74379) - Media Fields: Add "Date added" and "Date modified" fields (https://github.com/WordPress/gutenberg/pull/74401) - Fix missing dependencies for packages (https://github.com/WordPress/gutenberg/pull/74310) - DataForm validation story: add support for the details layout (https://github.com/WordPress/gutenberg/pull/74445) - Quote: Fix transformation error (https://github.com/WordPress/gutenberg/pull/74253) - Stop building wp-build by renaming the src directory (https://github.com/WordPress/gutenberg/pull/74450) - Update: Use 12px as minimum font size for warning on fit text. (https://github.com/WordPress/gutenberg/pull/74387) - Render custom overlay template parts in Navigation block (behind experiment) (https://github.com/WordPress/gutenberg/pull/73967) - UI: add `Button` (https://github.com/WordPress/gutenberg/pull/74415) - iAPI: Preserve boolean HTML attributes during client side navigation (https://github.com/WordPress/gutenberg/pull/74446) - Blocks: cache url root when registering assets (https://github.com/WordPress/gutenberg/pull/74451) - Rename overlay area (https://github.com/WordPress/gutenberg/pull/74444) - Bump minimum required PHP version to 7.4. (https://github.com/WordPress/gutenberg/pull/74457) - Show Navigation overlay patterns on right sidebar (https://github.com/WordPress/gutenberg/pull/74069) - Blocks: Fix root url cache fatal error (https://github.com/WordPress/gutenberg/pull/74459) - CI: Run the PHP unit tests with the oldest and latest versions (https://github.com/WordPress/gutenberg/pull/74460) - added group label and 100vh (https://github.com/WordPress/gutenberg/pull/74458) - Convert dom-ready package to TypeScript (https://github.com/WordPress/gutenberg/pull/67671) - List View: Fix focus shift to the selected nested block (https://github.com/WordPress/gutenberg/pull/74431) - Media Fields: Add an attached_to field (https://github.com/WordPress/gutenberg/pull/74432) - Updated useBlockProps to utilize block visibility and device type from context, the intention is to reduce unnecessary store subscriptions. (https://github.com/WordPress/gutenberg/pull/74481) - Block Fields: Decouple the experiment from contentOnly/pattern editing experiments (https://github.com/WordPress/gutenberg/pull/74479) - Image: add focal point controls (https://github.com/WordPress/gutenberg/pull/73115) - MediaEdit: Add drag and drop functionality (https://github.com/WordPress/gutenberg/pull/74455) - DependencyExtractionWebpackPlugin: add ui as bundled package (https://github.com/WordPress/gutenberg/pull/74485) - Parent selector: Fix dot divider horizontal spacing (https://github.com/WordPress/gutenberg/pull/74329) - wp-build: Fix dynamic base-styles import (https://github.com/WordPress/gutenberg/pull/74434) - Plugin: Bump minimum required WordPress version to 6.8 (https://github.com/WordPress/gutenberg/pull/74218) - Pass `post_id` as an argument to `block_core_breadcrumbs_post_type_settings` filter to allow more granular term choice (https://github.com/WordPress/gutenberg/pull/74170) - Block Editor: Close the inserter on small screens after adding a block (https://github.com/WordPress/gutenberg/pull/74487) - `@wordpress/ui` `Button`: add `destructive` tone (https://github.com/WordPress/gutenberg/pull/74463) - Fix punctuation and formatting in README.md (https://github.com/WordPress/gutenberg/pull/74440) - Hide Display section from Nav Inspector Controls if empty (https://github.com/WordPress/gutenberg/pull/74495) - PHPCS: Include the `test` directory (https://github.com/WordPress/gutenberg/pull/48754) - dom-ready: Replace @ts-expect-error with MockDocument in tests (https://github.com/WordPress/gutenberg/pull/74482) - TypeScript: Migrate `packages/jest-puppeteer-axe` package to TypeScript (https://github.com/WordPress/gutenberg/pull/70523) - dom-ready: Refactor tests to use defineProperty (https://github.com/WordPress/gutenberg/pull/74514) - Dev: Fix file change logs not displaying in watch mode (https://github.com/WordPress/gutenberg/pull/74452) - Block Fields: show all form fields by default (https://github.com/WordPress/gutenberg/pull/74486) - Heading: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74383) - Fix the dataviews experiment locked fields position on toggle. (https://github.com/WordPress/gutenberg/pull/74326) - Fully resolve some intra-package import paths (https://github.com/WordPress/gutenberg/pull/74530) - TypeScript: Migrate shortcode package to TS. (https://github.com/WordPress/gutenberg/pull/74522) - Navigation Overlay: Fix area and icon name (https://github.com/WordPress/gutenberg/pull/74520) - Storybook: Update "Introduction" doc (https://github.com/WordPress/gutenberg/pull/74500) - Storybook: Retire old theme switcher (https://github.com/WordPress/gutenberg/pull/74499) - Add design-tokens.css to stylelintignore (https://github.com/WordPress/gutenberg/pull/74498) - fix nextpage-more-disable-visibility (https://github.com/WordPress/gutenberg/pull/74531) - `@wordpress/ui` `Button`: undo `destructive` tone variant (https://github.com/WordPress/gutenberg/pull/74540) - Update nested-blocks-inner-blocks.md (https://github.com/WordPress/gutenberg/pull/74534) - Clamp signaling server retries to prevent unbounded backoff (https://github.com/WordPress/gutenberg/pull/74372) - `@wordpress/ui` `Button`: refactor to base ui (https://github.com/WordPress/gutenberg/pull/74416) - Storybook: Remove "background" tools from toolbar (https://github.com/WordPress/gutenberg/pull/74538) - Storybook: Remove margin checker tool (https://github.com/WordPress/gutenberg/pull/74539) - Fix documentation title for @wordpress/build package (https://github.com/WordPress/gutenberg/pull/74541) - TypeScript: Convert notices package to TypeScript (https://github.com/WordPress/gutenberg/pull/67670) - Client side media: enhance queue system (https://github.com/WordPress/gutenberg/pull/74501) - Improve cross origin isolation support (https://github.com/WordPress/gutenberg/pull/74418) - Remove WebRTC and IndexedDB providers (https://github.com/WordPress/gutenberg/pull/74555) - Block Editor: Prevent browser autocomplete in Navigation link search (https://github.com/WordPress/gutenberg/pull/74305) - Query Title: Fix incorrect quotation marks with trailing spaces (https://github.com/WordPress/gutenberg/pull/74300) - Layout: Add allowWrap option to flex layout block support (https://github.com/WordPress/gutenberg/pull/74493) - Block visibility support: use CSS range syntax for media queries (https://github.com/WordPress/gutenberg/pull/74526) - Block visibility: add viewport modal and controls UI (https://github.com/WordPress/gutenberg/pull/74249) - Media Fields: Add readonly author field to media fields package and use in the media modal (https://github.com/WordPress/gutenberg/pull/74484) - Paragraph block: Stop using named export from block.json (https://github.com/WordPress/gutenberg/pull/74527) - Block Visibility: Fix block position shift when toggling (https://github.com/WordPress/gutenberg/pull/74535) - Block Fields: Remove normalization code and tidy up (https://github.com/WordPress/gutenberg/pull/74532) - Inserter: Prevent block-scope variations insertion in slash inserter (https://github.com/WordPress/gutenberg/pull/74259) - Fix formatting in block bindings documentation: Corrected links to core sources by adding hyphens (https://github.com/WordPress/gutenberg/pull/74414) - Theme/UI: Add intro docs to Storybook (https://github.com/WordPress/gutenberg/pull/74551) - Notes: Enable floating notes in template lock mode (https://github.com/WordPress/gutenberg/pull/74577) - Editor: Remove hardcoded autosave conditions for templates (https://github.com/WordPress/gutenberg/pull/73781) - Theme: enable color ramp tests and update snapshots (https://github.com/WordPress/gutenberg/pull/74403) - `@wordpress/ui` `Button`: tweak disabled styles and rework tokens (https://github.com/WordPress/gutenberg/pull/74470) - Fully resolve moment-timezone import, improve build optimization (https://github.com/WordPress/gutenberg/pull/74578) - Update navigation-overlay-close block to be used as server side rendering (https://github.com/WordPress/gutenberg/pull/74579) - Real-time collaboration: Allow post-locked-modal to be overridden when `collaborative-editing` is enabled (https://github.com/WordPress/gutenberg/pull/72326) - Menu: Remove animation on submenus (https://github.com/WordPress/gutenberg/pull/74548) - UI: Remove individual experimental tags from Storybook (https://github.com/WordPress/gutenberg/pull/74582) - UI: Add dark background for Storybook theme switcher (https://github.com/WordPress/gutenberg/pull/74318) - updates variant handling to pull files before access to temporary directory is removed (https://github.com/WordPress/gutenberg/pull/73986) - UI: Add `InputLayout` primitive (https://github.com/WordPress/gutenberg/pull/74313) - Customize: Preserve CSS cascade for Additional CSS in classic themes (https://github.com/WordPress/gutenberg/pull/74593) - Update TypeScript base config to use bundler module resolution (https://github.com/WordPress/gutenberg/pull/74560) - Block Editor: Add autoComplete attribute to prevent browser autocomplete (https://github.com/WordPress/gutenberg/pull/74595) - Publishing next packages: remove commit hash from version (https://github.com/WordPress/gutenberg/pull/74589) - Inserter: only show blocks that can be inserted on the page (https://github.com/WordPress/gutenberg/pull/74453) - Comments Title Block: Fix double quotes in non-English locales (https://github.com/WordPress/gutenberg/pull/74330) - DataViews stories: add custom layout (https://github.com/WordPress/gutenberg/pull/74605) - Navigation Overlay: Add default paragraph block (https://github.com/WordPress/gutenberg/pull/74592) - Components: Fix InputControl label overflow for long translations (https://github.com/WordPress/gutenberg/pull/74301) - Eslint: Add design token linting (https://github.com/WordPress/gutenberg/pull/74325) - Update Storybook to v10 with Vite builder (https://github.com/WordPress/gutenberg/pull/74396) - Navigations within overlays should not increment aria label attributs (https://github.com/WordPress/gutenberg/pull/74469) - Add template part context to navigation block (https://github.com/WordPress/gutenberg/pull/74614) - Navigation: When a navigation block has a custom overlay, the submenu colors should not apply to the overlay (https://github.com/WordPress/gutenberg/pull/74544) - Improve type safety with YMapWrap (https://github.com/WordPress/gutenberg/pull/73948) - Rename `--fast` build flag and use in Storybook build (https://github.com/WordPress/gutenberg/pull/74552) - Fix deprecations for Storybook component usage (https://github.com/WordPress/gutenberg/pull/74619) - Real-time collaboration: Use alternative diff in quill-delta, provide incremental text updates (https://github.com/WordPress/gutenberg/pull/73699) - Real-time collaboration: Move collaborative editing from experiments to default Gutenberg plugin experience (https://github.com/WordPress/gutenberg/pull/74562) - Real-time Collaboration: Add Yjs awareness foundation (https://github.com/WordPress/gutenberg/pull/74565) - Image Block: Fix empty block content tools when multiselecting image blocks (https://github.com/WordPress/gutenberg/pull/74604) - Content-only: remove `mapping` and `args` in favor of DataForm API (https://github.com/WordPress/gutenberg/pull/74575) - TypeScript: Convert redux-store types in data package to TS (https://github.com/WordPress/gutenberg/pull/67666) - Add list view inspector tab for pattern editing (https://github.com/WordPress/gutenberg/pull/74574) - api-fetch: Add named export to fix TypeScript callable issues (https://github.com/WordPress/gutenberg/pull/74576) - Fix: Dataview: column header move item in RTL moves in the opposite direction to the arrow (https://github.com/WordPress/gutenberg/pull/74644) - UI: Add `Input` primitive (https://github.com/WordPress/gutenberg/pull/74615) - Improve wp-build generated PHP files with proper prefixing and naming (https://github.com/WordPress/gutenberg/pull/74490) - Navigation Submenu: Show (Invalid) indicator when parent page is deleted (https://github.com/WordPress/gutenberg/pull/74461) - components: Fix generated TS types referencing unavailable `csstype` (https://github.com/WordPress/gutenberg/pull/74655) - Real-time collaboration: Refetch entity when it is saved by a peer (https://github.com/WordPress/gutenberg/pull/74637) - add a white background to the overlay default pattern (https://github.com/WordPress/gutenberg/pull/74659) - Infrastructure: Convert storybook to a workspace package (https://github.com/WordPress/gutenberg/pull/74640) - Remove unused dependencies (https://github.com/WordPress/gutenberg/pull/74624) - Apply only detected changes from the persisted CRDT document (https://github.com/WordPress/gutenberg/pull/74668) - Enable components manifest for Storybook (https://github.com/WordPress/gutenberg/pull/74626) - Move ESLint rules specific to `@wordpress/components` to custom rules (https://github.com/WordPress/gutenberg/pull/74611) - Navigaiton: Refactor SCSS to reduce duplication (https://github.com/WordPress/gutenberg/pull/74666) - Site Editor: If the route cannot be found treat the canvas mode as view (https://github.com/WordPress/gutenberg/pull/74642) - `@wordpress/components`: lint and fix `@wordpress/components-no-missing-40px-size-prop` rule (https://github.com/WordPress/gutenberg/pull/74622) - Block visibility supports: refactor metadata to use nested structure (https://github.com/WordPress/gutenberg/pull/74602) - Media Editor: Add a simple media editor package and integrate into the editor package (https://github.com/WordPress/gutenberg/pull/74601) - Embed: Fix Flickr double-padding with responsive wrapper (https://github.com/WordPress/gutenberg/pull/73902) - Block visibility: render blocks when hidden at all viewports (and other changes) (https://github.com/WordPress/gutenberg/pull/74679) - Add missing chevron-up-small icon. (https://github.com/WordPress/gutenberg/pull/74607) - List View: Ensure element exists in document before focusing (https://github.com/WordPress/gutenberg/pull/74613) - Allow for themes to define the overlay attribute without using a theme slug (https://github.com/WordPress/gutenberg/pull/74119) - DataViews: Fix insert left and right handling in table layout for RTL languages (https://github.com/WordPress/gutenberg/pull/74681) - SlotFill: unify registry and fill implementation (https://github.com/WordPress/gutenberg/pull/68056) - Storybook: Automate sidebar sort order (https://github.com/WordPress/gutenberg/pull/74672) - Fix: Update function names to include wp_ prefix (https://github.com/WordPress/gutenberg/pull/74688) - Make custom navigation overlay full width (https://github.com/WordPress/gutenberg/pull/74559) - Components: Add `@types/react` to dependencies for TypeScript type resolution (https://github.com/WordPress/gutenberg/pull/74692) - Core backport for Global Styles: Allow arbitrary CSS, protect from KSES mangling (https://github.com/WordPress/gutenberg/pull/74371) - UI: Add `Select` primitive (https://github.com/WordPress/gutenberg/pull/74661) - Badge: Use stories for "Choosing intent" doc (https://github.com/WordPress/gutenberg/pull/74675) - Add `Tooltip` component to `@wordpress/ui` (https://github.com/WordPress/gutenberg/pull/74625) - Image block: show aspect ratio control for wide and full alignment (https://github.com/WordPress/gutenberg/pull/74519) - Bump the github-actions group across 1 directory with 3 updates (https://github.com/WordPress/gutenberg/pull/74002) - Bump mdast-util-to-hast from 13.1.0 to 13.2.1 in /platform-docs (https://github.com/WordPress/gutenberg/pull/73683) - Updated Minor Typo in Compatibility Rest API File (https://github.com/WordPress/gutenberg/pull/74718) - Block Editor Provider: Fix conditional useMemo call when media processing experiment is active (https://github.com/WordPress/gutenberg/pull/74680) - Reset inspector tab selection if the selected tab is no longer present (https://github.com/WordPress/gutenberg/pull/74682) - Remove react-refresh bundling (https://github.com/WordPress/gutenberg/pull/74721) - iAPI: Fix and refactor runtime initialization logic (https://github.com/WordPress/gutenberg/pull/71123) - Comment Edit Link: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74720) - Update wp-build documentation to describe 'wpPlugin.name' (https://github.com/WordPress/gutenberg/pull/74741) - Navigation Overlay: insert default pattern on creation (https://github.com/WordPress/gutenberg/pull/74650) - DataViews: Use regular casing for bulk selection count (https://github.com/WordPress/gutenberg/pull/74573) - Fix wp-theme dependencies in the build. (https://github.com/WordPress/gutenberg/pull/74743) - Do not wrap persisted doc applied update in transaction (https://github.com/WordPress/gutenberg/pull/74753) - Revert "Fixed Media & Text Block - Image not rendered properly on frontend when inside stack (https://github.com/WordPress/gutenberg/pull/68610)" (https://github.com/WordPress/gutenberg/pull/74715) - Create Block: Simplify blocks-manifest registration (https://github.com/WordPress/gutenberg/pull/74647) - Pattern Editing: Prevent double-click editing template parts and synced patterns (https://github.com/WordPress/gutenberg/pull/74755) - Paragraph: Add text column support (https://github.com/WordPress/gutenberg/pull/74656) - Update overlay control labels (https://github.com/WordPress/gutenberg/pull/74690) - Comment Date: Add textAlign Support (https://github.com/WordPress/gutenberg/pull/74599) - Don't show overlay settings for navigation blocks that are inside oth… (https://github.com/WordPress/gutenberg/pull/74408) - Remove the apiFetch named export (https://github.com/WordPress/gutenberg/pull/74761) - MediaEdit: Support `custom` validation (https://github.com/WordPress/gutenberg/pull/74704) - components: Add `displayName` to the anonymous components (https://github.com/WordPress/gutenberg/pull/74716) - Pattern Overrides: Remove obsolete documentation (https://github.com/WordPress/gutenberg/pull/74749) - Verse Block: Add new textAlign support (https://github.com/WordPress/gutenberg/pull/74724) - DataViews: Move filtering logic in field types (https://github.com/WordPress/gutenberg/pull/74733) - Fix: can't disable textColumns UI (https://github.com/WordPress/gutenberg/pull/74767) - Navigation: Don't use a nav tag for navigation blocks inside overlays (https://github.com/WordPress/gutenberg/pull/74764) - Allow grid layout to use theme blockGap values for columns calculation (https://github.com/WordPress/gutenberg/pull/74725) - Move grid manual mode sync into 7.1 folder (https://github.com/WordPress/gutenberg/pull/74792) - Show block content for label in List View (https://github.com/WordPress/gutenberg/pull/74794) - Ensure grid column never exceeds parent's width (https://github.com/WordPress/gutenberg/pull/74795) - Term List block: Pre-select current term on term archive pages (https://github.com/WordPress/gutenberg/pull/74603) - Update performance results endpoint to codevitals.run (https://github.com/WordPress/gutenberg/pull/74802) - Update performance results endpoint to use fetch API for redirect handling (https://github.com/WordPress/gutenberg/pull/74803) - iAPI: Update deprecation warning for unique ID format (https://github.com/WordPress/gutenberg/pull/74580) - Cover Block: Enable focal point picker for fixed background (https://github.com/WordPress/gutenberg/pull/74600) - Blocks: Always trigger borwser console warnings for blocks with apiVersion below 2 (https://github.com/WordPress/gutenberg/pull/74057) - Fix typo in comment for value change check (https://github.com/WordPress/gutenberg/pull/74730) - Move useIsDraggingWithin to a shared hook (https://github.com/WordPress/gutenberg/pull/74804) - Include totals items count in DataView footer (https://github.com/WordPress/gutenberg/pull/73491) - Storybook: Fix missing props from component stories (https://github.com/WordPress/gutenberg/pull/74807) - Breadcrumbs :Add example block previews (https://github.com/WordPress/gutenberg/pull/74808) - Core backport for gutenberg_filter_global_styles_post: Protect from KSES mangling (https://github.com/WordPress/gutenberg/pull/74731) - Move selectLabelText to shared utility (https://github.com/WordPress/gutenberg/pull/74805) - Bump node-forge from 1.3.1 to 1.3.3 in /platform-docs (https://github.com/WordPress/gutenberg/pull/74292) - Fix blockGap styles not working in block style variations (https://github.com/WordPress/gutenberg/pull/74529) - Comment Reply Link: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74760) - Try storing global styles in static var in layout render (https://github.com/WordPress/gutenberg/pull/74828) - List View support: show full block titles (https://github.com/WordPress/gutenberg/pull/74798) - Pattern Editing: Update template part to use tabs (https://github.com/WordPress/gutenberg/pull/74793) - Block visibility: create selectors for block visibility in current viewport (device setting or responsive) (https://github.com/WordPress/gutenberg/pull/74517) - Fix: add border-box sizing for verse block (https://github.com/WordPress/gutenberg/pull/74722) - Block Visibility: fix failing unit test (https://github.com/WordPress/gutenberg/pull/74840) - Breadcrumbs: Fix placeholder separator preview (https://github.com/WordPress/gutenberg/pull/74842) - Dataviews: Fix actions visibility on smaller viewpoints and for lone action with isPrimary as true (https://github.com/WordPress/gutenberg/pull/74836) - Navigation Overlay: Add sidebar preview (https://github.com/WordPress/gutenberg/pull/74780) - Show submenu colors but remove the word overlay (https://github.com/WordPress/gutenberg/pull/74818) - E2e tests: remove editor.switchToLegacyCanvas from multi select and a11y suite (https://github.com/WordPress/gutenberg/pull/74845) - Enable build-blocks-manifest by default (https://github.com/WordPress/gutenberg/pull/74846) - Direct drag: fix glitching around scrolling (https://github.com/WordPress/gutenberg/pull/74608) - Handle deleted navigation overlays (https://github.com/WordPress/gutenberg/pull/74766) - iAPI Router: Prevent router regions with `data-wp-key` from being recreated on navigation (https://github.com/WordPress/gutenberg/pull/74750) - iAPI Router: Fix initial router regions with `attachTo` being duplicated after `navigate()` (https://github.com/WordPress/gutenberg/pull/74857) - DataViews: Adjust table primary media field styles (https://github.com/WordPress/gutenberg/pull/74813) - Fix: Escape less-than character in HTML attributes to prevent block recovery errors (https://github.com/WordPress/gutenberg/pull/74732) - DataViews: Update storybook to add more context (https://github.com/WordPress/gutenberg/pull/74819) - Sync: Refactor ProviderCreator signature to an object (https://github.com/WordPress/gutenberg/pull/74871) - Real-time Collaboration: Add user and selection information to awareness (https://github.com/WordPress/gutenberg/pull/74728) - Add custom CSS support for individual block instances (https://github.com/WordPress/gutenberg/pull/73959) - Style Engine: Bail early when adding a declaration if not passed a string (https://github.com/WordPress/gutenberg/pull/74881) - Stabilise viewport based block visibility (https://github.com/WordPress/gutenberg/pull/74839) - Navigation: Add a new option that toggles submenus always open (https://github.com/WordPress/gutenberg/pull/74653) - Fix: Fit Text not working on calculated line heights. (https://github.com/WordPress/gutenberg/pull/74860) - Fix: Safari "Edit as HTML" for Fit Text deletes content (https://github.com/WordPress/gutenberg/pull/74864) - Route: Add notFound to public API and add route validation (https://github.com/WordPress/gutenberg/pull/74867) - DataForm: add `combobox` control (https://github.com/WordPress/gutenberg/pull/74891) - Real-time collaboration: Use relative positions in undo stack (https://github.com/WordPress/gutenberg/pull/74878) - MediaReplaceFlow: Move Reset option to bottom of menu (https://github.com/WordPress/gutenberg/pull/74882) - Real-time collaboration: Sync collections (https://github.com/WordPress/gutenberg/pull/74665) - Feat/core tabs restructure (https://github.com/WordPress/gutenberg/pull/74412) - Inserter: Fix missing onClose prop for Inserter Menu (https://github.com/WordPress/gutenberg/pull/74920) - Post Excerpt Block: Fixing max limits for generated excerpts (https://github.com/WordPress/gutenberg/pull/74140) - Post Excerpt Block: Fix excerpt trimming logic to handle whitespace correctly (https://github.com/WordPress/gutenberg/pull/74925) - e2e: fix flaky tests for settings sidebar (https://github.com/WordPress/gutenberg/pull/74929) - Comments Title: Copy deprecate from block.json to deprecated.js to avoid legacy attribute usage (https://github.com/WordPress/gutenberg/pull/74924) - Added Missing Global Documentation (https://github.com/WordPress/gutenberg/pull/74868) - dataviews: Fix missing dependency - @storybook/addon-docs (https://github.com/WordPress/gutenberg/pull/74935) - Patterns: restore rename and delete actions for user patterns (https://github.com/WordPress/gutenberg/pull/74927) - DataViews: Add card form layout validation (https://github.com/WordPress/gutenberg/pull/74547) - E2e tests: remove switchToLegacyCanvas from inserter drag and drop tests (https://github.com/WordPress/gutenberg/pull/74892) - Navigation Overlays: Default new blocks to "always" show overlays (https://github.com/WordPress/gutenberg/pull/74890) - Remove link underline style from default theme.json (https://github.com/WordPress/gutenberg/pull/74901) - selectBlock: fall back to next block if no previous block is present (https://github.com/WordPress/gutenberg/pull/74938) - Update: Preserve additional meta properties on client side abilities. (https://github.com/WordPress/gutenberg/pull/73918) - E2e tests: bump all test blocks to API v3 (https://github.com/WordPress/gutenberg/pull/74941) - Cover Block: Show current embed URL in dialog (https://github.com/WordPress/gutenberg/pull/74885) - core-data: Fix missing dependencies (https://github.com/WordPress/gutenberg/pull/74934) - Build script: Increase memory limit for storybook build process (https://github.com/WordPress/gutenberg/pull/74933) - Real-time collaboration: Pass non-cleaned (but merged) edits to `SyncManager#update` (https://github.com/WordPress/gutenberg/pull/74912) - Navigation overlay patterns: overlay with black background (https://github.com/WordPress/gutenberg/pull/74847) - Navigation overlay patterns: overlay with accent background (https://github.com/WordPress/gutenberg/pull/74849) - Shortcode: Fix non-string attribute values being silently dropped (https://github.com/WordPress/gutenberg/pull/74949) - core-data: Fix yjs import and missing dependency (https://github.com/WordPress/gutenberg/pull/74950) - Icons: Add a manifest containing icons metadata (https://github.com/WordPress/gutenberg/pull/74943) - Babel Preset Default: Remove legacy plugins (https://github.com/WordPress/gutenberg/pull/74916) - Real-time collaboration: Fix undo tests (https://github.com/WordPress/gutenberg/pull/74955) - BlockBreadcrumb: Show custom block name (https://github.com/WordPress/gutenberg/pull/73690) - Fix: Stretchy text issue when nested on flex containers. (https://github.com/WordPress/gutenberg/pull/73652) - iAPI: Don't use deprecated `data-wp-on-async` in docs (https://github.com/WordPress/gutenberg/pull/72591) - Comments Title: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74945) - iAPI Docs: add config to state/context guide (https://github.com/WordPress/gutenberg/pull/71355) - Add content element guidelines for fields in DataForm (https://github.com/WordPress/gutenberg/pull/74817) - Navigation overlay patterns: centered navigation with info (https://github.com/WordPress/gutenberg/pull/74862) - In-editor revisions (initial changes, no diffing) (https://github.com/WordPress/gutenberg/pull/74771) - Docs: Add missing @global documentation in REST assets controller (https://github.com/WordPress/gutenberg/pull/74973) - Docs: Add missing @return tags to experimental functions (https://github.com/WordPress/gutenberg/pull/74960) - Docs: Replace @see with @link for URL references (https://github.com/WordPress/gutenberg/pull/74961) - Gallery block: Image Caption Blur Issue Fix (https://github.com/WordPress/gutenberg/pull/74063) - Inserter Component: Improving Stories (https://github.com/WordPress/gutenberg/pull/74922) - Block Visibility: fix flaky e2e test (https://github.com/WordPress/gutenberg/pull/74931) - Media Modal Experiment: Add a simple notices-based uploading state (https://github.com/WordPress/gutenberg/pull/74965) - Docs: Standardize use of @link tag for URL references in lib directory (https://github.com/WordPress/gutenberg/pull/74984) - Pattern editing: stabilize and remove the experiment flag (https://github.com/WordPress/gutenberg/pull/74843) - Remove comment about non-existing property (https://github.com/WordPress/gutenberg/pull/75003) - Video block: Fix video URLs pasted without "https://" show broken media (https://github.com/WordPress/gutenberg/pull/74964) - Fix flaky 'Revisions' e2e test (https://github.com/WordPress/gutenberg/pull/75002) - Build: deduplicate and minify embedded styles (https://github.com/WordPress/gutenberg/pull/74651) - Navigation overlay patterns: centered navigation (https://github.com/WordPress/gutenberg/pull/74861) - wp-env: Add experimental WordPress Playground runtime support (https://github.com/WordPress/gutenberg/pull/74609) - Consolidate border tokens (https://github.com/WordPress/gutenberg/pull/74617) - Add the `has-custom-css` class name to the editor and dynamic blocks. (https://github.com/WordPress/gutenberg/pull/74969) - Real-time collaboration: Add default HTTP polling sync provider (https://github.com/WordPress/gutenberg/pull/74564) - eslint-plugin: Add "never" option for dependency-group rule (https://github.com/WordPress/gutenberg/pull/74990) - Design System: Add guidelines for destructive actions UX (https://github.com/WordPress/gutenberg/pull/74778) - DataViews: Show validation errors when a panel closes (https://github.com/WordPress/gutenberg/pull/74995) - DataForm: Sync React-level validation to native inputs on date fields. (https://github.com/WordPress/gutenberg/pull/74994) - Pattern Editing: Hide List View child blocks in Content panel (https://github.com/WordPress/gutenberg/pull/75007) - Infrastructure: Add storybook to tsconfig project references (https://github.com/WordPress/gutenberg/pull/74887) - Real-time Collaboration: Add hook for accessing awareness data (https://github.com/WordPress/gutenberg/pull/75009) - Hide grid visualiser if the grid block is hidden (https://github.com/WordPress/gutenberg/pull/74963) - Add unit test for gap in block style variations fix (https://github.com/WordPress/gutenberg/pull/75038) - Post Excerpt: Disable HTML element insertion (https://github.com/WordPress/gutenberg/pull/74928) - Deprecate 'Post author' block (https://github.com/WordPress/gutenberg/pull/55352) - Fix emdashes in HTML anchor description (https://github.com/WordPress/gutenberg/pull/75043) - In-editor revisions: preserve client IDs to prevent flashes/remounts (https://github.com/WordPress/gutenberg/pull/75028) - Playlist block (https://github.com/WordPress/gutenberg/pull/50664) - Media & Text: Respect image_default_link_type option (https://github.com/WordPress/gutenberg/pull/74295) - Author Biography: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/74997) - Dataform: Adds validation support to the DataForm details layout (https://github.com/WordPress/gutenberg/pull/74996) - Docs: Clarifies cherry-picking permissions and improves minor release workflow documentation (https://github.com/WordPress/gutenberg/pull/75034) - Routing Boot Package: Remove left border from stage and inspector surfaces (https://github.com/WordPress/gutenberg/pull/75036) - Replace install-path command with status command in wp-env (https://github.com/WordPress/gutenberg/pull/75020) - Remove temp files (https://github.com/WordPress/gutenberg/pull/75061) - Update and unpin sync package dependencies (https://github.com/WordPress/gutenberg/pull/75059) - Navigation Overlay: Add Create Overlay button (https://github.com/WordPress/gutenberg/pull/74971) - Try hiding parent grid cells when child grid is selected. (https://github.com/WordPress/gutenberg/pull/75078) - Notes: Use preferences store when applicable (https://github.com/WordPress/gutenberg/pull/75008) - Notes: Don't trigger reflow for pinned sidebar (https://github.com/WordPress/gutenberg/pull/75010) - Resize meta box pane without `ResizableBox` (https://github.com/WordPress/gutenberg/pull/66735) - `@wordpress/ui`: add `IconButton` (https://github.com/WordPress/gutenberg/pull/74697) - Private APIs: remove duplicate `@wordpress/ui` entry (https://github.com/WordPress/gutenberg/pull/75051) - DataViews: Fix title truncation in `list` layout (https://github.com/WordPress/gutenberg/pull/75063) - Custom CSS support: Add attributes for dynamic blocks. (https://github.com/WordPress/gutenberg/pull/75052) - DataViews: Fix fields async validation (https://github.com/WordPress/gutenberg/pull/74948) - Unified view persistence: Share one persisted view across all tabs (https://github.com/WordPress/gutenberg/pull/74970) - SVG Icon registration API (https://github.com/WordPress/gutenberg/pull/72215) - Navigation: Use :where on the :not(.disable-default-overlay) selector so that the scope doesn't change. (https://github.com/WordPress/gutenberg/pull/75090) - wp-env: Fix MySQL startup race condition causing database connection errors (https://github.com/WordPress/gutenberg/pull/75046) - RichText: fix white space collapsing arround formatting (https://github.com/WordPress/gutenberg/pull/74820) - Docs: Add missing @global documentation in rtl.php and meta-box.php (https://github.com/WordPress/gutenberg/pull/75082) - Blocks: Try prepending 'https' to URLs without protocol (https://github.com/WordPress/gutenberg/pull/75005) - wp-env: Add cleanup command and force flag (https://github.com/WordPress/gutenberg/pull/75045) - DataViews: Add `title` attribute in `grid` item title field (https://github.com/WordPress/gutenberg/pull/75085) - wp-env: Fix mixed runtime detection issues (https://github.com/WordPress/gutenberg/pull/75057) - `@wordpress/ui`: add `Tabs` (https://github.com/WordPress/gutenberg/pull/74652) - Run generate-worker-placeholders script in dev (https://github.com/WordPress/gutenberg/pull/75104) - Docs: Add missing @global documentation in block library (https://github.com/WordPress/gutenberg/pull/75004) - Site Editor: Prevent welcome guide from appearing during loading (https://github.com/WordPress/gutenberg/pull/75102) - Media Fields: Fix filename field truncation (https://github.com/WordPress/gutenberg/pull/75091) - Block Supports: Add Line Indent support using enum setting (https://github.com/WordPress/gutenberg/pull/74889) - useBlockVisibility: consolidate useMemo calls to the output object (https://github.com/WordPress/gutenberg/pull/75120) - Post Author Name: Migrate to text-align block support (https://github.com/WordPress/gutenberg/pull/75109) - Restore deprecated Pullquote Block (https://github.com/WordPress/gutenberg/pull/75122) - useBlockVisibility: Remove the last 'useMemo' call (https://github.com/WordPress/gutenberg/pull/75125) - remove horizontal scroll (https://github.com/WordPress/gutenberg/pull/75086) - Refactor activeFilters to activeViewOverrides with date sort for User tab (https://github.com/WordPress/gutenberg/pull/75094) - Post Content Block: Improve removal confirmation modal (https://github.com/WordPress/gutenberg/pull/75001) - DataViews: Consistent rendering of selection checkbox and actions in `grid` layout (https://github.com/WordPress/gutenberg/pull/75056) - Pullquote: Fix deprecated block validation when anchor/id attribute is present (https://github.com/WordPress/gutenberg/pull/75132) - Add URL validation in LinkControl using ValidatedInputControl (https://github.com/WordPress/gutenberg/pull/73486) - Components: remove "text-wrap: balance" fallback from Text (https://github.com/WordPress/gutenberg/pull/75089) - Image Block: Handle image URLs without protocol (https://github.com/WordPress/gutenberg/pull/75135) - fix the color of the overlay to fix contrast issues on dark themes (https://github.com/WordPress/gutenberg/pull/74979) - Admin UI: apply 'text-wrap: pretty' to Page (https://github.com/WordPress/gutenberg/pull/74907) - Fix dev build for fresh checkouts (or with build/scripts/block-library missing) (https://github.com/WordPress/gutenberg/pull/75108) - Calculate viewport based on iframe size in resizable editor. (https://github.com/WordPress/gutenberg/pull/75156) - Media Modal Experiment: Remove default value for allowedTypes so that the file block can accept all types (https://github.com/WordPress/gutenberg/pull/75159) - wp-env Playground: Support zip archive themes (https://github.com/WordPress/gutenberg/pull/75155) - Block Editor: Allow stable block IDs in block editor store (https://github.com/WordPress/gutenberg/pull/74687) - Code Quality: Remove deprecated __nextHasNoMarginBottom prop (https://github.com/WordPress/gutenberg/pull/75139) - Migrate textAlign attributes from the Author block to block support when migrating. (https://github.com/WordPress/gutenberg/pull/75153) - Scripts: Fix contributor guide link in README (https://github.com/WordPress/gutenberg/pull/75161) - ToggleGroupControl: add visual emphasis to selected item (https://github.com/WordPress/gutenberg/pull/75138) - Image block: Add missing space between sentences (https://github.com/WordPress/gutenberg/pull/75142) - DOM: exclude inert elements from focus.focusable (https://github.com/WordPress/gutenberg/pull/75172) - Writing flow: fix Cmd+A from empty RichText (https://github.com/WordPress/gutenberg/pull/75175) - Theme: Update dimension tokens (https://github.com/WordPress/gutenberg/pull/75054) - Build: Add vendorScripts config to build packages from node_modules (https://github.com/WordPress/gutenberg/pull/74343) - ui/`Button`: add min width (https://github.com/WordPress/gutenberg/pull/75133) - Navigation: Consolidate SVG rendering functions to a shared helper (https://github.com/WordPress/gutenberg/pull/74853) - RangeControl: support forced-colors mode (https://github.com/WordPress/gutenberg/pull/75165) - Restrict base-ui imports outside of UI component packages (https://github.com/WordPress/gutenberg/pull/75143) - Remove the React Native test status badges. (https://github.com/WordPress/gutenberg/pull/74674) - DataViews: externalize theme stylesheet (https://github.com/WordPress/gutenberg/pull/75182) - Media Modal Experiment: Update preview size to be a little smaller (https://github.com/WordPress/gutenberg/pull/75191) - Env: Remove non-functional `WP_ENV_MULTISITE` config (https://github.com/WordPress/gutenberg/pull/72567) - Cover block: Force LTR direction for the background URL input field (https://github.com/WordPress/gutenberg/pull/75169) - Tabs block: Polish (https://github.com/WordPress/gutenberg/pull/75128) - Real-time Collaboration: Add collaborators presence UI (https://github.com/WordPress/gutenberg/pull/75065) - DataForm: mark fields as required or optional automatically (https://github.com/WordPress/gutenberg/pull/74430) - ToggleControl: pass full props to the input element (https://github.com/WordPress/gutenberg/pull/74956) - Media & Text: Fix RTLCSS control directives appearing in production CSS (https://github.com/WordPress/gutenberg/pull/73205) - @wordpress/ui: use semantic dimension tokens (https://github.com/WordPress/gutenberg/pull/74557) - Fix duplicate content when navigation overlay is open and nav has non-link inner blocks (https://github.com/WordPress/gutenberg/pull/75180) - Group fix example text-align attributes (https://github.com/WordPress/gutenberg/pull/75200) - Editor: Introduce new selectedNote editor state (https://github.com/WordPress/gutenberg/pull/75177) - Block Support: Allow serialization skipping for ariaLabel (https://github.com/WordPress/gutenberg/pull/75192) git-svn-id: https://develop.svn.wordpress.org/trunk@61605 602fd350-edb4-49c9-b593-d223f7449a82 --- package.json | 2 +- src/wp-admin/includes/update-core.php | 4 - .../class-wp-rest-posts-controller.php | 4 +- .../data/blocks/do-blocks-expected.html | 4 +- .../blocks/fixtures/core__column.server.html | 12 +-- .../blocks/fixtures/core__columns.server.html | 32 +++---- .../core__columns__deprecated.server.html | 24 ++--- .../fixtures/core__media-text.server.html | 2 +- ...media-text__image-alt-no-align.server.html | 2 +- ...dia-text__is-stacked-on-mobile.server.html | 2 +- ...text__media-right-custom-width.server.html | 2 +- .../core__media-text__video.server.html | 2 +- .../core__paragraph__align-right.server.html | 2 +- .../includes/unregister-blocks-hooks.php | 89 +++---------------- .../tests/block-bindings/postMetaSource.php | 18 ++-- tests/phpunit/tests/block-bindings/render.php | 10 +-- tests/phpunit/tests/blocks/render.php | 3 + tests/phpunit/tests/blocks/renderReusable.php | 4 +- .../tests/formatting/excerptRemoveBlocks.php | 8 +- tests/phpunit/tests/post/output.php | 8 +- .../rest-api/rest-widgets-controller.php | 2 +- tools/gutenberg/copy-gutenberg-build.js | 19 ++++ 22 files changed, 104 insertions(+), 151 deletions(-) diff --git a/package.json b/package.json index 9ff5ddd3dae97..66c8e0b5b23af 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://develop.svn.wordpress.org/trunk" }, "gutenberg": { - "ref": "7bf80ea84eb8b62eceb1bb3fe82e42163673ca79" + "ref": "59a08c5496008ca88f4b6b86f38838c3612d88c8" }, "engines": { "node": ">=20.10.0", diff --git a/src/wp-admin/includes/update-core.php b/src/wp-admin/includes/update-core.php index 2f8045e8d193f..47cbc9e16fb64 100644 --- a/src/wp-admin/includes/update-core.php +++ b/src/wp-admin/includes/update-core.php @@ -840,10 +840,6 @@ 'wp-includes/js/dist/fields.min.js', 'wp-includes/js/dist/fields.js', // 6.9 - 'wp-includes/blocks/post-author/editor.css', - 'wp-includes/blocks/post-author/editor.min.css', - 'wp-includes/blocks/post-author/editor-rtl.css', - 'wp-includes/blocks/post-author/editor-rtl.min.css', 'wp-includes/SimplePie/src/Decode', 'wp-includes/SimplePie/src/Core.php', ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 14afb8c2eeddf..a69ce9fa95454 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1988,7 +1988,7 @@ public function prepare_item_for_response( $item, $request ) { add_filter( 'excerpt_length', $override_excerpt_length, - 20 + PHP_INT_MAX ); } @@ -2008,7 +2008,7 @@ public function prepare_item_for_response( $item, $request ) { remove_filter( 'excerpt_length', $override_excerpt_length, - 20 + PHP_INT_MAX ); } } diff --git a/tests/phpunit/data/blocks/do-blocks-expected.html b/tests/phpunit/data/blocks/do-blocks-expected.html index f413182051334..f46ab0b7628c5 100644 --- a/tests/phpunit/data/blocks/do-blocks-expected.html +++ b/tests/phpunit/data/blocks/do-blocks-expected.html @@ -3,7 +3,7 @@ -

First Gutenberg Paragraph

+

First Gutenberg Paragraph

Second Auto Paragraph

@@ -11,7 +11,7 @@ -

Third Gutenberg Paragraph

+

Third Gutenberg Paragraph

Third Auto Paragraph

diff --git a/tests/phpunit/data/blocks/fixtures/core__column.server.html b/tests/phpunit/data/blocks/fixtures/core__column.server.html index d0b6ab4016d91..2487ea0f93c9b 100644 --- a/tests/phpunit/data/blocks/fixtures/core__column.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__column.server.html @@ -1,10 +1,10 @@
- -

Column One, Paragraph One

- - -

Column One, Paragraph Two

- + +

Column One, Paragraph One

+ + +

Column One, Paragraph Two

+
diff --git a/tests/phpunit/data/blocks/fixtures/core__columns.server.html b/tests/phpunit/data/blocks/fixtures/core__columns.server.html index 5b5faf4f3d7a4..fa39f71320056 100644 --- a/tests/phpunit/data/blocks/fixtures/core__columns.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__columns.server.html @@ -1,24 +1,24 @@
- +
- -

Column One, Paragraph One

- - -

Column One, Paragraph Two

- + +

Column One, Paragraph One

+ + +

Column One, Paragraph Two

+
- - + +
- -

Column Two, Paragraph One

- - -

Column Three, Paragraph One

- + +

Column Two, Paragraph One

+ + +

Column Three, Paragraph One

+
- +
diff --git a/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html b/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html index b19349744dfbc..af37434febcf8 100644 --- a/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html @@ -1,16 +1,16 @@
- -

Column One, Paragraph One

- - -

Column One, Paragraph Two

- - -

Column Two, Paragraph One

- - -

Column Three, Paragraph One

- + +

Column One, Paragraph One

+ + +

Column One, Paragraph Two

+ + +

Column Two, Paragraph One

+ + +

Column Three, Paragraph One

+
diff --git a/tests/phpunit/data/blocks/fixtures/core__media-text.server.html b/tests/phpunit/data/blocks/fixtures/core__media-text.server.html index e472aeb90e182..9093acb972b23 100644 --- a/tests/phpunit/data/blocks/fixtures/core__media-text.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__media-text.server.html @@ -5,7 +5,7 @@
-

My Content

+

My Content

diff --git a/tests/phpunit/data/blocks/fixtures/core__media-text__image-alt-no-align.server.html b/tests/phpunit/data/blocks/fixtures/core__media-text__image-alt-no-align.server.html index c25f5431b6809..15d5feec2b849 100644 --- a/tests/phpunit/data/blocks/fixtures/core__media-text__image-alt-no-align.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__media-text__image-alt-no-align.server.html @@ -5,7 +5,7 @@
-

Content

+

Content

diff --git a/tests/phpunit/data/blocks/fixtures/core__media-text__is-stacked-on-mobile.server.html b/tests/phpunit/data/blocks/fixtures/core__media-text__is-stacked-on-mobile.server.html index 5a1c3993f54c9..a8ddd142e6c00 100644 --- a/tests/phpunit/data/blocks/fixtures/core__media-text__is-stacked-on-mobile.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__media-text__is-stacked-on-mobile.server.html @@ -5,7 +5,7 @@
-

My Content

+

My Content

diff --git a/tests/phpunit/data/blocks/fixtures/core__media-text__media-right-custom-width.server.html b/tests/phpunit/data/blocks/fixtures/core__media-text__media-right-custom-width.server.html index 41edc3e02ebb8..2c2cbe3da310d 100644 --- a/tests/phpunit/data/blocks/fixtures/core__media-text__media-right-custom-width.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__media-text__media-right-custom-width.server.html @@ -5,7 +5,7 @@
-

My video

+

My video

diff --git a/tests/phpunit/data/blocks/fixtures/core__media-text__video.server.html b/tests/phpunit/data/blocks/fixtures/core__media-text__video.server.html index 88e9393dd75f2..4328c16b367a0 100644 --- a/tests/phpunit/data/blocks/fixtures/core__media-text__video.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__media-text__video.server.html @@ -5,7 +5,7 @@
-

My Content

+

My Content

diff --git a/tests/phpunit/data/blocks/fixtures/core__paragraph__align-right.server.html b/tests/phpunit/data/blocks/fixtures/core__paragraph__align-right.server.html index 1db164ca1fb5b..4c6b5d6ffad0a 100644 --- a/tests/phpunit/data/blocks/fixtures/core__paragraph__align-right.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__paragraph__align-right.server.html @@ -1,3 +1,3 @@ -

... like this one, which is separate from the above and right aligned.

+

... like this one, which is separate from the above and right aligned.

diff --git a/tests/phpunit/includes/unregister-blocks-hooks.php b/tests/phpunit/includes/unregister-blocks-hooks.php index b67936a103c81..116b4191766bb 100644 --- a/tests/phpunit/includes/unregister-blocks-hooks.php +++ b/tests/phpunit/includes/unregister-blocks-hooks.php @@ -1,79 +1,14 @@ get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

Custom field value

', + '

Custom field value

', $content, 'The post content should show the value of the custom field . ' ); @@ -123,7 +123,7 @@ public function test_custom_field_value_is_not_shown_in_password_protected_posts remove_filter( 'post_password_required', '__return_true' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value instead of the custom field value.' ); @@ -153,7 +153,7 @@ public function test_custom_field_value_is_not_shown_in_non_viewable_posts() { remove_filter( 'is_post_status_viewable', '__return_false' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value instead of the custom field value.' ); @@ -168,7 +168,7 @@ public function test_binding_to_non_existing_meta_key() { $content = $this->get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value.' ); @@ -183,7 +183,7 @@ public function test_binding_without_key_renders_the_fallback() { $content = $this->get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value.' ); @@ -209,7 +209,7 @@ public function test_protected_field_value_is_not_shown() { $content = $this->get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value instead of the protected value.' ); @@ -235,7 +235,7 @@ public function test_custom_field_not_exposed_in_rest_api_is_not_shown() { $content = $this->get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

Fallback value

', + '

Fallback value

', $content, 'The post content should show the fallback value instead of the protected value.' ); @@ -261,7 +261,7 @@ public function test_custom_field_with_unsafe_html_is_sanitized() { $content = $this->get_modified_post_content( '

Fallback value

' ); $this->assertSame( - '

alert(“Unsafe HTML”)

', + '

alert(“Unsafe HTML”)

', $content, 'The post content should not include the script tag.' ); @@ -298,7 +298,7 @@ public function test_filter_block_bindings_source_value() { remove_filter( 'block_bindings_source_value', $filter_value ); $this->assertSame( - '

Filtered value: tests_filter_field

', + '

Filtered value: tests_filter_field

', $content, 'The post content should show the filtered value.' ); diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index fa8178cbd39b2..77b0975105dc5 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -90,7 +90,7 @@ public function data_update_block_with_value_from_source() { HTML , - '

test source value

', + '

test source value

', ), 'button block' => array( 'text', @@ -179,19 +179,19 @@ function ( $source_args, $block_instance, $attribute_name ) { $value = $source_args['key']; return "The attribute name is '$attribute_name' and its binding has argument 'key' with value '$value'."; }, - "

The attribute name is 'content' and its binding has argument 'key' with value 'test'.

", + "

The attribute name is 'content' and its binding has argument 'key' with value 'test'.

", ), 'unsafe HTML should be sanitized' => array( function () { return ''; }, - '

alert("Unsafe HTML")

', + '

alert("Unsafe HTML")

', ), 'symbols and numbers should be rendered correctly' => array( function () { return '$12.50'; }, - '

$12.50

', + '

$12.50

', ), ); } @@ -418,7 +418,7 @@ public function test_filter_block_bindings_source_value() { remove_filter( 'block_bindings_source_value', $filter_value ); $this->assertSame( - '

Filtered value: test_arg. Block instance: core/paragraph. Attribute name: content.

', + '

Filtered value: test_arg. Block instance: core/paragraph. Attribute name: content.

', trim( $result ), 'The block content should show the filtered value.' ); diff --git a/tests/phpunit/tests/blocks/render.php b/tests/phpunit/tests/blocks/render.php index 84b2382a4affe..7b20dac147601 100644 --- a/tests/phpunit/tests/blocks/render.php +++ b/tests/phpunit/tests/blocks/render.php @@ -75,6 +75,9 @@ public function test_the_content() { // Block rendering add some extra blank lines, but we're not worried about them. $block_filtered_content = preg_replace( "/\n{2,}/", "\n", $block_filtered_content ); + // Paragraph blocks now add a class, strip it for comparison with classic content. + $block_filtered_content = str_replace( ' class="wp-block-paragraph"', '', $block_filtered_content ); + remove_shortcode( 'someshortcode' ); $this->assertSame( trim( $classic_filtered_content ), trim( $block_filtered_content ) ); diff --git a/tests/phpunit/tests/blocks/renderReusable.php b/tests/phpunit/tests/blocks/renderReusable.php index 0a88818394780..f38ae41a41173 100644 --- a/tests/phpunit/tests/blocks/renderReusable.php +++ b/tests/phpunit/tests/blocks/renderReusable.php @@ -83,7 +83,7 @@ public function test_render() { ); $block = new WP_Block( $parsed_block ); $output = $block->render(); - $this->assertSame( '

Hello world!

', $output ); + $this->assertSame( '

Hello world!

', $output ); } /** @@ -99,7 +99,7 @@ public function test_render_subsequent() { $block = new WP_Block( $parsed_block ); $output = $block->render(); $output .= $block->render(); - $this->assertSame( '

Hello world!

Hello world!

', $output ); + $this->assertSame( '

Hello world!

Hello world!

', $output ); } public function test_ref_empty() { diff --git a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php index 1f07596903fbf..2097c35bbf5b8 100644 --- a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php +++ b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php @@ -12,7 +12,7 @@ class Tests_Formatting_ExcerptRemoveBlocks extends WP_UnitTestCase { public $content = ' -

paragraph

+

paragraph

@@ -25,7 +25,7 @@ class Tests_Formatting_ExcerptRemoveBlocks extends WP_UnitTestCase { -

paragraph inside column

+

paragraph inside column

@@ -35,12 +35,12 @@ class Tests_Formatting_ExcerptRemoveBlocks extends WP_UnitTestCase { public $filtered_content = ' -

paragraph

+

paragraph

-

paragraph inside column

+

paragraph inside column

'; diff --git a/tests/phpunit/tests/post/output.php b/tests/phpunit/tests/post/output.php index a08f7524eefab..c1d04303161ab 100644 --- a/tests/phpunit/tests/post/output.php +++ b/tests/phpunit/tests/post/output.php @@ -202,13 +202,13 @@ public function test_the_content_should_handle_more_block_on_singular() { $expected_without_teaser = << -

Second block.

+

Second block.

EOF; $expected_with_teaser = <<Teaser part.

+

Teaser part.

-

Second block.

+

Second block.

EOF; $this->go_to( get_permalink( $post_id ) ); @@ -253,7 +253,7 @@ public function test_the_content_should_handle_more_block_when_noteaser_on_singu $expected = << -

Second block.

+

Second block.

EOF; $this->go_to( get_permalink( $post_id ) ); diff --git a/tests/phpunit/tests/rest-api/rest-widgets-controller.php b/tests/phpunit/tests/rest-api/rest-widgets-controller.php index 246ad3b61eb09..27a58eb638f9c 100644 --- a/tests/phpunit/tests/rest-api/rest-widgets-controller.php +++ b/tests/phpunit/tests/rest-api/rest-widgets-controller.php @@ -406,7 +406,7 @@ public function test_get_items() { 'id' => 'block-1', 'id_base' => 'block', 'sidebar' => 'sidebar-1', - 'rendered' => '

Block test

', + 'rendered' => '

Block test

', ), array( 'id' => 'rss-1', diff --git a/tools/gutenberg/copy-gutenberg-build.js b/tools/gutenberg/copy-gutenberg-build.js index f123d3776617a..e5332f806f74a 100644 --- a/tools/gutenberg/copy-gutenberg-build.js +++ b/tools/gutenberg/copy-gutenberg-build.js @@ -259,6 +259,25 @@ function copyBlockAssets( config ) { const content = fs.readFileSync( blockPhpSrc, 'utf8' ); fs.writeFileSync( phpDest, content ); } + + // 4. Copy PHP subdirectories from packages (e.g., shared/helpers.php) + const blockPhpDir = path.join( phpSrc, blockName ); + if ( fs.existsSync( blockPhpDir ) ) { + const rootIndex = path.join( blockPhpDir, 'index.php' ); + fs.cpSync( blockPhpDir, blockDest, { + recursive: true, + filter: function hasPhpFiles( src ) { + const stat = fs.statSync( src ); + if ( stat.isDirectory() ) { + return fs.readdirSync( src, { withFileTypes: true } ).some( + ( entry ) => hasPhpFiles( path.join( src, entry.name ) ) + ); + } + // Copy PHP files, but skip root index.php (handled by step 3) + return src.endsWith( '.php' ) && src !== rootIndex; + }, + } ); + } } console.log( From 9d9a0f11106d80507e4d4c95864ee507b5ba3083 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 10 Feb 2026 00:57:51 +0000 Subject: [PATCH 011/145] Site Health: Account for missing or slashed `$_SERVER` data in `WP_Debug_Data`. Developed in https://github.com/WordPress/wordpress-develop/pull/10870 Follow-up to [56056], [45156], [44986] Props vishalkakadiya, sabernhardt, peterwilsoncc, mukesh27, westonruter. Fixes #64599. git-svn-id: https://develop.svn.wordpress.org/trunk@61606 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/class-wp-debug-data.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index aa0f3eb10ce45..e7e90622dca12 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -373,8 +373,8 @@ private static function get_wp_server(): array { ); $fields['httpd_software'] = array( 'label' => __( 'Web server' ), - 'value' => $_SERVER['SERVER_SOFTWARE'] ?? __( 'Unable to determine what web server software is used' ), - 'debug' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown', + 'value' => ! empty( $_SERVER['SERVER_SOFTWARE'] ) ? wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) : __( 'Unable to determine what web server software is used' ), + 'debug' => ! empty( $_SERVER['SERVER_SOFTWARE'] ) ? wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) : 'unknown', ); $fields['php_version'] = array( 'label' => __( 'PHP version' ), @@ -545,7 +545,7 @@ private static function get_wp_server(): array { ); $fields['server-time'] = array( 'label' => __( 'Current Server time' ), - 'value' => wp_date( 'c', $_SERVER['REQUEST_TIME'] ), + 'value' => isset( $_SERVER['REQUEST_TIME'] ) ? wp_date( 'c', (int) $_SERVER['REQUEST_TIME'] ) : __( 'Unable to determine server time' ), ); return array( From e9f34bd1c1b8aa8e6aea904d904ceac43ae3146a Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 10 Feb 2026 15:19:36 +0000 Subject: [PATCH 012/145] Editor: Add support for pseudo elements for the block and its variations on theme.json. Adds support for pseudo elements on the core/button block for ( ':hover', ':focus', ':focus-visible', ':active' ) at the theme.json level. This is also allowing the block's variations to control the same pseudo elements, so now we can style hover for the outline variation too. Example usage: {{{ "styles": { "blocks": { "core/button": { "color": { "background": "blue" }, ":hover": { "color": { "background": "green" } }, ":focus": { "color": { "background": "purple" } }, "variations": { "outline": { ":hover": { "color": { "background": "pink" } } } } } } } }}} Reviewed by palak678, getdave, MaggieCabrera. Props MaggieCabrera, scruffian, palak678. joedolson, getdave, mikachan. Fixes #64263. git-svn-id: https://develop.svn.wordpress.org/trunk@61607 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-theme-json.php | 107 ++++++++- .../global-styles-and-settings.php | 4 +- tests/phpunit/tests/theme/wpThemeJson.php | 216 +++++++++++++++++- 3 files changed, 322 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 3e7f6f3f78475..37134501dc1b3 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -609,6 +609,16 @@ class WP_Theme_JSON { 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ), ); + /** + * The valid pseudo-selectors that can be used for blocks. + * + * @since 7.0 + * @var array + */ + const VALID_BLOCK_PSEUDO_SELECTORS = array( + 'core/button' => array( ':hover', ':focus', ':focus-visible', ':active' ), + ); + /** * The valid elements that can be found under styles. * @@ -699,6 +709,35 @@ protected static function schema_in_root_and_per_origin( $schema ) { return $schema_in_root_and_per_origin; } + + /** + * Processes pseudo-selectors for any node (block or variation). + * + * @param array $node The node data (block or variation). + * @param string $base_selector The base selector. + * @param array $settings The theme settings. + * @param string $block_name The block name. + * @return array Array of pseudo-selector declarations. + */ + private static function process_pseudo_selectors( $node, $base_selector, $settings, $block_name ) { + $pseudo_declarations = array(); + + if ( ! isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) ) { + return $pseudo_declarations; + } + + foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] as $pseudo_selector ) { + if ( isset( $node[ $pseudo_selector ] ) ) { + $combined_selector = static::append_to_selector( $base_selector, $pseudo_selector ); + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, null ); + $pseudo_declarations[ $combined_selector ] = $declarations; + } + } + + return $pseudo_declarations; + } + + /** * Returns a class name by an element name. * @@ -1018,6 +1057,13 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; $schema_styles_blocks[ $block ] = $styles_non_top_level; $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + + // Add pseudo-selectors for blocks that support them + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) { + foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) { + $schema_styles_blocks[ $block ][ $pseudo_selector ] = $styles_non_top_level; + } + } } $block_style_variation_styles = static::VALID_STYLES; @@ -1040,7 +1086,18 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { - $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); + foreach ( $style_variation_names as $variation_name ) { + $variation_schema = $block_style_variation_styles; + + // Add pseudo-selectors to variations for blocks that support them + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] ) ) { + foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block ] as $pseudo_selector ) { + $variation_schema[ $pseudo_selector ] = $styles_non_top_level; + } + } + + $schema_styles_variations[ $variation_name ] = $variation_schema; + } } $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; @@ -2742,6 +2799,23 @@ private static function get_block_nodes( $theme_json, $selectors = array(), $opt 'variations' => $variation_selectors, 'css' => $selector, ); + + // Handle any pseudo selectors for the block. + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] ) ) { + foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $name ] as $pseudo_selector ) { + if ( isset( $theme_json['styles']['blocks'][ $name ][ $pseudo_selector ] ) ) { + $nodes[] = array( + 'name' => $name, + 'path' => array( 'styles', 'blocks', $name, $pseudo_selector ), + 'selector' => static::append_to_selector( $selector, $pseudo_selector ), + 'selectors' => $feature_selectors, + 'duotone' => $duotone_selector, + 'variations' => $variation_selectors, + 'css' => static::append_to_selector( $selector, $pseudo_selector ), + ); + } + } + } } if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { @@ -2838,6 +2912,12 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { // Compute declarations for remaining styles not covered by feature level selectors. $style_variation_declarations[ $style_variation['selector'] ] = static::compute_style_properties( $style_variation_node, $settings, null, $this->theme_json ); + + // Process pseudo-selectors for this variation (e.g., :hover, :focus) + $block_name = isset( $block_metadata['name'] ) ? $block_metadata['name'] : ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 3 ? $block_metadata['path'][2] : null ); + $variation_pseudo_declarations = static::process_pseudo_selectors( $style_variation_node, $style_variation['selector'], $settings, $block_name ); + $style_variation_declarations = array_merge( $style_variation_declarations, $variation_pseudo_declarations ); + // Store custom CSS for the style variation. if ( isset( $style_variation_node['css'] ) ) { $style_variation_custom_css[ $style_variation['selector'] ] = $this->process_blocks_custom_css( $style_variation_node['css'], $style_variation['selector'] ); @@ -2872,6 +2952,23 @@ static function ( $split_selector ) use ( $clean_style_variation_selector ) { $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; } + /* + * Check if we're processing a block pseudo-selector. + * $block_metadata['path'] = array( 'styles', 'blocks', 'core/button', ':hover' ); + */ + $is_processing_block_pseudo = false; + $block_pseudo_selector = null; + if ( in_array( 'blocks', $block_metadata['path'], true ) && count( $block_metadata['path'] ) >= 4 ) { + $block_name = $block_metadata['path'][2]; // 'core/button' + $last_path_element = $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ]; // ':hover' + + if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ] ) && + in_array( $last_path_element, static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_name ], true ) ) { + $is_processing_block_pseudo = true; + $block_pseudo_selector = $last_path_element; + } + } + /* * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). * This also resets the array keys. @@ -2901,6 +2998,14 @@ static function ( $pseudo_selector ) use ( $selector ) { && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) ) { $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json, $selector, $use_root_padding ); + } elseif ( $is_processing_block_pseudo ) { + // Process block pseudo-selector styles + // For block pseudo-selectors, we need to get the block data first, then access the pseudo-selector + $block_name = $block_metadata['path'][2]; // 'core/button' + $block_data = _wp_array_get( $this->theme_json, array( 'styles', 'blocks', $block_name ), array() ); + $pseudo_data = isset( $block_data[ $block_pseudo_selector ] ) ? $block_data[ $block_pseudo_selector ] : array(); + + $declarations = static::compute_style_properties( $pseudo_data, $settings, null, $this->theme_json, $selector, $use_root_padding ); } else { $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json, $selector, $use_root_padding ); } diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index d50ee14e22015..4b08301f9c7db 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -276,8 +276,8 @@ function wp_add_global_styles_for_blocks() { foreach ( $block_nodes as $metadata ) { if ( $can_use_cached ) { - // Use the block name as the key for cached CSS data. Otherwise, use a hash of the metadata. - $cache_node_key = $metadata['name'] ?? md5( wp_json_encode( $metadata ) ); + // Generate a unique cache key based on the full metadata to ensure pseudo-selectors and other variations get unique keys. + $cache_node_key = md5( wp_json_encode( $metadata ) ); if ( isset( $cached['blocks'][ $cache_node_key ] ) ) { $block_css = $cached['blocks'][ $cache_node_key ]; diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index b26ff2b9a9c4c..965210a80afbe 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -6069,8 +6069,8 @@ public function test_internal_syntax_is_converted_to_css_variables() { * @ticket 58588 * @ticket 60613 * - * @covers WP_Theme_JSON_Gutenberg::resolve_variables - * @covers WP_Theme_JSON_Gutenberg::convert_variables_to_value + * @covers WP_Theme_JSON::resolve_variables + * @covers WP_Theme_JSON::convert_variables_to_value */ public function test_resolve_variables() { $primary_color = '#9DFF20'; @@ -6693,4 +6693,216 @@ public function test_merge_incoming_data_unique_slugs_always_preserved() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + + /** + * Test that block pseudo selectors are processed correctly. + */ + public function test_block_pseudo_selectors_are_processed() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'text' => 'white', + 'background' => 'blue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'blue', + 'background' => 'white', + ), + ), + ':focus' => array( + 'color' => array( + 'text' => 'red', + 'background' => 'yellow', + ), + ), + ), + ), + ), + ) + ); + + $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}:root :where(.wp-block-button .wp-block-button__link:focus){background-color: yellow;color: red;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); + } + + /** + * Test that block pseudo selectors are processed correctly within variations. + */ + public function test_block_variation_pseudo_selectors_are_processed() { + register_block_style( + 'core/button', + array( + 'name' => 'outline', + 'label' => 'Outline', + ) + ); + + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'text' => 'white', + 'background' => 'blue', + ), + 'variations' => array( + 'outline' => array( + 'color' => array( + 'text' => 'currentColor', + 'background' => 'transparent', + ), + 'border' => array( + 'color' => 'currentColor', + 'width' => '1px', + 'style' => 'solid', + ), + ':hover' => array( + 'color' => array( + 'text' => 'white', + 'background' => 'red', + ), + ), + ':focus' => array( + 'color' => array( + 'text' => 'black', + 'background' => 'yellow', + ), + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link){background-color: transparent;border-color: currentColor;border-width: 1px;border-style: solid;color: currentColor;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link:hover){background-color: red;color: white;}:root :where(.wp-block-button.is-style-outline .wp-block-button__link:focus){background-color: yellow;color: black;}'; + $actual = $theme_json->get_stylesheet( + array( 'styles' ), + null, + array( + 'skip_root_layout_styles' => true, + 'include_block_style_variations' => true, + ) + ); + + unregister_block_style( 'core/button', 'outline' ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Test that non-whitelisted pseudo selectors are ignored for blocks. + */ + public function test_block_pseudo_selectors_ignores_non_whitelisted() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'text' => 'white', + 'background' => 'blue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'blue', + 'background' => 'white', + ), + ), + ':levitate' => array( + 'color' => array( + 'text' => 'yellow', + 'background' => 'black', + ), + ), + ), + ), + ), + ) + ); + + $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); + $this->assertStringNotContainsString( '.wp-block-button .wp-block-button__link:levitate{', $theme_json->get_stylesheet( array( 'styles' ) ) ); + } + + /** + * Test that blocks without pseudo selector support ignore pseudo selectors. + */ + public function test_blocks_without_pseudo_support_ignore_pseudo_selectors() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'text' => 'black', + ), + ':hover' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ), + ) + ); + + $expected = ':root :where(p){color: black;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); + $this->assertStringNotContainsString( 'p:hover{', $theme_json->get_stylesheet( array( 'styles' ) ) ); + } + + /** + * Test that block pseudo selectors work with elements within blocks. + */ + public function test_block_pseudo_selectors_with_elements() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/button' => array( + 'color' => array( + 'text' => 'white', + 'background' => 'blue', + ), + ':hover' => array( + 'color' => array( + 'text' => 'blue', + 'background' => 'white', + ), + ), + 'elements' => array( + 'button' => array( + 'color' => array( + 'text' => 'green', + ), + ':hover' => array( + 'color' => array( + 'text' => 'orange', + ), + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = ':root :where(.wp-block-button .wp-block-button__link){background-color: blue;color: white;}:root :where(.wp-block-button .wp-block-button__link:hover){background-color: white;color: blue;}:root :where(.wp-block-button .wp-block-button__link .wp-element-button,.wp-block-button .wp-block-button__link .wp-block-button__link){color: green;}:root :where(.wp-block-button .wp-block-button__link .wp-element-button:hover,.wp-block-button .wp-block-button__link .wp-block-button__link:hover){color: orange;}'; + $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ) ); + } } From 3dbdf1aba9db20583a83d9960a30f4a82bc55fd9 Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Tue, 10 Feb 2026 16:05:25 +0000 Subject: [PATCH 013/145] Twenty Twenty-One: Remove redundant comment for conditionally-defined function. This also improves consistency with other themes. Follow-up to [49216], [53121], [61552], [61272]. Props huzaifaalmesbah, sabernhardt. See #64226. git-svn-id: https://develop.svn.wordpress.org/trunk@61608 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-content/themes/twentytwentyone/inc/block-patterns.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/wp-content/themes/twentytwentyone/inc/block-patterns.php b/src/wp-content/themes/twentytwentyone/inc/block-patterns.php index 2eeaeed6d7f95..4533ed5aa46b8 100644 --- a/src/wp-content/themes/twentytwentyone/inc/block-patterns.php +++ b/src/wp-content/themes/twentytwentyone/inc/block-patterns.php @@ -27,12 +27,9 @@ function twenty_twenty_one_register_block_pattern_category() { add_action( 'init', 'twenty_twenty_one_register_block_pattern_category' ); } -/** - * Register Block Patterns. - */ if ( function_exists( 'register_block_pattern' ) ) { /** - * Registers Block Pattern. + * Registers Block Patterns. * * @since Twenty Twenty-One 1.0 * From db26faa1574bbbf7c47cf4d118c8032be37febf4 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 10 Feb 2026 16:36:25 +0000 Subject: [PATCH 014/145] Editor: Navigation overlay - patterns and template part definition. Adds a new template part for the Navigation block called WP_TEMPLATE_PART_AREA_NAVIGATION_OVERLAY, corresponding area definition in block-template-utils.php to support navigation overlay template parts. Also adds the navigation block pattern category registration in block-patterns.php and five new navigation overlay block patterns. Reviewed by mikachan, get_dave. Props onemaggie, scruffian, get_dave, mikachan, wildworks. Fixes #64589. git-svn-id: https://develop.svn.wordpress.org/trunk@61609 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/block-patterns.php | 12 +++++ .../navigation-overlay-accent-bg.php | 29 ++++++++++++ .../navigation-overlay-black-bg.php | 19 ++++++++ ...avigation-overlay-centered-with-extras.php | 45 +++++++++++++++++++ .../navigation-overlay-centered.php | 21 +++++++++ .../block-patterns/navigation-overlay.php | 19 ++++++++ src/wp-includes/block-template-utils.php | 12 +++++ 7 files changed, 157 insertions(+) create mode 100644 src/wp-includes/block-patterns/navigation-overlay-accent-bg.php create mode 100644 src/wp-includes/block-patterns/navigation-overlay-black-bg.php create mode 100644 src/wp-includes/block-patterns/navigation-overlay-centered-with-extras.php create mode 100644 src/wp-includes/block-patterns/navigation-overlay-centered.php create mode 100644 src/wp-includes/block-patterns/navigation-overlay.php diff --git a/src/wp-includes/block-patterns.php b/src/wp-includes/block-patterns.php index 133c6d54ea33a..50ca1a426378d 100644 --- a/src/wp-includes/block-patterns.php +++ b/src/wp-includes/block-patterns.php @@ -79,6 +79,11 @@ function _register_core_block_patterns_and_categories() { 'query-grid-posts', 'query-large-title-posts', 'query-offset-posts', + 'navigation-overlay', + 'navigation-overlay-black-bg', + 'navigation-overlay-accent-bg', + 'navigation-overlay-centered', + 'navigation-overlay-centered-with-extras', ); foreach ( $core_block_patterns as $core_block_pattern ) { @@ -228,6 +233,13 @@ function _register_core_block_patterns_and_categories() { 'description' => __( 'A variety of header designs displaying your site title and navigation.' ), ) ); + register_block_pattern_category( + 'navigation', + array( + 'label' => _x( 'Navigation', 'Block pattern category' ), + 'description' => __( 'A variety of designs displaying site navigation.' ), + ) + ); } /** diff --git a/src/wp-includes/block-patterns/navigation-overlay-accent-bg.php b/src/wp-includes/block-patterns/navigation-overlay-accent-bg.php new file mode 100644 index 0000000000000..f9139f38fc655 --- /dev/null +++ b/src/wp-includes/block-patterns/navigation-overlay-accent-bg.php @@ -0,0 +1,29 @@ + _x( 'Overlay with orange background', 'Block pattern title' ), + 'blockTypes' => array( 'core/template-part/navigation-overlay' ), + 'categories' => array( 'navigation' ), + 'content' => ' + +', +); diff --git a/src/wp-includes/block-patterns/navigation-overlay-black-bg.php b/src/wp-includes/block-patterns/navigation-overlay-black-bg.php new file mode 100644 index 0000000000000..0ec87fff322e8 --- /dev/null +++ b/src/wp-includes/block-patterns/navigation-overlay-black-bg.php @@ -0,0 +1,19 @@ + _x( 'Overlay with black background', 'Block pattern title' ), + 'blockTypes' => array( 'core/template-part/navigation-overlay' ), + 'categories' => array( 'navigation' ), + 'content' => ' + +', +); diff --git a/src/wp-includes/block-patterns/navigation-overlay-centered-with-extras.php b/src/wp-includes/block-patterns/navigation-overlay-centered-with-extras.php new file mode 100644 index 0000000000000..14748a4331bd2 --- /dev/null +++ b/src/wp-includes/block-patterns/navigation-overlay-centered-with-extras.php @@ -0,0 +1,45 @@ + _x( 'Overlay with site info and CTA', 'Block pattern title' ), + 'blockTypes' => array( 'core/template-part/navigation-overlay' ), + 'categories' => array( 'navigation' ), + 'content' => ' + +', +); diff --git a/src/wp-includes/block-patterns/navigation-overlay-centered.php b/src/wp-includes/block-patterns/navigation-overlay-centered.php new file mode 100644 index 0000000000000..3428aabf5011d --- /dev/null +++ b/src/wp-includes/block-patterns/navigation-overlay-centered.php @@ -0,0 +1,21 @@ + _x( 'Overlay with centered navigation', 'Block pattern title' ), + 'blockTypes' => array( 'core/template-part/navigation-overlay' ), + 'categories' => array( 'navigation' ), + 'content' => ' + +', +); diff --git a/src/wp-includes/block-patterns/navigation-overlay.php b/src/wp-includes/block-patterns/navigation-overlay.php new file mode 100644 index 0000000000000..c979973a82684 --- /dev/null +++ b/src/wp-includes/block-patterns/navigation-overlay.php @@ -0,0 +1,19 @@ + _x( 'Navigation Overlay', 'Block pattern title' ), + 'blockTypes' => array( 'core/template-part/navigation-overlay' ), + 'categories' => array( 'navigation' ), + 'content' => ' +
+
+ + +
+', +); diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index df016c4a1d0fa..ed23647ab01d0 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -19,6 +19,9 @@ if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) { define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' ); } +if ( ! defined( 'WP_TEMPLATE_PART_AREA_NAVIGATION_OVERLAY' ) ) { + define( 'WP_TEMPLATE_PART_AREA_NAVIGATION_OVERLAY', 'navigation-overlay' ); +} /** * For backward compatibility reasons, @@ -96,6 +99,15 @@ function get_allowed_block_template_part_areas() { 'icon' => 'footer', 'area_tag' => 'footer', ), + array( + 'area' => WP_TEMPLATE_PART_AREA_NAVIGATION_OVERLAY, + 'label' => _x( 'Navigation Overlay', 'template part area' ), + 'description' => __( + 'The Navigation Overlay template defines a full-screen overlay area that typically contains navigation links and can be toggled on and off.' + ), + 'icon' => 'overlay', + 'area_tag' => 'div', + ), ); /** From 163bc042c5753f204c9d7400c80e67ce85e56796 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Tue, 10 Feb 2026 19:51:56 +0000 Subject: [PATCH 015/145] Login and Registration: Populate username after password reset. Accessibility: to meet WCAG 2.2/3.3.7: Redundant entry, the username should be auto-populated when a user performs a password reset. There is an existing cookie set that contains this information, but was deleted before displaying the login form. Move cookie deletion to occur after displaying login form and use to set `$user_login`. Props estelaris, alh0319, sabernhardt, oglekler, peterwilsoncc, rcreators, rishavdutta, chaion07, stoyangeorgiev, rinkalpagdar, pratiklondhe, lukasfritzedev, ferdoused, audrasjb, westonruter, joedolson. Fixes #60726. git-svn-id: https://develop.svn.wordpress.org/trunk@61610 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-login.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wp-login.php b/src/wp-login.php index c9db31826bbdb..4bd2284c5244c 100644 --- a/src/wp-login.php +++ b/src/wp-login.php @@ -1000,7 +1000,6 @@ function wp_login_viewport_meta() { if ( ( ! $errors->has_errors() ) && isset( $_POST['pass1'] ) && ! empty( $_POST['pass1'] ) ) { reset_password( $user, $_POST['pass1'] ); - setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); login_header( __( 'Password Reset' ), wp_get_admin_notice( @@ -1487,6 +1486,14 @@ function wp_login_viewport_meta() { wp_clear_auth_cookie(); } + // Obtain user from password reset cookie flow before clearing the cookie. + $rp_cookie = 'wp-resetpass-' . COOKIEHASH; + if ( isset( $_COOKIE[ $rp_cookie ] ) && is_string( $_COOKIE[ $rp_cookie ] ) ) { + $user_login = sanitize_user( strtok( wp_unslash( $_COOKIE[ $rp_cookie ] ), ':' ) ); + list( $rp_path ) = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ); + setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + } + login_header( __( 'Log In' ), '', $errors ); if ( isset( $_POST['log'] ) ) { From 81885a9ff64f3902d4f3162c011745a35b46e6ba Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 10 Feb 2026 22:29:49 +0000 Subject: [PATCH 016/145] Code Editor: Switch from Esprima to Espree for JavaScript linting in CodeMirror. Esprima is no longer maintained, and it does not support the latest JavaScript features in ES11, as Espree does. - **New Linter Integration:** Introduces `src/js/_enqueues/vendor/codemirror/javascript-lint.js` using `espree` for parsing and error reporting, replacing the dependency on `jshint` and `esprima` scripts. - **Script Modules:** Registers `espree` as a script module and leverages the `module_dependencies` argument in `wp_register_script()` to ensure `espree` is available as a dynamic import. - **Editor Settings:** Updates `wp_get_code_editor_settings()` to use ES11 (ECMAScript 2020) defaults and synchronizes JSHint settings from `.jshintrc` for compatibility. - **Editable Extensions:** Adds `.mjs` to the list of editable file extensions for plugins and themes. - **Deprecations:** Marks `esprima` and `jshint` script handles as deprecated. - **Build Tools:** Updates Webpack configuration to bundle `espree` as a module and use the new local `javascript-lint.js`. Developed in https://github.com/WordPress/wordpress-develop/pull/10806 Follow-up to [61587], [61544], [61539], [42547]. Props westonruter, jonsurrell. See #64562, #61500, #48456, #42850. Fixes #64558. git-svn-id: https://develop.svn.wordpress.org/trunk@61611 602fd350-edb4-49c9-b593-d223f7449a82 --- package-lock.json | 26 ++- package.json | 2 + .../vendor/codemirror/javascript-lint.js | 121 ++++++++++++++ src/wp-admin/includes/file.php | 2 + src/wp-includes/general-template.php | 60 ++++--- src/wp-includes/script-loader.php | 5 +- src/wp-includes/script-modules.php | 7 + tests/phpunit/tests/dependencies/scripts.php | 22 ++- .../tests/widgets/wpWidgetCustomHtml.php | 1 - tools/vendors/codemirror-entry.js | 153 +++++++++--------- tools/webpack/codemirror.config.js | 61 ++++--- 11 files changed, 320 insertions(+), 140 deletions(-) create mode 100644 src/js/_enqueues/vendor/codemirror/javascript-lint.js diff --git a/package-lock.json b/package-lock.json index 87af6af9dd1c2..cae57f5937eca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "core-js-url-browser": "3.6.4", "csslint": "1.0.5", "element-closest": "3.0.2", + "espree": "9.6.1", "esprima": "4.0.1", "formdata-polyfill": "4.0.10", "hoverintent": "2.2.1", @@ -44,6 +45,7 @@ "@lodder/grunt-postcss": "^3.1.1", "@playwright/test": "1.56.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", + "@types/codemirror": "5.60.17", "@wordpress/e2e-test-utils-playwright": "1.33.2", "@wordpress/prettier-config": "4.33.1", "@wordpress/scripts": "30.26.2", @@ -5131,6 +5133,16 @@ "@types/node": "*" } }, + "node_modules/@types/codemirror": { + "version": "5.60.17", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.17.tgz", + "integrity": "sha512-AZq2FIsUHVMlp7VSe2hTfl5w4pcUkoFkM3zVsRKsn1ca8CXRDYvnin04+HP2REkwsxemuHqvDofdlhUWNpbwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -5420,6 +5432,16 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", @@ -7445,7 +7467,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7468,7 +7489,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -13285,7 +13305,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -13302,7 +13321,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" diff --git a/package.json b/package.json index 66c8e0b5b23af..766e241ff8d6d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@lodder/grunt-postcss": "^3.1.1", "@playwright/test": "1.56.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", + "@types/codemirror": "5.60.17", "@wordpress/e2e-test-utils-playwright": "1.33.2", "@wordpress/prettier-config": "4.33.1", "@wordpress/scripts": "30.26.2", @@ -79,6 +80,7 @@ "core-js-url-browser": "3.6.4", "csslint": "1.0.5", "element-closest": "3.0.2", + "espree": "9.6.1", "esprima": "4.0.1", "formdata-polyfill": "4.0.10", "hoverintent": "2.2.1", diff --git a/src/js/_enqueues/vendor/codemirror/javascript-lint.js b/src/js/_enqueues/vendor/codemirror/javascript-lint.js new file mode 100644 index 0000000000000..3f98ad523346c --- /dev/null +++ b/src/js/_enqueues/vendor/codemirror/javascript-lint.js @@ -0,0 +1,121 @@ +/** + * CodeMirror JavaScript linter. + * + * @since 7.0.0 + */ + +import CodeMirror from 'codemirror'; + +/** + * CodeMirror Lint Error. + * + * @see https://codemirror.net/5/doc/manual.html#addon_lint + * + * @typedef {Object} CodeMirrorLintError + * @property {string} message - Error message. + * @property {'error'} severity - Severity. + * @property {CodeMirror.Position} from - From position. + * @property {CodeMirror.Position} to - To position. + */ + +/** + * JSHint options supported by Espree. + * + * @see https://jshint.com/docs/options/ + * @see https://www.npmjs.com/package/espree#options + * + * @typedef {Object} SupportedJSHintOptions + * @property {number} [esversion] - "This option is used to specify the ECMAScript version to which the code must adhere." + * @property {boolean} [es5] - "This option enables syntax first defined in the ECMAScript 5.1 specification. This includes allowing reserved keywords as object properties." + * @property {boolean} [es3] - "This option tells JSHint that your code needs to adhere to ECMAScript 3 specification. Use this option if you need your program to be executable in older browsers—such as Internet Explorer 6/7/8/9—and other legacy JavaScript environments." + * @property {boolean} [module] - "This option informs JSHint that the input code describes an ECMAScript 6 module. All module code is interpreted as strict mode code." + * @property {'implied'} [strict] - "This option requires the code to run in ECMAScript 5's strict mode." + */ + +/** + * Validates JavaScript. + * + * @since 7.0.0 + * + * @param {string} text - Source. + * @param {SupportedJSHintOptions} options - Linting options. + * @returns {Promise} + */ +async function validator( text, options ) { + const errors = /** @type {CodeMirrorLintError[]} */ []; + try { + const espree = await import( /* webpackIgnore: true */ 'espree' ); + espree.parse( text, { + ...getEspreeOptions( options ), + loc: true, + } ); + } catch ( error ) { + if ( + // This is an `EnhancedSyntaxError` in Espree: . + error instanceof SyntaxError && + typeof error.lineNumber === 'number' && + typeof error.column === 'number' + ) { + const line = error.lineNumber - 1; + errors.push( { + message: error.message, + severity: 'error', + from: CodeMirror.Pos( line, error.column - 1 ), + to: CodeMirror.Pos( line, error.column ), + } ); + } else { + console.warn( '[CodeMirror] Unable to lint JavaScript:', error ); // jshint ignore:line + } + } + + return errors; +} + +CodeMirror.registerHelper( 'lint', 'javascript', validator ); + +/** + * Gets the options for Espree from the supported JSHint options. + * + * @since 7.0.0 + * + * @param {SupportedJSHintOptions} options - Linting options for JSHint. + * @return {{ + * ecmaVersion?: number|'latest', + * ecmaFeatures?: { + * impliedStrict?: true + * } + * }} + */ +function getEspreeOptions( options ) { + const ecmaFeatures = {}; + if ( options.strict === 'implied' ) { + ecmaFeatures.impliedStrict = true; + } + + return { + ecmaVersion: getEcmaVersion( options ), + sourceType: options.module ? 'module' : 'script', + ecmaFeatures, + }; +} + +/** + * Gets the ECMAScript version. + * + * @since 7.0.0 + * + * @param {SupportedJSHintOptions} options - Options. + * @return {number|'latest'} ECMAScript version. + */ +function getEcmaVersion( options ) { + if ( typeof options.esversion === 'number' ) { + return options.esversion; + } + if ( options.es5 ) { + return 5; + } + if ( options.es3 ) { + return 3; + } + return 'latest'; +} diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index 610125b252ec0..09d080da2dd6e 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -202,6 +202,7 @@ function wp_get_plugin_file_editable_extensions( $plugin ) { 'inc', 'include', 'js', + 'mjs', 'json', 'jsx', 'less', @@ -261,6 +262,7 @@ function wp_get_theme_file_editable_extensions( $theme ) { 'inc', 'include', 'js', + 'mjs', 'json', 'jsx', 'less', diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index f5dacf28f7327..7afab9f8c059d 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -4069,7 +4069,6 @@ function wp_enqueue_code_editor( $args ) { case 'text/x-php': wp_enqueue_script( 'htmlhint' ); wp_enqueue_script( 'csslint' ); - wp_enqueue_script( 'jshint' ); if ( ! current_user_can( 'unfiltered_html' ) ) { wp_enqueue_script( 'htmlhint-kses' ); } @@ -4081,7 +4080,6 @@ function wp_enqueue_code_editor( $args ) { case 'application/ld+json': case 'text/typescript': case 'application/typescript': - wp_enqueue_script( 'jshint' ); wp_enqueue_script( 'jsonlint' ); break; } @@ -4153,30 +4151,39 @@ function wp_get_code_editor_settings( $args ) { 'outline-none' => true, ), 'jshint' => array( - // The following are copied from . - 'boss' => true, - 'curly' => true, - 'eqeqeq' => true, - 'eqnull' => true, - 'es3' => true, - 'expr' => true, - 'immed' => true, - 'noarg' => true, - 'nonbsp' => true, - 'onevar' => true, - 'quotmark' => 'single', - 'trailing' => true, - 'undef' => true, - 'unused' => true, - - 'browser' => true, - - 'globals' => array( - '_' => false, - 'Backbone' => false, - 'jQuery' => false, - 'JSON' => false, - 'wp' => false, + 'esversion' => 11, + 'module' => str_ends_with( $args['file'] ?? '', '.mjs' ), + + // The following JSHint *linting rule* options are copied from + // . + // Parsing-related options such as `esversion` (and, in other contexts, `es5`, `es3`, `module`, `strict`) + // are honored by the Espree-based integration, but these linting-rule options are not interpreted by Espree + // and are kept only for compatibility/documentation with the original JSHint configuration. + 'boss' => true, + 'curly' => true, + 'eqeqeq' => true, + 'eqnull' => true, + 'expr' => true, + 'immed' => true, + 'noarg' => true, + 'nonbsp' => true, + 'quotmark' => 'single', + 'undef' => true, + 'unused' => true, + 'browser' => true, + 'globals' => array( + '_' => false, + 'Backbone' => false, + 'jQuery' => false, + 'JSON' => false, + 'wp' => false, + 'export' => false, + 'module' => false, + 'require' => false, + 'WorkerGlobalScope' => false, + 'self' => false, + 'OffscreenCanvas' => false, + 'Promise' => false, ), ), 'htmlhint' => array( @@ -4233,6 +4240,7 @@ function wp_get_code_editor_settings( $args ) { $type = 'message/http'; break; case 'js': + case 'mjs': $type = 'text/javascript'; break; case 'json': diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 87689423c37ac..4e9de5a0a7ed9 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1196,9 +1196,10 @@ function wp_default_scripts( $scripts ) { ); $scripts->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.65.20' ); + did_action( 'init' ) && $scripts->add_data( 'wp-codemirror', 'module_dependencies', array( 'espree' ) ); $scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' ); - $scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); - $scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' ); + $scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); // Deprecated. Use 'espree' script module. + $scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' ); // Deprecated. $scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.3' ); $scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '1.8.0' ); $scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) ); diff --git a/src/wp-includes/script-modules.php b/src/wp-includes/script-modules.php index ee91ee4361a7d..0a39efea1dc27 100644 --- a/src/wp-includes/script-modules.php +++ b/src/wp-includes/script-modules.php @@ -194,6 +194,13 @@ function wp_default_script_modules() { $module_deps = $script_module_data['module_dependencies'] ?? array(); wp_register_script_module( $script_module_id, $path, $module_deps, $script_module_data['version'], $args ); } + + wp_register_script_module( + 'espree', + includes_url( 'js/codemirror/espree.min.js' ), + array(), + '9.6.1' + ); } /** diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php index 995d46ad6ae61..6050983cc5f5e 100644 --- a/tests/phpunit/tests/dependencies/scripts.php +++ b/tests/phpunit/tests/dependencies/scripts.php @@ -3158,14 +3158,13 @@ public function test_wp_enqueue_code_editor_when_php_file_will_be_passed() { 'curly', 'eqeqeq', 'eqnull', - 'es3', + 'esversion', 'expr', 'immed', + 'module', 'noarg', 'nonbsp', - 'onevar', 'quotmark', - 'trailing', 'undef', 'unused', 'browser', @@ -3242,14 +3241,13 @@ public function test_wp_enqueue_code_editor_when_generated_array_by_compact_will 'curly', 'eqeqeq', 'eqnull', - 'es3', + 'esversion', 'expr', 'immed', + 'module', 'noarg', 'nonbsp', - 'onevar', 'quotmark', - 'trailing', 'undef', 'unused', 'browser', @@ -3340,14 +3338,13 @@ public function test_wp_enqueue_code_editor_when_generated_array_by_array_merge_ 'curly', 'eqeqeq', 'eqnull', - 'es3', + 'esversion', 'expr', 'immed', + 'module', 'noarg', 'nonbsp', - 'onevar', 'quotmark', - 'trailing', 'undef', 'unused', 'browser', @@ -3435,14 +3432,13 @@ public function test_wp_enqueue_code_editor_when_simple_array_will_be_passed() { 'curly', 'eqeqeq', 'eqnull', - 'es3', + 'esversion', 'expr', 'immed', + 'module', 'noarg', 'nonbsp', - 'onevar', 'quotmark', - 'trailing', 'undef', 'unused', 'browser', @@ -4020,7 +4016,7 @@ static function ( $dependency ) { ); // Exclude packages that are not registered in WordPress. - $exclude = array( 'react-is', 'json2php' ); + $exclude = array( 'react-is', 'json2php', 'espree' ); $package_json_dependencies = array_diff( $package_json_dependencies, $exclude ); /* diff --git a/tests/phpunit/tests/widgets/wpWidgetCustomHtml.php b/tests/phpunit/tests/widgets/wpWidgetCustomHtml.php index 1a61d944719b6..c9377ba54e655 100644 --- a/tests/phpunit/tests/widgets/wpWidgetCustomHtml.php +++ b/tests/phpunit/tests/widgets/wpWidgetCustomHtml.php @@ -251,7 +251,6 @@ public function test_enqueue_admin_scripts_when_logged_in_and_syntax_highlightin $this->assertTrue( wp_script_is( 'code-editor', 'enqueued' ) ); $this->assertTrue( wp_script_is( 'wp-codemirror', 'enqueued' ) ); $this->assertTrue( wp_script_is( 'csslint', 'enqueued' ) ); - $this->assertTrue( wp_script_is( 'jshint', 'enqueued' ) ); $this->assertTrue( wp_script_is( 'htmlhint', 'enqueued' ) ); } diff --git a/tools/vendors/codemirror-entry.js b/tools/vendors/codemirror-entry.js index cf3b7523d0edf..a8856f55d11da 100644 --- a/tools/vendors/codemirror-entry.js +++ b/tools/vendors/codemirror-entry.js @@ -1,90 +1,91 @@ // Import CodeMirror core to be exposed as window.wp.CodeMirror. -var CodeMirror = require( 'codemirror/lib/codemirror' ); +import CodeMirror from 'codemirror/lib/codemirror'; // Keymaps -require( 'codemirror/keymap/emacs' ); -require( 'codemirror/keymap/sublime' ); -require( 'codemirror/keymap/vim' ); +import 'codemirror/keymap/emacs'; +import 'codemirror/keymap/sublime'; +import 'codemirror/keymap/vim'; // Addons (Hinting) -require( 'codemirror/addon/hint/show-hint' ); -require( 'codemirror/addon/hint/anyword-hint' ); -require( 'codemirror/addon/hint/css-hint' ); -require( 'codemirror/addon/hint/html-hint' ); -require( 'codemirror/addon/hint/javascript-hint' ); -require( 'codemirror/addon/hint/sql-hint' ); -require( 'codemirror/addon/hint/xml-hint' ); +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/hint/anyword-hint'; +import 'codemirror/addon/hint/css-hint'; +import 'codemirror/addon/hint/html-hint'; +import 'codemirror/addon/hint/javascript-hint'; +import 'codemirror/addon/hint/sql-hint'; +import 'codemirror/addon/hint/xml-hint'; // Addons (Linting) -require( 'codemirror/addon/lint/lint' ); -require( 'codemirror/addon/lint/css-lint' ); -require( 'codemirror/addon/lint/html-lint' ); -require( 'codemirror/addon/lint/javascript-lint' ); -require( 'codemirror/addon/lint/json-lint' ); +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/lint/css-lint'; +import 'codemirror/addon/lint/html-lint'; + +import '../../src/js/_enqueues/vendor/codemirror/javascript-lint'; +import 'codemirror/addon/lint/json-lint'; // Addons (Other) -require( 'codemirror/addon/comment/comment' ); -require( 'codemirror/addon/comment/continuecomment' ); -require( 'codemirror/addon/fold/xml-fold' ); -require( 'codemirror/addon/mode/overlay' ); -require( 'codemirror/addon/edit/closebrackets' ); -require( 'codemirror/addon/edit/closetag' ); -require( 'codemirror/addon/edit/continuelist' ); -require( 'codemirror/addon/edit/matchbrackets' ); -require( 'codemirror/addon/edit/matchtags' ); -require( 'codemirror/addon/edit/trailingspace' ); -require( 'codemirror/addon/dialog/dialog' ); -require( 'codemirror/addon/display/autorefresh' ); -require( 'codemirror/addon/display/fullscreen' ); -require( 'codemirror/addon/display/panel' ); -require( 'codemirror/addon/display/placeholder' ); -require( 'codemirror/addon/display/rulers' ); -require( 'codemirror/addon/fold/brace-fold' ); -require( 'codemirror/addon/fold/comment-fold' ); -require( 'codemirror/addon/fold/foldcode' ); -require( 'codemirror/addon/fold/foldgutter' ); -require( 'codemirror/addon/fold/indent-fold' ); -require( 'codemirror/addon/fold/markdown-fold' ); -require( 'codemirror/addon/merge/merge' ); -require( 'codemirror/addon/mode/loadmode' ); -require( 'codemirror/addon/mode/multiplex' ); -require( 'codemirror/addon/mode/simple' ); -require( 'codemirror/addon/runmode/runmode' ); -require( 'codemirror/addon/runmode/colorize' ); -require( 'codemirror/addon/runmode/runmode-standalone' ); -require( 'codemirror/addon/scroll/annotatescrollbar' ); -require( 'codemirror/addon/scroll/scrollpastend' ); -require( 'codemirror/addon/scroll/simplescrollbars' ); -require( 'codemirror/addon/search/search' ); -require( 'codemirror/addon/search/jump-to-line' ); -require( 'codemirror/addon/search/match-highlighter' ); -require( 'codemirror/addon/search/matchesonscrollbar' ); -require( 'codemirror/addon/search/searchcursor' ); -require( 'codemirror/addon/tern/tern' ); -require( 'codemirror/addon/tern/worker' ); -require( 'codemirror/addon/wrap/hardwrap' ); -require( 'codemirror/addon/selection/active-line' ); -require( 'codemirror/addon/selection/mark-selection' ); -require( 'codemirror/addon/selection/selection-pointer' ); +import 'codemirror/addon/comment/comment'; +import 'codemirror/addon/comment/continuecomment'; +import 'codemirror/addon/fold/xml-fold'; +import 'codemirror/addon/mode/overlay'; +import 'codemirror/addon/edit/closebrackets'; +import 'codemirror/addon/edit/closetag'; +import 'codemirror/addon/edit/continuelist'; +import 'codemirror/addon/edit/matchbrackets'; +import 'codemirror/addon/edit/matchtags'; +import 'codemirror/addon/edit/trailingspace'; +import 'codemirror/addon/dialog/dialog'; +import 'codemirror/addon/display/autorefresh'; +import 'codemirror/addon/display/fullscreen'; +import 'codemirror/addon/display/panel'; +import 'codemirror/addon/display/placeholder'; +import 'codemirror/addon/display/rulers'; +import 'codemirror/addon/fold/brace-fold'; +import 'codemirror/addon/fold/comment-fold'; +import 'codemirror/addon/fold/foldcode'; +import 'codemirror/addon/fold/foldgutter'; +import 'codemirror/addon/fold/indent-fold'; +import 'codemirror/addon/fold/markdown-fold'; +import 'codemirror/addon/merge/merge'; +import 'codemirror/addon/mode/loadmode'; +import 'codemirror/addon/mode/multiplex'; +import 'codemirror/addon/mode/simple'; +import 'codemirror/addon/runmode/runmode'; +import 'codemirror/addon/runmode/colorize'; +import 'codemirror/addon/runmode/runmode-standalone'; +import 'codemirror/addon/scroll/annotatescrollbar'; +import 'codemirror/addon/scroll/scrollpastend'; +import 'codemirror/addon/scroll/simplescrollbars'; +import 'codemirror/addon/search/search'; +import 'codemirror/addon/search/jump-to-line'; +import 'codemirror/addon/search/match-highlighter'; +import 'codemirror/addon/search/matchesonscrollbar'; +import 'codemirror/addon/search/searchcursor'; +import 'codemirror/addon/tern/tern'; +import 'codemirror/addon/tern/worker'; +import 'codemirror/addon/wrap/hardwrap'; +import 'codemirror/addon/selection/active-line'; +import 'codemirror/addon/selection/mark-selection'; +import 'codemirror/addon/selection/selection-pointer'; // Modes -require( 'codemirror/mode/meta' ); -require( 'codemirror/mode/clike/clike' ); -require( 'codemirror/mode/css/css' ); -require( 'codemirror/mode/diff/diff' ); -require( 'codemirror/mode/htmlmixed/htmlmixed' ); -require( 'codemirror/mode/http/http' ); -require( 'codemirror/mode/javascript/javascript' ); -require( 'codemirror/mode/jsx/jsx' ); -require( 'codemirror/mode/markdown/markdown' ); -require( 'codemirror/mode/gfm/gfm' ); -require( 'codemirror/mode/nginx/nginx' ); -require( 'codemirror/mode/php/php' ); -require( 'codemirror/mode/sass/sass' ); -require( 'codemirror/mode/shell/shell' ); -require( 'codemirror/mode/sql/sql' ); -require( 'codemirror/mode/xml/xml' ); -require( 'codemirror/mode/yaml/yaml' ); +import 'codemirror/mode/meta'; +import 'codemirror/mode/clike/clike'; +import 'codemirror/mode/css/css'; +import 'codemirror/mode/diff/diff'; +import 'codemirror/mode/htmlmixed/htmlmixed'; +import 'codemirror/mode/http/http'; +import 'codemirror/mode/javascript/javascript'; +import 'codemirror/mode/jsx/jsx'; +import 'codemirror/mode/markdown/markdown'; +import 'codemirror/mode/gfm/gfm'; +import 'codemirror/mode/nginx/nginx'; +import 'codemirror/mode/php/php'; +import 'codemirror/mode/sass/sass'; +import 'codemirror/mode/shell/shell'; +import 'codemirror/mode/sql/sql'; +import 'codemirror/mode/xml/xml'; +import 'codemirror/mode/yaml/yaml'; /** * Please note that the codemirror-standalone "runmode" addon is setting `window.CodeMirror` diff --git a/tools/webpack/codemirror.config.js b/tools/webpack/codemirror.config.js index aac048dccc1ef..b6e99dd289daf 100644 --- a/tools/webpack/codemirror.config.js +++ b/tools/webpack/codemirror.config.js @@ -6,32 +6,36 @@ const codemirrorBanner = require( './codemirror-banner' ); module.exports = ( env = { buildTarget: 'src/' } ) => { const buildTarget = env.buildTarget || 'src/'; + const outputPath = path.resolve( __dirname, '../../', buildTarget, 'wp-includes/js/codemirror' ); - return { + const optimization = { + minimize: true, + minimizer: [ + new TerserPlugin( { + terserOptions: { + format: { + comments: /^!/, + }, + }, + extractComments: false, + } ), + ], + }; + + const codemirrorConfig = { target: 'browserslist', mode: 'production', - entry: './tools/vendors/codemirror-entry.js', - output: { - path: path.resolve( __dirname, '../../', buildTarget, 'wp-includes/js/codemirror' ), - filename: 'codemirror.min.js', + entry: { + 'codemirror.min': './tools/vendors/codemirror-entry.js', }, - optimization: { - minimize: true, - minimizer: [ - new TerserPlugin( { - terserOptions: { - format: { - comments: /^!/, - }, - }, - extractComments: false, - } ), - ], + output: { + path: outputPath, + filename: '[name].js', }, + optimization, externals: { 'csslint': 'window.CSSLint', 'htmlhint': 'window.HTMLHint', - 'jshint': 'window.JSHINT', 'jsonlint': 'window.jsonlint', }, plugins: [ @@ -42,4 +46,25 @@ module.exports = ( env = { buildTarget: 'src/' } ) => { } ), ], }; + + const espreeConfig = { + target: 'browserslist', + mode: 'production', + entry: { + 'espree.min': 'espree', + }, + output: { + path: outputPath, + filename: '[name].js', + library: { + type: 'module', + }, + }, + experiments: { + outputModule: true, + }, + optimization, + }; + + return [ codemirrorConfig, espreeConfig ]; }; From c0145eb36e7e7d00cfebbb7c8f35e388c793353e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Feb 2026 20:14:09 +0000 Subject: [PATCH 017/145] Site Health: Add test and debug data for Opcode Cache. Developed in https://github.com/WordPress/wordpress-develop/pull/9260 Props rollybueno, westonruter, swissspidy, peterwilsoncc, szepeviktor, ozgursar, oglekler, johnbillion, ugyensupport, abcd95, shailu25, noruzzaman. Fixes #63697. git-svn-id: https://develop.svn.wordpress.org/trunk@61612 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/class-wp-debug-data.php | 77 +++++++++++++++++++ .../includes/class-wp-site-health.php | 62 +++++++++++++-- tests/phpunit/tests/admin/wpSiteHealth.php | 59 ++++++++++++++ 3 files changed, 192 insertions(+), 6 deletions(-) diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php index e7e90622dca12..98927f94e68fd 100644 --- a/src/wp-admin/includes/class-wp-debug-data.php +++ b/src/wp-admin/includes/class-wp-debug-data.php @@ -471,6 +471,83 @@ private static function get_wp_server(): array { 'debug' => $imagick_loaded, ); + // Opcode Cache. + if ( function_exists( 'opcache_get_status' ) ) { + $opcache_status = @opcache_get_status( false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in failure case. + + if ( false === $opcache_status ) { + $fields['opcode_cache'] = array( + 'label' => __( 'Opcode cache' ), + 'value' => __( 'Disabled by configuration' ), + 'debug' => 'not available', + ); + } else { + $fields['opcode_cache'] = array( + 'label' => __( 'Opcode cache' ), + 'value' => $opcache_status['opcache_enabled'] ? __( 'Enabled' ) : __( 'Disabled' ), + 'debug' => $opcache_status['opcache_enabled'], + ); + + if ( true === $opcache_status['opcache_enabled'] ) { + $fields['opcode_cache_memory_usage'] = array( + 'label' => __( 'Opcode cache memory usage' ), + 'value' => sprintf( + /* translators: 1: Used memory, 2: Total memory */ + __( '%1$s of %2$s' ), + size_format( $opcache_status['memory_usage']['used_memory'] ), + size_format( $opcache_status['memory_usage']['free_memory'] + $opcache_status['memory_usage']['used_memory'] ) + ), + 'debug' => sprintf( + '%s of %s', + $opcache_status['memory_usage']['used_memory'], + $opcache_status['memory_usage']['free_memory'] + $opcache_status['memory_usage']['used_memory'] + ), + ); + + if ( 0 !== $opcache_status['interned_strings_usage']['buffer_size'] ) { + $fields['opcode_cache_interned_strings_usage'] = array( + 'label' => __( 'Opcode cache interned strings usage' ), + 'value' => sprintf( + /* translators: 1: Percentage used, 2: Total memory, 3: Free memory */ + __( '%1$s%% of %2$s (%3$s free)' ), + number_format_i18n( ( $opcache_status['interned_strings_usage']['used_memory'] / $opcache_status['interned_strings_usage']['buffer_size'] ) * 100, 2 ), + size_format( $opcache_status['interned_strings_usage']['buffer_size'] ), + size_format( $opcache_status['interned_strings_usage']['free_memory'] ) + ), + 'debug' => sprintf( + '%s%% of %s (%s free)', + round( ( $opcache_status['interned_strings_usage']['used_memory'] / $opcache_status['interned_strings_usage']['buffer_size'] ) * 100, 2 ), + $opcache_status['interned_strings_usage']['buffer_size'], + $opcache_status['interned_strings_usage']['free_memory'] + ), + ); + } + + $fields['opcode_cache_hit_rate'] = array( + 'label' => __( 'Opcode cache hit rate' ), + 'value' => sprintf( + /* translators: %s: Hit rate percentage */ + __( '%s%%' ), + number_format_i18n( $opcache_status['opcache_statistics']['opcache_hit_rate'], 2 ) + ), + 'debug' => round( $opcache_status['opcache_statistics']['opcache_hit_rate'], 2 ), + ); + + $fields['opcode_cache_full'] = array( + 'label' => __( 'Is the Opcode cache full?' ), + 'value' => $opcache_status['cache_full'] ? __( 'Yes' ) : __( 'No' ), + 'debug' => $opcache_status['cache_full'], + ); + } + } + } else { + $fields['opcode_cache'] = array( + 'label' => __( 'Opcode cache' ), + 'value' => __( 'Disabled' ), + 'debug' => 'not available', + ); + } + // Pretty permalinks. $pretty_permalinks_supported = got_url_rewrite(); diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php index dd537296a8655..93a45f56236c3 100644 --- a/src/wp-admin/includes/class-wp-site-health.php +++ b/src/wp-admin/includes/class-wp-site-health.php @@ -2755,6 +2755,52 @@ public function get_test_search_engine_visibility() { return $result; } + /** + * Tests if opcode cache is enabled and available. + * + * @since 7.0.0 + * + * @return array> The test result. + */ + public function get_test_opcode_cache(): array { + $opcode_cache_enabled = false; + if ( function_exists( 'opcache_get_status' ) ) { + $status = @opcache_get_status( false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in failure case. + if ( $status && true === $status['opcache_enabled'] ) { + $opcode_cache_enabled = true; + } + } + + $result = array( + 'label' => __( 'Opcode cache is enabled' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Performance' ), + 'color' => 'blue', + ), + 'description' => sprintf( + '

%s

', + __( 'Opcode cache improves PHP performance by storing precompiled script bytecode in memory, reducing the need for PHP to load and parse scripts on each request.' ) + ), + 'actions' => sprintf( + '

%s %s

', + esc_url( 'https://www.php.net/manual/en/book.opcache.php' ), + __( 'Learn more about OPcache.' ), + /* translators: Hidden accessibility text. */ + __( '(opens in a new tab)' ) + ), + 'test' => 'opcode_cache', + ); + + if ( ! $opcode_cache_enabled ) { + $result['status'] = 'recommended'; + $result['label'] = __( 'Opcode cache is not enabled' ); + $result['description'] .= '

' . __( 'Enabling this cache can significantly improve the performance of your site.' ) . '

'; + } + + return $result; + } + /** * Returns a set of tests that belong to the site status page. * @@ -2847,6 +2893,10 @@ public static function get_tests() { 'label' => __( 'Search Engine Visibility' ), 'test' => 'search_engine_visibility', ), + 'opcode_cache' => array( + 'label' => __( 'Opcode cache' ), + 'test' => 'opcode_cache', + ), ), 'async' => array( 'dotorg_communication' => array( @@ -3415,14 +3465,14 @@ public function get_page_cache_headers() { 'x-srcache-fetch-status' => $cache_hit_callback, // Generic caching proxies (Nginx, Varnish, etc.) - 'x-cache' => $cache_hit_callback, - 'x-cache-status' => $cache_hit_callback, - 'x-litespeed-cache' => $cache_hit_callback, - 'x-proxy-cache' => $cache_hit_callback, - 'via' => '', + 'x-cache' => $cache_hit_callback, + 'x-cache-status' => $cache_hit_callback, + 'x-litespeed-cache' => $cache_hit_callback, + 'x-proxy-cache' => $cache_hit_callback, + 'via' => '', // Cloudflare - 'cf-cache-status' => $cache_hit_callback, + 'cf-cache-status' => $cache_hit_callback, ); /** diff --git a/tests/phpunit/tests/admin/wpSiteHealth.php b/tests/phpunit/tests/admin/wpSiteHealth.php index 2d32bbb14ec4d..12b563cdfe41f 100644 --- a/tests/phpunit/tests/admin/wpSiteHealth.php +++ b/tests/phpunit/tests/admin/wpSiteHealth.php @@ -572,4 +572,63 @@ public static function set_autoloaded_option( $bytes = 800000 ) { // Force autoloading so that WordPress core does not override it. See https://core.trac.wordpress.org/changeset/57920. add_option( 'test_set_autoloaded_option', $heavy_option_string, '', true ); } + + /** + * Tests get_test_opcode_cache() return structure. + * + * @ticket 63697 + * + * @covers ::get_test_opcode_cache() + */ + public function test_get_test_opcode_cache_return_structure() { + $result = $this->instance->get_test_opcode_cache(); + + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'label', $result ); + $this->assertArrayHasKey( 'status', $result ); + $this->assertArrayHasKey( 'badge', $result ); + $this->assertArrayHasKey( 'description', $result ); + $this->assertArrayHasKey( 'actions', $result ); + $this->assertArrayHasKey( 'test', $result ); + + $this->assertSame( 'opcode_cache', $result['test'] ); + $this->assertSame( + array( + 'label' => __( 'Performance' ), + 'color' => 'blue', + ), + $result['badge'] + ); + $this->assertContains( $result['status'], array( 'good', 'recommended' ), 'Status must be good or recommended.' ); + } + + /** + * Tests get_test_opcode_cache() result when opcode cache is enabled or not. + * + * Covers: opcache enabled, disabled, not available, and opcache_get_status() returns false. + * + * @ticket 63697 + * + * @covers ::get_test_opcode_cache() + */ + public function test_get_test_opcode_cache_result_by_environment() { + $result = $this->instance->get_test_opcode_cache(); + + $opcache_enabled = false; + if ( function_exists( 'opcache_get_status' ) ) { + $status = @opcache_get_status( false ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Warning emitted in failure case. + if ( $status && true === $status['opcache_enabled'] ) { + $opcache_enabled = true; + } + } + + if ( $opcache_enabled ) { + $this->assertSame( 'good', $result['status'], 'When opcache is enabled, status should be "good".' ); + $this->assertSame( __( 'Opcode cache is enabled' ), $result['label'] ); + } else { + $this->assertSame( 'recommended', $result['status'] ); + $this->assertSame( __( 'Opcode cache is not enabled' ), $result['label'] ); + $this->assertStringContainsString( __( 'Enabling this cache can significantly improve the performance of your site.' ), $result['description'] ); + } + } } From a77775692568bdf1ab6d9395d7adc1dbbbee41f2 Mon Sep 17 00:00:00 2001 From: Jb Audras Date: Wed, 11 Feb 2026 20:26:24 +0000 Subject: [PATCH 018/145] Build/Test Tools: Update the Playground PR comment in GitHub Actions. This changeset removes the "Plugin and Theme Directories cannot be accessed within Playground" bullet point from the Playground Pull Request Comment GitHub Action, as it is not the case anymore. Props audrasjb, westonruter. Fixes #64578. git-svn-id: https://develop.svn.wordpress.org/trunk@61613 602fd350-edb4-49c9-b593-d223f7449a82 --- .github/workflows/pull-request-comments.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pull-request-comments.yml b/.github/workflows/pull-request-comments.yml index dc7e6e7c7a7e6..da30e2feb7f11 100644 --- a/.github/workflows/pull-request-comments.yml +++ b/.github/workflows/pull-request-comments.yml @@ -167,7 +167,6 @@ jobs: [WordPress Playground](https://developer.wordpress.org/playground/) is an experimental project that creates a full WordPress instance entirely within the browser. ### Some things to be aware of - - The Plugin and Theme Directories cannot be accessed within Playground. - All changes will be lost when closing a tab with a Playground instance. - All changes will be lost when refreshing the page. - A fresh instance is created each time the link below is clicked. From 6cb574b0f75d8a87d348d22fb3493315d5238a34 Mon Sep 17 00:00:00 2001 From: Jb Audras Date: Wed, 11 Feb 2026 21:09:12 +0000 Subject: [PATCH 019/145] Site Health: Allow direct linking to site health check result. This changeset does the following changes: - Add an ID to each accordion button - Update the URL hash each time an accordion button is clicked - On page load, open the related accordion when provided This way, people can use the URL of the page to share a direct link to the site health info section they want. Props sippis, kabir93, audrasjb, saratheonline, pratiklondhe, vgnavada, SirLouen, nikunj8866, pmbaldha, sajjad67, huzaifaalmesbah, westonruter. Fixes #62846. git-svn-id: https://develop.svn.wordpress.org/trunk@61614 602fd350-edb4-49c9-b593-d223f7449a82 --- src/js/_enqueues/admin/site-health.js | 18 ++++++++++++++++++ src/wp-admin/site-health-info.php | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js index 416295df69b17..57d5c9cbcf289 100644 --- a/src/js/_enqueues/admin/site-health.js +++ b/src/js/_enqueues/admin/site-health.js @@ -44,6 +44,10 @@ jQuery( function( $ ) { $( '.health-check-accordion' ).on( 'click', '.health-check-accordion-trigger', function() { var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) ); + if ( $( this ).prop( 'id' ) ) { + window.location.hash = $( this ).prop( 'id' ); + } + if ( isExpanded ) { $( this ).attr( 'aria-expanded', 'false' ); $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true ); @@ -53,6 +57,20 @@ jQuery( function( $ ) { } } ); + /* global setTimeout */ + wp.domReady( function() { + // Get hash from query string and open the related accordion. + var hash = window.location.hash; + + if ( hash ) { + var requestedPanel = $( hash ); + + if ( requestedPanel.is( '.health-check-accordion-trigger' ) ) { + requestedPanel.trigger( 'click' ); + } + } + } ); + // Site Health test handling. $( '.site-health-view-passed' ).on( 'click', function() { diff --git a/src/wp-admin/site-health-info.php b/src/wp-admin/site-health-info.php index bfdd77df01553..faffb21636827 100644 --- a/src/wp-admin/site-health-info.php +++ b/src/wp-admin/site-health-info.php @@ -73,7 +73,7 @@ ?>

-