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;
+ }
}