diff --git a/src/wp-admin/includes/export.php b/src/wp-admin/includes/export.php index a77cb804f0780..0400dcbdad20f 100644 --- a/src/wp-admin/includes/export.php +++ b/src/wp-admin/includes/export.php @@ -93,9 +93,11 @@ function export_wp( $args = array() ) { */ $filename = apply_filters( 'export_wp_filename', $wp_filename, $sitename, $date ); - header( 'Content-Description: File Transfer' ); - header( 'Content-Disposition: attachment; filename=' . $filename ); - header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); + if ( ! headers_sent() ) { + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: attachment; filename=' . $filename ); + header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true ); + } if ( 'all' !== $args['content'] && post_type_exists( $args['content'] ) ) { $ptype = get_post_type_object( $args['content'] ); @@ -234,266 +236,272 @@ function export_wp( $args = array() ) { unset( $categories, $custom_taxonomies, $custom_terms ); } - /** - * Wraps given string in XML CDATA tag. - * - * @since 2.1.0 - * - * @param string|null $str String to wrap in XML CDATA tag. May be null. - * @return string - */ - function wxr_cdata( $str ) { - $str = (string) $str; + static $wxr_functions_defined = false; - if ( ! wp_is_valid_utf8( $str ) ) { - $str = utf8_encode( $str ); - } - // $str = ent2ncr(esc_html($str)); - $str = '', ']]]]>', $str ) . ']]>'; + if ( ! $wxr_functions_defined ) { + /** + * Wraps given string in XML CDATA tag. + * + * @since 2.1.0 + * + * @param string|null $str String to wrap in XML CDATA tag. May be null. + * @return string + */ + function wxr_cdata( $str ) { + $str = (string) $str; - return $str; - } + if ( ! wp_is_valid_utf8( $str ) ) { + $str = utf8_encode( $str ); + } + // $str = ent2ncr(esc_html($str)); + $str = '', ']]]]>', $str ) . ']]>'; - /** - * Returns the URL of the site. - * - * @since 2.5.0 - * - * @return string Site URL. - */ - function wxr_site_url() { - if ( is_multisite() ) { - // Multisite: the base URL. - return network_home_url(); - } else { - // WordPress (single site): the site URL. - return get_bloginfo_rss( 'url' ); + return $str; } - } - /** - * Outputs a cat_name XML tag from a given category object. - * - * @since 2.1.0 - * - * @param WP_Term $category Category Object. - */ - function wxr_cat_name( $category ) { - if ( empty( $category->name ) ) { - return; + /** + * Returns the URL of the site. + * + * @since 2.5.0 + * + * @return string Site URL. + */ + function wxr_site_url() { + if ( is_multisite() ) { + // Multisite: the base URL. + return network_home_url(); + } else { + // WordPress (single site): the site URL. + return get_bloginfo_rss( 'url' ); + } } - echo '' . wxr_cdata( $category->name ) . "\n"; - } + /** + * Outputs a cat_name XML tag from a given category object. + * + * @since 2.1.0 + * + * @param WP_Term $category Category Object. + */ + function wxr_cat_name( $category ) { + if ( empty( $category->name ) ) { + return; + } - /** - * Outputs a category_description XML tag from a given category object. - * - * @since 2.1.0 - * - * @param WP_Term $category Category Object. - */ - function wxr_category_description( $category ) { - if ( empty( $category->description ) ) { - return; + echo '' . wxr_cdata( $category->name ) . "\n"; } - echo '' . wxr_cdata( $category->description ) . "\n"; - } + /** + * Outputs a category_description XML tag from a given category object. + * + * @since 2.1.0 + * + * @param WP_Term $category Category Object. + */ + function wxr_category_description( $category ) { + if ( empty( $category->description ) ) { + return; + } - /** - * Outputs a tag_name XML tag from a given tag object. - * - * @since 2.3.0 - * - * @param WP_Term $tag Tag Object. - */ - function wxr_tag_name( $tag ) { - if ( empty( $tag->name ) ) { - return; + echo '' . wxr_cdata( $category->description ) . "\n"; } - echo '' . wxr_cdata( $tag->name ) . "\n"; - } + /** + * Outputs a tag_name XML tag from a given tag object. + * + * @since 2.3.0 + * + * @param WP_Term $tag Tag Object. + */ + function wxr_tag_name( $tag ) { + if ( empty( $tag->name ) ) { + return; + } - /** - * Outputs a tag_description XML tag from a given tag object. - * - * @since 2.3.0 - * - * @param WP_Term $tag Tag Object. - */ - function wxr_tag_description( $tag ) { - if ( empty( $tag->description ) ) { - return; + echo '' . wxr_cdata( $tag->name ) . "\n"; } - echo '' . wxr_cdata( $tag->description ) . "\n"; - } + /** + * Outputs a tag_description XML tag from a given tag object. + * + * @since 2.3.0 + * + * @param WP_Term $tag Tag Object. + */ + function wxr_tag_description( $tag ) { + if ( empty( $tag->description ) ) { + return; + } - /** - * Outputs a term_name XML tag from a given term object. - * - * @since 2.9.0 - * - * @param WP_Term $term Term Object. - */ - function wxr_term_name( $term ) { - if ( empty( $term->name ) ) { - return; + echo '' . wxr_cdata( $tag->description ) . "\n"; } - echo '' . wxr_cdata( $term->name ) . "\n"; - } + /** + * Outputs a term_name XML tag from a given term object. + * + * @since 2.9.0 + * + * @param WP_Term $term Term Object. + */ + function wxr_term_name( $term ) { + if ( empty( $term->name ) ) { + return; + } - /** - * Outputs a term_description XML tag from a given term object. - * - * @since 2.9.0 - * - * @param WP_Term $term Term Object. - */ - function wxr_term_description( $term ) { - if ( empty( $term->description ) ) { - return; + echo '' . wxr_cdata( $term->name ) . "\n"; } - echo "\t\t" . wxr_cdata( $term->description ) . "\n"; - } - - /** - * Outputs term meta XML tags for a given term object. - * - * @since 4.6.0 - * - * @global wpdb $wpdb WordPress database abstraction object. - * - * @param WP_Term $term Term object. - */ - function wxr_term_meta( $term ) { - global $wpdb; - - $termmeta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->termmeta WHERE term_id = %d", $term->term_id ) ); - - foreach ( $termmeta as $meta ) { - /** - * Filters whether to selectively skip term meta used for WXR exports. - * - * Returning a truthy value from the filter will skip the current meta - * object from being exported. - * - * @since 4.6.0 - * - * @param bool $skip Whether to skip the current piece of term meta. Default false. - * @param string $meta_key Current meta key. - * @param object $meta Current meta object. - */ - if ( ! apply_filters( 'wxr_export_skip_termmeta', false, $meta->meta_key, $meta ) ) { - printf( "\t\t\n\t\t\t%s\n\t\t\t%s\n\t\t\n", wxr_cdata( $meta->meta_key ), wxr_cdata( $meta->meta_value ) ); + /** + * Outputs a term_description XML tag from a given term object. + * + * @since 2.9.0 + * + * @param WP_Term $term Term Object. + */ + function wxr_term_description( $term ) { + if ( empty( $term->description ) ) { + return; } + + echo "\t\t" . wxr_cdata( $term->description ) . "\n"; } - } - /** - * Outputs list of authors with posts. - * - * @since 3.1.0 - * - * @global wpdb $wpdb WordPress database abstraction object. - * - * @param int[] $post_ids Optional. Array of post IDs to filter the query by. - */ - function wxr_authors_list( ?array $post_ids = null ) { - global $wpdb; - - if ( ! empty( $post_ids ) ) { - $post_ids = array_map( 'absint', $post_ids ); - $post_id_chunks = array_chunk( $post_ids, 20 ); - } else { - $post_id_chunks = array( array() ); + /** + * Outputs term meta XML tags for a given term object. + * + * @since 4.6.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param WP_Term $term Term object. + */ + function wxr_term_meta( $term ) { + global $wpdb; + + $termmeta = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->termmeta WHERE term_id = %d", $term->term_id ) ); + + foreach ( $termmeta as $meta ) { + /** + * Filters whether to selectively skip term meta used for WXR exports. + * + * Returning a truthy value from the filter will skip the current meta + * object from being exported. + * + * @since 4.6.0 + * + * @param bool $skip Whether to skip the current piece of term meta. Default false. + * @param string $meta_key Current meta key. + * @param object $meta Current meta object. + */ + if ( ! apply_filters( 'wxr_export_skip_termmeta', false, $meta->meta_key, $meta ) ) { + printf( "\t\t\n\t\t\t%s\n\t\t\t%s\n\t\t\n", wxr_cdata( $meta->meta_key ), wxr_cdata( $meta->meta_value ) ); + } + } } - $authors = array(); + /** + * Outputs list of authors with posts. + * + * @since 3.1.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param int[] $post_ids Optional. Array of post IDs to filter the query by. + */ + function wxr_authors_list( ?array $post_ids = null ) { + global $wpdb; + + if ( ! empty( $post_ids ) ) { + $post_ids = array_map( 'absint', $post_ids ); + $post_id_chunks = array_chunk( $post_ids, 20 ); + } else { + $post_id_chunks = array( array() ); + } - foreach ( $post_id_chunks as $next_posts ) { - $and = ! empty( $next_posts ) ? 'AND ID IN (' . implode( ', ', $next_posts ) . ')' : ''; + $authors = array(); - $results = $wpdb->get_results( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft' $and" ); + foreach ( $post_id_chunks as $next_posts ) { + $and = ! empty( $next_posts ) ? 'AND ID IN (' . implode( ', ', $next_posts ) . ')' : ''; - foreach ( (array) $results as $result ) { - $authors[] = get_userdata( $result->post_author ); + $results = $wpdb->get_results( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft' $and" ); + + foreach ( (array) $results as $result ) { + $authors[] = get_userdata( $result->post_author ); + } } - } - $authors = array_filter( $authors ); - $authors = array_unique( $authors, SORT_REGULAR ); // Remove duplicate authors. - - foreach ( $authors as $author ) { - echo "\t"; - echo '' . (int) $author->ID . ''; - echo '' . wxr_cdata( $author->user_login ) . ''; - echo '' . wxr_cdata( $author->user_email ) . ''; - echo '' . wxr_cdata( $author->display_name ) . ''; - echo '' . wxr_cdata( $author->first_name ) . ''; - echo '' . wxr_cdata( $author->last_name ) . ''; - echo "\n"; + $authors = array_filter( $authors ); + $authors = array_unique( $authors, SORT_REGULAR ); // Remove duplicate authors. + + foreach ( $authors as $author ) { + echo "\t"; + echo '' . (int) $author->ID . ''; + echo '' . wxr_cdata( $author->user_login ) . ''; + echo '' . wxr_cdata( $author->user_email ) . ''; + echo '' . wxr_cdata( $author->display_name ) . ''; + echo '' . wxr_cdata( $author->first_name ) . ''; + echo '' . wxr_cdata( $author->last_name ) . ''; + echo "\n"; + } } - } - /** - * Outputs all navigation menu terms. - * - * @since 3.1.0 - */ - function wxr_nav_menu_terms() { - $nav_menus = wp_get_nav_menus(); - if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) { - return; - } + /** + * Outputs all navigation menu terms. + * + * @since 3.1.0 + */ + function wxr_nav_menu_terms() { + $nav_menus = wp_get_nav_menus(); + if ( empty( $nav_menus ) || ! is_array( $nav_menus ) ) { + return; + } - foreach ( $nav_menus as $menu ) { - echo "\t"; - echo '' . (int) $menu->term_id . ''; - echo 'nav_menu'; - echo '' . wxr_cdata( $menu->slug ) . ''; - wxr_term_name( $menu ); - echo "\n"; + foreach ( $nav_menus as $menu ) { + echo "\t"; + echo '' . (int) $menu->term_id . ''; + echo 'nav_menu'; + echo '' . wxr_cdata( $menu->slug ) . ''; + wxr_term_name( $menu ); + echo "\n"; + } } - } - /** - * Outputs list of taxonomy terms, in XML tag format, associated with a post. - * - * @since 2.3.0 - */ - function wxr_post_taxonomy() { - $post = get_post(); + /** + * Outputs list of taxonomy terms, in XML tag format, associated with a post. + * + * @since 2.3.0 + */ + function wxr_post_taxonomy() { + $post = get_post(); - $taxonomies = get_object_taxonomies( $post->post_type ); - if ( empty( $taxonomies ) ) { - return; - } - $terms = wp_get_object_terms( $post->ID, $taxonomies ); + $taxonomies = get_object_taxonomies( $post->post_type ); + if ( empty( $taxonomies ) ) { + return; + } + $terms = wp_get_object_terms( $post->ID, $taxonomies ); - foreach ( (array) $terms as $term ) { - echo "\t\ttaxonomy}\" nicename=\"{$term->slug}\">" . wxr_cdata( $term->name ) . "\n"; + foreach ( (array) $terms as $term ) { + echo "\t\ttaxonomy}\" nicename=\"{$term->slug}\">" . wxr_cdata( $term->name ) . "\n"; + } } - } - /** - * Determines whether to selectively skip post meta used for WXR exports. - * - * @since 3.3.0 - * - * @param bool $return_me Whether to skip the current post meta. Default false. - * @param string $meta_key Meta key. - * @return bool - */ - function wxr_filter_postmeta( $return_me, $meta_key ) { - if ( '_edit_lock' === $meta_key ) { - $return_me = true; + /** + * Determines whether to selectively skip post meta used for WXR exports. + * + * @since 3.3.0 + * + * @param bool $return_me Whether to skip the current post meta. Default false. + * @param string $meta_key Meta key. + * @return bool + */ + function wxr_filter_postmeta( $return_me, $meta_key ) { + if ( '_edit_lock' === $meta_key ) { + $return_me = true; + } + return $return_me; } - return $return_me; + + $wxr_functions_defined = true; } add_filter( 'wxr_export_skip_postmeta', 'wxr_filter_postmeta', 10, 2 ); diff --git a/src/wp-includes/class-wp-token-map.php b/src/wp-includes/class-wp-token-map.php index fc223b187f8c5..507b3365852d8 100644 --- a/src/wp-includes/class-wp-token-map.php +++ b/src/wp-includes/class-wp-token-map.php @@ -352,8 +352,8 @@ static function ( array $a, array $b ): int { foreach ( $groups[ $group ] as $group_word ) { list( $word, $mapping ) = $group_word; - $word_length = pack( 'C', strlen( $word ) ); - $mapping_length = pack( 'C', strlen( $mapping ) ); + $word_length = chr( strlen( $word ) ); + $mapping_length = chr( strlen( $mapping ) ); $group_string .= "{$word_length}{$word}{$mapping_length}{$mapping}"; } @@ -472,10 +472,10 @@ public function contains( string $word, string $case_sensitivity = 'case-sensiti $at = 0; while ( $at < $group_length ) { - $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token_length = ord( $group[ $at++ ] ); $token_at = $at; $at += $token_length; - $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping_length = ord( $group[ $at++ ] ); $mapping_at = $at; if ( $token_length === $length && 0 === substr_compare( $group, $slug, $token_at, $token_length, $ignore_case ) ) { @@ -559,10 +559,10 @@ public function read_token( string $text, int $offset = 0, &$matched_token_byte_ $group_length = strlen( $group ); $at = 0; while ( $at < $group_length ) { - $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token_length = ord( $group[ $at++ ] ); $token = substr( $group, $at, $token_length ); $at += $token_length; - $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping_length = ord( $group[ $at++ ] ); $mapping_at = $at; if ( 0 === substr_compare( $text, $token, $offset + $this->key_length, $token_length, $ignore_case ) ) { @@ -666,11 +666,11 @@ public function to_array(): array { $group_length = strlen( $group ); $at = 0; while ( $at < $group_length ) { - $length = unpack( 'C', $group[ $at++ ] )[1]; + $length = ord( $group[ $at++ ] ); $key = $prefix . substr( $group, $at, $length ); $at += $length; - $length = unpack( 'C', $group[ $at++ ] )[1]; + $length = ord( $group[ $at++ ] ); $value = substr( $group, $at, $length ); $tokens[ $key ] = $value; @@ -737,10 +737,10 @@ public function precomputed_php_source_table( string $indent = "\t" ): string { $data_line = "{$i3}\""; $at = 0; while ( $at < $group_length ) { - $token_length = unpack( 'C', $group[ $at++ ] )[1]; + $token_length = ord( $group[ $at++ ] ); $token = substr( $group, $at, $token_length ); $at += $token_length; - $mapping_length = unpack( 'C', $group[ $at++ ] )[1]; + $mapping_length = ord( $group[ $at++ ] ); $mapping = substr( $group, $at, $mapping_length ); $at += $mapping_length; diff --git a/src/wp-includes/compat-utf8.php b/src/wp-includes/compat-utf8.php index 5fa8cde158789..cba1fe85d82e9 100644 --- a/src/wp-includes/compat-utf8.php +++ b/src/wp-includes/compat-utf8.php @@ -506,37 +506,63 @@ function _wp_utf8_decode_fallback( $utf8_text ) { continue; } - $next_at = $at; $invalid_length = 0; - $found = _wp_scan_utf8( $utf8_text, $next_at, $invalid_length, null, 1 ); - $span_length = $next_at - $at; + $span_length = 0; $next_byte = '?'; + $byte1 = ord( $utf8_text[ $at ] ); + $byte2 = ord( $utf8_text[ $at + 1 ] ?? "\xC0" ); + $byte3 = ord( $utf8_text[ $at + 2 ] ?? "\xC0" ); + $byte4 = ord( $utf8_text[ $at + 3 ] ?? "\xC0" ); + + if ( $byte1 >= 0xC2 && $byte1 <= 0xDF && $byte2 >= 0x80 && $byte2 <= 0xBF ) { + $span_length = 2; + $code_point = ( ( $byte1 & 0x1F ) << 6 ) | ( $byte2 & 0x3F ); + $next_byte = $code_point <= 0xFF ? chr( $code_point ) : '?'; + } elseif ( + $byte3 >= 0x80 && $byte3 <= 0xBF && + ( + ( 0xE0 === $byte1 && $byte2 >= 0xA0 && $byte2 <= 0xBF ) || + ( $byte1 >= 0xE1 && $byte1 <= 0xEC && $byte2 >= 0x80 && $byte2 <= 0xBF ) || + ( 0xED === $byte1 && $byte2 >= 0x80 && $byte2 <= 0x9F ) || + ( $byte1 >= 0xEE && $byte1 <= 0xEF && $byte2 >= 0x80 && $byte2 <= 0xBF ) + ) + ) { + $span_length = 3; + } elseif ( + $byte3 >= 0x80 && $byte3 <= 0xBF && + $byte4 >= 0x80 && $byte4 <= 0xBF && + ( + ( 0xF0 === $byte1 && $byte2 >= 0x90 && $byte2 <= 0xBF ) || + ( $byte1 >= 0xF1 && $byte1 <= 0xF3 && $byte2 >= 0x80 && $byte2 <= 0xBF ) || + ( 0xF4 === $byte1 && $byte2 >= 0x80 && $byte2 <= 0x8F ) + ) + ) { + $span_length = 4; + } else { + $next_byte = ''; + $invalid_length = 1; + + if ( 0xE0 === ( $byte1 & 0xF0 ) ) { + $byte2_valid = ( + ( 0xE0 === $byte1 && $byte2 >= 0xA0 && $byte2 <= 0xBF ) || + ( $byte1 >= 0xE1 && $byte1 <= 0xEC && $byte2 >= 0x80 && $byte2 <= 0xBF ) || + ( 0xED === $byte1 && $byte2 >= 0x80 && $byte2 <= 0x9F ) || + ( $byte1 >= 0xEE && $byte1 <= 0xEF && $byte2 >= 0x80 && $byte2 <= 0xBF ) + ); - if ( 1 !== $found ) { - if ( $invalid_length > 0 ) { - $next_byte = ''; - goto flush_sub_part; - } - - break; - } - - // All convertible code points are two-bytes long. - $byte1 = ord( $utf8_text[ $at ] ); - if ( 0xC0 !== ( $byte1 & 0xE0 ) ) { - goto flush_sub_part; - } + $invalid_length = min( $end - $at, $byte2_valid ? 2 : 1 ); + } elseif ( 0xF0 === ( $byte1 & 0xF8 ) ) { + $byte2_valid = ( + ( 0xF0 === $byte1 && $byte2 >= 0x90 && $byte2 <= 0xBF ) || + ( $byte1 >= 0xF1 && $byte1 <= 0xF3 && $byte2 >= 0x80 && $byte2 <= 0xBF ) || + ( 0xF4 === $byte1 && $byte2 >= 0x80 && $byte2 <= 0x8F ) + ); + $byte3_valid = $byte3 >= 0x80 && $byte3 <= 0xBF; - // All convertible code points are not greater than U+FF. - $byte2 = ord( $utf8_text[ $at + 1 ] ); - $code_point = ( ( $byte1 & 0x1F ) << 6 ) | ( ( $byte2 & 0x3F ) ); - if ( $code_point > 0xFF ) { - goto flush_sub_part; + $invalid_length = min( $end - $at, $byte2_valid ? ( $byte3_valid ? 3 : 2 ) : 1 ); + } } - $next_byte = chr( $code_point ); - - flush_sub_part: $iso_8859_1_text .= substr( $utf8_text, $was_at, $at - $was_at ); $iso_8859_1_text .= $next_byte; $at += $span_length; diff --git a/src/wp-includes/compat.php b/src/wp-includes/compat.php index 3387b1d85c935..3e8ab8020b83c 100644 --- a/src/wp-includes/compat.php +++ b/src/wp-includes/compat.php @@ -213,41 +213,56 @@ function _mb_ord( $string, $encoding = null ) { return false; } - $byte_length = 0; - $invalid_length = 0; - $found_count = _wp_scan_utf8( $string, $byte_length, $invalid_length, null, 1 ); + $b0 = ord( $string[0] ); - if ( 1 !== $found_count ) { - return false; + if ( $b0 <= 0x7F ) { + return $b0; } - // These are valid code points, so no further validation is required. - $b0 = ord( $string[0] ); + $b1 = ord( $string[1] ?? "\x00" ); - switch ( $byte_length ) { - case 1: - return $b0; - - case 2: - return ( - ( ( $b0 & 0x1F ) << 6 ) | - ( ( ord( $string[1] ) & 0x3F ) ) - ); - - case 3: - return ( - ( ( $b0 & 0x0F ) << 12 ) | - ( ( ord( $string[1] ) & 0x3F ) << 6 ) | - ( ( ord( $string[2] ) & 0x3F ) ) - ); - - case 4: - return ( - ( ( $b0 & 0x07 ) << 18 ) | - ( ( ord( $string[1] ) & 0x3F ) << 12 ) | - ( ( ord( $string[2] ) & 0x3F ) << 6 ) | - ( ( ord( $string[3] ) & 0x3F ) ) - ); + if ( $b0 >= 0xC2 && $b0 <= 0xDF && $b1 >= 0x80 && $b1 <= 0xBF ) { + return ( + ( ( $b0 & 0x1F ) << 6 ) | + ( $b1 & 0x3F ) + ); + } + + $b2 = ord( $string[2] ?? "\x00" ); + + if ( + $b2 >= 0x80 && $b2 <= 0xBF && + ( + ( 0xE0 === $b0 && $b1 >= 0xA0 && $b1 <= 0xBF ) || + ( $b0 >= 0xE1 && $b0 <= 0xEC && $b1 >= 0x80 && $b1 <= 0xBF ) || + ( 0xED === $b0 && $b1 >= 0x80 && $b1 <= 0x9F ) || + ( $b0 >= 0xEE && $b0 <= 0xEF && $b1 >= 0x80 && $b1 <= 0xBF ) + ) + ) { + return ( + ( ( $b0 & 0x0F ) << 12 ) | + ( ( $b1 & 0x3F ) << 6 ) | + ( $b2 & 0x3F ) + ); + } + + $b3 = ord( $string[3] ?? "\x00" ); + + if ( + $b2 >= 0x80 && $b2 <= 0xBF && + $b3 >= 0x80 && $b3 <= 0xBF && + ( + ( 0xF0 === $b0 && $b1 >= 0x90 && $b1 <= 0xBF ) || + ( $b0 >= 0xF1 && $b0 <= 0xF3 && $b1 >= 0x80 && $b1 <= 0xBF ) || + ( 0xF4 === $b0 && $b1 >= 0x80 && $b1 <= 0x8F ) + ) + ) { + return ( + ( ( $b0 & 0x07 ) << 18 ) | + ( ( $b1 & 0x3F ) << 12 ) | + ( ( $b2 & 0x3F ) << 6 ) | + ( $b3 & 0x3F ) + ); } return false; diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 4657b5872eb18..1a452890cad4f 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -6593,7 +6593,9 @@ function wp_start_cross_origin_isolation_output_buffer(): void { ob_start( static function ( string $output ): string { - header( 'Document-Isolation-Policy: isolate-and-credentialless' ); + if ( ! headers_sent() ) { + header( 'Document-Isolation-Policy: isolate-and-credentialless' ); + } return wp_add_crossorigin_attributes( $output ); } @@ -6674,4 +6676,3 @@ function wp_add_crossorigin_attributes( string $html ): string { return $processor->get_updated_html(); } - diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index b8e8598362ec5..27f783b3fdf2f 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -25,6 +25,12 @@ abstract class WP_UnitTestCase_Base extends PHPUnit_Adapter_TestCase { protected static $hooks_saved = array(); protected static $ignore_files; + protected static $expected_annotations_cache = array(); + protected static $core_registration_snapshots = array(); + protected static $db_autocommit_is_disabled = false; + protected static $test_context_hooks_registered = false; + protected static $deprecation_tracking_test = null; + /** * Fixture factory. * @@ -71,12 +77,19 @@ public static function set_up_before_class() { $wpdb->suppress_errors = false; $wpdb->show_errors = true; $wpdb->db_connect(); + self::$db_autocommit_is_disabled = false; ini_set( 'display_errors', 1 ); $class = get_called_class(); if ( method_exists( $class, 'wpSetUpBeforeClass' ) ) { - call_user_func( array( $class, 'wpSetUpBeforeClass' ), static::factory() ); + add_filter( 'wp_hash_password_options', array( __CLASS__, 'wp_hash_password_options_for_tests' ), 1, 2 ); + + try { + call_user_func( array( $class, 'wpSetUpBeforeClass' ), static::factory() ); + } finally { + remove_filter( 'wp_hash_password_options', array( __CLASS__, 'wp_hash_password_options_for_tests' ), 1 ); + } } self::commit_transaction(); @@ -116,6 +129,8 @@ public function set_up() { self::$ignore_files = $this->scan_user_uploads(); } + self::register_test_context_hooks(); + if ( ! self::$hooks_saved ) { $this->_backup_hooks(); } @@ -129,9 +144,7 @@ public function set_up() { * taxonomies at 'init'. */ if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) { - $this->reset_post_types(); - $this->reset_taxonomies(); - $this->reset_post_statuses(); + $this->reset_core_registrations(); $this->reset__SERVER(); if ( $wp_rewrite->permalink_structure ) { @@ -141,8 +154,6 @@ public function set_up() { $this->start_transaction(); $this->expectDeprecated(); - add_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) ); - add_filter( 'wp_hash_password_options', array( $this, 'wp_hash_password_options' ), 1, 2 ); } /** @@ -152,8 +163,18 @@ public function set_up() { * @param string $algorithm The algorithm to use for hashing. */ public function wp_hash_password_options( array $options, string $algorithm ): array { + return self::wp_hash_password_options_for_tests( $options, $algorithm ); + } + + /** + * Sets the bcrypt cost option for password hashing during tests. + * + * @param array $options The options for password hashing. + * @param string $algorithm The algorithm to use for hashing. + */ + public static function wp_hash_password_options_for_tests( array $options, string $algorithm ): array { if ( PASSWORD_BCRYPT === $algorithm ) { - $options['cost'] = 5; + $options['cost'] = 4; } return $options; @@ -170,64 +191,68 @@ public function wp_hash_password_options( array $options, string $algorithm ): a public function tear_down() { global $wpdb, $wp_the_query, $wp_query, $wp; - $wpdb->query( 'ROLLBACK' ); + try { + $wpdb->query( 'ROLLBACK' ); - if ( is_multisite() ) { - while ( ms_is_switched() ) { - restore_current_blog(); + if ( is_multisite() ) { + while ( ms_is_switched() ) { + restore_current_blog(); + } } - } - // Reset query, main query, and WP globals similar to wp-settings.php. - $wp_the_query = new WP_Query(); - $wp_query = $wp_the_query; - $wp = new WP(); + // Reset query, main query, and WP globals similar to wp-settings.php. + $wp_the_query = new WP_Query(); + $wp_query = $wp_the_query; + $wp = new WP(); - // Reset globals related to the post loop and `setup_postdata()`. - $post_globals = array( 'post', 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' ); - foreach ( $post_globals as $global ) { - $GLOBALS[ $global ] = null; - } + // Reset globals related to the post loop and `setup_postdata()`. + $post_globals = array( 'post', 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' ); + foreach ( $post_globals as $global ) { + $GLOBALS[ $global ] = null; + } - /* - * Reset globals related to current screen to provide a consistent global starting state - * for tests that interact with admin screens. Replaces the need for individual tests - * to invoke `set_current_screen( 'front' )` (or an alternative implementation) as a reset. - * - * The globals are from `WP_Screen::set_current_screen()`. - * - * Why not invoke `set_current_screen( 'front' )`? - * Performance (faster test runs with less memory usage). How so? For each test, - * it saves creating an instance of WP_Screen, making two method calls, - * and firing of the `current_screen` action. - */ - $current_screen_globals = array( 'current_screen', 'taxnow', 'typenow' ); - foreach ( $current_screen_globals as $global ) { - $GLOBALS[ $global ] = null; - } + /* + * Reset globals related to current screen to provide a consistent global starting state + * for tests that interact with admin screens. Replaces the need for individual tests + * to invoke `set_current_screen( 'front' )` (or an alternative implementation) as a reset. + * + * The globals are from `WP_Screen::set_current_screen()`. + * + * Why not invoke `set_current_screen( 'front' )`? + * Performance (faster test runs with less memory usage). How so? For each test, + * it saves creating an instance of WP_Screen, making two method calls, + * and firing of the `current_screen` action. + */ + $current_screen_globals = array( 'current_screen', 'taxnow', 'typenow' ); + foreach ( $current_screen_globals as $global ) { + $GLOBALS[ $global ] = null; + } + + // Reset comment globals. + $comment_globals = array( 'comment_alt', 'comment_depth', 'comment_thread_alt' ); + foreach ( $comment_globals as $global ) { + $GLOBALS[ $global ] = null; + } + + /* + * Reset $wp_sitemap global so that sitemap-related dynamic $wp->public_query_vars + * are added when the next test runs. + */ + $GLOBALS['wp_sitemaps'] = null; - // Reset comment globals. - $comment_globals = array( 'comment_alt', 'comment_depth', 'comment_thread_alt' ); - foreach ( $comment_globals as $global ) { - $GLOBALS[ $global ] = null; + // Reset template globals. + $GLOBALS['wp_stylesheet_path'] = null; + $GLOBALS['wp_template_path'] = null; + + $this->unregister_all_meta_keys(); + remove_theme_support( 'html5' ); + remove_filter( 'query', array( $this, '_create_temporary_tables' ) ); + remove_filter( 'query', array( $this, '_drop_temporary_tables' ) ); + $this->_restore_hooks(); + } finally { + self::$deprecation_tracking_test = null; } - /* - * Reset $wp_sitemap global so that sitemap-related dynamic $wp->public_query_vars - * are added when the next test runs. - */ - $GLOBALS['wp_sitemaps'] = null; - - // Reset template globals. - $GLOBALS['wp_stylesheet_path'] = null; - $GLOBALS['wp_template_path'] = null; - - $this->unregister_all_meta_keys(); - remove_theme_support( 'html5' ); - remove_filter( 'query', array( $this, '_create_temporary_tables' ) ); - remove_filter( 'query', array( $this, '_drop_temporary_tables' ) ); - remove_filter( 'wp_die_handler', array( $this, 'get_wp_die_handler' ) ); - $this->_restore_hooks(); wp_set_current_user( 0 ); $this->reset_lazyload_queue(); @@ -462,6 +487,216 @@ public static function flush_cache() { wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) ); } + /** + * Resets core post type, taxonomy, and post status registrations. + */ + protected function reset_core_registrations() { + if ( ! $this->can_cache_core_registration_reset() ) { + $this->reset_post_types(); + $this->reset_taxonomies(); + $this->reset_post_statuses(); + return; + } + + $cache_key = $this->get_core_registration_cache_key(); + + if ( isset( self::$core_registration_snapshots[ $cache_key ] ) ) { + $this->restore_core_registration_snapshot( self::$core_registration_snapshots[ $cache_key ] ); + return; + } + + $this->reset_post_types(); + $this->reset_taxonomies(); + $this->reset_post_statuses(); + + self::$core_registration_snapshots[ $cache_key ] = $this->capture_core_registration_snapshot(); + } + + /** + * Determines whether the registration reset can use cached snapshots. + * + * @return bool Whether caching is safe for the current global state. + */ + protected function can_cache_core_registration_reset() { + global $wp_rewrite; + + /* + * Base setup clears active permalink structures immediately after the + * registration reset. Avoid caching this transient pre-clear state, as + * rewrite-heavy tests rebuild their rules explicitly after setup. + */ + if ( $wp_rewrite->permalink_structure ) { + return false; + } + + /* + * Preserve registration-time hooks and filters for tests that explicitly + * observe them. These hooks are rare and bypassing the cache keeps their + * behavior identical to the existing reset path. + */ + if ( + has_filter( 'post_format_rewrite_base' ) || + has_action( 'registered_post_type' ) || + has_action( 'unregistered_post_type' ) || + has_action( 'registered_taxonomy' ) || + has_action( 'unregistered_taxonomy' ) || + has_action( 'registered_taxonomy_for_object_type' ) || + has_action( 'unregistered_taxonomy_for_object_type' ) + ) { + return false; + } + + foreach ( array_keys( (array) $GLOBALS['wp_post_types'] ) as $post_type ) { + if ( + has_action( "registered_post_type_{$post_type}" ) || + has_filter( "post_type_labels_{$post_type}" ) + ) { + return false; + } + + if ( ! empty( $GLOBALS['wp_post_types'][ $post_type ]->tests_no_auto_unregister ) ) { + return false; + } + } + + foreach ( array_keys( (array) $GLOBALS['wp_taxonomies'] ) as $taxonomy ) { + if ( + has_action( "registered_taxonomy_{$taxonomy}" ) || + has_filter( "taxonomy_labels_{$taxonomy}" ) + ) { + return false; + } + } + + return true; + } + + /** + * Builds a cache key for the current registration defaults. + * + * @return string Cache key. + */ + protected function get_core_registration_cache_key() { + global $wp_rewrite; + + return md5( + serialize( + array( + 'is_admin' => is_admin(), + 'did_init' => did_action( 'init' ), + 'locale' => get_locale(), + 'category_base' => get_option( 'category_base' ), + 'tag_base' => get_option( 'tag_base' ), + 'wp_attachment_pages_enabled' => get_option( 'wp_attachment_pages_enabled' ), + 'post_formats' => current_theme_supports( 'post-formats' ), + 'permalink_structure' => $wp_rewrite->permalink_structure, + 'using_index_permalinks' => $wp_rewrite->using_index_permalinks(), + 'front' => $wp_rewrite->front, + 'root' => $wp_rewrite->root, + 'index' => $wp_rewrite->index, + ) + ) + ); + } + + /** + * Captures the registration globals after the normal reset path runs. + * + * @return array Snapshot of registration-related globals. + */ + protected function capture_core_registration_snapshot() { + global $wp, $wp_rewrite, $wp_post_types, $wp_taxonomies, $wp_post_statuses, $_wp_post_type_features, $post_type_meta_caps; + + return array( + 'wp_post_types' => self::clone_core_registration_objects( $wp_post_types ), + 'wp_taxonomies' => self::clone_core_registration_objects( $wp_taxonomies ), + 'wp_post_statuses' => self::clone_core_registration_objects( $wp_post_statuses ), + 'post_type_features' => $_wp_post_type_features, + 'post_type_meta_caps' => $post_type_meta_caps, + 'public_query_vars' => $wp->public_query_vars, + 'private_query_vars' => $wp->private_query_vars, + 'extra_query_vars' => $wp->extra_query_vars, + 'rewritecode' => $wp_rewrite->rewritecode, + 'rewritereplace' => $wp_rewrite->rewritereplace, + 'queryreplace' => $wp_rewrite->queryreplace, + 'rules' => $wp_rewrite->rules, + 'matches' => $wp_rewrite->matches, + 'extra_rules' => $wp_rewrite->extra_rules, + 'extra_rules_top' => $wp_rewrite->extra_rules_top, + 'extra_permastructs' => $wp_rewrite->extra_permastructs, + 'non_wp_rules' => $wp_rewrite->non_wp_rules, + 'endpoints' => $wp_rewrite->endpoints, + 'use_verbose_page_rules' => $wp_rewrite->use_verbose_page_rules, + ); + } + + /** + * Restores a cached registration snapshot. + * + * @param array $snapshot Snapshot of registration-related globals. + */ + protected function restore_core_registration_snapshot( array $snapshot ) { + global $wp, $wp_rewrite, $wp_post_types, $wp_taxonomies, $wp_post_statuses, $_wp_post_type_features, $post_type_meta_caps; + + WP_Post_Type::reset_default_labels(); + WP_Taxonomy::reset_default_labels(); + + $wp_post_types = self::clone_core_registration_objects( $snapshot['wp_post_types'] ); + $wp_taxonomies = self::clone_core_registration_objects( $snapshot['wp_taxonomies'] ); + $wp_post_statuses = self::clone_core_registration_objects( $snapshot['wp_post_statuses'] ); + $_wp_post_type_features = $snapshot['post_type_features']; + $post_type_meta_caps = $snapshot['post_type_meta_caps']; + + $wp->public_query_vars = $snapshot['public_query_vars']; + $wp->private_query_vars = $snapshot['private_query_vars']; + $wp->extra_query_vars = $snapshot['extra_query_vars']; + + $wp_rewrite->rewritecode = $snapshot['rewritecode']; + $wp_rewrite->rewritereplace = $snapshot['rewritereplace']; + $wp_rewrite->queryreplace = $snapshot['queryreplace']; + $wp_rewrite->rules = $snapshot['rules']; + $wp_rewrite->matches = $snapshot['matches']; + $wp_rewrite->extra_rules = $snapshot['extra_rules']; + $wp_rewrite->extra_rules_top = $snapshot['extra_rules_top']; + $wp_rewrite->extra_permastructs = $snapshot['extra_permastructs']; + $wp_rewrite->non_wp_rules = $snapshot['non_wp_rules']; + $wp_rewrite->endpoints = $snapshot['endpoints']; + $wp_rewrite->use_verbose_page_rules = $snapshot['use_verbose_page_rules']; + } + + /** + * Clones registration objects so cached snapshots cannot be mutated by tests. + * + * @param array $objects Registration objects. + * @return array Cloned registration objects. + */ + protected static function clone_core_registration_objects( array $objects ) { + $clones = array(); + + foreach ( $objects as $name => $object ) { + if ( ! is_object( $object ) ) { + $clones[ $name ] = $object; + continue; + } + + $clones[ $name ] = clone $object; + + foreach ( array( 'labels', 'cap', 'label_count' ) as $property ) { + if ( isset( $clones[ $name ]->$property ) && is_object( $clones[ $name ]->$property ) ) { + $clones[ $name ]->$property = clone $clones[ $name ]->$property; + } + } + + foreach ( array( 'rest_controller', 'revisions_rest_controller', 'autosave_rest_controller' ) as $property ) { + if ( property_exists( $clones[ $name ], $property ) ) { + $clones[ $name ]->$property = null; + } + } + } + + return $clones; + } + /** * Cleans up any registered meta keys. * @@ -493,7 +728,11 @@ public function unregister_all_meta_keys() { public function start_transaction() { global $wpdb; - $wpdb->query( 'SET autocommit = 0;' ); + if ( ! self::$db_autocommit_is_disabled ) { + $wpdb->query( 'SET autocommit = 0;' ); + self::$db_autocommit_is_disabled = true; + } + $wpdb->query( 'START TRANSACTION;' ); add_filter( 'query', array( $this, '_create_temporary_tables' ) ); @@ -584,46 +823,153 @@ public function wp_die_handler( $message, $title, $args ) { * @since 3.7.0 */ public function expectDeprecated() { - if ( method_exists( $this, 'getAnnotations' ) ) { - // PHPUnit < 9.5.0. - $annotations = $this->getAnnotations(); - } else { - // PHPUnit >= 9.5.0. - $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( - static::class, - $this->getName( false ) - ); - } + self::$deprecation_tracking_test = $this; - foreach ( array( 'class', 'method' ) as $depth ) { - if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) { - $this->expected_deprecated = array_merge( - $this->expected_deprecated, - $annotations[ $depth ]['expectedDeprecated'] + $cache_key = static::class . '::' . $this->getName( false ); + + if ( ! isset( self::$expected_annotations_cache[ $cache_key ] ) ) { + if ( method_exists( $this, 'getAnnotations' ) ) { + // PHPUnit < 9.5.0. + $annotations = $this->getAnnotations(); + } else { + // PHPUnit >= 9.5.0. + $annotations = \PHPUnit\Util\Test::parseTestMethodAnnotations( + static::class, + $this->getName( false ) ); } - if ( ! empty( $annotations[ $depth ]['expectedIncorrectUsage'] ) ) { - $this->expected_doing_it_wrong = array_merge( - $this->expected_doing_it_wrong, - $annotations[ $depth ]['expectedIncorrectUsage'] - ); + $expected_deprecated = array(); + $expected_doing_it_wrong = array(); + + foreach ( array( 'class', 'method' ) as $depth ) { + if ( ! empty( $annotations[ $depth ]['expectedDeprecated'] ) ) { + $expected_deprecated = array_merge( + $expected_deprecated, + $annotations[ $depth ]['expectedDeprecated'] + ); + } + + if ( ! empty( $annotations[ $depth ]['expectedIncorrectUsage'] ) ) { + $expected_doing_it_wrong = array_merge( + $expected_doing_it_wrong, + $annotations[ $depth ]['expectedIncorrectUsage'] + ); + } } + + self::$expected_annotations_cache[ $cache_key ] = array( + 'expected_deprecated' => $expected_deprecated, + 'expected_doing_it_wrong' => $expected_doing_it_wrong, + ); } - add_action( 'deprecated_function_run', array( $this, 'deprecated_function_run' ), 10, 3 ); - add_action( 'deprecated_argument_run', array( $this, 'deprecated_function_run' ), 10, 3 ); - add_action( 'deprecated_class_run', array( $this, 'deprecated_function_run' ), 10, 3 ); - add_action( 'deprecated_file_included', array( $this, 'deprecated_function_run' ), 10, 4 ); - add_action( 'deprecated_hook_run', array( $this, 'deprecated_function_run' ), 10, 4 ); - add_action( 'doing_it_wrong_run', array( $this, 'doing_it_wrong_run' ), 10, 3 ); + $this->expected_deprecated = array_merge( + $this->expected_deprecated, + self::$expected_annotations_cache[ $cache_key ]['expected_deprecated'] + ); + + $this->expected_doing_it_wrong = array_merge( + $this->expected_doing_it_wrong, + self::$expected_annotations_cache[ $cache_key ]['expected_doing_it_wrong'] + ); + } + + /** + * Registers persistent hooks for detecting deprecated and incorrect usage calls during tests. + */ + protected static function register_test_context_hooks() { + if ( self::$test_context_hooks_registered ) { + return; + } + + add_action( 'deprecated_function_run', array( __CLASS__, 'deprecated_function_run_for_current_test' ), 10, 3 ); + add_action( 'deprecated_argument_run', array( __CLASS__, 'deprecated_function_run_for_current_test' ), 10, 3 ); + add_action( 'deprecated_class_run', array( __CLASS__, 'deprecated_function_run_for_current_test' ), 10, 3 ); + add_action( 'deprecated_file_included', array( __CLASS__, 'deprecated_function_run_for_current_test' ), 10, 4 ); + add_action( 'deprecated_hook_run', array( __CLASS__, 'deprecated_function_run_for_current_test' ), 10, 4 ); + add_action( 'doing_it_wrong_run', array( __CLASS__, 'doing_it_wrong_run_for_current_test' ), 10, 3 ); + + add_filter( 'deprecated_function_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'deprecated_argument_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'deprecated_class_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'deprecated_file_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'deprecated_hook_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'doing_it_wrong_trigger_error', array( __CLASS__, 'deprecation_trigger_error_for_current_test' ) ); + add_filter( 'wp_die_handler', array( __CLASS__, 'wp_die_handler_for_current_test' ) ); + add_filter( 'wp_hash_password_options', array( __CLASS__, 'wp_hash_password_options_for_current_test' ), 1, 2 ); + + self::$test_context_hooks_registered = true; + } + + /** + * Retrieves the `wp_die()` handler for the currently running test. + * + * @param callable $handler The current die handler. + * @return callable The test die handler. + */ + public static function wp_die_handler_for_current_test( $handler ) { + if ( self::$deprecation_tracking_test instanceof self ) { + return array( self::$deprecation_tracking_test, 'wp_die_handler' ); + } + + return $handler; + } + + /** + * Sets the password hashing options for the currently running test. + * + * @param array $options The options for password hashing. + * @param string $algorithm The algorithm to use for hashing. + * @return array Password hashing options. + */ + public static function wp_hash_password_options_for_current_test( array $options, string $algorithm ): array { + if ( self::$deprecation_tracking_test instanceof self ) { + return self::$deprecation_tracking_test->wp_hash_password_options( $options, $algorithm ); + } + + return $options; + } + + /** + * Records deprecated calls for the currently running test. + * + * @param string $function_name Name of the deprecated function, class, file, or hook. + * @param string $replacement Replacement. + * @param string $version Version. + * @param string $message Optional message. + */ + public static function deprecated_function_run_for_current_test( $function_name, $replacement, $version, $message = '' ) { + if ( self::$deprecation_tracking_test instanceof self ) { + self::$deprecation_tracking_test->deprecated_function_run( $function_name, $replacement, $version, $message ); + } + } + + /** + * Records incorrect usage calls for the currently running test. + * + * @param string $function_name Function name. + * @param string $message Message. + * @param string $version Version. + */ + public static function doing_it_wrong_run_for_current_test( $function_name, $message, $version ) { + if ( self::$deprecation_tracking_test instanceof self ) { + self::$deprecation_tracking_test->doing_it_wrong_run( $function_name, $message, $version ); + } + } + + /** + * Suppresses PHP notices for deprecated and incorrect usage calls only while a test is running. + * + * @param bool $trigger Whether to trigger the PHP notice. + * @return bool Whether to trigger the PHP notice. + */ + public static function deprecation_trigger_error_for_current_test( $trigger ) { + if ( self::$deprecation_tracking_test instanceof self ) { + return false; + } - add_action( 'deprecated_function_trigger_error', '__return_false' ); - add_action( 'deprecated_argument_trigger_error', '__return_false' ); - add_action( 'deprecated_class_trigger_error', '__return_false' ); - add_action( 'deprecated_file_trigger_error', '__return_false' ); - add_action( 'deprecated_hook_trigger_error', '__return_false' ); - add_action( 'doing_it_wrong_trigger_error', '__return_false' ); + return $trigger; } /** diff --git a/tests/phpunit/includes/testcase-rest-api.php b/tests/phpunit/includes/testcase-rest-api.php index 27349809306f6..45340af0a303a 100644 --- a/tests/phpunit/includes/testcase-rest-api.php +++ b/tests/phpunit/includes/testcase-rest-api.php @@ -28,4 +28,72 @@ protected function assertErrorResponse( $code, $response, $status = null, $messa $this->assertSame( $status, $data['status'], $message . ' The expected status code does not match.' ); } } + + protected function do_rest_api_init_without_initial_routes() { + $priority = has_action( 'rest_api_init', 'create_initial_rest_routes' ); + + if ( false !== $priority ) { + remove_action( 'rest_api_init', 'create_initial_rest_routes', $priority ); + } + + try { + do_action( 'rest_api_init', $GLOBALS['wp_rest_server'] ); + } finally { + if ( false !== $priority ) { + add_action( 'rest_api_init', 'create_initial_rest_routes', $priority ); + } + } + } + + protected function register_post_type_rest_routes_for_test( $post_type_names ) { + foreach ( $post_type_names as $post_type_name ) { + $post_type = get_post_type_object( $post_type_name ); + + if ( ! $post_type ) { + continue; + } + + $controller = $post_type->get_rest_controller(); + + if ( ! $controller ) { + continue; + } + + if ( ! $post_type->late_route_registration ) { + $controller->register_routes(); + } + + $revisions_controller = $post_type->get_revisions_rest_controller(); + if ( $revisions_controller ) { + $revisions_controller->register_routes(); + } + + $autosaves_controller = $post_type->get_autosave_rest_controller(); + if ( $autosaves_controller ) { + $autosaves_controller->register_routes(); + } + + if ( $post_type->late_route_registration ) { + $controller->register_routes(); + } + } + } + + protected function register_taxonomy_rest_routes_for_test( $taxonomy_names ) { + foreach ( $taxonomy_names as $taxonomy_name ) { + $taxonomy = get_taxonomy( $taxonomy_name ); + + if ( ! $taxonomy ) { + continue; + } + + $controller = $taxonomy->get_rest_controller(); + + if ( ! $controller ) { + continue; + } + + $controller->register_routes(); + } + } } diff --git a/tests/phpunit/includes/testcase-rest-controller.php b/tests/phpunit/includes/testcase-rest-controller.php index 9a4c2a0b61b2c..f71676d458858 100644 --- a/tests/phpunit/includes/testcase-rest-controller.php +++ b/tests/phpunit/includes/testcase-rest-controller.php @@ -6,21 +6,43 @@ abstract class WP_Test_REST_Controller_Testcase extends WP_Test_REST_TestCase { public function set_up() { parent::set_up(); + + if ( ! $this->should_register_rest_routes() ) { + return; + } + add_filter( 'rest_url', array( $this, 'filter_rest_url_for_leading_slash' ), 10, 2 ); /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; $wp_rest_server = new Spy_REST_Server(); - do_action( 'rest_api_init', $wp_rest_server ); + if ( $this->should_create_initial_rest_routes() ) { + do_action( 'rest_api_init', $wp_rest_server ); + } else { + $this->do_rest_api_init_without_initial_routes(); + $this->register_initial_rest_routes_for_test(); + } } public function tear_down() { - remove_filter( 'rest_url', array( $this, 'test_rest_url_for_leading_slash' ), 10, 2 ); - /** @var WP_REST_Server $wp_rest_server */ - global $wp_rest_server; - $wp_rest_server = null; + if ( $this->should_register_rest_routes() ) { + remove_filter( 'rest_url', array( $this, 'filter_rest_url_for_leading_slash' ), 10, 2 ); + /** @var WP_REST_Server $wp_rest_server */ + global $wp_rest_server; + $wp_rest_server = null; + } parent::tear_down(); } + protected function should_register_rest_routes() { + return true; + } + + protected function should_create_initial_rest_routes() { + return true; + } + + protected function register_initial_rest_routes_for_test() {} + abstract public function test_register_routes(); abstract public function test_context_param(); diff --git a/tests/phpunit/tests/admin/exportWp.php b/tests/phpunit/tests/admin/exportWp.php index f17ef0d4ad343..6b96654339d44 100644 --- a/tests/phpunit/tests/admin/exportWp.php +++ b/tests/phpunit/tests/admin/exportWp.php @@ -6,9 +6,6 @@ * * @covers ::export_wp * - * Tests run in a separate process to prevent "headers already sent" error. - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ class Tests_Admin_ExportWp extends WP_UnitTestCase { /** diff --git a/tests/phpunit/tests/admin/wpUpgrader.php b/tests/phpunit/tests/admin/wpUpgrader.php index ffea594ae87bc..a5f371cdd091f 100644 --- a/tests/phpunit/tests/admin/wpUpgrader.php +++ b/tests/phpunit/tests/admin/wpUpgrader.php @@ -999,13 +999,8 @@ public function test_install_package_should_return_wp_error_when_source_director * @ticket 61114 * * @covers WP_Upgrader::install_package - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_install_package_should_return_wp_error_when_a_filtered_source_directory_file_list_cannot_be_retrieved() { - define( 'FS_CHMOD_DIR', 0755 ); - self::$instance->generic_strings(); self::$upgrader_skin_mock @@ -1097,23 +1092,11 @@ function ( $source ) { * Tests that `WP_Upgrader::install_package()` applies * 'upgrader_clear_destination' filters with arguments. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 54245 * * @covers WP_Upgrader::install_package - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_install_package_should_clear_destination_when_clear_destination_is_true() { - define( 'FS_CHMOD_FILE', 0644 ); - self::$instance->generic_strings(); self::$upgrader_skin_mock @@ -1149,6 +1132,18 @@ public function test_install_package_should_clear_destination_when_clear_destina ->withConsecutive( ...$dirlist_args ) ->willReturn( $dirlist_results ); + self::$wp_filesystem_mock + ->expects( $this->once() ) + ->method( 'is_writable' ) + ->with( '/dest_dir/file1.php' ) + ->willReturn( true ); + + self::$wp_filesystem_mock + ->expects( $this->once() ) + ->method( 'delete' ) + ->with( '/dest_dir/', true ) + ->willReturn( true ); + add_filter( 'upgrader_clear_destination', function ( $removed, $local_destination, $remote_destination, $hook_extra ) { @@ -1191,28 +1186,16 @@ function ( $removed, $local_destination, $remote_destination, $hook_extra ) { * Tests that `WP_Upgrader::install_package()` makes the * remote destination safe when set to a protected directory. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 54245 * * @covers WP_Upgrader::install_package * * @dataProvider data_install_package_should_make_remote_destination_safe_when_set_to_a_protected_directory * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @param string $protected_directory The path to a protected directory. * @param string $expected The expected safe remote destination. */ public function test_install_package_should_make_remote_destination_safe_when_set_to_a_protected_directory( $protected_directory, $expected ) { - define( 'FS_CHMOD_FILE', 0644 ); - self::$instance->generic_strings(); self::$upgrader_skin_mock @@ -1248,6 +1231,18 @@ public function test_install_package_should_make_remote_destination_safe_when_se ->withConsecutive( ...$dirlist_args ) ->willReturn( $dirlist_results ); + self::$wp_filesystem_mock + ->expects( $this->once() ) + ->method( 'is_writable' ) + ->with( $expected . 'file1.php' ) + ->willReturn( true ); + + self::$wp_filesystem_mock + ->expects( $this->once() ) + ->method( 'delete' ) + ->with( $expected, true ) + ->willReturn( true ); + add_filter( 'upgrader_clear_destination', function ( $removed, $local_destination, $remote_destination ) use ( $expected ) { diff --git a/tests/phpunit/tests/auth.php b/tests/phpunit/tests/auth.php index 409496a1167bd..7b354f8ca38f0 100644 --- a/tests/phpunit/tests/auth.php +++ b/tests/phpunit/tests/auth.php @@ -36,6 +36,14 @@ class Tests_Auth extends WP_UnitTestCase { protected static $password_length_limit = 4096; + protected static $phpass_length_limit_hash = '$P$BuCJXibHerqQlUjDbCXINsNu5pYqWd0'; + + protected static $plain_bcrypt_hash = '$2y$04$oPhLTndsIu9lp9LwxaaZJOscaqyfc1cAChJ0ppzRgKb11wIMoPsc.'; + + protected static $argon2i_hash = '$argon2i$v=19$m=1024,t=1,p=1$NUZUeUltZk1lZk9kTnNEag$y53Ml5m/6u0sE/74+f4hAc5cqqt2SFsVCuYkEhYeG+I'; + + protected static $argon2id_hash = '$argon2id$v=19$m=1024,t=1,p=1$Wi5seTFGMjRzeE1MbW9zTg$w9+Y093vlc6CFuQ63MnU8+dbdR91Z9HSRbmfZ/gGAh4'; + /** * Action hook. */ @@ -212,8 +220,8 @@ public function test_wp_check_password_supports_phpass_hash() { /** * Ensure wp_check_password() remains compatible with an increase to the default bcrypt cost. * - * The test verifies this by reducing the cost used to generate the hash, therefore mimicing a hash - * which was generated prior to the default cost being increased. + * The test verifies this by raising the active default cost after generating the hash, therefore + * mimicing a hash which was generated prior to the default cost being increased. * * Notably the bcrypt cost was increased in PHP 8.4: https://wiki.php.net/rfc/bcrypt_cost_2023 . * @@ -222,14 +230,16 @@ public function test_wp_check_password_supports_phpass_hash() { public function test_wp_check_password_supports_hash_with_increased_bcrypt_cost() { $password = 'password'; - // Reducing the cost mimics an increase to the default cost. - add_filter( 'wp_hash_password_options', array( $this, 'reduce_hash_cost' ) ); $hash = wp_hash_password( $password, PASSWORD_BCRYPT ); - remove_filter( 'wp_hash_password_options', array( $this, 'reduce_hash_cost' ) ); + + // Increasing the cost before validation mimics a default cost increase. + add_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); $this->assertTrue( wp_check_password( $password, $hash ) ); $this->assertSame( 1, did_filter( 'check_password' ) ); $this->assertTrue( wp_password_needs_rehash( $hash ) ); + + remove_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); } /** @@ -273,8 +283,7 @@ public function test_wp_check_password_supports_wp_hash_with_default_bcrypt_cost */ public function test_wp_check_password_supports_plain_bcrypt_hash_with_default_bcrypt_cost() { $password = 'password'; - - $hash = password_hash( $password, PASSWORD_BCRYPT ); + $hash = self::$plain_bcrypt_hash; $this->assertTrue( wp_check_password( $password, $hash ) ); $this->assertSame( 1, did_filter( 'check_password' ) ); @@ -292,7 +301,7 @@ public function test_wp_check_password_supports_argon2i_hash() { } $password = 'password'; - $hash = password_hash( trim( $password ), PASSWORD_ARGON2I ); + $hash = self::$argon2i_hash; $this->assertTrue( wp_check_password( $password, $hash ) ); $this->assertSame( 1, did_filter( 'check_password' ) ); } @@ -310,7 +319,7 @@ public function test_wp_check_password_supports_argon2id_hash() { } $password = 'password'; - $hash = password_hash( trim( $password ), PASSWORD_ARGON2ID ); + $hash = self::$argon2id_hash; $this->assertTrue( wp_check_password( $password, $hash ) ); $this->assertSame( 1, did_filter( 'check_password' ) ); } @@ -685,7 +694,7 @@ public function test_invalid_password_at_phpass_length_limit_is_rejected() { $limit = str_repeat( 'a', self::$phpass_length_limit ); // Set the user password with the old phpass algorithm. - self::set_user_password_with_phpass( $limit, self::$user_id ); + self::set_user_password_hash( self::$phpass_length_limit_hash, self::$user_id ); // Authenticate. $user = wp_authenticate( $this->user->user_login, 'aaaaaaaa' ); @@ -699,7 +708,7 @@ public function test_valid_password_at_phpass_length_limit_is_accepted() { $limit = str_repeat( 'a', self::$phpass_length_limit ); // Set the user password with the old phpass algorithm. - self::set_user_password_with_phpass( $limit, self::$user_id ); + self::set_user_password_hash( self::$phpass_length_limit_hash, self::$user_id ); // Authenticate. $user = wp_authenticate( $this->user->user_login, $limit ); @@ -714,7 +723,7 @@ public function test_too_long_password_at_phpass_length_limit_is_rejected() { $limit = str_repeat( 'a', self::$phpass_length_limit ); // Set the user password with the old phpass algorithm. - self::set_user_password_with_phpass( $limit, self::$user_id ); + self::set_user_password_hash( self::$phpass_length_limit_hash, self::$user_id ); // Authenticate with a password that is one character too long. $user = wp_authenticate( $this->user->user_login, $limit . 'a' ); @@ -1038,13 +1047,10 @@ public function check_password_needs_rehashing() { $this->assertFalse( wp_password_needs_rehash( $hash ) ); // A future upgrade from a previously lower cost. - $default = self::get_default_bcrypt_cost(); - $opts = array( - // Reducing the cost mimics an increase in the default cost. - 'cost' => $default - 1, - ); - $hash = password_hash( $password, PASSWORD_BCRYPT, $opts ); + $hash = wp_hash_password( $password ); + add_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); $this->assertTrue( wp_password_needs_rehash( $hash ) ); + remove_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); // Previous phpass algorithm. $hash = self::$wp_hasher->HashPassword( $password ); @@ -1237,10 +1243,9 @@ public function test_md5_password_is_rehashed_after_successful_user_password_aut public function test_bcrypt_password_is_rehashed_with_new_cost_after_successful_user_password_authentication( $username_or_email ) { $password = 'password'; - // Hash the user password with a lower cost than default to mimic a cost upgrade. - add_filter( 'wp_hash_password_options', array( $this, 'reduce_hash_cost' ) ); + // Hash the user password before increasing the default cost. wp_set_password( $password, self::$user_id ); - remove_filter( 'wp_hash_password_options', array( $this, 'reduce_hash_cost' ) ); + add_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); // Verify that the password needs rehashing. $hash = get_userdata( self::$user_id )->user_pass; @@ -1249,7 +1254,7 @@ public function test_bcrypt_password_is_rehashed_with_new_cost_after_successful_ // Authenticate. $user = wp_authenticate( $username_or_email, $password ); - // Verify that the reduced cost password hash was valid. + // Verify that the previous-cost password hash was valid. $this->assertNotWPError( $user ); $this->assertInstanceOf( 'WP_User', $user ); $this->assertSame( self::$user_id, $user->ID ); @@ -1257,7 +1262,7 @@ public function test_bcrypt_password_is_rehashed_with_new_cost_after_successful_ // Verify that the password has been rehashed with the increased cost. $hash = get_userdata( self::$user_id )->user_pass; $this->assertFalse( wp_password_needs_rehash( $hash, self::$user_id ) ); - $this->assertSame( self::get_default_bcrypt_cost(), password_get_info( substr( $hash, 3 ) )['options']['cost'] ); + $this->assertSame( self::get_default_bcrypt_cost() + 1, password_get_info( substr( $hash, 3 ) )['options']['cost'] ); // Authenticate a second time to ensure the new hash is valid. $user = wp_authenticate( $username_or_email, $password ); @@ -1266,10 +1271,12 @@ public function test_bcrypt_password_is_rehashed_with_new_cost_after_successful_ $this->assertNotWPError( $user ); $this->assertInstanceOf( 'WP_User', $user ); $this->assertSame( self::$user_id, $user->ID ); + + remove_filter( 'wp_hash_password_options', array( $this, 'increase_hash_cost' ) ); } public function reduce_hash_cost( array $options ): array { - $options['cost'] = self::get_default_bcrypt_cost() - 1; + $options['cost'] = max( 4, self::get_default_bcrypt_cost() - 1 ); return $options; } @@ -1916,12 +1923,16 @@ public function test_set_user_password_with_phpass() { } private static function set_user_password_with_phpass( string $password, int $user_id ) { + self::set_user_password_hash( self::$wp_hasher->HashPassword( $password ), $user_id ); + } + + private static function set_user_password_hash( string $hash, int $user_id ) { global $wpdb; $wpdb->update( $wpdb->users, array( - 'user_pass' => self::$wp_hasher->HashPassword( $password ), + 'user_pass' => $hash, ), array( 'ID' => $user_id, @@ -1985,7 +1996,13 @@ private static function set_user_password_with_plain_bcrypt( string $password, i $wpdb->update( $wpdb->users, array( - 'user_pass' => password_hash( 'password', PASSWORD_BCRYPT ), + 'user_pass' => password_hash( + $password, + PASSWORD_BCRYPT, + array( + 'cost' => self::get_default_bcrypt_cost(), + ) + ), ), array( 'ID' => $user_id, @@ -2022,7 +2039,16 @@ public function test_set_application_password_with_plain_bcrypt() { * @return string The UUID of the application password. */ private static function set_application_password_with_plain_bcrypt( string $password, int $user_id ) { - return self::set_application_password( password_hash( $password, PASSWORD_BCRYPT ), $user_id ); + return self::set_application_password( + password_hash( + $password, + PASSWORD_BCRYPT, + array( + 'cost' => self::get_default_bcrypt_cost(), + ) + ), + $user_id + ); } /** @@ -2089,6 +2115,6 @@ private static function set_application_password( string $hash, int $user_id ) { } private static function get_default_bcrypt_cost(): int { - return 5; + return 4; } } diff --git a/tests/phpunit/tests/compat/mbChr.php b/tests/phpunit/tests/compat/mbChr.php index 6862ed9170479..54fcd36264577 100644 --- a/tests/phpunit/tests/compat/mbChr.php +++ b/tests/phpunit/tests/compat/mbChr.php @@ -14,13 +14,13 @@ class Tests_Compat_mbChr extends WP_UnitTestCase { */ public function test_mb_chr_polyfill_matches_spec() { for ( $code_point = 0; $code_point <= 0x10FFFF; $code_point++ ) { - $this->assertSame( - mb_chr( $code_point ), - _mb_chr( $code_point ), - 'Failed to properly decode the code point from the string.' - ); + if ( mb_chr( $code_point ) !== _mb_chr( $code_point ) ) { + $hex_char = strtoupper( str_pad( dechex( $code_point ), 4, '0', STR_PAD_LEFT ) ); + $this->fail( "Failed to properly encode U+{$hex_char}." ); + } } + $this->assertTrue( true, 'All valid code points were encoded properly.' ); $this->assertFalse( _mb_chr( ord( 'A' ), 'latin1' ), 'Should have rejected non-UTF-8 encoding.' ); $this->assertFalse( _mb_ord( ord( 'A' ), 'utf8' ), 'Should have rejected non-UTF-8 encoding.' ); $this->assertSame( 'A', _mb_chr( ord( 'A' ), 'UTF-8' ), 'Should have accepted UTF-8 encoding.' ); diff --git a/tests/phpunit/tests/compat/mbOrd.php b/tests/phpunit/tests/compat/mbOrd.php index 214547498d643..0f9be48d5a351 100644 --- a/tests/phpunit/tests/compat/mbOrd.php +++ b/tests/phpunit/tests/compat/mbOrd.php @@ -22,15 +22,15 @@ public function test_mb_ord_polyfill_matches_spec() { * and spot-check an unpaired and incorrectly-converted surrogate * half below. */ - if ( false !== mb_chr( $code_point ) ) { - $this->assertSame( - $code_point, - _mb_ord( mb_chr( $code_point ) ), - 'Failed to properly decode the code point from the string.' - ); + $char = mb_chr( $code_point ); + + if ( false !== $char && _mb_ord( $char ) !== $code_point ) { + $hex_char = strtoupper( str_pad( dechex( $code_point ), 4, '0', STR_PAD_LEFT ) ); + $this->fail( "Failed to properly decode U+{$hex_char} from the string." ); } } + $this->assertTrue( true, 'All valid code points were decoded properly.' ); $this->assertFalse( _mb_ord( '' ), 'Should have failed on empty string.' ); $this->assertFalse( _mb_ord( 'hi', 'latin1' ), 'Should have rejected non-UTF-8 encoding.' ); $this->assertFalse( _mb_ord( 'hi', 'utf8' ), 'Should have rejected non-UTF-8 encoding.' ); diff --git a/tests/phpunit/tests/filesystem/wpFilesystemDirect/mkdir.php b/tests/phpunit/tests/filesystem/wpFilesystemDirect/mkdir.php index 0d87bd536b51d..0b7586f831427 100644 --- a/tests/phpunit/tests/filesystem/wpFilesystemDirect/mkdir.php +++ b/tests/phpunit/tests/filesystem/wpFilesystemDirect/mkdir.php @@ -19,27 +19,15 @@ class Tests_Filesystem_WpFilesystemDirect_Mkdir extends WP_Filesystem_Direct_Uni /** * Tests that `WP_Filesystem_Direct::mkdir()` creates a directory. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 57774 * * @dataProvider data_should_create_directory * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @param mixed $path The path to create. */ public function test_should_create_directory( $path ) { - define( 'FS_CHMOD_DIR', 0755 ); - $path = str_replace( 'TEST_DIR', self::$file_structure['test_dir']['path'], $path ); - $actual = self::$filesystem->mkdir( $path ); + $actual = self::$filesystem->mkdir( $path, 0755 ); if ( $path !== self::$file_structure['test_dir']['path'] && is_dir( $path ) ) { rmdir( $path ); @@ -67,27 +55,15 @@ public function data_should_create_directory() { /** * Tests that `WP_Filesystem_Direct::mkdir()` does not create a directory. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 57774 * * @dataProvider data_should_not_create_directory * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @param mixed $path The path to create. */ public function test_should_not_create_directory( $path ) { - define( 'FS_CHMOD_DIR', 0755 ); - $path = str_replace( 'TEST_DIR', self::$file_structure['test_dir']['path'], $path ); - $actual = self::$filesystem->mkdir( $path ); + $actual = self::$filesystem->mkdir( $path, 0755 ); if ( $path !== self::$file_structure['test_dir']['path'] && is_dir( $path ) ) { rmdir( $path ); @@ -112,6 +88,39 @@ public function data_should_not_create_directory() { ); } + /** + * Tests that `WP_Filesystem_Direct::mkdir()` uses FS_CHMOD_DIR when chmod is not passed. + * + * This test runs in a separate process so that it can define + * constants without impacting other tests. + * + * This test does not preserve global state to prevent the exception + * "Serialization of 'Closure' is not allowed." when running in a + * separate process. + * + * @ticket 57774 + * + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function test_should_use_fs_chmod_dir_when_chmod_not_passed() { + define( 'FS_CHMOD_DIR', 0755 ); + + $path = self::$file_structure['test_dir']['path'] . 'directory-to-create'; + + $created = self::$filesystem->mkdir( $path ); + $chmod = substr( sprintf( '%o', fileperms( $path ) ), -4 ); + + if ( $path !== self::$file_structure['test_dir']['path'] && is_dir( $path ) ) { + rmdir( $path ); + } + + $expected_permissions = $this->is_windows() ? '0777' : '0755'; + + $this->assertTrue( $created, 'The directory was not created.' ); + $this->assertSame( $expected_permissions, $chmod, 'The permissions are incorrect.' ); + } + /** * Tests that `WP_Filesystem_Direct::mkdir()` sets chmod. * @@ -136,25 +145,13 @@ public function test_should_set_chmod() { /** * Tests that `WP_Filesystem_Direct::mkdir()` sets the owner. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 57774 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_should_set_owner() { - define( 'FS_CHMOD_DIR', 0755 ); - $path = self::$file_structure['test_dir']['path'] . 'directory-to-create'; // Get the default owner. - self::$filesystem->mkdir( $path ); + self::$filesystem->mkdir( $path, 0755 ); $original_owner = fileowner( $path ); rmdir( $path ); @@ -173,25 +170,13 @@ public function test_should_set_owner() { /** * Tests that `WP_Filesystem_Direct::mkdir()` sets the group. * - * This test runs in a separate process so that it can define - * constants without impacting other tests. - * - * This test does not preserve global state to prevent the exception - * "Serialization of 'Closure' is not allowed." when running in a - * separate process. - * * @ticket 57774 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_should_set_group() { - define( 'FS_CHMOD_DIR', 0755 ); - $path = self::$file_structure['test_dir']['path'] . 'directory-to-create'; // Get the default group. - self::$filesystem->mkdir( $path ); + self::$filesystem->mkdir( $path, 0755 ); $original_group = filegroup( $path ); rmdir( $path ); diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php index 5bad5435472d4..e350e8209445b 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontCollectionsController.php @@ -53,6 +53,15 @@ public static function wpTearDownAfterClass() { wp_unregister_font_collection( 'mock-col-slug' ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Font_Collections_Controller(); + $controller->register_routes(); + } + /** * @covers WP_REST_Font_Collections_Controller::register_routes */ diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php index 19a2a6a86b8b0..9811f86251e47 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFacesController.php @@ -86,6 +86,14 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'wp_font_family', 'wp_font_face' ) ); + } + public static function create_font_face_post( $parent_id, $settings = array() ) { $settings = array_merge( self::$default_settings, $settings ); $title = WP_Font_Utils::get_font_face_slug( $settings ); diff --git a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php index fada1d1643890..da0457ac03874 100644 --- a/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php +++ b/tests/phpunit/tests/fonts/font-library/wpRestFontFamiliesController.php @@ -99,6 +99,14 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'wp_font_family', 'wp_font_face' ) ); + } + public static function create_font_family_post( $settings = array() ) { $settings = array_merge( self::$default_settings, $settings ); $post_id = self::factory()->post->create( diff --git a/tests/phpunit/tests/formatting/deprecatedUtfEncodeDecode.php b/tests/phpunit/tests/formatting/deprecatedUtfEncodeDecode.php index e685df3429e15..162a54e1e438a 100644 --- a/tests/phpunit/tests/formatting/deprecatedUtfEncodeDecode.php +++ b/tests/phpunit/tests/formatting/deprecatedUtfEncodeDecode.php @@ -58,9 +58,9 @@ public static function data_utf8_strings() { * @ticket 63863. */ public function test_utf8_decode_characters() { - for ( $i = 0; $i <= 0x10FFFF; $i++ ) { - $hex_i = strtoupper( str_pad( dechex( $i ), 2, '0', STR_PAD_LEFT ) ); + $input = ''; + for ( $i = 0; $i <= 0x10FFFF; $i++ ) { if ( $i < 0xD800 || $i > 0xE000 ) { $c = mb_chr( $i ); } else { @@ -75,12 +75,14 @@ public function test_utf8_decode_characters() { $c = "{$byte1}{$byte2}{$byte3}"; } - $this->assertSame( - bin2hex( mb_convert_encoding( $c, 'ISO-8859-1', 'UTF-8' ) ), - bin2hex( _wp_utf8_decode_fallback( $c ) ), - "Failed to convert U+{$hex_i} properly." - ); + $input .= "{$c} "; } + + $this->assertSame( + bin2hex( mb_convert_encoding( $input, 'ISO-8859-1', 'UTF-8' ) ), + bin2hex( _wp_utf8_decode_fallback( $input ) ), + 'Failed to convert all Unicode code points properly.' + ); } /** diff --git a/tests/phpunit/tests/functions/wpUniquePrefixedId.php b/tests/phpunit/tests/functions/wpUniquePrefixedId.php index ed6e489a3e259..e49e48d06f24d 100644 --- a/tests/phpunit/tests/functions/wpUniquePrefixedId.php +++ b/tests/phpunit/tests/functions/wpUniquePrefixedId.php @@ -19,9 +19,6 @@ class Tests_Functions_WpUniquePrefixedId extends WP_UnitTestCase { * * @dataProvider data_should_create_unique_prefixed_ids * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @param mixed $prefix The prefix. * @param array $expected The next two expected IDs. */ @@ -76,9 +73,6 @@ public function data_should_create_unique_prefixed_ids() { * * @dataProvider data_should_raise_notice_and_use_empty_string_prefix_when_nonstring_given * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @param mixed $non_string_prefix Non-string prefix. * @param int $number_of_ids_to_generate Number of IDs to generate. * As the prefix will default to an empty string, changing the number of IDs generated within each dataset further tests ID uniqueness. diff --git a/tests/phpunit/tests/image/functions.php b/tests/phpunit/tests/image/functions.php index 94cc0111f0d4c..babf5a3a1003b 100644 --- a/tests/phpunit/tests/image/functions.php +++ b/tests/phpunit/tests/image/functions.php @@ -687,11 +687,10 @@ public function test_wp_crop_image_should_fail_with_wp_error_object_if_file_does /** * @covers ::wp_crop_image - * @requires extension openssl */ public function test_wp_crop_image_should_fail_with_wp_error_object_if_url_does_not_exist() { $file = wp_crop_image( - 'https://wordpress.org/screenshots/3.9/canoladoesnotexist.jpg', + 'http://127.0.0.1:0/canoladoesnotexist.jpg', 0, 0, 100, diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index c3e118ee718d5..b8ba1139cc2ed 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -1864,16 +1864,9 @@ public function test_wp_calculate_image_srcset() { */ public function test_wp_calculate_image_srcset_no_date_uploads() { $_wp_additional_image_sizes = wp_get_additional_image_sizes(); - - // Disable date organized uploads. - add_filter( 'upload_dir', '_upload_dir_no_subdir' ); - - // Make an image. - $filename = DIR_TESTDATA . '/images/' . self::$large_filename; - $id = self::factory()->attachment->create_upload_object( $filename ); - - $image_meta = wp_get_attachment_metadata( $id ); - $uploads_dir_url = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/'; + $image_meta = wp_get_attachment_metadata( self::$large_id ); + $image_meta['file'] = wp_basename( $image_meta['file'] ); + $uploads_dir_url = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/'; // Set up test cases for all expected size names. $intermediates = array( 'medium', 'medium_large', 'large', 'full' ); @@ -1896,13 +1889,13 @@ public function test_wp_calculate_image_srcset_no_date_uploads() { $expected = trim( $expected, ' ,' ); foreach ( $intermediates as $int_size ) { - $image_urls[ $int_size ] = wp_get_attachment_image_url( $id, $int_size ); + if ( 'full' === $int_size ) { + $image_urls[ $int_size ] = $uploads_dir_url . $image_meta['file']; + } else { + $image_urls[ $int_size ] = $uploads_dir_url . $image_meta['sizes'][ $int_size ]['file']; + } } - // Remove the attachment. - wp_delete_attachment( $id, true ); - remove_filter( 'upload_dir', '_upload_dir_no_subdir' ); - foreach ( $intermediates as $int_size ) { $size_array = $this->get_image_size_array_from_meta( $image_meta, $int_size ); $image_url = $image_urls[ $int_size ]; @@ -5650,8 +5643,8 @@ private function reset_high_priority_element_flag() { public function test_quality_with_image_conversion_file_sizes() { add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_jpeg' ) ); $temp_dir = get_temp_dir(); - $file = $temp_dir . '/33772.jpg'; - copy( DIR_TESTDATA . '/images/33772.jpg', $file ); + $file = $temp_dir . '/a2-small.jpg'; + copy( DIR_TESTDATA . '/images/a2-small.jpg', $file ); // Set JPEG output quality very low and WebP quality very high, this should force all generated WebP images to // be larger than the matching generated JPEGs. @@ -5671,27 +5664,40 @@ public function test_quality_with_image_conversion_file_sizes() { ) ); - add_filter( 'big_image_size_threshold', array( $this, 'add_big_image_size_threshold' ) ); + add_image_size( 'test-size-250', 250, 250 ); + add_image_size( 'test-size-200', 200, 200 ); + + add_filter( + 'big_image_size_threshold', + static function () { + return 300; + } + ); - // Generate all sizes as JPEGs. - $jpeg_sizes = wp_generate_attachment_metadata( $attachment_id, $file ); - remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_jpeg' ) ); + try { + // Generate all sizes as JPEGs. + $jpeg_sizes = wp_generate_attachment_metadata( $attachment_id, $file ); + remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_jpeg' ) ); - // Generate all sizes as WebP. - add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_webp' ) ); - $webp_sizes = wp_generate_attachment_metadata( $attachment_id, $file ); - remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_webp' ) ); + // Generate all sizes as WebP. + add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_webp' ) ); + $webp_sizes = wp_generate_attachment_metadata( $attachment_id, $file ); + remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_webp' ) ); - // The main (scaled) image: the JPEG should be smaller than the WebP. - $this->assertLessThan( $webp_sizes['filesize'], $jpeg_sizes['filesize'], 'The JPEG should be smaller than the WebP.' ); + // The main (scaled) image: the JPEG should be smaller than the WebP. + $this->assertLessThan( $webp_sizes['filesize'], $jpeg_sizes['filesize'], 'The JPEG should be smaller than the WebP.' ); - // Sub-sizes: for each size, the JPEGs should be smaller than the WebP. - $sizes_to_compare = array_intersect_key( $jpeg_sizes['sizes'], $webp_sizes['sizes'] ); + // Sub-sizes: for each size, the JPEGs should be smaller than the WebP. + $sizes_to_compare = array_intersect_key( $jpeg_sizes['sizes'], $webp_sizes['sizes'] ); - $this->assertNotEmpty( $sizes_to_compare ); + $this->assertNotEmpty( $sizes_to_compare ); - foreach ( $sizes_to_compare as $size => $size_data ) { - $this->assertLessThan( $webp_sizes['sizes'][ $size ]['filesize'], $jpeg_sizes['sizes'][ $size ]['filesize'] ); + foreach ( $sizes_to_compare as $size => $size_data ) { + $this->assertLessThan( $webp_sizes['sizes'][ $size ]['filesize'], $jpeg_sizes['sizes'][ $size ]['filesize'] ); + } + } finally { + remove_image_size( 'test-size-250' ); + remove_image_size( 'test-size-200' ); } } @@ -7094,9 +7100,9 @@ public function test_heic_image_upload_is_converted_to_jpeg( bool $apply_big_ima */ public function test_jpeg_image_converts_to_webp_when_filtered( bool $apply_big_image_size_threshold ) { $temp_dir = get_temp_dir(); - $file = $temp_dir . '/33772.jpg'; + $file = $temp_dir . '/a2-small.jpg'; $scaled_suffix = $apply_big_image_size_threshold ? '-scaled' : ''; - copy( DIR_TESTDATA . '/images/33772.jpg', $file ); + copy( DIR_TESTDATA . '/images/a2-small.jpg', $file ); $editor = wp_get_image_editor( $file ); @@ -7113,7 +7119,12 @@ public function test_jpeg_image_converts_to_webp_when_filtered( bool $apply_big_ ); if ( $apply_big_image_size_threshold ) { - add_filter( 'big_image_size_threshold', array( $this, 'add_big_image_size_threshold' ) ); + add_filter( + 'big_image_size_threshold', + static function () { + return 300; + } + ); } // Generate all sizes as WebP. @@ -7122,8 +7133,8 @@ public function test_jpeg_image_converts_to_webp_when_filtered( bool $apply_big_ $image_meta = wp_generate_attachment_metadata( $attachment_id, $file ); $this->assertStringEndsNotWith( '.jpg', $image_meta['file'], 'The file extension is expected to change.' ); - $this->assertSame( "33772{$scaled_suffix}.webp", basename( $image_meta['file'] ), "The file name is expected to be 33772{$scaled_suffix}.webp." ); - $this->assertSame( '33772.jpg', $image_meta['original_image'], 'The original image name is expected to be stored in the meta data.' ); + $this->assertSame( "a2-small{$scaled_suffix}.webp", basename( $image_meta['file'] ), "The file name is expected to be a2-small{$scaled_suffix}.webp." ); + $this->assertSame( 'a2-small.jpg', $image_meta['original_image'], 'The original image name is expected to be stored in the meta data.' ); $this->assertSame( 'image/webp', wp_get_image_mime( $image_meta['file'] ), 'The image mime type is expected to be image/webp.' ); } diff --git a/tests/phpunit/tests/media/wpCrossOriginIsolation.php b/tests/phpunit/tests/media/wpCrossOriginIsolation.php index 3ec4231d5bede..b1f6125943b01 100644 --- a/tests/phpunit/tests/media/wpCrossOriginIsolation.php +++ b/tests/phpunit/tests/media/wpCrossOriginIsolation.php @@ -30,12 +30,18 @@ class Tests_Media_wpCrossOriginIsolation extends WP_UnitTestCase { */ private ?string $original_get_action; + /** + * Original output buffer level. + */ + private int $original_ob_level; + public function set_up() { parent::set_up(); $this->original_user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null; $this->original_http_host = $_SERVER['HTTP_HOST'] ?? null; $this->original_https = $_SERVER['HTTPS'] ?? null; $this->original_get_action = $_GET['action'] ?? null; + $this->original_ob_level = ob_get_level(); } public function tear_down() { @@ -64,7 +70,7 @@ public function tear_down() { } // Clean up any output buffers started during tests. - while ( ob_get_level() > 1 ) { + while ( ob_get_level() > $this->original_ob_level ) { ob_end_clean(); } @@ -99,14 +105,7 @@ public function test_returns_early_when_no_screen() { } /** - * This test must run in a separate process because the output buffer - * callback sends HTTP headers via header(), which would fail in the - * main PHPUnit process where output has already started. - * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_starts_output_buffer_for_chrome_137() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; @@ -120,6 +119,29 @@ public function test_starts_output_buffer_for_chrome_137() { ob_end_clean(); } + /** + * @ticket 64766 + * + * @requires function xdebug_get_headers + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function test_output_buffer_sends_document_isolation_policy_header() { + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; + + ob_start(); + + wp_start_cross_origin_isolation_output_buffer(); + echo ''; + + ob_end_flush(); + ob_get_clean(); + + $headers = xdebug_get_headers(); + + $this->assertContains( 'Document-Isolation-Policy: isolate-and-credentialless', $headers ); + } + /** * @ticket 64766 */ @@ -189,10 +211,6 @@ public function test_client_side_processing_enabled_on_localhost() { * Verifies that cross-origin elements get crossorigin="anonymous" added. * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @dataProvider data_elements_that_should_get_crossorigin * * @param string $html HTML input to process. @@ -244,10 +262,6 @@ public function data_elements_that_should_get_crossorigin() { * in credentialless mode without needing explicit CORS headers. * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled - * * @dataProvider data_elements_that_should_not_get_crossorigin * * @param string $html HTML input to process. @@ -294,9 +308,6 @@ public function data_elements_that_should_not_get_crossorigin() { * Uses site_url() at runtime since the test domain varies by CI config. * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_output_buffer_does_not_add_crossorigin_to_same_origin() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; @@ -316,9 +327,6 @@ public function test_output_buffer_does_not_add_crossorigin_to_same_origin() { * Elements that already have a crossorigin attribute should not be modified. * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_output_buffer_does_not_override_existing_crossorigin() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; @@ -339,9 +347,6 @@ public function test_output_buffer_does_not_override_existing_crossorigin() { * Multiple tags in the same output should each be handled correctly. * * @ticket 64766 - * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_output_buffer_handles_mixed_tags() { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'; diff --git a/tests/phpunit/tests/media/wpGenerateAttachmentMetadata.php b/tests/phpunit/tests/media/wpGenerateAttachmentMetadata.php index 625d6ff665d83..0e41aa3a2f7a4 100644 --- a/tests/phpunit/tests/media/wpGenerateAttachmentMetadata.php +++ b/tests/phpunit/tests/media/wpGenerateAttachmentMetadata.php @@ -93,8 +93,14 @@ static function ( $mimes ) { * @ticket 62900 */ public function test_wp_generate_attachment_metadata_png_thumbnail_smaller_than_original() { - // Use the test-image-large.png test file. - $attachment = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/png-tests/test-image-large.png' ); + add_filter( + 'big_image_size_threshold', + static function () { + return 25; + } + ); + + $attachment = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.png' ); $metadata = wp_get_attachment_metadata( $attachment ); diff --git a/tests/phpunit/tests/rest-api.php b/tests/phpunit/tests/rest-api.php index fcb8e3da87f4c..b409e8bbdbee2 100644 --- a/tests/phpunit/tests/rest-api.php +++ b/tests/phpunit/tests/rest-api.php @@ -19,7 +19,12 @@ public function set_up() { // Override the normal server with our spying server. $GLOBALS['wp_rest_server'] = new Spy_REST_Server(); - do_action( 'rest_api_init', $GLOBALS['wp_rest_server'] ); + + if ( $this->requires_initial_rest_routes() ) { + do_action( 'rest_api_init', $GLOBALS['wp_rest_server'] ); + } else { + $this->do_rest_api_init_without_initial_routes(); + } } public function tear_down() { @@ -31,6 +36,35 @@ public function filter_wp_rest_server_class( $class_name ) { return 'Spy_REST_Server'; } + private function do_rest_api_init_without_initial_routes() { + $priority = has_action( 'rest_api_init', 'create_initial_rest_routes' ); + + if ( false !== $priority ) { + remove_action( 'rest_api_init', 'create_initial_rest_routes', $priority ); + } + + try { + do_action( 'rest_api_init', $GLOBALS['wp_rest_server'] ); + } finally { + if ( false !== $priority ) { + add_action( 'rest_api_init', 'create_initial_rest_routes', $priority ); + } + } + } + + private function requires_initial_rest_routes() { + return in_array( + $this->getName( false ), + array( + 'test_rest_preload_api_request_with_method', + 'test_rest_preload_api_request_removes_trailing_slashes', + 'test_rest_preload_api_request_embeds_links', + 'test_rest_preload_api_request_fields', + ), + true + ); + } + public function test_rest_get_server_fails_with_undefined_method() { $this->expectException( Error::class ); rest_get_server()->does_not_exist(); diff --git a/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php b/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php index 060a5c0912a94..8830e6bd8d590 100644 --- a/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php +++ b/tests/phpunit/tests/rest-api/rest-application-passwords-controller.php @@ -67,6 +67,18 @@ public function set_up() { add_filter( 'wp_is_application_passwords_available', '__return_true' ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Application_Passwords_Controller(); + $controller->register_routes(); + } + public function tear_down() { unset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'], $GLOBALS['wp_rest_application_password_status'], $GLOBALS['wp_rest_application_password_uuid'] ); parent::tear_down(); diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 79e9d23cf9dd3..26662a430246c 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -194,6 +194,17 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page', 'attachment' ) ); + + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + } + /** * Enables client-side media processing and reinitializes the REST server * so that the sideload and finalize routes are registered. diff --git a/tests/phpunit/tests/rest-api/rest-autosaves-controller.php b/tests/phpunit/tests/rest-api/rest-autosaves-controller.php index 7815f8ced23c9..eafa172e757d5 100644 --- a/tests/phpunit/tests/rest-api/rest-autosaves-controller.php +++ b/tests/phpunit/tests/rest-api/rest-autosaves-controller.php @@ -126,6 +126,14 @@ public function set_up() { $this->post_autosave = wp_get_post_autosave( self::$post_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page' ) ); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/posts/(?P[\d]+)/autosaves', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-block-directory-controller.php b/tests/phpunit/tests/rest-api/rest-block-directory-controller.php index f61b317240532..f06b6ebefcc2e 100644 --- a/tests/phpunit/tests/rest-api/rest-block-directory-controller.php +++ b/tests/phpunit/tests/rest-api/rest-block-directory-controller.php @@ -41,6 +41,15 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$admin_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Block_Directory_Controller(); + $controller->register_routes(); + } + /** * @ticket 50321 */ diff --git a/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php b/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php index 3573ecb6e0bea..e2c50d6bbef9a 100644 --- a/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php +++ b/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php @@ -148,6 +148,15 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Block_Renderer_Controller(); + $controller->register_routes(); + } + /** * Register test block. * diff --git a/tests/phpunit/tests/rest-api/rest-block-type-controller.php b/tests/phpunit/tests/rest-api/rest-block-type-controller.php index 7ba693286c993..2c442e1ea2281 100644 --- a/tests/phpunit/tests/rest-api/rest-block-type-controller.php +++ b/tests/phpunit/tests/rest-api/rest-block-type-controller.php @@ -66,6 +66,15 @@ public static function wpTearDownAfterClass() { unregister_block_type( 'fake/false' ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Block_Types_Controller(); + $controller->register_routes(); + } + /** * @ticket 47620 */ diff --git a/tests/phpunit/tests/rest-api/rest-categories-controller.php b/tests/phpunit/tests/rest-api/rest-categories-controller.php index 5d1f893d87fd4..f94c28babb79b 100644 --- a/tests/phpunit/tests/rest-api/rest-categories-controller.php +++ b/tests/phpunit/tests/rest-api/rest-categories-controller.php @@ -104,6 +104,14 @@ public function set_up() { ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_taxonomy_rest_routes_for_test( array( 'category' ) ); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/categories', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index 547757ae6042e..21f127b09aa2b 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -177,6 +177,20 @@ public function set_up() { } } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page', 'attachment' ) ); + + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Comments_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 08ed7f65f818b..c803645b0a43b 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -52,6 +52,15 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Global_Styles_Controller(); + $controller->register_routes(); + } + /** * Create fake data before our tests run. * diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php index a715899979ad1..bc397f8abac22 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php @@ -224,6 +224,14 @@ public function set_up() { $this->revision_3_id = $this->revision_3->ID; } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'wp_global_styles' ) ); + } + /** * @ticket 58524 * @ticket 59810 diff --git a/tests/phpunit/tests/rest-api/rest-navigation-fallback-controller.php b/tests/phpunit/tests/rest-api/rest-navigation-fallback-controller.php index 3be0bba59f26f..a26dac32bb8ad 100644 --- a/tests/phpunit/tests/rest-api/rest-navigation-fallback-controller.php +++ b/tests/phpunit/tests/rest-api/rest-navigation-fallback-controller.php @@ -35,6 +35,17 @@ public function set_up() { wp_set_current_user( self::$admin_user ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Navigation_Fallback_Controller(); + $controller->register_routes(); + + $this->register_post_type_rest_routes_for_test( array( 'wp_navigation' ) ); + } + /** * @ticket 58557 * @covers WP_REST_Navigation_Fallback_Controller::register_routes diff --git a/tests/phpunit/tests/rest-api/rest-pages-controller.php b/tests/phpunit/tests/rest-api/rest-pages-controller.php index 9717a7fcda1c6..625ea43997729 100644 --- a/tests/phpunit/tests/rest-api/rest-pages-controller.php +++ b/tests/phpunit/tests/rest-api/rest-pages-controller.php @@ -33,6 +33,31 @@ public function set_up() { $GLOBALS['wp_rest_server']->override_by_default = false; } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page', 'attachment' ) ); + + $controller = new WP_REST_Post_Types_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Post_Statuses_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Taxonomies_Controller(); + $controller->register_routes(); + + $this->register_taxonomy_rest_routes_for_test( array( 'category', 'post_tag' ) ); + + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Comments_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/pages', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php b/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php index 6f84306dad61f..460bb9d49e2d9 100644 --- a/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php +++ b/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php @@ -65,6 +65,15 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$contributor_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Pattern_Directory_Controller(); + $controller->register_routes(); + } + /** * Clear the captured request URLs after each test. * diff --git a/tests/phpunit/tests/rest-api/rest-plugins-controller.php b/tests/phpunit/tests/rest-api/rest-plugins-controller.php index d6290b071bf22..ebdc651627690 100644 --- a/tests/phpunit/tests/rest-api/rest-plugins-controller.php +++ b/tests/phpunit/tests/rest-api/rest-plugins-controller.php @@ -111,6 +111,15 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Plugins_Controller(); + $controller->register_routes(); + } + /** * @ticket 50321 */ diff --git a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php index 5ce72a57fa55f..858df252aa105 100644 --- a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php @@ -258,7 +258,12 @@ public function set_up() { /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; $wp_rest_server = new Spy_REST_Server(); - do_action( 'rest_api_init', $wp_rest_server ); + $this->do_rest_api_init_without_initial_routes(); + $this->register_initial_rest_routes_for_test(); + } + + private function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page', 'cpt' ) ); } protected function grant_write_permission() { diff --git a/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php b/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php index f6bb1d795a114..e6db20a139e0a 100644 --- a/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php +++ b/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php @@ -9,6 +9,15 @@ */ class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Testcase { + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Post_Statuses_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/statuses', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-post-types-controller.php b/tests/phpunit/tests/rest-api/rest-post-types-controller.php index f230373ce27a4..4f3f80737f725 100644 --- a/tests/phpunit/tests/rest-api/rest-post-types-controller.php +++ b/tests/phpunit/tests/rest-api/rest-post-types-controller.php @@ -9,6 +9,15 @@ */ class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcase { + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Post_Types_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/types', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 212ddde70dd83..0d0b85c8c6f5f 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -139,6 +139,31 @@ public function save_posts_clauses( $orderby, $query ) { return $orderby; } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page', 'attachment' ) ); + + $controller = new WP_REST_Post_Types_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Post_Statuses_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Taxonomies_Controller(); + $controller->register_routes(); + + $this->register_taxonomy_rest_routes_for_test( array( 'category', 'post_tag' ) ); + + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Comments_Controller(); + $controller->register_routes(); + } + public function assertPostsClause( $clause, $pattern ) { global $wpdb; $expected_clause = str_replace( '{posts}', $wpdb->posts, $pattern ); diff --git a/tests/phpunit/tests/rest-api/rest-revisions-controller.php b/tests/phpunit/tests/rest-api/rest-revisions-controller.php index 52011afcb9318..904504a483ed1 100644 --- a/tests/phpunit/tests/rest-api/rest-revisions-controller.php +++ b/tests/phpunit/tests/rest-api/rest-revisions-controller.php @@ -105,6 +105,14 @@ public function set_up() { $this->revision_2_1_id = $post_2_revision->ID; } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( 'post', 'page' ) ); + } + public function _filter_map_meta_cap_remove_no_allow_revisions( $caps, $cap, $user_id, $args ) { if ( 'delete_post' !== $cap || empty( $args ) ) { return $caps; diff --git a/tests/phpunit/tests/rest-api/rest-search-controller.php b/tests/phpunit/tests/rest-api/rest-search-controller.php index e4235fd699798..09cfa2e3cc544 100644 --- a/tests/phpunit/tests/rest-api/rest-search-controller.php +++ b/tests/phpunit/tests/rest-api/rest-search-controller.php @@ -44,6 +44,26 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase { */ private static $my_tag_id; + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $search_handlers = array( + new WP_REST_Post_Search_Handler(), + new WP_REST_Term_Search_Handler(), + new WP_REST_Post_Format_Search_Handler(), + ); + + $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers ); + + $controller = new WP_REST_Search_Controller( $search_handlers ); + $controller->register_routes(); + + $this->register_post_type_rest_routes_for_test( array( 'post', 'page' ) ); + $this->register_taxonomy_rest_routes_for_test( array( 'category', 'post_tag' ) ); + } + /** * Create fake data before our tests run. * diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php index e8f90b53f20f1..05487f3a4e6c7 100644 --- a/tests/phpunit/tests/rest-api/rest-settings-controller.php +++ b/tests/phpunit/tests/rest-api/rest-settings-controller.php @@ -41,6 +41,15 @@ public function set_up() { $this->endpoint = new WP_REST_Settings_Controller(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Settings_Controller(); + $controller->register_routes(); + } + public function tear_down() { $settings_to_unregister = array( 'mycustomsetting', diff --git a/tests/phpunit/tests/rest-api/rest-sidebars-controller.php b/tests/phpunit/tests/rest-api/rest-sidebars-controller.php index dd01d4f2de4ee..f9cb2e903beaa 100644 --- a/tests/phpunit/tests/rest-api/rest-sidebars-controller.php +++ b/tests/phpunit/tests/rest-api/rest-sidebars-controller.php @@ -59,6 +59,18 @@ public function set_up() { update_option( 'sidebars_widgets', array() ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Sidebars_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Widgets_Controller(); + $controller->register_routes(); + } + public function clean_up_global_scope() { global $wp_widget_factory, $wp_registered_sidebars, $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates; diff --git a/tests/phpunit/tests/rest-api/rest-tags-controller.php b/tests/phpunit/tests/rest-api/rest-tags-controller.php index 3b23135c93706..52cf95426fe0c 100644 --- a/tests/phpunit/tests/rest-api/rest-tags-controller.php +++ b/tests/phpunit/tests/rest-api/rest-tags-controller.php @@ -122,6 +122,14 @@ public function set_up() { ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_taxonomy_rest_routes_for_test( array( 'post_tag' ) ); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/wp/v2/tags', $routes ); diff --git a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php index 4d8a57d5602b8..d0712e1a75bc3 100644 --- a/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php +++ b/tests/phpunit/tests/rest-api/rest-taxonomies-controller.php @@ -23,6 +23,15 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$contributor_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Taxonomies_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); diff --git a/tests/phpunit/tests/rest-api/rest-term-meta-fields.php b/tests/phpunit/tests/rest-api/rest-term-meta-fields.php index 737fa90d84633..65d5a4cc13c90 100644 --- a/tests/phpunit/tests/rest-api/rest-term-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-term-meta-fields.php @@ -193,7 +193,8 @@ public function set_up() { /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; $wp_rest_server = new Spy_REST_Server(); - do_action( 'rest_api_init', $wp_rest_server ); + $this->do_rest_api_init_without_initial_routes(); + $this->register_taxonomy_rest_routes_for_test( array( 'category', 'post_tag', 'customtax' ) ); } protected function grant_write_permission() { @@ -326,7 +327,8 @@ public function test_get_value_types() { /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; $wp_rest_server = new Spy_REST_Server(); - do_action( 'rest_api_init', $wp_rest_server ); + $this->do_rest_api_init_without_initial_routes(); + $this->register_taxonomy_rest_routes_for_test( array( 'category', 'post_tag', 'customtax' ) ); add_term_meta( self::$category_id, 'test_string', 42 ); add_term_meta( self::$category_id, 'test_number', '42' ); diff --git a/tests/phpunit/tests/rest-api/rest-themes-controller.php b/tests/phpunit/tests/rest-api/rest-themes-controller.php index aeaf92a8a27a1..02306d2cc2389 100644 --- a/tests/phpunit/tests/rest-api/rest-themes-controller.php +++ b/tests/phpunit/tests/rest-api/rest-themes-controller.php @@ -144,6 +144,15 @@ public function set_up() { switch_theme( 'rest-api' ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Themes_Controller(); + $controller->register_routes(); + } + /** * Theme routes should be registered correctly. * diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index b78e95b95f48d..499f4f40f4058 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -165,6 +165,15 @@ public function set_up() { $this->endpoint = new WP_REST_Users_Controller(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Users_Controller(); + $controller->register_routes(); + } + public function test_register_routes() { $routes = rest_get_server()->get_routes(); diff --git a/tests/phpunit/tests/rest-api/rest-widget-types-controller.php b/tests/phpunit/tests/rest-api/rest-widget-types-controller.php index 3003c2e9741de..7d9cc394270a5 100644 --- a/tests/phpunit/tests/rest-api/rest-widget-types-controller.php +++ b/tests/phpunit/tests/rest-api/rest-widget-types-controller.php @@ -57,6 +57,15 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$subscriber_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Widget_Types_Controller(); + $controller->register_routes(); + } + private function setup_widget( $id_base, $number, $settings ) { global $wp_widget_factory; diff --git a/tests/phpunit/tests/rest-api/rest-widgets-controller.php b/tests/phpunit/tests/rest-api/rest-widgets-controller.php index 27a58eb638f9c..afbd4a2089039 100644 --- a/tests/phpunit/tests/rest-api/rest-widgets-controller.php +++ b/tests/phpunit/tests/rest-api/rest-widgets-controller.php @@ -139,6 +139,21 @@ static function () { ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Widget_Types_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Sidebars_Controller(); + $controller->register_routes(); + + $controller = new WP_REST_Widgets_Controller(); + $controller->register_routes(); + } + public function clean_up_global_scope() { global $wp_widget_factory, diff --git a/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php b/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php index a5b76af3438d2..e1c8ca17932d0 100644 --- a/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php +++ b/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php @@ -65,6 +65,15 @@ public static function wpTearDownAfterClass() { self::delete_user( self::$subscriber_id ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( self::POST_TYPE ) ); + $this->register_taxonomy_rest_routes_for_test( array( 'nav_menu' ) ); + } + /** * */ diff --git a/tests/phpunit/tests/rest-api/wpRestMenuLocationsController.php b/tests/phpunit/tests/rest-api/wpRestMenuLocationsController.php index 1a0ffc4bb536b..c5ccc6459df62 100644 --- a/tests/phpunit/tests/rest-api/wpRestMenuLocationsController.php +++ b/tests/phpunit/tests/rest-api/wpRestMenuLocationsController.php @@ -42,6 +42,15 @@ public function set_up() { } } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Menu_Locations_Controller(); + $controller->register_routes(); + } + /** * Register nav menu locations. * diff --git a/tests/phpunit/tests/rest-api/wpRestMenusController.php b/tests/phpunit/tests/rest-api/wpRestMenusController.php index 864b09417d2cb..e823fd757154a 100644 --- a/tests/phpunit/tests/rest-api/wpRestMenusController.php +++ b/tests/phpunit/tests/rest-api/wpRestMenusController.php @@ -64,6 +64,17 @@ public static function wpSetUpBeforeClass( $factory ) { ); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_taxonomy_rest_routes_for_test( array( self::TAXONOMY ) ); + + $controller = new WP_REST_Menu_Locations_Controller(); + $controller->register_routes(); + } + /** * */ diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php index d3cbf91260488..ab4877f6e2639 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateAutosavesController.php @@ -39,6 +39,14 @@ class Tests_REST_wpRestTemplateAutosavesController extends WP_Test_REST_Controll */ const PARENT_POST_TYPE = 'wp_template'; + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( self::TEMPLATE_POST_TYPE, self::TEMPLATE_PART_POST_TYPE ) ); + } + /** * Admin user ID. * diff --git a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php index e8a18b275e7cd..b91e6fc0cc1a0 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplateRevisionsController.php @@ -108,6 +108,14 @@ class Tests_REST_wpRestTemplateRevisionsController extends WP_Test_REST_Controll */ private static $template_part_revisions = array(); + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $this->register_post_type_rest_routes_for_test( array( self::TEMPLATE_POST_TYPE, self::TEMPLATE_PART_POST_TYPE ) ); + } + /** * Create fake data before our tests run. * diff --git a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php index 0bbd6b151c6c0..b315130f4b117 100644 --- a/tests/phpunit/tests/rest-api/wpRestTemplatesController.php +++ b/tests/phpunit/tests/rest-api/wpRestTemplatesController.php @@ -98,6 +98,18 @@ public function tear_down() { parent::tear_down(); } + protected function should_create_initial_rest_routes() { + return false; + } + + protected function register_initial_rest_routes_for_test() { + $controller = new WP_REST_Templates_Controller( 'wp_template' ); + $controller->register_routes(); + + $controller = new WP_REST_Templates_Controller( 'wp_template_part' ); + $controller->register_routes(); + } + /** * @covers WP_REST_Templates_Controller::register_routes * @ticket 54596 diff --git a/tests/phpunit/tests/rest-api/wpRestUrlDetailsController.php b/tests/phpunit/tests/rest-api/wpRestUrlDetailsController.php index 7187d1696c05a..6b04aca4b93c8 100644 --- a/tests/phpunit/tests/rest-api/wpRestUrlDetailsController.php +++ b/tests/phpunit/tests/rest-api/wpRestUrlDetailsController.php @@ -88,8 +88,11 @@ public static function wpTearDownAfterClass() { public function set_up() { parent::set_up(); - add_filter( 'pre_http_request', array( $this, 'mock_success_request_to_remote_url' ), 10, 3 ); + if ( ! $this->should_register_rest_routes() ) { + return; + } + add_filter( 'pre_http_request', array( $this, 'mock_success_request_to_remote_url' ), 10, 3 ); // Disables usage of cache during major of tests. add_filter( 'pre_site_transient_' . $this->get_transient_name(), '__return_null' ); } @@ -99,6 +102,25 @@ public function tear_down() { parent::tear_down(); } + protected function should_register_rest_routes() { + return ! in_array( + $this->getName( false ), + array( + 'test_get_title', + 'test_get_icon', + 'test_get_description', + 'test_get_image', + 'test_context_param', + 'test_get_item', + 'test_create_item', + 'test_update_item', + 'test_delete_item', + 'test_prepare_item', + ), + true + ); + } + /** * @covers WP_REST_URL_Details_Controller::register_routes * diff --git a/tests/phpunit/tests/sitemaps/sitemaps.php b/tests/phpunit/tests/sitemaps/sitemaps.php index 85f9965245842..501d2d6cb7cf1 100644 --- a/tests/phpunit/tests/sitemaps/sitemaps.php +++ b/tests/phpunit/tests/sitemaps/sitemaps.php @@ -464,12 +464,12 @@ public function test_sitemaps_enabled() { /** * @ticket 50643 - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_disable_sitemap_should_return_404() { add_filter( 'wp_sitemaps_enabled', '__return_false' ); + wp_sitemaps_get_server(); + $this->go_to( home_url( '/?sitemap=index' ) ); wp_sitemaps_get_server()->render_sitemaps(); @@ -481,8 +481,6 @@ public function test_disable_sitemap_should_return_404() { /** * @ticket 50643 - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_empty_url_list_should_return_404() { wp_register_sitemap_provider( 'foo', new WP_Sitemaps_Empty_Test_Provider( 'foo' ) ); diff --git a/tests/phpunit/tests/theme.php b/tests/phpunit/tests/theme.php index aa67f7189c64d..c0e5c540a6915 100644 --- a/tests/phpunit/tests/theme.php +++ b/tests/phpunit/tests/theme.php @@ -276,8 +276,6 @@ public function test_default_theme_matches_constant() { * * @coversNothing * - * @runInSeparateProcess - * @preserveGlobalState disabled */ public function test_default_themes_are_included_in_new_files() { require_once ABSPATH . 'wp-admin/includes/update-core.php'; diff --git a/tests/phpunit/tests/unicode/wpHasNoncharacters.php b/tests/phpunit/tests/unicode/wpHasNoncharacters.php index 073b57b65134e..0fb0b43d2a333 100644 --- a/tests/phpunit/tests/unicode/wpHasNoncharacters.php +++ b/tests/phpunit/tests/unicode/wpHasNoncharacters.php @@ -68,22 +68,13 @@ public function test_detects_non_characters_when_string_contains_invalid_utf8() * @ticket 63863 */ public function test_avoids_false_positives() { - // Get all the noncharacters in one long string, each surrounded on both sides by null bytes. - $noncharacters = implode( - "\x00", - array_map( - static function ( $c ) { - return "\x00{$c}"; - }, - array_column( array_values( iterator_to_array( self::data_noncharacters() ) ), 0 ) - ) - ) . "\x00"; - $this->assertFalse( wp_has_noncharacters( "\x00" ), 'Falsely detected noncharacter in U+0000' ); + $characters = ''; + for ( $code_point = 1; $code_point <= 0x10FFFF; $code_point++ ) { // Surrogate halves are invalid UTF-8. if ( $code_point >= 0xD800 && $code_point <= 0xDFFF ) { @@ -93,18 +84,20 @@ static function ( $c ) { $char = mb_chr( $code_point ); $hex_char = strtoupper( str_pad( dechex( $code_point ), 4, '0', STR_PAD_LEFT ) ); - if ( str_contains( $noncharacters, $char ) ) { + if ( ( $code_point >= 0xFDD0 && $code_point <= 0xFDEF ) || 0xFFFE === ( $code_point & 0xFFFE ) ) { $this->assertTrue( wp_has_noncharacters( $char ), "Failed to detect noncharacter as test verification for U+{$hex_char}" ); } else { - $this->assertFalse( - wp_has_noncharacters( $char ), - "Falsely detected noncharacter in U+{$hex_char}." - ); + $characters .= $char; } } + + $this->assertFalse( + wp_has_noncharacters( $characters ), + 'Falsely detected a noncharacter in a string containing every valid Unicode character.' + ); } /** diff --git a/tests/phpunit/tests/utils.php b/tests/phpunit/tests/utils.php index 6a4f0d45f6dc7..09520653d582c 100644 --- a/tests/phpunit/tests/utils.php +++ b/tests/phpunit/tests/utils.php @@ -55,4 +55,103 @@ public function test_mask_input_value() { EOF; $this->assertSame( $expected, mask_input_value( $in ) ); } + + /** + * @covers WP_UnitTestCase_Base::reset_core_registrations + */ + public function test_core_registration_snapshot_restore_uses_clean_clones() { + self::$core_registration_snapshots = array(); + + $this->reset_core_registrations(); + + $GLOBALS['wp_post_types']['post']->labels->name = 'Mutated Posts'; + $GLOBALS['wp_taxonomies']['category']->labels->name = 'Mutated Categories'; + $GLOBALS['_wp_post_type_features']['post']['title'] = false; + + $this->reset_core_registrations(); + + $this->assertNotSame( 'Mutated Posts', get_post_type_object( 'post' )->labels->name ); + $this->assertNotSame( 'Mutated Categories', get_taxonomy( 'category' )->labels->name ); + $this->assertNotFalse( $GLOBALS['_wp_post_type_features']['post']['title'] ); + } +} + +/** + * Tests registration reset behavior that must happen before parent setup. + * + * @group testsuite + */ +class Tests_Utils_Core_Registration_Reset extends WP_UnitTestCase { + + /** + * Primes the registration snapshot before this class adds label filters + * ahead of parent setup. + */ + public static function set_up_before_class() { + parent::set_up_before_class(); + + self::$core_registration_snapshots = array(); + + $testcase = new self( 'test_core_registration_reset_cache_is_bypassed_for_label_filters_before_parent_setup' ); + $testcase->reset_core_registrations(); + } + + /** + * Adds label filters before the base setup reset runs. + */ + public function set_up() { + if ( ! self::$hooks_saved ) { + $this->_backup_hooks(); + } + + add_filter( 'post_type_labels_post', array( $this, 'filter_post_type_labels' ) ); + add_filter( 'taxonomy_labels_category', array( $this, 'filter_taxonomy_labels' ) ); + + parent::set_up(); + + remove_filter( 'post_type_labels_post', array( $this, 'filter_post_type_labels' ) ); + remove_filter( 'taxonomy_labels_category', array( $this, 'filter_taxonomy_labels' ) ); + } + + /** + * Removes filters after parent teardown restores the saved hooks. + */ + public function tear_down() { + parent::tear_down(); + + remove_filter( 'post_type_labels_post', array( $this, 'filter_post_type_labels' ) ); + remove_filter( 'taxonomy_labels_category', array( $this, 'filter_taxonomy_labels' ) ); + } + + /** + * @covers WP_UnitTestCase_Base::reset_core_registrations + */ + public function test_core_registration_reset_cache_is_bypassed_for_label_filters_before_parent_setup() { + $this->assertSame( 'Filtered Posts', get_post_type_object( 'post' )->labels->name ); + $this->assertSame( 'Filtered Categories', get_taxonomy( 'category' )->labels->name ); + } + + /** + * Filters core post type labels. + * + * @param object $labels Post type labels. + * @return object Filtered labels. + */ + public function filter_post_type_labels( $labels ) { + $labels->name = 'Filtered Posts'; + + return $labels; + } + + /** + * Filters core taxonomy labels. + * + * @param object $labels Taxonomy labels. + * @return object Filtered labels. + */ + public function filter_taxonomy_labels( $labels ) { + $labels->name = 'Filtered Categories'; + + return $labels; + } }