From 1862cad0e194159a0fceda7ec14164e7a225e71f Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Sun, 16 Aug 2026 12:11:23 +0530 Subject: [PATCH 01/11] Docs: Remove the blank line between `@param` and `@return` in core docblocks. The PHP inline documentation standards place `@return` immediately after the last `@param` tag, with no blank line separating them. This corrects 44 docblocks across 33 files in `src/wp-includes` that used a line break between the two tags. Note that this only applies to PHP. The JavaScript documentation standards do require a blank line before `@return`, so JSDoc blocks are left unchanged. Files under `src/wp-includes/blocks/`, the test suite, and bundled third-party libraries are out of scope for this change. Co-Authored-By: Claude Opus 5 (1M context) --- src/wp-includes/block-editor.php | 2 -- src/wp-includes/block-supports/aria-label.php | 1 - src/wp-includes/block-supports/block-style-variations.php | 3 --- src/wp-includes/block-supports/colors.php | 1 - src/wp-includes/block-supports/custom-classname.php | 1 - src/wp-includes/block-supports/settings.php | 1 - src/wp-includes/block-supports/utils.php | 1 - src/wp-includes/block-template-utils.php | 1 - src/wp-includes/block-template.php | 1 - src/wp-includes/blocks.php | 3 --- src/wp-includes/class-wp-block-type.php | 2 -- src/wp-includes/class-wp-term-query.php | 2 -- src/wp-includes/class-wp-textdomain-registry.php | 1 - src/wp-includes/class-wp-theme-json-data.php | 1 - src/wp-includes/class-wp-theme-json-schema.php | 2 -- src/wp-includes/class-wp-token-map.php | 2 -- src/wp-includes/deprecated.php | 1 - src/wp-includes/functions.php | 2 -- src/wp-includes/global-styles-and-settings.php | 1 - src/wp-includes/html-api/class-wp-html-doctype-info.php | 1 - src/wp-includes/html-api/class-wp-html-processor.php | 1 - src/wp-includes/html-api/class-wp-html-tag-processor.php | 1 - src/wp-includes/kses.php | 1 - src/wp-includes/l10n.php | 1 - src/wp-includes/rest-api.php | 1 - src/wp-includes/rest-api/class-wp-rest-server.php | 1 - .../endpoints/class-wp-rest-global-styles-controller.php | 1 - .../rest-api/endpoints/class-wp-rest-menu-items-controller.php | 1 - .../endpoints/class-wp-rest-widget-types-controller.php | 2 -- src/wp-includes/script-loader.php | 1 - src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php | 1 - src/wp-includes/theme.php | 1 - src/wp-includes/user.php | 1 - 33 files changed, 44 deletions(-) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index f08c934864916..1e6d60f637592 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -109,7 +109,6 @@ function get_block_categories( $post_or_block_editor_context ) { * @since 5.8.0 * * @param WP_Block_Editor_Context $block_editor_context The current block editor context. - * * @return bool|string[] Array of block type slugs, or boolean to enable/disable all. */ function get_allowed_block_types( $block_editor_context ) { @@ -484,7 +483,6 @@ function wp_get_post_content_block_attributes() { * * @param array $custom_settings Custom settings to use with the given editor type. * @param WP_Block_Editor_Context $block_editor_context The current block editor context. - * * @return array The contextualized block editor settings. */ function get_block_editor_settings( array $custom_settings, $block_editor_context ) { diff --git a/src/wp-includes/block-supports/aria-label.php b/src/wp-includes/block-supports/aria-label.php index b51cd3499e0d4..c44333221101a 100644 --- a/src/wp-includes/block-supports/aria-label.php +++ b/src/wp-includes/block-supports/aria-label.php @@ -40,7 +40,6 @@ function wp_register_aria_label_support( $block_type ) { * * @param WP_Block_Type $block_type Block Type. * @param array $block_attributes Block attributes. - * * @return array Block aria-label. */ function wp_apply_aria_label_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 5daccd629c055..a808ea3d74ecf 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -13,7 +13,6 @@ * @since 6.6.0 * * @param string $class_string CSS class string to look for a variation in. - * * @return array|null The block style variation name if found. */ function wp_get_block_style_variation_name_from_class( $class_string ) { @@ -75,7 +74,6 @@ function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_j * @access private * * @param array $parsed_block The parsed block. - * * @return array The parsed block with block style variation classname added. */ function wp_render_block_style_variation_support_styles( $parsed_block ) { @@ -215,7 +213,6 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { * * @param string $block_content Rendered block content. * @param array $block Block object. - * * @return string Filtered block content. */ function wp_render_block_style_variation_class_name( $block_content, $block ) { diff --git a/src/wp-includes/block-supports/colors.php b/src/wp-includes/block-supports/colors.php index 18da234e55e37..330f32e352b36 100644 --- a/src/wp-includes/block-supports/colors.php +++ b/src/wp-includes/block-supports/colors.php @@ -78,7 +78,6 @@ function wp_register_colors_support( $block_type ) { * * @param WP_Block_Type $block_type Block type. * @param array $block_attributes Block attributes. - * * @return array Colors CSS classes and inline styles. */ function wp_apply_colors_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/custom-classname.php b/src/wp-includes/block-supports/custom-classname.php index 1889918644fc9..3c1215723d30b 100644 --- a/src/wp-includes/block-supports/custom-classname.php +++ b/src/wp-includes/block-supports/custom-classname.php @@ -38,7 +38,6 @@ function wp_register_custom_classname_support( $block_type ) { * * @param WP_Block_Type $block_type Block Type. * @param array $block_attributes Block attributes. - * * @return array Block CSS classes and inline styles. */ function wp_apply_custom_classname_support( $block_type, $block_attributes ) { diff --git a/src/wp-includes/block-supports/settings.php b/src/wp-includes/block-supports/settings.php index 59c3cbd9a28de..2af6f865f76b3 100644 --- a/src/wp-includes/block-supports/settings.php +++ b/src/wp-includes/block-supports/settings.php @@ -71,7 +71,6 @@ function _wp_add_block_level_presets_class( $block_content, $block ) { * * @param string|null $pre_render The pre-rendered content. Default null. * @param array $block The block being rendered. - * * @return null */ function _wp_add_block_level_preset_styles( $pre_render, $block ) { diff --git a/src/wp-includes/block-supports/utils.php b/src/wp-includes/block-supports/utils.php index 7c5d7db48a150..c668586674de4 100644 --- a/src/wp-includes/block-supports/utils.php +++ b/src/wp-includes/block-supports/utils.php @@ -17,7 +17,6 @@ * @param WP_Block_Type $block_type Block type. * @param string $feature_set Name of block support feature set.. * @param string $feature Optional name of individual feature to check. - * * @return bool Whether to serialize block support styles & classes. */ function wp_should_skip_block_supports_serialization( $block_type, $feature_set, $feature = null ) { diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index a7d5d4aa1141d..2d23f8545e87f 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -31,7 +31,6 @@ * @since 5.9.0 * * @param string $theme_stylesheet The stylesheet. Default is to leverage the main theme root. - * * @return string[] { * Folder names used by block themes. * diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 85ad05cfc91ae..8c34b05d016ae 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -431,7 +431,6 @@ function _strip_template_file_suffix( $template_file ) { * @since 5.8.0 * * @param array $context Default context. - * * @return array Filtered context. */ function _block_template_render_without_post_block_context( $context ) { diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index f2df7226a6d78..4a048c80baf3e 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -623,7 +623,6 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { * @param array $attributes Block attributes. * @param string $content Block default content. * @param WP_Block $block Block instance. - * * @return string Returns the block content. */ $settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) { @@ -1929,7 +1928,6 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb * @since 7.0.0 Adds metadata to attributes of single-pattern container blocks. * * @param array $blocks An array blocks. - * * @return array An array of blocks with patterns replaced by their content. */ function resolve_pattern_blocks( $blocks ) { @@ -2814,7 +2812,6 @@ function wp_migrate_old_typography_shape( $metadata ) { * * @param WP_Block $block Block instance. * @param int $page Current query's page. - * * @return array Returns the constructed WP_Query arguments. */ function build_query_vars_from_query_block( $block, $page ) { diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php index 86f0ea21a2a3c..d06279879c1f8 100644 --- a/src/wp-includes/class-wp-block-type.php +++ b/src/wp-includes/class-wp-block-type.php @@ -357,7 +357,6 @@ public function __construct( $block_type, $args = array() ) { * @since 6.1.0 * * @param string $name Deprecated property name. - * * @return string|string[]|null The value read from the new property if the first item in the array provided, * null when value not found or when unknown property name provided. */ @@ -393,7 +392,6 @@ public function __get( $name ) { * @since 6.1.0 * * @param string $name Deprecated property name. - * * @return bool Returns true when for the new property the first item in the array exists, * or false otherwise. */ diff --git a/src/wp-includes/class-wp-term-query.php b/src/wp-includes/class-wp-term-query.php index a30d887aa56d1..909da85433a51 100644 --- a/src/wp-includes/class-wp-term-query.php +++ b/src/wp-includes/class-wp-term-query.php @@ -973,7 +973,6 @@ protected function parse_orderby( $orderby_raw ) { * * @param WP_Term[] $term_objects Array of term objects. * @param string $_fields Field to format. - * * @return WP_Term[]|int[]|string[] Array of terms / strings / ints depending on field requested. */ protected function format_terms( $term_objects, $_fields ) { @@ -1156,7 +1155,6 @@ protected function populate_terms( $terms ) { * * @param array $args WP_Term_Query arguments. * @param string $sql SQL statement. - * * @return string Cache key. */ protected function generate_cache_key( array $args, $sql ) { diff --git a/src/wp-includes/class-wp-textdomain-registry.php b/src/wp-includes/class-wp-textdomain-registry.php index bb2135e3650a2..c62e751b6e47b 100644 --- a/src/wp-includes/class-wp-textdomain-registry.php +++ b/src/wp-includes/class-wp-textdomain-registry.php @@ -87,7 +87,6 @@ public function init() { * * @param string $domain Text domain. * @param string $locale Locale. - * * @return string|false Languages directory path or false if there is none available. */ public function get( $domain, $locale ) { diff --git a/src/wp-includes/class-wp-theme-json-data.php b/src/wp-includes/class-wp-theme-json-data.php index 9a9e73ecffdbf..a1bf64b1d8232 100644 --- a/src/wp-includes/class-wp-theme-json-data.php +++ b/src/wp-includes/class-wp-theme-json-data.php @@ -50,7 +50,6 @@ public function __construct( $data = array( 'version' => WP_Theme_JSON::LATEST_S * @since 6.1.0 * * @param array $new_data Array following the theme.json specification. - * * @return WP_Theme_JSON_Data The own instance with access to the modified data. */ public function update_with( $new_data ) { diff --git a/src/wp-includes/class-wp-theme-json-schema.php b/src/wp-includes/class-wp-theme-json-schema.php index aaa2f968a6395..47c0f016ec3f3 100644 --- a/src/wp-includes/class-wp-theme-json-schema.php +++ b/src/wp-includes/class-wp-theme-json-schema.php @@ -73,7 +73,6 @@ public static function migrate( $theme_json, $origin = 'theme' ) { * @since 5.9.0 * * @param array $old Data to migrate. - * * @return array Data without the custom prefixes. */ private static function migrate_v1_to_v2( $old ) { @@ -169,7 +168,6 @@ private static function migrate_v2_to_v3( $old, $origin ) { * * @param array $settings Array to process. * @param array $paths_to_rename Paths to rename. - * * @return array The settings in the new format. */ private static function rename_paths( $settings, $paths_to_rename ) { diff --git a/src/wp-includes/class-wp-token-map.php b/src/wp-includes/class-wp-token-map.php index fc223b187f8c5..317a9830cba85 100644 --- a/src/wp-includes/class-wp-token-map.php +++ b/src/wp-includes/class-wp-token-map.php @@ -528,7 +528,6 @@ public function contains( string $word, string $case_sensitivity = 'case-sensiti * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found token matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. - * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ public function read_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { @@ -589,7 +588,6 @@ public function read_token( string $text, int $offset = 0, &$matched_token_byte_ * @param int $offset Optional. How many bytes into the string where the lookup key ought to start. Default 0. * @param int|null &$matched_token_byte_length Optional. Holds byte-length of found lookup key if matched, otherwise not set. Default null. * @param string $case_sensitivity Optional. Pass 'ascii-case-insensitive' to ignore ASCII case when matching. Default 'case-sensitive'. - * * @return string|null Mapped value of lookup key if found, otherwise `null`. */ private function read_small_token( string $text, int $offset = 0, &$matched_token_byte_length = null, $case_sensitivity = 'case-sensitive' ): ?string { diff --git a/src/wp-includes/deprecated.php b/src/wp-includes/deprecated.php index 14f5c24aec914..3b78d1610fdad 100644 --- a/src/wp-includes/deprecated.php +++ b/src/wp-includes/deprecated.php @@ -6394,7 +6394,6 @@ function wp_enqueue_global_styles_custom_css() { * * @param array $block Block object. * @param string $variation Slug for the block style variation. - * * @return string The unique variation name. */ function wp_create_block_style_variation_instance_name( $block, $variation ) { diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f5002a45de1e8..47ad6a3e2246b 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -5300,7 +5300,6 @@ function _wp_array_set( &$input_array, $path, $value = null ) { * @link https://github.com/lodash-php/lodash-php/blob/master/src/internal/unicodeWords.php * * @param string $input_string The string to kebab-case. - * * @return string kebab-cased-string. */ function _wp_to_kebab_case( $input_string ) { @@ -8699,7 +8698,6 @@ function wp_get_default_update_php_url() { * @param string $before Markup to output before the annotation. Default `

`. * @param string $after Markup to output after the annotation. Default `

`. * @param bool $display Whether to echo or return the markup. Default `true` for echo. - * * @return string|null Update PHP page annotation if available and $display is false, null otherwise. */ function wp_update_php_annotation( $before = '

', $after = '

', $display = true ) { diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index 4b08301f9c7db..0534d8b0dee58 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -498,7 +498,6 @@ function wp_get_theme_data_template_parts() { * @param WP_Block_Type $block_type The block's type. * @param string|array $target The desired selector's target, `root` or array path. * @param boolean $fallback Whether to fall back to broader selector. - * * @return string|null CSS selector or `null` if no selector available. */ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = false ) { diff --git a/src/wp-includes/html-api/class-wp-html-doctype-info.php b/src/wp-includes/html-api/class-wp-html-doctype-info.php index f448aabde3ad9..ce6004a940cf3 100644 --- a/src/wp-includes/html-api/class-wp-html-doctype-info.php +++ b/src/wp-includes/html-api/class-wp-html-doctype-info.php @@ -410,7 +410,6 @@ private function __construct( * @since 6.7.0 * * @param string $doctype_html The complete raw DOCTYPE HTML string, e.g. ``. - * * @return WP_HTML_Doctype_Info|null A WP_HTML_Doctype_Info instance will be returned if the * provided DOCTYPE HTML is a valid DOCTYPE. Otherwise, null. */ 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 b4c590ef5141a..9e18d0e9b7121 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1267,7 +1267,6 @@ public function get_current_depth(): int { * @since 6.7.0 * * @param string $html Input HTML to normalize. - * * @return string|null Normalized output, or `null` if unable to normalize. */ public static function normalize( string $html ): ?string { diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index dd99a8324b5c6..88487fb068d11 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -3233,7 +3233,6 @@ public function get_qualified_tag_name(): ?string { * @since 6.7.0 * * @param string $attribute_name Which attribute to adjust. - * * @return string|null */ public function get_qualified_attribute_name( $attribute_name ): ?string { diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php index 9cd0f50f22487..9394b75989912 100644 --- a/src/wp-includes/kses.php +++ b/src/wp-includes/kses.php @@ -1390,7 +1390,6 @@ function _wp_kses_split_callback( $matches ) { * or a context name such as 'post'. See wp_kses_allowed_html() * for the list of accepted context names. * @param string[] $allowed_protocols Array of allowed URL protocols. - * * @return string Fixed HTML element */ function wp_kses_split2( $content, $allowed_html, $allowed_protocols ) { diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 2f27c2180037a..c6bd4482c1ba5 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1993,7 +1993,6 @@ function is_locale_switched() { * @param string|string[]|array[]|object $i18n_schema I18n schema for the setting. * @param string|string[]|array[] $settings Value for the settings. * @param string $textdomain Textdomain to use with translations. - * * @return string|string[]|array[] Translated settings. */ function translate_settings_using_i18n_schema( $i18n_schema, $settings, $textdomain ) { diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index d54cee18c5b39..892716b5d69e0 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -3458,7 +3458,6 @@ function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::C * @since 5.7.0 * * @param WP_Error $error WP_Error instance. - * * @return WP_REST_Response List of associative arrays with code and message keys. */ function rest_convert_error_to_response( $error ) { diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php index c331d73363541..c7ffb5b7677d0 100644 --- a/src/wp-includes/rest-api/class-wp-rest-server.php +++ b/src/wp-includes/rest-api/class-wp-rest-server.php @@ -245,7 +245,6 @@ protected function json_error( $code, $message, $status = null ) { * @since 6.1.0 * * @param \WP_REST_Request $request The current request object. - * * @return int The JSON encode options. */ protected function get_json_encode_options( WP_REST_Request $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index e8e81c3617fc4..ed8195cbb91b7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -626,7 +626,6 @@ public function get_theme_items_permissions_check( $request ) { * @since 6.6.0 Added custom relative theme file URIs to `_links` for each item. * * @param WP_REST_Request $request The request instance. - * * @return WP_REST_Response|WP_Error */ public function get_theme_items( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php index dd72bc1c15210..396d657fd2546 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php @@ -337,7 +337,6 @@ public function delete_item( $request ) { * @since 5.9.0 * * @param WP_REST_Request $request Request object. - * * @return object|WP_Error */ protected function prepare_item_for_database( $request ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php index 1d07c27251c3b..a36fe356e9070 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php @@ -604,7 +604,6 @@ private function get_widget_form( $widget_object, $instance ) { * @since 5.9.0 * * @param WP_REST_Request $request Full details about the request. - * * @return array An array with rendered Legacy Widget HTML. */ public function render( $request ) { @@ -623,7 +622,6 @@ public function render( $request ) { * * @param string $id_base The id base of the requested widget. * @param array $instance The widget instance attributes. - * * @return string Rendered Legacy Widget block preview. */ private function render_legacy_widget_preview_iframe( $id_base, $instance ) { diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index a364439f0abbb..7d5ba24e5617d 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -3212,7 +3212,6 @@ static function ( $a, $b ) { * * @param string $css The CSS to make URLs relative to the WordPress installation. * @param string $stylesheet_url The URL to the stylesheet. - * * @return string The CSS with URLs made relative to the WordPress installation. */ function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { diff --git a/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php b/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php index 7436f5ff0083b..95bd10c5e4947 100644 --- a/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php +++ b/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php @@ -58,7 +58,6 @@ public function get_object_subtypes() { * * @param int $page_num Page of results. * @param string $object_subtype Optional. Post type name. Default empty. - * * @return array[] Array of URL information for a sitemap. */ public function get_url_list( $page_num, $object_subtype = '' ) { diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 07869ae61d0ff..3262ebde134f2 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -4369,7 +4369,6 @@ function wp_is_block_theme() { * @since 6.1.0 * * @param string $element The name of the element. - * * @return string The name of the class. */ function wp_theme_get_element_class_name( $element ) { diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 539240a42e9d2..1a1ce8359ccbe 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -853,7 +853,6 @@ function delete_user_option( $user_id, $option_name, $is_global = false ) { * @since 6.7.0 * * @param int $user_id User ID. - * * @return WP_User|false WP_User object on success, false on failure. */ function get_user( $user_id ) { From 2b0b3b8bc6c68a62f1eb2cfb9b296c5dcd7a9799 Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 16 Aug 2026 16:10:24 +0000 Subject: [PATCH 02/11] Toolbar: Revert focus and hover style changes. Reverts the focus and hover style changes in the admin toolbar. Reverts [63190], [63009], and [63188], restoring the appearance in WordPress 7.0. Props fushar, wildworks, ravichudasama01, afercia, joen, keoshi, cbusquets1989, annezazu. Fixes #65849. See #65445. git-svn-id: https://develop.svn.wordpress.org/trunk@63306 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/css/admin-menu.css | 17 +++++++--- src/wp-admin/css/colors/_admin.scss | 32 +++++++++--------- src/wp-includes/css/admin-bar.css | 50 ++++++++++++++--------------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/wp-admin/css/admin-menu.css b/src/wp-admin/css/admin-menu.css index aa6a8bc1414aa..3747613282be1 100644 --- a/src/wp-admin/css/admin-menu.css +++ b/src/wp-admin/css/admin-menu.css @@ -800,10 +800,12 @@ li#wp-admin-bar-menu-toggle { display: block; padding: 0; overflow: hidden; + outline: none; text-decoration: none; + border: 1px solid transparent; background: none; - height: 46px; - box-sizing: border-box; + height: 44px; + margin-left: -1px; } .wp-responsive-open #wpadminbar #wp-admin-bar-menu-toggle a { @@ -814,17 +816,22 @@ li#wp-admin-bar-menu-toggle { display: block; } + #wpadminbar #wp-admin-bar-menu-toggle a:hover { + border: 1px solid transparent; + } + #wpadminbar #wp-admin-bar-menu-toggle .ab-icon:before { content: "\f228"; display: inline-block; float: left; - font: normal 40px/46px dashicons; + font: normal 40px/45px dashicons; vertical-align: middle; + outline: none; margin: 0; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - height: 46px; - width: 52px; + height: 44px; + width: 50px; padding: 0; border: none; text-align: center; diff --git a/src/wp-admin/css/colors/_admin.scss b/src/wp-admin/css/colors/_admin.scss index 91d6a4b66bc8c..366f1b86b4f16 100644 --- a/src/wp-admin/css/colors/_admin.scss +++ b/src/wp-admin/css/colors/_admin.scss @@ -416,11 +416,9 @@ ul#adminmenu > li.current > a.current:after { background: variables.$menu-submenu-background; } -#wpadminbar > #wp-toolbar li:hover span.ab-label, -#wpadminbar > #wp-toolbar li.hover span.ab-label, -/* The adminbar menu may output either links or focusable div elements. */ -/* As such, we target the focus state without specifying the element type. */ -#wpadminbar > #wp-toolbar :focus span.ab-label { +#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar li.hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label { color: variables.$menu-submenu-focus-text; } @@ -451,9 +449,7 @@ ul#adminmenu > li.current > a.current:after { } #wpadminbar .quicklinks li .blavatar, -#wpadminbar .menupop .menupop > .ab-item:before, -#wpadminbar.mobile .quicklinks .ab-icon:before, -#wpadminbar.mobile .quicklinks .ab-item:before { +#wpadminbar .menupop .menupop > .ab-item:before { color: variables.$menu-icon; } @@ -478,17 +474,21 @@ ul#adminmenu > li.current > a.current:after { color: variables.$menu-submenu-focus-text; } -// Note that the icon of the site-name item is not wrapped within a span with class ab-icon like other items. #wpadminbar .quicklinks li a:hover .blavatar, #wpadminbar .quicklinks li a:focus .blavatar, #wpadminbar .quicklinks .ab-sub-wrapper .menupop.hover > a .blavatar, #wpadminbar .menupop .menupop > .ab-item:hover:before, #wpadminbar.mobile .quicklinks .hover .ab-icon:before, -#wpadminbar.mobile .quicklinks .hover .ab-item:before, -#wpadminbar.mobile .quicklinks .ab-item:focus:before { +#wpadminbar.mobile .quicklinks .hover .ab-item:before { color: variables.$menu-submenu-focus-text; } +#wpadminbar.mobile .quicklinks .ab-icon:before, +#wpadminbar.mobile .quicklinks .ab-item:before { + color: variables.$menu-icon; +} + + /* Admin Bar: search */ #wpadminbar #adminbarsearch:before { @@ -531,16 +531,14 @@ ul#adminmenu > li.current > a.current:after { color: variables.$menu-text; } +#wpadminbar #wp-admin-bar-user-info a:hover .display-name { + color: variables.$menu-submenu-focus-text; +} + #wpadminbar #wp-admin-bar-user-info .username { color: variables.$menu-submenu-text; } -#wpadminbar #wp-admin-bar-user-info a:hover .display-name, -#wpadminbar #wp-admin-bar-user-info a:focus .display-name, -#wpadminbar #wp-admin-bar-user-info a:hover .username, -#wpadminbar #wp-admin-bar-user-info a:focus .username { - color: variables.$menu-submenu-focus-text; -} /* Pointers */ diff --git a/src/wp-includes/css/admin-bar.css b/src/wp-includes/css/admin-bar.css index e0411701a28e8..77e196525657a 100644 --- a/src/wp-includes/css/admin-bar.css +++ b/src/wp-includes/css/admin-bar.css @@ -77,28 +77,12 @@ html:lang(he-il) .rtl #wpadminbar * { box-shadow: none; } -#wpadminbar a:focus, -#wpadminbar .ab-item[tabindex="0"]:focus { - outline-offset: -2px; +#wpadminbar a:focus { + outline-offset: -1px; /* Only visible in Windows High Contrast mode */ outline: 2px solid transparent; } -#wpadminbar a:hover, -#wpadminbar a:focus, -#wpadminbar .ab-item[tabindex="0"]:hover, -#wpadminbar .ab-item[tabindex="0"]:focus { - box-shadow: inset 0 -4px 0 0 currentColor; - transition: box-shadow 0.1s linear; -} - -#wpadminbar .ab-submenu a:hover, -#wpadminbar .ab-submenu a:focus, -#wpadminbar .ab-submenu .ab-item[tabindex="0"]:hover, -#wpadminbar .ab-submenu .ab-item[tabindex="0"]:focus { - box-shadow: inset 4px 0 0 0 currentColor; -} - #wpadminbar { direction: ltr; color: #c3c4c7; @@ -239,11 +223,9 @@ html:lang(he-il) .rtl #wpadminbar * { color: #72aee6; } -#wpadminbar > #wp-toolbar li:hover span.ab-label, +#wpadminbar:not(.mobile) > #wp-toolbar li:hover span.ab-label, #wpadminbar > #wp-toolbar li.hover span.ab-label, -/* The adminbar menu may output either links or focusable div elements. */ -/* As such, we target the focus state without specifying the element type. */ -#wpadminbar > #wp-toolbar :focus span.ab-label { +#wpadminbar:not(.mobile) > #wp-toolbar a:focus span.ab-label { color: #72aee6; } @@ -312,8 +294,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar li.hover .ab-icon:before, #wpadminbar li.hover .ab-item:before, #wpadminbar li:hover #adminbarsearch:before, -#wpadminbar li #adminbarsearch.adminbar-focused:before, -#wpadminbar.mobile .quicklinks .ab-item:focus:before { +#wpadminbar li #adminbarsearch.adminbar-focused:before { color: #72aee6; } @@ -398,6 +379,11 @@ html:lang(he-il) .rtl #wpadminbar * { float: right; } +#wpadminbar ul li:last-child, +#wpadminbar ul li:last-child .ab-item { + box-shadow: none; +} + /** * Recovery Mode */ @@ -466,7 +452,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wp-admin-bar-user-info .avatar { position: absolute; left: -72px; - top: 0; + top: 4px; width: 64px; height: 64px; border-radius: 50%; @@ -480,7 +466,7 @@ html:lang(he-il) .rtl #wpadminbar * { #wpadminbar #wp-admin-bar-user-info span { background: none; padding: 0; - line-height: 1.38461538; + height: 18px; } #wpadminbar #wp-admin-bar-user-info .display-name, @@ -489,6 +475,7 @@ html:lang(he-il) .rtl #wpadminbar * { } #wpadminbar #wp-admin-bar-user-info .username { + color: #a7aaad; font-size: 11px; } @@ -924,6 +911,7 @@ html:lang(he-il) .rtl #wpadminbar * { overflow: hidden; width: 52px; padding: 0; + color: #a7aaad; /* @todo not needed? this text is hidden */ position: relative; } @@ -1043,6 +1031,16 @@ html:lang(he-il) .rtl #wpadminbar * { height: auto; font-size: 16px; line-height: 1.5; + color: #f0f0f1; + } + + #wpadminbar #wp-admin-bar-user-info a { + padding-top: 4px; + } + + #wpadminbar #wp-admin-bar-user-info .username { + line-height: 0.8 !important; + margin-bottom: -2px; } /* Show only default top level items */ From e7739d54141679d2b07bb0a3143677fc4b5a1fff Mon Sep 17 00:00:00 2001 From: Joe Dolson Date: Sun, 16 Aug 2026 16:55:59 +0000 Subject: [PATCH 03/11] Rewrite Rules: Only a leading `www.` is optional in `url_to_postid()`. Fixes the behavior of `url_to_postid()`, which treated a `www.` string anywhere in the URL as optional, breaking some URLs. Anchor the string so that is only considered optional when at the beginning of a URL string. Follow up to [63207]. Props youknowriad, johnbillion, wildworks, irozum, joedolson. Fixes #65016. git-svn-id: https://develop.svn.wordpress.org/trunk@63307 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/rewrite.php | 5 ++-- tests/phpunit/tests/rewrite.php | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/rewrite.php b/src/wp-includes/rewrite.php index 0a6b84b75e534..976d2a014b9db 100644 --- a/src/wp-includes/rewrite.php +++ b/src/wp-includes/rewrite.php @@ -502,7 +502,8 @@ function url_to_postid( $url ) { $url_host = parse_url( $url, PHP_URL_HOST ); if ( is_string( $url_host ) ) { - $url_host = str_replace( 'www.', '', $url_host ); + // Only a leading 'www.' is optional. Removing it anywhere else would match a different host. + $url_host = preg_replace( '|^www\.|', '', $url_host ); } else { $url_host = ''; } @@ -510,7 +511,7 @@ function url_to_postid( $url ) { $home_url_host = parse_url( home_url(), PHP_URL_HOST ); if ( is_string( $home_url_host ) ) { - $home_url_host = str_replace( 'www.', '', $home_url_host ); + $home_url_host = preg_replace( '|^www\.|', '', $home_url_host ); } else { $home_url_host = ''; } diff --git a/tests/phpunit/tests/rewrite.php b/tests/phpunit/tests/rewrite.php index 24c7e4e1459fc..085998e4b7eec 100644 --- a/tests/phpunit/tests/rewrite.php +++ b/tests/phpunit/tests/rewrite.php @@ -262,6 +262,48 @@ public function test_url_to_postid_url_has_only_path() { $this->assertSame( 0, url_to_postid( '/example/' ) ); } + /** + * Only a leading 'www.' is optional when comparing the URL's host to the site's. + * + * A 'www.' elsewhere in the host belongs to a different domain, which an attacker + * can register: stripping it everywhere makes 'exwww.ample.com' match 'example.com'. + * + * @ticket 65016 + * + * @covers ::url_to_postid + * + * @dataProvider data_url_to_postid_host_matching + * + * @param string $host Host of the URL to resolve. + * @param bool $is_local Whether the host should be treated as this site. + */ + public function test_url_to_postid_matches_www_prefix_only( $host, $is_local ) { + update_option( 'home', 'https://example.com' ); + update_option( 'siteurl', 'https://example.com' ); + + $post_id = self::factory()->post->create(); + + $expected = $is_local ? $post_id : 0; + + $this->assertSame( $expected, url_to_postid( "https://$host/?p=$post_id" ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_url_to_postid_host_matching() { + return array( + 'the site host' => array( 'example.com', true ), + 'the site host with www' => array( 'www.example.com', true ), + 'www inside the domain' => array( 'exwww.ample.com', false ), + 'www inside the TLD' => array( 'example.cwww.om', false ), + 'an unrelated host' => array( 'evil.com', false ), + 'the site host as subdomain' => array( 'example.com.evil.com', false ), + ); + } + /** * @covers ::url_to_postid */ From 1fcc5c04cb8055087ee5b414cd10d1ce0c0e6b9f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 16 Aug 2026 18:46:59 +0000 Subject: [PATCH 04/11] Build/Test Tools: Allow text compression in the local environment. Add gzip directives to the nginx config template used by the local Docker environment so that HTML, JavaScript, CSS, JSON, XML, and SVG responses can be served compressed, matching normal production behavior. Without compression, frontend performance analysis against the local environment is not representative of a real user's experience. Compression is controlled by a new `LOCAL_NGINX_COMPRESSION` option, and it defaults to `off` so that it is opt-in. Since this changes `docker-compose.yml` in addition to the nginx template, existing checkouts need to recreate the web server container to pick up the change. Developed in https://github.com/WordPress/wordpress-develop/pull/12529. Follow-up to r45745, r45783. Props westonruter, jblz. Fixes #65634. git-svn-id: https://develop.svn.wordpress.org/trunk@63308 602fd350-edb4-49c9-b593-d223f7449a82 --- .env.example | 4 ++++ docker-compose.yml | 3 ++- tools/local-env/default.template | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 6974d8554611a..dddbb4be4c260 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,10 @@ LOCAL_PORT=8889 # Where to run WordPress from. Valid options are 'src' and 'build'. LOCAL_DIR=src +# Whether or not to enable text compression (gzip) in the web server. +# Valid options are 'on' and 'off'. +LOCAL_NGINX_COMPRESSION=off + # The PHP version to use. Valid options are 'latest', and '{version}-fpm'. LOCAL_PHP=latest diff --git a/docker-compose.yml b/docker-compose.yml index 7ab6ae8c9b4d5..02cd542c245db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,13 +14,14 @@ services: environment: LOCAL_DIR: ${LOCAL_DIR-src} + LOCAL_NGINX_COMPRESSION: ${LOCAL_NGINX_COMPRESSION:-off} volumes: - ./tools/local-env/default.template:/etc/nginx/conf.d/default.template - ./:/var/www # Load our config file, substituting environment variables into the config. - command: /bin/sh -c "envsubst '$$LOCAL_DIR' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" + command: /bin/sh -c "envsubst '$$LOCAL_DIR $$LOCAL_NGINX_COMPRESSION' < /etc/nginx/conf.d/default.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" depends_on: php: diff --git a/tools/local-env/default.template b/tools/local-env/default.template index 995913fb453a2..4d8dec3c131fc 100644 --- a/tools/local-env/default.template +++ b/tools/local-env/default.template @@ -15,6 +15,21 @@ server { absolute_redirect off; + gzip ${LOCAL_NGINX_COMPRESSION}; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 5; + gzip_min_length 256; + gzip_types + application/javascript + text/javascript + application/json + application/xml + application/rss+xml + text/css + text/plain + image/svg+xml; + if (!-e $request_filename) { rewrite /wp-admin$ $scheme://$host$request_uri/ permanent; rewrite ^(/[^/]+)?(/wp-.*) $2 last; From 3150f656e219b587e4014f8d3b10c68981d48222 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Sun, 16 Aug 2026 20:24:05 +0000 Subject: [PATCH 05/11] Build/Test Tools: Remove all `npx` commands. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an `npx` command is run, the specified package will be downloaded and installed when it does not exist locally. This will also install all of the package’s direct and transitive dependencies, and any installation scripts present for every installed package are run. This is potentially dangerous because a compromised package would be able to run code on a local machine or within a GitHub Actions workflow runner. This replaces every `npx` call with `npm exec --no`, which runs an installed binary only and will fail when the package is missing. Additionally, `update-browserslist-db` is now defined as a direct `devDependency`. Props adrianmoldovanwp, desrosj, lancewillett, johnbillion. Fixes #65864. git-svn-id: https://develop.svn.wordpress.org/trunk@63309 602fd350-edb4-49c9-b593-d223f7449a82 --- .github/workflows/reusable-end-to-end-tests.yml | 2 +- .github/workflows/reusable-performance-test-v2.yml | 2 +- Gruntfile.js | 10 +++++----- package-lock.json | 7 ++++--- package.json | 1 + tests/qunit/playwright.config.js | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/reusable-end-to-end-tests.yml b/.github/workflows/reusable-end-to-end-tests.yml index 36a71d1711d2f..9b5586973aacf 100644 --- a/.github/workflows/reusable-end-to-end-tests.yml +++ b/.github/workflows/reusable-end-to-end-tests.yml @@ -100,7 +100,7 @@ jobs: - name: Install Playwright browsers if: ${{ inputs.install-playwright }} - run: npx playwright install --with-deps chromium + run: npm exec --no -- playwright install --with-deps chromium - name: Build WordPress run: npm run build diff --git a/.github/workflows/reusable-performance-test-v2.yml b/.github/workflows/reusable-performance-test-v2.yml index a9b911e718cba..b92893f52f7f1 100644 --- a/.github/workflows/reusable-performance-test-v2.yml +++ b/.github/workflows/reusable-performance-test-v2.yml @@ -148,7 +148,7 @@ jobs: run: npm ci - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + run: npm exec --no -- playwright install --with-deps chromium - name: Start Docker environment run: npm run env:start diff --git a/Gruntfile.js b/Gruntfile.js index 61f18481e23a8..ab40643d05a80 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2279,8 +2279,8 @@ module.exports = function(grunt) { grunt.registerTask( 'qunit', 'Runs QUnit tests.', function() { var done = this.async(); grunt.util.spawn( { - cmd: 'npx', - args: [ 'playwright', 'test', '--config', 'tests/qunit/playwright.config.js' ], + cmd: 'npm', + args: [ 'exec', '--no', '--', 'playwright', 'test', '--config', 'tests/qunit/playwright.config.js' ], opts: { stdio: 'inherit' } }, function( error, result, code ) { if ( code !== 0 ) { @@ -2363,15 +2363,15 @@ module.exports = function(grunt) { grunt.registerTask( 'wp-packages:update', 'Update WordPress packages', function() { const distTag = grunt.option('dist-tag') || 'latest'; grunt.log.writeln( `Updating WordPress packages (--dist-tag=${distTag})` ); - spawn( 'npx', [ 'wp-scripts', 'packages-update', `--dist-tag=${distTag}` ], { + spawn( 'npm', [ 'exec', '--no', '--', 'wp-scripts', 'packages-update', `--dist-tag=${distTag}` ], { cwd: __dirname, stdio: 'inherit', } ); } ); grunt.registerTask( 'browserslist:update', 'Update the local database of browser supports', function() { - grunt.log.writeln( `Updating browsers list` ); - spawn( 'npx', [ 'update-browserslist-db@latest' ], { + grunt.log.writeln( 'Updating browsers list' ); + spawn( 'npm', [ 'exec', '--no', '--', 'update-browserslist-db' ], { cwd: __dirname, stdio: 'inherit', } ); diff --git a/package-lock.json b/package-lock.json index fe92d5381d923..f1d7ebd89b2c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,6 +89,7 @@ "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "typescript": "6.0.3", + "update-browserslist-db": "1.3.1", "uuid": "14.0.1", "wait-on": "9.0.10", "webpack": "5.108.4" @@ -32266,9 +32267,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index e87e95546a204..9bb1fd5d0af5b 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "sinon-test": "~3.1.6", "source-map-loader": "5.0.0", "typescript": "6.0.3", + "update-browserslist-db": "1.3.1", "uuid": "14.0.1", "wait-on": "9.0.10", "webpack": "5.108.4" diff --git a/tests/qunit/playwright.config.js b/tests/qunit/playwright.config.js index fc6651e0917ac..9e49301727a6a 100644 --- a/tests/qunit/playwright.config.js +++ b/tests/qunit/playwright.config.js @@ -12,7 +12,7 @@ module.exports = defineConfig( { workers: 1, use: { headless: true, - /* This avoids the need to run `npx playwright install` in CI. */ + /* The system Chrome channel avoids a browser download in CI. */ channel: process.env.CI ? 'chrome' : undefined, }, reporter: process.env.CI ? 'github' : 'list', From b2e9a22e684fc5d9438b2dbb659823e95bec80c4 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Sun, 16 Aug 2026 22:27:51 +0000 Subject: [PATCH 06/11] Build/Test Tools: Distinguish between cancelled and timed out workflow runs. GitHub Actions expressions and contexts do not currently expose the details about a workflow run necessary to determine whether a workflow has timed out. Instead, all timed out runs are considered `cancelled`. However, the REST API does return a `timed_out` conclusion in this scenario. This creates a new job for the `timed_out` conlclusion and adjusts the logic for cancelled Slack notifications to provide more useful notifications. Props lancewillett. See #65845. git-svn-id: https://develop.svn.wordpress.org/trunk@63310 602fd350-edb4-49c9-b593-d223f7449a82 --- .github/workflows/coding-standards.yml | 1 + .github/workflows/end-to-end-tests.yml | 1 + .github/workflows/install-testing.yml | 1 + .github/workflows/javascript-tests.yml | 1 + .../workflows/javascript-type-checking.yml | 1 + .../workflows/local-docker-environment.yml | 1 + .github/workflows/performance.yml | 1 + .github/workflows/php-compatibility.yml | 1 + .github/workflows/phpstan-static-analysis.yml | 1 + .github/workflows/phpunit-tests.yml | 1 + .github/workflows/slack-notifications.yml | 42 ++++++++++++++++++- .../workflows/test-and-zip-default-themes.yml | 1 + .github/workflows/test-build-processes.yml | 1 + .github/workflows/test-coverage.yml | 1 + .github/workflows/test-old-branches.yml | 1 + .github/workflows/upgrade-develop-testing.yml | 1 + .github/workflows/upgrade-testing.yml | 1 + .github/workflows/workflow-lint.yml | 1 + 18 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 8f1e9fe701330..d579368eeb079 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -89,6 +89,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index d91b6512f4b49..f2eb5abb83ddb 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -89,6 +89,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/install-testing.yml b/.github/workflows/install-testing.yml index 140e5f679701a..d69dc103d2d3a 100644 --- a/.github/workflows/install-testing.yml +++ b/.github/workflows/install-testing.yml @@ -152,6 +152,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/javascript-tests.yml b/.github/workflows/javascript-tests.yml index 8b1241038773d..08a118e4a4056 100644 --- a/.github/workflows/javascript-tests.yml +++ b/.github/workflows/javascript-tests.yml @@ -77,6 +77,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/javascript-type-checking.yml b/.github/workflows/javascript-type-checking.yml index 65561cabbb1e1..4983e0c619ec8 100644 --- a/.github/workflows/javascript-type-checking.yml +++ b/.github/workflows/javascript-type-checking.yml @@ -70,6 +70,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/local-docker-environment.yml b/.github/workflows/local-docker-environment.yml index 856db171726b6..0f88d54ff4eb8 100644 --- a/.github/workflows/local-docker-environment.yml +++ b/.github/workflows/local-docker-environment.yml @@ -135,6 +135,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index ef02c51101557..22a3a27392859 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -153,6 +153,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml index 4bae7e0a381b0..37de91a73b24b 100644 --- a/.github/workflows/php-compatibility.yml +++ b/.github/workflows/php-compatibility.yml @@ -66,6 +66,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/phpstan-static-analysis.yml b/.github/workflows/phpstan-static-analysis.yml index 6a26c75b16525..a07ee13e16467 100644 --- a/.github/workflows/phpstan-static-analysis.yml +++ b/.github/workflows/phpstan-static-analysis.yml @@ -66,6 +66,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index f0f1282f73a93..30cf72bc606ae 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -411,6 +411,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/slack-notifications.yml b/.github/workflows/slack-notifications.yml index 0877405409e83..4b4dfc831aaeb 100644 --- a/.github/workflows/slack-notifications.yml +++ b/.github/workflows/slack-notifications.yml @@ -25,6 +25,9 @@ on: SLACK_GHA_FAILURE_WEBHOOK: description: 'The Slack webhook URL for a failed build.' required: true + SLACK_GHA_TIMEOUT_WEBHOOK: + description: 'The Slack webhook URL for a timed out build.' + required: true # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. @@ -55,6 +58,7 @@ jobs: if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event.workflow_run.event != 'pull_request' }} outputs: previous_conclusion: ${{ steps.previous-attempt-result.outputs.result }} + timed_out: ${{ steps.timeout-check.outputs.result }} payload: ${{ steps.create-payload.outputs.payload }} steps: @@ -128,6 +132,25 @@ jobs: env: CALLING_STATUS: ${{ inputs.calling_status }} + # The REST API exposes a distinct `timed_out` job conclusion that is unavailable through workflow contexts. + - name: Determine whether the workflow timed out + id: timeout-check + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + if: ${{ inputs.calling_status == 'cancelled' }} + with: + retries: 2 + retry-exempt-status-codes: 418 + result-encoding: string + script: | + const jobs = await github.paginate( github.rest.actions.listJobsForWorkflowRunAttempt, { + owner: context.repo.owner, + repo: context.repo.repo, + run_id: `${context.runId}`, + attempt_number: Number( process.env.GITHUB_RUN_ATTEMPT ), + }, ( response ) => response.data.jobs ); + + return jobs.some( ( job ) => job.conclusion === 'timed_out' ) ? 'true' : 'false'; + - name: Get the commit message id: current-commit-message uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -211,14 +234,13 @@ jobs: webhook: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }} payload: ${{ needs.prepare.outputs.payload }} - # Posts notifications when a workflow is cancelled. cancelled: name: Cancelled notifications permissions: {} runs-on: ubuntu-24.04 timeout-minutes: 20 needs: [ prepare ] - if: ${{ inputs.calling_status == 'cancelled' && github.run_attempt == 2 || cancelled() }} + if: ${{ ( inputs.calling_status == 'cancelled' && github.run_attempt == 2 && needs.prepare.outputs.timed_out != 'true' ) || cancelled() }} steps: - name: Post cancelled notifications to Slack @@ -227,3 +249,19 @@ jobs: webhook-type: webhook-trigger webhook: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} payload: ${{ needs.prepare.outputs.payload }} + + timeout: + name: Timeout notifications + permissions: {} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + needs: [ prepare ] + if: ${{ inputs.calling_status == 'cancelled' && github.run_attempt == 2 && needs.prepare.outputs.timed_out == 'true' }} + + steps: + - name: Post timeout notifications to Slack + uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 + with: + webhook-type: webhook-trigger + webhook: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} + payload: ${{ needs.prepare.outputs.payload }} diff --git a/.github/workflows/test-and-zip-default-themes.yml b/.github/workflows/test-and-zip-default-themes.yml index 30c15408f9736..0464a1652d36b 100644 --- a/.github/workflows/test-and-zip-default-themes.yml +++ b/.github/workflows/test-and-zip-default-themes.yml @@ -284,6 +284,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-build-processes.yml b/.github/workflows/test-build-processes.yml index cd32d99cacb1f..ab3bea392cef8 100644 --- a/.github/workflows/test-build-processes.yml +++ b/.github/workflows/test-build-processes.yml @@ -118,6 +118,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 191445ba7167c..94f57628254c3 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -97,6 +97,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/test-old-branches.yml b/.github/workflows/test-old-branches.yml index b5bef1b88c0bc..7094550485c4f 100644 --- a/.github/workflows/test-old-branches.yml +++ b/.github/workflows/test-old-branches.yml @@ -158,3 +158,4 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} diff --git a/.github/workflows/upgrade-develop-testing.yml b/.github/workflows/upgrade-develop-testing.yml index f14736af874a1..4147d3f054b8b 100644 --- a/.github/workflows/upgrade-develop-testing.yml +++ b/.github/workflows/upgrade-develop-testing.yml @@ -140,6 +140,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/upgrade-testing.yml b/.github/workflows/upgrade-testing.yml index 9a9c239e3093d..0d89c062f3b3b 100644 --- a/.github/workflows/upgrade-testing.yml +++ b/.github/workflows/upgrade-testing.yml @@ -206,6 +206,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index b2d5c2638e7b4..1ef050f1b9b92 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -61,6 +61,7 @@ jobs: SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }} SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }} SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }} + SLACK_GHA_TIMEOUT_WEBHOOK: ${{ secrets.SLACK_GHA_TIMEOUT_WEBHOOK }} failed-workflow: name: Failed workflow tasks From 582236f30b73a9de268da3df117d590174ce108d Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Sun, 16 Aug 2026 22:28:40 +0000 Subject: [PATCH 07/11] Tests: Use `assertIsCallable()` for callable assertions. Replaces eight generic `assertTrue( is_callable() )` assertions with PHPUnit's dedicated `assertIsCallable()` assertion. Developed in https://github.com/WordPress/wordpress-develop/pull/13017. Follow-up to r50996, r61760. Props raulsalvat, Soean. See #65819. git-svn-id: https://develop.svn.wordpress.org/trunk@63311 602fd350-edb4-49c9-b593-d223f7449a82 --- tests/phpunit/tests/customize/widgets.php | 12 ++++++------ .../tests/multisite/wpCacheSwitchToBlogFallback.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/phpunit/tests/customize/widgets.php b/tests/phpunit/tests/customize/widgets.php index 5b64f85ce3392..c2ed6dbc661e2 100644 --- a/tests/phpunit/tests/customize/widgets.php +++ b/tests/phpunit/tests/customize/widgets.php @@ -397,8 +397,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); $this->assertSame( 'WIDGET_FOO[2]', $args['uppercase_id_set_by_filter'] ); $default_args = array( @@ -411,8 +411,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); remove_theme_support( 'customize-selective-refresh-widgets' ); $args = $this->manager->widgets->get_setting_args( 'widget_search[2]' ); @@ -443,8 +443,8 @@ public function test_get_setting_args() { foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } - $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); - $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); + $this->assertIsCallable( $args['sanitize_callback'], 'sanitize_callback is callable' ); + $this->assertIsCallable( $args['sanitize_js_callback'], 'sanitize_js_callback is callable' ); $this->assertSame( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] ); $override_args = array( diff --git a/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php b/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php index 35083088017b0..ffc3e1f263f6d 100644 --- a/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php +++ b/tests/phpunit/tests/multisite/wpCacheSwitchToBlogFallback.php @@ -121,8 +121,8 @@ public function test_wp_cache_switch_to_blog_function_exists() { $this->assertTrue( function_exists( 'wp_cache_switch_to_blog_fallback' ), 'wp_cache_switch_to_blog_fallback() should always exist' ); // Both should be callable. - $this->assertTrue( is_callable( 'wp_cache_switch_to_blog' ) ); - $this->assertTrue( is_callable( 'wp_cache_switch_to_blog_fallback' ) ); + $this->assertIsCallable( 'wp_cache_switch_to_blog' ); + $this->assertIsCallable( 'wp_cache_switch_to_blog_fallback' ); } /** From 1ae6d6965ee668cb5d23fc5d48b3ea3049a983e1 Mon Sep 17 00:00:00 2001 From: Jake Spurlock Date: Sun, 16 Aug 2026 22:29:34 +0000 Subject: [PATCH 08/11] Filesystem API: Reject UNC and device paths in `validate_file()`. `validate_file()` runs `wp_normalize_path()` before testing for a drive letter at offset 1. That call folds backslashes to forward slashes, but its slash-collapsing regex deliberately spares a leading `//` to allow for network shares. UNC paths such as `//server/share`, and the device namespaces `//./` and `//?/`, therefore arrived with no colon in the second byte and returned `0` rather than `2`. The docblock described a return value of `2` as meaning the path contains a Windows drive path, which reads as a guarantee that Windows absolute paths are screened. Most forms were not. Anchoring a second test to the start of the string closes that gap. Stream wrappers stay at `0` by two separate mechanisms: a registered wrapper keeps its `://` through the scheme split, placing those slashes past the second byte, while an unregistered scheme has its `//` collapsed. Code `2` now fires on strictly more inputs and never fewer, so no existing rejection becomes an acceptance. Absolute POSIX paths such as `/etc/passwd` continue to return `0`; screening those would be a larger change affecting plugins that pass absolute template paths. Paths beginning with two successive slashes are now rejected regardless of platform, which includes the implementation-defined POSIX doubled-slash form, consistent with the assumption `wp_normalize_path()` already makes about a leading `//`. Props zieladam, SergeyBiryukov. Fixes #51368. git-svn-id: https://develop.svn.wordpress.org/trunk@63312 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 28 +++++++- tests/phpunit/tests/functions.php | 115 ++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f5002a45de1e8..5d84c0eeb32d5 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -6359,11 +6359,20 @@ function iis7_supports_permalinks() { * * A return value of `1` means the file path contains directory traversal. * - * A return value of `2` means the file path contains a Windows drive path. + * A return value of `2` means the file path contains an absolute Windows path. + * This covers drive paths such as `C:/WINDOWS`, UNC network share paths such as + * `//server/share`, and the Windows device namespaces `//./` and `//?/`. * * A return value of `3` means the file is not in the allowed files list. * + * Note that absolute POSIX paths such as `/etc/passwd` are *not* rejected, and + * never have been. Callers that must reject them are responsible for their own + * check. The convention in core is to concatenate the validated value onto a + * trusted base directory and then confirm the result exists, rather than to + * treat this function as an absolute-path guard. + * * @since 1.2.0 + * @since 7.2.0 A return value of `2` also covers UNC and Windows device paths. * * @param string $file File path. * @param string[] $allowed_files Optional. Array of allowed files. Default empty array. @@ -6399,8 +6408,21 @@ function validate_file( $file, $allowed_files = array() ) { return 3; } - // Absolute Windows drive paths are not allowed: - if ( ':' === substr( $file, 1, 1 ) ) { + /* + * Absolute Windows paths are not allowed. + * + * The drive-letter test predates validate_file() itself, arriving from + * b2/cafelog by way of a long series of moves. It only ever matched the + * `X:` form, which left UNC and device paths accepted: wp_normalize_path() + * above has already folded backslashes to forward slashes, and it + * deliberately preserves a leading `//` for network shares, so those + * paths arrive with no colon in the second byte. + * + * Anchoring the second test to the start of the string is what keeps + * stream wrappers working. A registered wrapper keeps its `://` through + * wp_normalize_path(), placing those slashes past the second byte. + */ + if ( ':' === substr( $file, 1, 1 ) || str_starts_with( $file, '//' ) ) { return 2; } diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 19c721aeaa46a..3c709f3ea7090 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1984,6 +1984,121 @@ public function data_validate_file() { 0, ), + /* + * Windows UNC and device paths. + * + * wp_normalize_path() folds backslashes to forward slashes but + * deliberately preserves a leading '//' for network shares, so + * these arrive with no colon at offset 1 and must be matched on + * the '//' prefix instead. + */ + array( + '//system07/C$/', + array(), + 2, + ), + array( + '//Server2/Share/Test/Foo.txt', + array(), + 2, + ), + array( + '//127.0.0.1/c$/temp/test-file.txt', + array(), + 2, + ), + array( + '//./c:/temp/test-file.txt', + array(), + 2, + ), + array( + '//?/c:/temp/test-file.txt', + array(), + 2, + ), + array( + '//./UNC/LOCALHOST/c$/temp/test-file.txt', + array(), + 2, + ), + // The backslash form a Windows caller actually supplies. + array( + '\\\\system07\\C$\\', + array(), + 2, + ), + // Shortest matching input. + array( + '//', + array(), + 2, + ), + // A UNC path is rejected even when explicitly allowed, matching + // the precedence the drive-letter check already has. + array( + '//system07/C$/foo.php', + array( '//system07/C$/foo.php' ), + 2, + ), + + /* + * Absolute POSIX paths are NOT rejected. validate_file() has never + * screened them and does not begin to here. Callers needing that + * must check separately; core's convention is to concatenate onto + * a trusted base directory and stat the result. + */ + array( + '/etc/passwd', + array(), + 0, + ), + array( + '/Server2/Share/Test/Foo.txt', + array(), + 0, + ), + array( + '/', + array(), + 0, + ), + /* + * A deliberate exception. POSIX.1 leaves a pathname beginning with + * two successive slashes implementation-defined, and Linux resolves + * it as a single slash, so this is a valid POSIX path. It is still + * rejected: after normalization it cannot be told apart from a UNC + * path, and wp_normalize_path() already preserves a leading '//' on + * the assumption that it denotes a network share. + */ + array( + '//home/user/file.php', + array(), + 2, + ), + + /* + * Stream wrappers stay allowed via two different mechanisms. + * A registered wrapper keeps its '://' through wp_normalize_path()'s + * scheme split; an unregistered one has its '//' collapsed. Neither + * ends up with a leading '//'. + */ + array( + 'php://memory', + array(), + 0, + ), + array( + 'file:///tmp/test-file.txt', + array(), + 0, + ), + array( + 'myapp://foo/bar', + array(), + 0, + ), + // Disallowed files: array( 'foo.ext', From 62668dec7bcfbec8d1688f5719f0bbf39054e8f6 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Sun, 16 Aug 2026 22:40:20 +0000 Subject: [PATCH 09/11] Build/Test Tools: Make `SLACK_GHA_TIMEOUT_WEBHOOK` optional. This temporarily marks the `SLACK_GHA_TIMEOUT_WEBHOOK` secret as optional until [63310] can be backported to older branches to avoid workflow failures. Follow-up to [63310]. See #65845. git-svn-id: https://develop.svn.wordpress.org/trunk@63313 602fd350-edb4-49c9-b593-d223f7449a82 --- .github/workflows/slack-notifications.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/slack-notifications.yml b/.github/workflows/slack-notifications.yml index 4b4dfc831aaeb..6a2f23714e291 100644 --- a/.github/workflows/slack-notifications.yml +++ b/.github/workflows/slack-notifications.yml @@ -27,7 +27,7 @@ on: required: true SLACK_GHA_TIMEOUT_WEBHOOK: description: 'The Slack webhook URL for a timed out build.' - required: true + required: false # Disable permissions for all available scopes by default. # Any needed permissions should be configured at the job level. From 32c0322f614d786ec65a3e3fa8e06b81d2e906a1 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sun, 16 Aug 2026 22:53:07 +0000 Subject: [PATCH 10/11] Code Quality: Resolve return.empty PHPStan errors in baseline. Fixes a couple of functions with return-type problems. One is resolved by adding the missing `void` type, while the other is resolved by updating the function to conform to the existing `string` return type contract, a likely oversight in the original commit. This change was part of Contributor Day at WordCamp US 2026. Developed in: https://github.com/WordPress/wordpress-develop/pull/13081 Discussed in: https://core.trac.wordpress.org/ticket/65817 Props dmsnell, nomadmystic. See #65817. git-svn-id: https://develop.svn.wordpress.org/trunk@63314 602fd350-edb4-49c9-b593-d223f7449a82 --- phpstan.neon.dist | 1 - .../themes/twentytwenty/inc/custom-css.php | 6 +--- src/wp-includes/l10n.php | 2 +- tests/phpstan/baselines/return.empty.neon | 30 ------------------- 4 files changed, 2 insertions(+), 37 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e2e72fb367482..248623a5c01d6 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -79,7 +79,6 @@ includes: - tests/phpstan/baselines/property.phpDocType.neon - tests/phpstan/baselines/property.private.neon - tests/phpstan/baselines/property.protected.neon - - tests/phpstan/baselines/return.empty.neon - tests/phpstan/baselines/return.missing.neon - tests/phpstan/baselines/return.type.neon - tests/phpstan/baselines/return.unusedType.neon diff --git a/src/wp-content/themes/twentytwenty/inc/custom-css.php b/src/wp-content/themes/twentytwenty/inc/custom-css.php index 25f8525d8511a..50af2e6e72c0e 100644 --- a/src/wp-content/themes/twentytwenty/inc/custom-css.php +++ b/src/wp-content/themes/twentytwenty/inc/custom-css.php @@ -23,15 +23,11 @@ * @return string Generated CSS. */ function twentytwenty_generate_css( $selector, $style, $value, $prefix = '', $suffix = '', $display = true ) { - - $return = ''; - /* * Bail early if we have no $selector elements or properties and $value. */ if ( ! $value || ! $selector ) { - - return; + return ''; } $return = sprintf( '%s { %s: %s; }', $selector, $style, $prefix . $value . $suffix ); diff --git a/src/wp-includes/l10n.php b/src/wp-includes/l10n.php index 2f27c2180037a..43b32017becac 100644 --- a/src/wp-includes/l10n.php +++ b/src/wp-includes/l10n.php @@ -1735,7 +1735,7 @@ function wp_get_l10n_php_file_data( $php_file ) { * @type bool $explicit_option_en_us Whether the English (United States) option uses an explicit value of en_US * instead of an empty value. Default false. * } - * @return string HTML dropdown list of languages. + * @return string|void HTML dropdown list of languages. */ function wp_dropdown_languages( $args = array() ) { diff --git a/tests/phpstan/baselines/return.empty.neon b/tests/phpstan/baselines/return.empty.neon index 5abf2badb5636..e69de29bb2d1d 100644 --- a/tests/phpstan/baselines/return.empty.neon +++ b/tests/phpstan/baselines/return.empty.neon @@ -1,30 +0,0 @@ -# PHPStan baseline for the `return.empty` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/return.empty -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=return.empty -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Function twentytwenty_generate_css\(\) should return string but empty return statement found\.$#' - identifier: return.empty - count: 1 - path: ../../../src/wp-content/themes/twentytwenty/inc/custom-css.php - - - message: '#^Function wp_dropdown_languages\(\) should return string but empty return statement found\.$#' - identifier: return.empty - count: 1 - path: ../../../src/wp-includes/l10n.php From 888f0d9a4a96ecb85453c25f477956e804ee2174 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Sun, 16 Aug 2026 23:07:40 +0000 Subject: [PATCH 11/11] Build/Test Tools: Stop committing changes to tracked files in pull requests. This workflow has not proven to be as useful as originally hoped, so it's being removed to reduce maintenance burden. All workflows that run the build script already fail if they result in untracked changes. Developed in https://github.com/WordPress/wordpress-develop/pull/13087 Props desrosj See #65845 git-svn-id: https://develop.svn.wordpress.org/trunk@63315 602fd350-edb4-49c9-b593-d223f7449a82 --- .github/workflows/check-built-files.yml | 54 ------ .../workflows/commit-built-file-changes.yml | 166 ------------------ .../workflows/reusable-check-built-files.yml | 104 ----------- .../reusable-test-core-build-process.yml | 4 + 4 files changed, 4 insertions(+), 324 deletions(-) delete mode 100644 .github/workflows/check-built-files.yml delete mode 100644 .github/workflows/commit-built-file-changes.yml delete mode 100644 .github/workflows/reusable-check-built-files.yml diff --git a/.github/workflows/check-built-files.yml b/.github/workflows/check-built-files.yml deleted file mode 100644 index 01a239c4eb3b0..0000000000000 --- a/.github/workflows/check-built-files.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Checks for uncommitted changes to built files in pull requests. -name: Check Built Files (PRs) - -on: - # Because all commits happen through SVN and should always be manually reviewed by a committer, this workflow only - # runs for pull requests. - # - # Other workflows that run for the push event will detect changes to versioned files and fail. - pull_request: - branches: - - trunk - - '6.[8-9]' - - '[7-9].[0-9]' - paths: - # Any change to a CSS, JavaScript, JSON, or SASS file should run checks. - - '**.css' - - '**.js' - - '**.json' - - '**.sass' - # These files configure npm and the task runner. Changes could affect the outcome. - - 'package*.json' - - '.npmrc' - - '.nvmrc' - - 'Gruntfile.js' - - 'webpack.config.js' - - 'tools/gutenberg/**' - - 'tools/vendors/**' - - 'tools/webpack/**' - # These files configure Composer. Changes could affect the outcome. - - 'composer.*' - # Confirm any changes to relevant workflow files. - - '.github/workflows/check-built-files.yml' - - '.github/workflows/reusable-check-built-files.yml' - # Changes to the default themes should be handled by the themes workflows. - - '!src/wp-content/themes/twenty**' - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - check-for-built-file-changes: - name: Check built files - if: ${{ github.repository == 'wordpress/wordpress-develop' }} - uses: ./.github/workflows/reusable-check-built-files.yml - permissions: - contents: read diff --git a/.github/workflows/commit-built-file-changes.yml b/.github/workflows/commit-built-file-changes.yml deleted file mode 100644 index 9a959187505c9..0000000000000 --- a/.github/workflows/commit-built-file-changes.yml +++ /dev/null @@ -1,166 +0,0 @@ -# Commits all missed changes to built files back to pull request branches. -name: Commit Built File Changes (PRs) - -on: - workflow_run: - workflows: - - 'Check Built Files (PRs)' - - 'Test Default Themes & Create ZIPs' - types: - - completed - -# Cancels all previous workflow runs for pull requests that have not completed. -concurrency: - # The concurrency group contains the workflow name and the branch name for pull requests - # or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'workflow_run' && format( '{0}-{1}', github.event.workflow_run.head_branch, github.event.workflow_run.head_repository.name ) || github.sha }} - -# Disable permissions for all available scopes by default. -# Any needed permissions should be configured at the job level. -permissions: {} - -jobs: - # Checks a PR for uncommitted changes to built files. - # - # Performs the following steps: - # - Attempts to download the artifact containing the PR diff. - # - Checks for the existence of an artifact. - # - Unzips the artifact. - # - Generates a token for authenticating with the GitHub App. - # - Checks out the repository. - # - Applies the patch file. - # - Displays the result of git diff. - # - Configures the Git author. - # - Stages changes. - # - Commits changes. - # - Pushes changes. - update-built-files: - name: Check and update built files - runs-on: ubuntu-24.04 - if: ${{ github.repository == 'wordpress/wordpress-develop' }} - timeout-minutes: 10 - permissions: - # The actual `git push` is authenticated via a dedicated GitHub App installation token - # generated below, so `GITHUB_TOKEN` only needs read access to the triggering workflow's artifacts. - actions: read # Required to list and download the artifact uploaded by the triggering workflow run. - steps: - - name: Download artifact - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const artifacts = await github.rest.actions.listWorkflowRunArtifacts( { - owner: context.repo.owner, - repo: context.repo.repo, - run_id: process.env.RUN_ID, - } ); - - const matchArtifact = artifacts.data.artifacts.filter( ( artifact ) => { - return artifact.name === 'pr-built-file-changes' - } )[0]; - - if ( ! matchArtifact ) { - core.info( 'No artifact found!' ); - return; - } - - const download = await github.rest.actions.downloadArtifact( { - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - } ); - - const fs = require( 'fs' ); - fs.writeFileSync( '${{ github.workspace }}/pr-built-file-changes.zip', Buffer.from( download.data ) ) - env: - RUN_ID: ${{ github.event.workflow_run.id }} - - - name: Check for artifact - id: artifact-check - run: | - if [ -f "pr-built-file-changes.zip" ]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Unzip the artifact containing the PR data - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - run: unzip pr-built-file-changes.zip - - - name: Generate Installation Token - id: generate_token - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - env: - GH_APP_ID: ${{ vars.GH_PR_BUILT_FILES_APP_ID }} - GH_APP_PRIVATE_KEY: ${{ secrets.GH_PR_BUILT_FILES_PRIVATE_KEY }} - run: | - # Generate JWT - JWT=$(python3 - <> "$GITHUB_OUTPUT" - - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - with: - repository: ${{ github.event.workflow_run.head_repository.full_name }} - ref: ${{ github.event.workflow_run.head_branch }} - path: 'pr-repo' - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - token: ${{ steps.generate_token.outputs.access-token }} - persist-credentials: true - - - name: Apply patch - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git apply "$GITHUB_WORKSPACE/changes.diff" - - - name: Display changes to versioned files - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git diff - - - name: Configure git user name and email - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - env: - GH_APP_ID: ${{ vars.GH_PR_BUILT_FILES_APP_ID }} - run: | - git config user.name "wordpress-develop-pr-bot[bot]" - git config user.email "${GH_APP_ID}+wordpress-develop-pr-bot[bot]@users.noreply.github.com" - - - name: Stage changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git add . - - - name: Commit changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: | - git commit -m "Automation: Updating built files with changes." - - - name: Push changes - if: ${{ steps.artifact-check.outputs.exists == 'true' }} - working-directory: 'pr-repo' - run: git push diff --git a/.github/workflows/reusable-check-built-files.yml b/.github/workflows/reusable-check-built-files.yml deleted file mode 100644 index 043c24581cc36..0000000000000 --- a/.github/workflows/reusable-check-built-files.yml +++ /dev/null @@ -1,104 +0,0 @@ -## -# A reusable workflow that checks for uncommitted changes to built files in pull requests. -## -name: Check Built Files (PRs) - -on: - workflow_call: - -permissions: {} - -jobs: - # Checks a PR for uncommitted changes to built files. - # - # When changes are detected, the patch is stored as an artifact for processing by the Commit Built File Changes - # workflow. - # - # Performs the following steps: - # - Checks out the repository. - # - Sets up Node.js. - # - Installs Composer dependencies. - # - Logs general debug information about the runner. - # - Installs npm dependencies. - # - Builds CSS file using SASS. - # - Builds Emoji files. - # - Builds bundled Root Certificate files. - # - Builds WordPress. - # - Checks for changes to versioned files. - # - Displays the result of git diff for debugging purposes. - # - Saves the diff to a patch file. - # - Uploads the patch file as an artifact. - update-built-files: - name: Check and update built files - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} - persist-credentials: false - - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version-file: '.nvmrc' - cache: npm - - # Since Composer dependencies are installed using `composer update` and no lock file is in version control, - # passing a custom cache suffix ensures that the cache is flushed at least once per week. - - name: Install Composer dependencies - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 - with: - custom-cache-suffix: $(/bin/date -u --date='last Mon' "+%F") - - - name: Log debug information - run: | - npm --version - node --version - curl --version - git --version - - - name: Install npm Dependencies - run: npm ci - - - name: Run SASS precommit tasks - run: npm run grunt precommit:css - - - name: Run Emoji precommit task - run: npm run grunt precommit:emoji - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Run certificate tasks - run: npm run grunt copy:certificates - - - name: Build WordPress - run: npm run build:dev - - - name: Check for changes to versioned files - id: built-file-check - run: | - if git diff --quiet; then - echo "uncommitted_changes=false" >> "$GITHUB_OUTPUT" - else - echo "uncommitted_changes=true" >> "$GITHUB_OUTPUT" - fi - - - name: Display changes to versioned files - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - run: git diff - - - name: Save diff to a file - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - run: git diff > ./changes.diff - - # Uploads the diff file as an artifact. - - name: Upload diff file as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - if: ${{ steps.built-file-check.outputs.uncommitted_changes == 'true' }} - with: - name: pr-built-file-changes - path: changes.diff diff --git a/.github/workflows/reusable-test-core-build-process.yml b/.github/workflows/reusable-test-core-build-process.yml index 0fe5dfe6d564b..1a41257418dec 100644 --- a/.github/workflows/reusable-test-core-build-process.yml +++ b/.github/workflows/reusable-test-core-build-process.yml @@ -112,6 +112,10 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run CSS precommit tasks + if: ${{ inputs.directory == 'src' }} + run: npm run grunt precommit:css + - name: Ensure certificates files are updated if: ${{ inputs.test-certificates }} run: npm run grunt copy:certificates && npm run grunt build:certificates