' )
+ && str_contains( $html, 'View all drafts' )
+ && str_contains( $html, 'edit.php?post_status=draft' )
+ && 3 === substr_count( $html, '
' )
+ && 3 === substr_count( $html, '
' )
+ && self::strings_in_order( $html, array_map( 'strval', $first_ids ) )
+ && ! str_contains( strtolower( $html ), ' Post ' . self::hostile_text( $ctx->fork( 'post-title' ) ),
+ 'post_status' => 'publish',
+ 'post_type' => 'post',
+ 'post_name' => 'commented-' . self::slug( $ctx->fork( 'post-slug' ), 'post' ),
+ )
+ );
+ $comment_id = self::insert_comment(
+ array(
+ 'comment_post_ID' => $post_id,
+ 'comment_author' => 'Author & "quoted" ' . self::slug( $ctx->fork( 'author' ), 'author' ),
+ 'comment_author_email' => 'author@example.test',
+ 'comment_author_url' => 'javascript:alert(1)',
+ 'comment_date' => '2026-06-20 12:00:00',
+ 'comment_date_gmt' => '2026-06-20 12:00:00',
+ 'comment_content' => 'Comment body ' . self::hostile_text( $ctx->fork( 'content' ) ),
+ 'comment_approved' => '0',
+ 'comment_type' => 'comment',
+ 'user_id' => $user_id,
+ )
+ );
+
+ $comment_snapshot = self::snapshot_globals( array( 'comment' ) );
+ $comment = \get_comment( $comment_id );
+
+ $result = self::with_capabilities(
+ array( 'edit_comment', 'edit_others_posts', 'edit_post', 'edit_posts', 'edit_published_posts', 'moderate_comments', 'read_post', 'read' ),
+ static function () use ( $comment ): array {
+ ob_start();
+ \_wp_dashboard_recent_comments_row( $comment, true );
+ return array(
+ 'html' => (string) ob_get_clean(),
+ 'globalAfter' => array(
+ 'exists' => array_key_exists( 'comment', $GLOBALS ),
+ 'value' => $GLOBALS['comment'] ?? null,
+ ),
+ );
+ }
+ );
+
+ self::restore_globals( $comment_snapshot );
+ $html = $result['html'];
+ $lower = strtolower( $html );
+
+ self::collect_failure(
+ $failures,
+ str_contains( $html, 'id="comment-' . $comment_id . '"' )
+ && str_contains( $html, 'comment.php?action=approvecomment' )
+ && str_contains( $html, 'comment.php?action=unapprovecomment' )
+ && str_contains( $html, 'comment.php?action=editcomment&c=' . $comment_id )
+ && str_contains( $html, 'comment.php?action=spamcomment' )
+ && str_contains( $html, 'comment.php?action=trashcomment' )
+ && str_contains( $html, 'class="comment-link"' )
+ && str_contains( $html, 'data-wp-lists=' )
+ && str_contains( $html, 'commentReply.open' )
+ && ! str_contains( $lower, '',
+ 'comment_approved' => '1',
+ 'comment_type' => 'comment',
+ 'comment_parent' => 0,
+ 'user_id' => $user_id,
+ )
+ );
+ }
+
+ $query_events = array();
+ $query_filter = static function ( $comment_data, \WP_Comment_Query $query ) use ( &$query_events, $comments ) {
+ $query_events[] = array(
+ 'number' => $query->query_vars['number'] ?? null,
+ 'offset' => $query->query_vars['offset'] ?? null,
+ 'status' => $query->query_vars['status'] ?? null,
+ );
+
+ return $comments;
+ };
+ $reply_filter = static function (): string {
+ return '';
+ };
+
+ \add_filter( 'comments_pre_query', $query_filter, 10, 2 );
+ \add_filter( 'wp_comment_reply', $reply_filter, 10, 0 );
+ try {
+ $result = self::with_capabilities(
+ array( 'read_post', 'read' ),
+ static function (): array {
+ ob_start();
+ $return = \wp_dashboard_recent_comments( 2 );
+ return array(
+ 'return' => $return,
+ 'html' => (string) ob_get_clean(),
+ );
+ }
+ );
+ } finally {
+ \remove_filter( 'wp_comment_reply', $reply_filter, 10 );
+ \remove_filter( 'comments_pre_query', $query_filter, 10 );
+ }
+
+ $html = $result['html'];
+
+ self::collect_failure(
+ $failures,
+ true === $result['return']
+ && 1 === count( $query_events )
+ && 10 === (int) ( $query_events[0]['number'] ?? 0 )
+ && 0 === (int) ( $query_events[0]['offset'] ?? -1 )
+ && 'approve' === ( $query_events[0]['status'] ?? null )
+ && str_contains( $html, 'id="latest-comments"' )
+ && str_contains( $html, 'id="the-comment-list"' )
+ && 2 === substr_count( $html, '';
+ echo '' . \esc_html( \wp_strip_all_tags( $called_feed_args['title'] ?? '' ) ) . '';
+ echo '';
+ echo '';
+ };
+
+ $loading_return = null;
+ $ajax_return = null;
+ $cached_return = null;
+ $loading = '';
+ $loading_calls = array();
+ $ajax = '';
+ $cached = '';
+ $cached_value = false;
+ $cleaned_value = null;
+ $http_requests = array();
+ $http_filter = static function ( $preempt, array $parsed_args, string $url ) use ( &$http_requests ) {
+ $http_requests[] = array(
+ 'url' => $url,
+ 'method' => $parsed_args['method'] ?? null,
+ );
+
+ return new \WP_Error( 'component_fuzz_unexpected_http', 'Component fuzz blocked an unexpected dashboard RSS HTTP request.' );
+ };
+
+ \add_filter( 'pre_http_request', $http_filter, 10, 3 );
+ try {
+ \delete_transient( $cache_key );
+ $loading = self::capture_output(
+ static function () use ( $callback, $feeds, $feed_args, $widget_id, &$loading_return ): void {
+ $loading_return = \wp_dashboard_cached_rss_widget( $widget_id, $callback, $feeds, $feed_args );
+ }
+ );
+ $loading_calls = $calls;
+
+ $ajax_filter = static function (): bool {
+ return true;
+ };
+
+ \add_filter( 'wp_doing_ajax', $ajax_filter );
+ try {
+ $ajax = self::capture_output(
+ static function () use ( $callback, $feeds, $feed_args, $widget_id, &$ajax_return ): void {
+ $ajax_return = \wp_dashboard_cached_rss_widget( $widget_id, $callback, $feeds, $feed_args );
+ }
+ );
+ } finally {
+ \remove_filter( 'wp_doing_ajax', $ajax_filter );
+ }
+
+ $cached_value = \get_transient( $cache_key );
+ $cached = self::capture_output(
+ static function () use ( $callback, $feeds, $feed_args, $widget_id, &$cached_return ): void {
+ $cached_return = \wp_dashboard_cached_rss_widget( $widget_id, $callback, $feeds, $feed_args );
+ }
+ );
+ \delete_transient( $cache_key );
+ $cleaned_value = \get_transient( $cache_key );
+ } finally {
+ \remove_filter( 'pre_http_request', $http_filter, 10 );
+ \delete_transient( $cache_key );
+ }
+
+ self::collect_failure(
+ $failures,
+ array() === $http_requests
+ && false === \has_filter( 'pre_http_request', $http_filter ),
+ 'cached dashboard RSS widget does not issue HTTP requests and removes the HTTP tripwire',
+ array(
+ 'httpRequests' => $http_requests,
+ 'filter' => \has_filter( 'pre_http_request', $http_filter ),
+ )
+ );
+
+ self::collect_failure(
+ $failures,
+ false === $cleaned_value,
+ 'cached dashboard RSS widget transient is explicitly cleaned after replay',
+ array( 'cleanedValue' => self::preview( $cleaned_value ) )
+ );
+
+ self::collect_failure(
+ $failures,
+ false === $loading_return
+ && str_contains( $loading, 'widget-loading hide-if-no-js' )
+ && str_contains( $loading, 'This widget requires JavaScript.' )
+ && array() === $loading_calls,
+ 'cached dashboard RSS widget emits loading fallback without AJAX or cache',
+ array(
+ 'return' => $loading_return,
+ 'html' => self::preview_string( $loading ),
+ 'calls' => $loading_calls,
+ )
+ );
+
+ self::collect_failure(
+ $failures,
+ true === $ajax_return
+ && 1 === count( $calls )
+ && $widget_id === ( $calls[0]['widgetId'] ?? null )
+ && array_keys( $feeds ) === ( $calls[0]['checkUrls'] ?? null )
+ && str_contains( $ajax, 'class="cfz-dashboard-rss"' )
+ && str_contains( $ajax, 'data-rss-token="' . \esc_attr( $token ) . '"' )
+ && $ajax === $cached_value,
+ 'cached dashboard RSS widget calls the callback only during AJAX generation and stores exact output',
+ array(
+ 'return' => $ajax_return,
+ 'calls' => $calls,
+ 'html' => self::preview_string( $ajax ),
+ 'cache' => self::preview( $cached_value ),
+ )
+ );
+
+ self::collect_failure(
+ $failures,
+ true === $cached_return
+ && $ajax === $cached
+ && 1 === count( $calls )
+ && false === \has_filter( 'wp_doing_ajax', $ajax_filter ),
+ 'cached dashboard RSS widget returns cached output without rerunning the callback and removes AJAX filter',
+ array(
+ 'return' => $cached_return,
+ 'cachedHtml' => self::preview_string( $cached ),
+ 'calls' => $calls,
+ 'filter' => \has_filter( 'wp_doing_ajax', $ajax_filter ),
+ )
+ );
+
+ return self::result(
+ $ctx,
+ 'admin-dashboard.helpers.cached-rss-widget-cache-branches',
+ $failures,
+ array(
+ 'widgetId' => $widget_id,
+ 'cacheKey' => $cache_key,
+ )
+ );
+ }
+
+ private static function check_browser_nag_remote_cache( \ComponentFuzz\FuzzContext $ctx ): array {
+ $missing = self::missing_browser_nag_requirements();
+ if ( array() !== $missing ) {
+ return $ctx->skip(
+ 'admin-dashboard.helpers.browser-nag-remote-cache',
+ 'Browser Happy dashboard helper dependencies are unavailable in this checkout.',
+ array( 'missing' => $missing )
+ );
+ }
+
+ $failures = array();
+ $requests = array();
+ $case_summaries = array();
+ $active_response = null;
+ $active_failure = null;
+ $expected_url = self::browser_happy_endpoint();
+ $expected_agent = 'WordPress/' . \wp_get_wp_version() . '; ' . \home_url( '/' );
+ $transients = array();
+ $global_snapshot = self::snapshot_globals( array( '_SERVER', 'is_IE' ) );
+ $options_before = self::options_snapshot();
+ $http_filter = static function ( $preempt, array $parsed_args, string $url ) use ( &$active_failure, &$active_response, &$requests ) {
+ $requests[] = array(
+ 'url' => $url,
+ 'method' => $parsed_args['method'] ?? null,
+ 'body' => $parsed_args['body'] ?? null,
+ 'userAgent' => $parsed_args['user-agent'] ?? null,
+ 'headers' => $parsed_args['headers'] ?? null,
+ );
+
+ if ( null === $active_response ) {
+ return new \WP_Error( 'component_fuzz_unexpected_browser_http', 'Component fuzz blocked an unexpected Browser Happy HTTP request.' );
+ }
+
+ if ( is_array( $active_failure ) ) {
+ if ( 'wp-error' === ( $active_failure['type'] ?? '' ) ) {
+ return new \WP_Error( 'component_fuzz_browser_http_failure', 'Generated Browser Happy HTTP failure.' );
+ }
+
+ return array(
+ 'headers' => array(),
+ 'body' => $active_failure['body'] ?? '',
+ 'response' => array(
+ 'code' => $active_failure['code'] ?? 500,
+ 'message' => $active_failure['message'] ?? 'Generated failure',
+ ),
+ 'cookies' => array(),
+ 'filename' => null,
+ );
+ }
+
+ return array(
+ 'headers' => array(),
+ 'body' => \wp_json_encode( $active_response ),
+ 'response' => array(
+ 'code' => 200,
+ 'message' => 'OK',
+ ),
+ 'cookies' => array(),
+ 'filename' => null,
+ );
+ };
+ $cases = array(
+ 'insecure' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-insecure' ), 'Chrome' ),
+ 'response' => self::browser_response_fixture( $ctx->fork( 'response-insecure' ), true, true ),
+ 'ssl' => true,
+ 'expectsClass' => true,
+ 'ie' => false,
+ 'messageFragment' => 'insecure version',
+ ),
+ 'recommended' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-recommended' ), 'Firefox' ),
+ 'response' => self::browser_response_fixture( $ctx->fork( 'response-recommended' ), true, false ),
+ 'ssl' => false,
+ 'expectsClass' => false,
+ 'ie' => false,
+ 'messageFragment' => 'old version',
+ ),
+ 'ie' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-ie' ), 'MSIE' ),
+ 'response' => self::browser_response_fixture( $ctx->fork( 'response-ie' ), true, true ),
+ 'ssl' => true,
+ 'expectsClass' => true,
+ 'ie' => true,
+ 'messageFragment' => 'Internet Explorer does not give you the best WordPress experience',
+ ),
+ );
+
+ foreach ( $cases as $case ) {
+ $transients[] = 'browser_' . md5( $case['userAgent'] );
+ }
+
+ \add_filter( 'pre_http_request', $http_filter, 10, 3 );
+ try {
+ foreach ( $cases as $label => $case ) {
+ $user_agent = $case['userAgent'];
+ $response = $case['response'];
+ $transient = 'browser_' . md5( $user_agent );
+
+ \delete_site_transient( $transient );
+
+ $active_response = $response;
+ $active_failure = null;
+ $_SERVER['HTTP_USER_AGENT'] = $user_agent;
+ $_SERVER['HTTPS'] = $case['ssl'] ? 'on' : 'off';
+ $_SERVER['SERVER_PORT'] = $case['ssl'] ? '443' : '80';
+ $GLOBALS['is_IE'] = (bool) $case['ie'];
+ $request_count_before = count( $requests );
+ $first_response = \wp_check_browser_version();
+ $cached_response = \get_site_transient( $transient );
+ $timeout_option = \get_site_option( '_site_transient_timeout_' . $transient );
+ $timeout_remaining = is_numeric( $timeout_option ) ? ( (int) $timeout_option - time() ) : null;
+ $second_response = \wp_check_browser_version();
+ $base_classes = array( 'postbox', 'cfz-browser-nag' );
+ $classes = \dashboard_browser_nag_class( $base_classes );
+ $nag_html = self::capture_output(
+ static function (): void {
+ \wp_dashboard_browser_nag();
+ }
+ );
+ $request_count_after_helpers = count( $requests );
+ $request = $requests[ $request_count_before ] ?? array();
+ $expected_image = ( $case['ssl'] && ! empty( $response['img_src_ssl'] ) ) ? $response['img_src_ssl'] : $response['img_src'];
+ $expected_browser_name_link = sprintf(
+ '%s',
+ \esc_url( $response['update_url'] ),
+ \esc_html( $response['name'] )
+ );
+ $expected_update_action_link = $case['ie']
+ ? 'browse happy'
+ : sprintf(
+ 'Update %2$s',
+ \esc_attr( $response['update_url'] ),
+ \esc_html( $response['name'] )
+ );
+ $expected_image_fragment = '
';
+ $has_insecure_class = in_array( 'browser-insecure', $classes, true );
+ $base_classes_preserved = array_values( array_intersect( $base_classes, $classes ) ) === $base_classes;
+ $cleaned_response = false;
+
+ \delete_site_transient( $transient );
+ $cleaned_response = \get_site_transient( $transient );
+
+ $case_summaries[ $label ] = array(
+ 'transient' => $transient,
+ 'requestCount' => $request_count_after_helpers - $request_count_before,
+ 'request' => $request,
+ 'classes' => $classes,
+ 'htmlHash' => sha1( $nag_html ),
+ 'timeout' => $timeout_remaining,
+ );
+
+ self::collect_failure(
+ $failures,
+ 1 === ( $request_count_after_helpers - $request_count_before )
+ && $response === $first_response
+ && $response === $second_response
+ && $response === $cached_response
+ && $expected_url === ( $request['url'] ?? null )
+ && 'POST' === ( $request['method'] ?? null )
+ && array( 'useragent' => $user_agent ) === ( $request['body'] ?? null )
+ && $expected_agent === ( $request['userAgent'] ?? null )
+ && is_int( $timeout_remaining )
+ && WEEK_IN_SECONDS - 5 <= $timeout_remaining
+ && WEEK_IN_SECONDS >= $timeout_remaining,
+ 'wp_check_browser_version() posts the generated user agent once, stores the site transient for one week, and reuses cached results',
+ array(
+ 'label' => $label,
+ 'expectedUrl' => $expected_url,
+ 'expectedAgent' => $expected_agent,
+ 'request' => $request,
+ 'firstResponse' => $first_response,
+ 'cachedResponse' => $cached_response,
+ 'timeout' => $timeout_option,
+ )
+ );
+
+ self::collect_failure(
+ $failures,
+ $base_classes_preserved
+ && $case['expectsClass'] === $has_insecure_class
+ && ( ! $case['expectsClass'] || count( $base_classes ) + 1 === count( $classes ) )
+ && ( $case['expectsClass'] || count( $base_classes ) === count( $classes ) ),
+ 'dashboard_browser_nag_class() preserves caller classes and adds browser-insecure only for insecure Browse Happy responses',
+ array(
+ 'label' => $label,
+ 'baseClassesPreserved' => $base_classes_preserved,
+ 'classes' => $classes,
+ )
+ );
+
+ self::collect_failure(
+ $failures,
+ str_contains( $nag_html, $case['messageFragment'] )
+ && ( $case['ie'] || str_contains( $nag_html, $expected_browser_name_link ) )
+ && str_contains( $nag_html, $expected_update_action_link )
+ && str_contains( $nag_html, $expected_image_fragment )
+ && str_contains( $nag_html, 'browser-update-nag has-browser-icon' )
+ && str_contains( $nag_html, 'browsehappy.com' )
+ && str_contains( $nag_html, 'class="dismiss"' )
+ && ( $case['ie'] || ! str_contains( $nag_html, $response['name'] ) )
+ && ! str_contains( strtolower( $nag_html ), ' & "quoted"',
+ 'version' => '1.' . $ctx->int( 0, 9 ) . '.' . $ctx->int( 0, 99 ),
+ 'current_version' => '120.' . $ctx->int( 0, 9 ) . '.' . $ctx->int( 0, 99 ),
+ 'upgrade' => $upgrade,
+ 'insecure' => $insecure,
+ 'update_url' => 'https://updates.example.test/' . rawurlencode( $token ) . '/download?channel=stable&name=""',
+ 'img_src' => 'http://images.example.test/' . rawurlencode( $token ) . '.png?label="
"',
+ 'img_src_ssl' => 'https://images.example.test/' . rawurlencode( $token ) . '.png?label="
"',
+ );
+ }
+
+ private static function browser_failure_cases( \ComponentFuzz\FuzzContext $ctx ): array {
+ return array(
+ 'wp-error' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-error' ), 'ErrorBrowser' ),
+ 'failure' => array(
+ 'type' => 'wp-error',
+ ),
+ ),
+ 'non-200' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-non-200' ), 'StatusBrowser' ),
+ 'failure' => array(
+ 'body' => \wp_json_encode( self::browser_response_fixture( $ctx->fork( 'response-non-200' ), true, true ) ),
+ 'code' => 503,
+ 'message' => 'Service Unavailable',
+ ),
+ ),
+ 'invalid-json' => array(
+ 'userAgent' => self::browser_user_agent( $ctx->fork( 'ua-invalid-json' ), 'JsonBrowser' ),
+ 'failure' => array(
+ 'body' => '{"not-valid-json":',
+ 'code' => 200,
+ 'message' => 'OK',
+ ),
+ ),
+ );
+ }
+
+ private static function browser_happy_endpoint(): string {
+ $url = 'http://api.wordpress.org/core/browse-happy/1.1/';
+
+ if ( \wp_http_supports( array( 'ssl' ) ) ) {
+ $url = \set_url_scheme( $url, 'https' );
+ }
+
+ return $url;
+ }
+
+ private static function options_snapshot(): ?array {
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_get_options' ) ) {
+ return $GLOBALS['wpdb']->component_fuzz_get_options();
+ }
+
+ return null;
+ }
+
+ private static function reset_runtime(): void {
+ if ( function_exists( 'create_initial_post_types' ) ) {
+ \create_initial_post_types();
+ }
+ if ( function_exists( 'create_initial_taxonomies' ) ) {
+ \create_initial_taxonomies();
+ }
+
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_reset_content' ) ) {
+ $GLOBALS['wpdb']->component_fuzz_reset_content();
+ }
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_reset_options' ) ) {
+ $GLOBALS['wpdb']->component_fuzz_reset_options(
+ array(
+ 'blog_charset' => 'UTF-8',
+ 'blogdescription' => 'Component Fuzz Site',
+ 'blogname' => 'Component Fuzz',
+ 'comments_per_page' => 50,
+ 'date_format' => 'F j, Y',
+ 'default_comments_page' => 'oldest',
+ 'gmt_offset' => 0,
+ 'home' => 'https://example.test',
+ 'page_comments' => 0,
+ 'permalink_structure' => '',
+ 'show_avatars' => 0,
+ 'siteurl' => 'https://example.test',
+ 'time_format' => 'g:i a',
+ 'timezone_string' => 'UTC',
+ )
+ );
+ }
+ if ( function_exists( 'wp_cache_flush' ) ) {
+ \wp_cache_flush();
+ }
+
+ $_COOKIE = array();
+ $_GET = array();
+ $_POST = array();
+ $_REQUEST = array();
+
+ $_SERVER['HTTP_HOST'] = 'example.test';
+ $_SERVER['PHP_SELF'] = '/wp-admin/index.php';
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/index.php';
+
+ $GLOBALS['hook_suffix'] = 'index.php';
+ $GLOBALS['pagenow'] = 'index.php';
+ $GLOBALS['wp_dashboard_control_callbacks'] = array();
+ $GLOBALS['wp_meta_boxes'] = array();
+ $GLOBALS['wp_query'] = new \WP_Query();
+ $GLOBALS['wp_the_query'] = $GLOBALS['wp_query'];
+ if ( class_exists( 'WP_Rewrite' ) ) {
+ $GLOBALS['wp_rewrite'] = new \WP_Rewrite();
+ }
+ $GLOBALS['post'] = null;
+ $GLOBALS['comment'] = null;
+
+ \wp_set_current_user( 0 );
+ self::dashboard_screen();
+ }
+
+ private static function reset_dashboard_boxes(): void {
+ $GLOBALS['wp_dashboard_control_callbacks'] = array();
+ $GLOBALS['wp_meta_boxes'] = array();
+ }
+
+ private static function dashboard_screen(): \WP_Screen {
+ return self::dashboard_screen_for_hook( 'index.php' );
+ }
+
+ private static function dashboard_screen_for_hook( string $hook ): \WP_Screen {
+ \set_current_screen( $hook );
+ $screen = \get_current_screen();
+ if ( ! $screen instanceof \WP_Screen ) {
+ throw new \RuntimeException( 'Could not initialize dashboard screen.' );
+ }
+
+ return $screen;
+ }
+
+ private static function set_screen_columns( \WP_Screen $screen, int $columns ): void {
+ $property = new \ReflectionProperty( \WP_Screen::class, 'columns' );
+ $property->setValue( $screen, $columns );
+ $GLOBALS['screen_layout_columns'] = $columns;
+ }
+
+ private static function column_cases( \ComponentFuzz\FuzzContext $ctx ): array {
+ $columns = array( 0, 1, 2, 3, 4, $ctx->int( 0, 4 ) );
+ $columns = array_values( array_unique( array_map( 'intval', $columns ) ) );
+ sort( $columns );
+
+ return $columns;
+ }
+
+ private static function seed_user( \ComponentFuzz\FuzzContext $ctx ): int {
+ $login = 'cfz_dash_' . self::slug( $ctx, 'user' );
+ $id = \wp_insert_user(
+ array(
+ 'user_login' => $login,
+ 'user_pass' => 'component-fuzz-pass',
+ 'user_email' => $login . '@example.test',
+ 'user_nicename' => $login,
+ 'display_name' => 'Component Fuzz ' . $login,
+ )
+ );
+
+ if ( \is_wp_error( $id ) ) {
+ throw new \RuntimeException( 'Could not seed user: ' . $id->get_error_message() );
+ }
+
+ return (int) $id;
+ }
+
+ private static function insert_post( array $data ): int {
+ global $wpdb;
+
+ $result = $wpdb->insert( $wpdb->posts, $data );
+ if ( ! $result ) {
+ throw new \RuntimeException( 'Could not seed post.' );
+ }
+
+ return (int) $wpdb->insert_id;
+ }
+
+ private static function insert_comment( array $data ): int {
+ global $wpdb;
+
+ $result = $wpdb->insert( $wpdb->comments, $data );
+ if ( ! $result ) {
+ throw new \RuntimeException( 'Could not seed comment.' );
+ }
+
+ return (int) $wpdb->insert_id;
+ }
+
+ private static function recent_post_fixture( \ComponentFuzz\FuzzContext $ctx, int $user_id, string $status, \DateTimeInterface $date, string $label ): \WP_Post {
+ $title = $label . ' ' . self::hostile_text( $ctx->fork( 'title' ) );
+ $id = self::insert_post(
+ array(
+ 'post_author' => $user_id,
+ 'post_date' => $date->format( 'Y-m-d H:i:s' ),
+ 'post_date_gmt' => $date->format( 'Y-m-d H:i:s' ),
+ 'post_modified' => $date->format( 'Y-m-d H:i:s' ),
+ 'post_modified_gmt' => $date->format( 'Y-m-d H:i:s' ),
+ 'post_content' => 'Activity body
' . self::hostile_text( $ctx->fork( 'content' ) ),
+ 'post_title' => $title,
+ 'post_status' => $status,
+ 'post_type' => 'post',
+ 'post_name' => \sanitize_title( $label . '-' . self::slug( $ctx->fork( 'slug' ), 'activity' ) ),
+ )
+ );
+ $post = \get_post( $id );
+ if ( ! $post instanceof \WP_Post ) {
+ throw new \RuntimeException( 'Could not load recent post fixture.' );
+ }
+
+ return $post;
+ }
+
+ private static function post_ids( array $posts ): array {
+ return array_map(
+ static function ( \WP_Post $post ): int {
+ return (int) $post->ID;
+ },
+ $posts
+ );
+ }
+
+ private static function with_capabilities( array $capabilities, callable $callback ) {
+ $cap_filter = static function ( array $allcaps ) use ( $capabilities ): array {
+ foreach ( $capabilities as $capability ) {
+ $allcaps[ $capability ] = true;
+ }
+
+ return $allcaps;
+ };
+
+ \add_filter( 'user_has_cap', $cap_filter, 10, 4 );
+ try {
+ return $callback();
+ } finally {
+ \remove_filter( 'user_has_cap', $cap_filter, 10 );
+ }
+ }
+
+ private static function find_meta_box( string $page, string $id ): ?array {
+ foreach ( (array) ( $GLOBALS['wp_meta_boxes'][ $page ] ?? array() ) as $context => $priorities ) {
+ foreach ( (array) $priorities as $priority => $boxes ) {
+ if ( isset( $boxes[ $id ] ) && is_array( $boxes[ $id ] ) ) {
+ return array(
+ 'context' => $context,
+ 'priority' => $priority,
+ 'box' => $boxes[ $id ],
+ );
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static function id( \ComponentFuzz\FuzzContext $ctx, string $prefix, int $max = 48 ): string {
+ return strtolower( substr( $prefix . '_' . hash( 'crc32b', (string) $ctx->seed() ), 0, $max ) );
+ }
+
+ private static function slug( \ComponentFuzz\FuzzContext $ctx, string $fallback ): string {
+ $value = preg_replace( '/[^A-Za-z0-9_-]+/', '-', strtolower( $ctx->identifier( 3, 12 ) ) );
+ $value = trim( (string) $value, '-' );
+
+ return '' === $value ? $fallback : $value;
+ }
+
+ private static function hostile_text( \ComponentFuzz\FuzzContext $ctx ): string {
+ return $ctx->text( 0, 24 ) . ' bold & "quoted"';
+ }
+
+ private static function strings_in_order( string $haystack, array $needles ): bool {
+ $offset = 0;
+
+ foreach ( $needles as $needle ) {
+ $position = strpos( $haystack, (string) $needle, $offset );
+ if ( false === $position ) {
+ return false;
+ }
+ $offset = $position + strlen( (string) $needle );
+ }
+
+ return true;
+ }
+
+ private static function all_call_values( array $calls, string $key, $expected ): bool {
+ foreach ( $calls as $call ) {
+ if ( ! array_key_exists( $key, $call ) || $expected !== $call[ $key ] ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static function snapshot_state(): array {
+ $options = null;
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_get_options' ) ) {
+ $options = $GLOBALS['wpdb']->component_fuzz_get_options();
+ }
+
+ return array(
+ 'globals' => self::snapshot_globals(
+ array(
+ '_COOKIE',
+ '_GET',
+ '_POST',
+ '_REQUEST',
+ '_SERVER',
+ 'comment',
+ 'current_screen',
+ 'current_user',
+ 'hook_suffix',
+ 'id',
+ 'pagenow',
+ 'post',
+ 'posts',
+ 'screen_layout_columns',
+ 'user_ID',
+ 'userdata',
+ 'wp_actions',
+ 'wp_current_filter',
+ 'wp_dashboard_control_callbacks',
+ 'wp_filter',
+ 'wp_filters',
+ 'wp_meta_boxes',
+ 'wp_query',
+ 'wp_rewrite',
+ 'wp_the_query',
+ )
+ ),
+ 'options' => $options,
+ 'contentCounts' => self::content_counts(),
+ 'outputBufferLevel' => ob_get_level(),
+ );
+ }
+
+ private static function restore_state( array $snapshot ): void {
+ while ( ob_get_level() > $snapshot['outputBufferLevel'] ) {
+ ob_end_clean();
+ }
+
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_reset_content' ) ) {
+ $GLOBALS['wpdb']->component_fuzz_reset_content();
+ }
+ if ( null !== $snapshot['options'] && isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_reset_options' ) ) {
+ $GLOBALS['wpdb']->component_fuzz_reset_options( $snapshot['options'] );
+ }
+
+ self::restore_globals( $snapshot['globals'] );
+
+ if ( function_exists( 'wp_cache_flush' ) ) {
+ \wp_cache_flush();
+ }
+ }
+
+ private static function state_matches( array $snapshot ): bool {
+ $options = null;
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_get_options' ) ) {
+ $options = $GLOBALS['wpdb']->component_fuzz_get_options();
+ }
+
+ return $snapshot['options'] === $options
+ && $snapshot['contentCounts'] === self::content_counts()
+ && $snapshot['outputBufferLevel'] === ob_get_level()
+ && self::globals_match( $snapshot['globals'] );
+ }
+
+ private static function content_counts(): array {
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_content_counts' ) ) {
+ return $GLOBALS['wpdb']->component_fuzz_content_counts();
+ }
+
+ return array();
+ }
+
+ private static function snapshot_globals( array $names ): array {
+ $snapshot = array();
+ foreach ( $names as $name ) {
+ $snapshot[ $name ] = array(
+ 'exists' => array_key_exists( $name, $GLOBALS ),
+ 'value' => array_key_exists( $name, $GLOBALS ) ? self::clone_value( $GLOBALS[ $name ] ) : null,
+ );
+ }
+
+ return $snapshot;
+ }
+
+ private static function restore_globals( array $snapshot ): void {
+ foreach ( $snapshot as $name => $entry ) {
+ if ( $entry['exists'] ) {
+ $GLOBALS[ $name ] = self::clone_value( $entry['value'] );
+ } else {
+ unset( $GLOBALS[ $name ] );
+ }
+ }
+ }
+
+ private static function globals_match( array $snapshot ): bool {
+ foreach ( $snapshot as $name => $entry ) {
+ $exists = array_key_exists( $name, $GLOBALS );
+ if ( $exists !== $entry['exists'] ) {
+ return false;
+ }
+ if ( $exists && $GLOBALS[ $name ] != $entry['value'] ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static function changed_globals( array $snapshot ): array {
+ $changed = array();
+ foreach ( $snapshot as $name => $entry ) {
+ $exists = array_key_exists( $name, $GLOBALS );
+ if ( $exists !== $entry['exists'] || ( $exists && $GLOBALS[ $name ] != $entry['value'] ) ) {
+ $changed[] = $name;
+ }
+ }
+
+ return $changed;
+ }
+
+ private static function clone_value( $value ) {
+ if ( is_array( $value ) ) {
+ $copy = array();
+ foreach ( $value as $key => $item ) {
+ $copy[ $key ] = self::clone_value( $item );
+ }
+ return $copy;
+ }
+
+ if ( is_object( $value ) ) {
+ try {
+ return clone $value;
+ } catch ( \Throwable $e ) {
+ return $value;
+ }
+ }
+
+ return $value;
+ }
+
+ private static function result( \ComponentFuzz\FuzzContext $ctx, string $invariant, array $failures, array $data = array() ): array {
+ return $ctx->result(
+ $invariant,
+ array() === $failures,
+ $data + array(
+ 'failures' => array_slice( $failures, 0, 8 ),
+ )
+ );
+ }
+
+ private static function collect_failure( array &$failures, bool $ok, string $message, array $data = array() ): void {
+ if ( $ok ) {
+ return;
+ }
+
+ $failures[] = array(
+ 'message' => $message,
+ 'data' => self::preview( $data ),
+ );
+ }
+
+ private static function capture_output( callable $callback ): string {
+ $level = ob_get_level();
+ ob_start();
+ try {
+ $callback();
+ return (string) ob_get_clean();
+ } catch ( \Throwable $e ) {
+ while ( ob_get_level() > $level ) {
+ ob_end_clean();
+ }
+ throw $e;
+ }
+ }
+
+ private static function preview( $value ) {
+ if ( is_string( $value ) ) {
+ return strlen( $value ) > self::PREVIEW_BYTES ? substr( $value, 0, self::PREVIEW_BYTES ) . '...' : $value;
+ }
+ if ( is_array( $value ) ) {
+ $json = \wp_json_encode( $value, JSON_UNESCAPED_SLASHES );
+ return false === $json ? '[array]' : self::preview( $json );
+ }
+ if ( is_object( $value ) ) {
+ return '[object ' . get_class( $value ) . ']';
+ }
+
+ return $value;
+ }
+
+ private static function preview_string( string $value ): array {
+ return array(
+ 'bytes' => strlen( $value ),
+ 'sha1' => sha1( $value ),
+ 'preview' => self::escape_bytes( $value ),
+ );
+ }
+
+ private static function describe_screen( $screen ): array {
+ if ( ! $screen instanceof \WP_Screen ) {
+ return array( 'type' => is_object( $screen ) ? get_class( $screen ) : gettype( $screen ) );
+ }
+
+ return array(
+ 'id' => $screen->id,
+ 'base' => $screen->base,
+ 'inAdmin' => $screen->in_admin(),
+ 'columns' => $screen->get_columns(),
+ 'postType' => $screen->post_type,
+ 'taxonomy' => $screen->taxonomy,
+ );
+ }
+
+ private static function describe_throwable( \Throwable $e ): array {
+ return array(
+ 'class' => get_class( $e ),
+ 'message' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ );
+ }
+
+ private static function escape_bytes( string $value, int $limit = self::PREVIEW_BYTES ): string {
+ $out = '';
+ $length = strlen( $value );
+ $shown = min( $length, $limit );
+
+ for ( $i = 0; $i < $shown; ++$i ) {
+ $byte = ord( $value[ $i ] );
+ if ( 0x5C === $byte ) {
+ $out .= '\\\\';
+ } elseif ( $byte >= 0x20 && $byte <= 0x7E ) {
+ $out .= chr( $byte );
+ } elseif ( 0x0A === $byte ) {
+ $out .= '\\n';
+ } elseif ( 0x0D === $byte ) {
+ $out .= '\\r';
+ } elseif ( 0x09 === $byte ) {
+ $out .= '\\t';
+ } else {
+ $out .= sprintf( '\\x%02X', $byte );
+ }
+ }
+
+ if ( $length > $shown ) {
+ $out .= '...';
+ }
+
+ return $out;
+ }
+}
diff --git a/tools/component-fuzz/surfaces/AdminEditMetaBoxesSurface.php b/tools/component-fuzz/surfaces/AdminEditMetaBoxesSurface.php
new file mode 100644
index 0000000000000..a3d09bb9a31d4
--- /dev/null
+++ b/tools/component-fuzz/surfaces/AdminEditMetaBoxesSurface.php
@@ -0,0 +1,1409 @@
+skip(
+ 'admin-edit-metaboxes.bootstrap-apis-available',
+ 'Required admin edit meta box APIs are unavailable.',
+ array( 'missing' => $missing )
+ ),
+ );
+ }
+
+ $snapshot = self::snapshot_state();
+ $rows = array();
+
+ try {
+ self::reset_runtime();
+
+ $rows[] = self::check_publish_box_matrix( $ctx->fork( 'publish-box' ) );
+ $rows[] = self::check_taxonomy_meta_boxes( $ctx->fork( 'taxonomy-boxes' ) );
+ $rows[] = self::check_content_meta_boxes( $ctx->fork( 'content-boxes' ) );
+ $rows[] = self::check_page_media_and_link_boxes( $ctx->fork( 'page-media-link' ) );
+ $rows[] = self::check_default_registration_matrix( $ctx->fork( 'registration' ) );
+ } catch ( \Throwable $e ) {
+ $rows[] = $ctx->fail(
+ 'admin-edit-metaboxes.surface-no-throw',
+ array( 'throwable' => self::describe_throwable( $e ) )
+ );
+ } finally {
+ self::restore_state( $snapshot );
+ }
+
+ $rows[] = $ctx->result(
+ 'admin-edit-metaboxes.state-restored',
+ self::state_matches( $snapshot ),
+ array(
+ 'trackedGlobals' => array_keys( $snapshot['globals'] ),
+ 'contentCounts' => self::content_counts(),
+ )
+ );
+
+ return $rows;
+ }
+
+ private static function missing_requirements(): array {
+ $missing = array();
+
+ foreach ( array( 'WP_Post', 'WP_Screen', 'WP_User' ) as $class ) {
+ if ( ! class_exists( $class ) ) {
+ $missing[] = "class {$class}";
+ }
+ }
+
+ foreach (
+ array(
+ 'add_filter',
+ 'add_meta_box',
+ 'add_post_meta',
+ 'add_post_type_support',
+ 'add_theme_support',
+ 'attachment_id3_data_meta_box',
+ 'attachment_submit_meta_box',
+ 'comments_open',
+ 'current_user_can',
+ 'do_meta_boxes',
+ 'get_current_screen',
+ 'get_hidden_meta_boxes',
+ 'get_post',
+ 'get_post_meta',
+ 'get_terms_to_edit',
+ 'link_advanced_meta_box',
+ 'link_submit_meta_box',
+ 'link_target_meta_box',
+ 'link_xfn_meta_box',
+ 'page_attributes_meta_box',
+ 'post_author_meta_box',
+ 'post_categories_meta_box',
+ 'post_comment_meta_box',
+ 'post_comment_meta_box_thead',
+ 'post_comment_status_meta_box',
+ 'post_custom_meta_box',
+ 'post_excerpt_meta_box',
+ 'post_format_meta_box',
+ 'post_slug_meta_box',
+ 'post_submit_meta_box',
+ 'post_tags_meta_box',
+ 'post_thumbnail_meta_box',
+ 'post_trackback_meta_box',
+ 'register_and_do_post_meta_boxes',
+ 'register_post_type',
+ 'register_taxonomy',
+ 'remove_filter',
+ 'set_current_screen',
+ 'stick_post',
+ 'update_post_meta',
+ 'wp_insert_comment',
+ 'wp_insert_post',
+ 'wp_insert_term',
+ 'wp_insert_user',
+ 'wp_set_current_user',
+ 'wp_set_object_terms',
+ 'xfn_check',
+ ) as $function
+ ) {
+ if ( ! function_exists( $function ) ) {
+ $missing[] = "function {$function}";
+ }
+ }
+
+ return $missing;
+ }
+
+ private static function check_publish_box_matrix( \ComponentFuzz\FuzzContext $ctx ): array {
+ $failures = array();
+ $user_id = self::seed_user( $ctx->fork( 'user' ) );
+ \wp_set_current_user( $user_id );
+ \set_current_screen( 'post' );
+
+ $statuses = array( 'draft', 'pending', 'publish', 'future', 'private', 'auto-draft' );
+ $cases = array();
+ foreach ( $statuses as $status ) {
+ $case_ctx = $ctx->fork( 'status-' . $status );
+ $cases[] = array(
+ 'status' => $status,
+ 'canPublish' => $case_ctx->bool( 70 ),
+ 'futureDate' => 'future' === $status || $case_ctx->bool( 35 ),
+ 'sticky' => $case_ctx->bool( 45 ),
+ 'password' => in_array( $status, array( 'private', 'auto-draft' ), true ) ? '' : self::safe_text( $case_ctx->fork( 'password' ), 4, 18 ),
+ 'revisions' => $case_ctx->bool( 45 ) ? $case_ctx->int( 2, 7 ) : 0,
+ 'revision_id' => $case_ctx->int( 200, 900 ),
+ );
+ }
+
+ $old_action = $GLOBALS['action'] ?? null;
+ $had_action = array_key_exists( 'action', $GLOBALS );
+ $GLOBALS['action'] = 'edit';
+
+ foreach ( $cases as $case ) {
+ $post_id = self::insert_post(
+ array(
+ 'post_type' => 'post',
+ 'post_status' => $case['status'],
+ 'post_title' => 'Publish box ' . $case['status'],
+ 'post_password' => $case['password'],
+ 'post_date' => $case['futureDate'] ? '2036-05-06 07:08:09' : '2024-05-06 07:08:09',
+ 'post_date_gmt' => $case['futureDate'] ? '2036-05-06 07:08:09' : '2024-05-06 07:08:09',
+ )
+ );
+ if ( $case['sticky'] ) {
+ \stick_post( $post_id );
+ }
+
+ $post = \get_post( $post_id );
+ $box_post = clone $post;
+ $args = array(
+ 'id' => 'submitdiv',
+ 'title' => 'Publish',
+ 'callback' => 'post_submit_meta_box',
+ 'args' => array(
+ 'revisions_count' => $case['revisions'],
+ 'revision_id' => $post_id,
+ ),
+ );
+
+ $html = self::with_capability_denials(
+ $case['canPublish'] ? array() : array( 'publish_posts' ),
+ static function () use ( $box_post, $args ): string {
+ return self::capture_output(
+ static function () use ( $box_post, $args ): void {
+ $had_post = array_key_exists( 'post', $GLOBALS );
+ $old_post = $GLOBALS['post'] ?? null;
+ $GLOBALS['post'] = $box_post;
+ try {
+ \post_submit_meta_box( $box_post, $args );
+ } finally {
+ if ( $had_post ) {
+ $GLOBALS['post'] = $old_post;
+ } else {
+ unset( $GLOBALS['post'] );
+ }
+ }
+ }
+ );
+ }
+ );
+
+ $expected_visibility = self::expected_visibility( $case['status'], $case['password'], $case['sticky'] );
+ $expected_action = self::expected_publish_action( $case );
+
+ self::collect_failure(
+ $failures,
+ str_contains( $html, 'class="submitbox"' )
+ && str_contains( $html, 'id="submitpost"' )
+ && str_contains( $html, 'id="post-status-display"' )
+ && str_contains( $html, 'id="post-visibility-display"' ),
+ 'post_submit_meta_box() renders the expected publish-box shell and status/visibility sections',
+ array( 'case' => $case, 'html' => self::describe_string( $html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $html, $expected_visibility )
+ && str_contains( $html, 'value="' . \esc_attr( $expected_action ) . '"' ),
+ 'post_submit_meta_box() selects generated visibility and primary action branches',
+ array(
+ 'case' => $case,
+ 'expectedVisibility' => $expected_visibility,
+ 'expectedAction' => $expected_action,
+ 'html' => self::describe_string( $html ),
+ )
+ );
+ self::collect_failure(
+ $failures,
+ $case['canPublish'] === str_contains( $html, 'class="edit-visibility hide-if-no-js"' )
+ && $case['canPublish'] === str_contains( $html, 'id="timestampdiv"' ),
+ 'post_submit_meta_box() gates visibility and timestamp editors on publish capability',
+ array( 'case' => $case, 'html' => self::describe_string( $html ) )
+ );
+ self::collect_failure(
+ $failures,
+ ( 0 === $case['revisions'] ) === ! str_contains( $html, 'misc-pub-revisions' ),
+ 'post_submit_meta_box() includes revision browse UI only when revision callback args request it',
+ array( 'case' => $case, 'html' => self::describe_string( $html ) )
+ );
+ self::collect_failure(
+ $failures,
+ 'private' !== $case['status'] || '' === $box_post->post_password,
+ 'post_submit_meta_box() clears the working post password for private visibility',
+ array( 'case' => $case, 'mutatedPassword' => $box_post->post_password )
+ );
+ }
+
+ if ( $had_action ) {
+ $GLOBALS['action'] = $old_action;
+ } else {
+ unset( $GLOBALS['action'] );
+ }
+
+ return self::result_from_failures(
+ $ctx,
+ 'admin-edit-metaboxes.publish-box.status-visibility-actions',
+ $failures,
+ array( 'cases' => $cases )
+ );
+ }
+
+ private static function check_taxonomy_meta_boxes( \ComponentFuzz\FuzzContext $ctx ): array {
+ $failures = array();
+ $user_id = self::seed_user( $ctx->fork( 'user' ) );
+ \wp_set_current_user( $user_id );
+ \set_current_screen( 'post' );
+
+ $flat_tax = self::key( $ctx->fork( 'flat-tax' ), 'cfz_flat_tax', 28 );
+ $tree_tax = self::key( $ctx->fork( 'tree-tax' ), 'cfz_tree_tax', 28 );
+ $assign_cap = 'assign_' . $flat_tax;
+ $edit_cap = 'edit_' . $tree_tax;
+
+ \register_taxonomy(
+ $flat_tax,
+ 'post',
+ array(
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => false,
+ 'labels' => array(
+ 'name' => 'Flat Terms & ' . self::safe_text( $ctx->fork( 'flat-label' ), 2, 10 ),
+ 'add_new_item' => 'Add Flat Term',
+ 'add_or_remove_items' => 'Add or remove flat terms',
+ 'choose_from_most_used' => 'Choose from used flat terms',
+ 'separate_items_with_commas' => 'Separate generated terms',
+ 'no_terms' => 'No generated terms',
+ ),
+ 'capabilities' => array(
+ 'assign_terms' => $assign_cap,
+ ),
+ )
+ );
+ \register_taxonomy(
+ $tree_tax,
+ 'post',
+ array(
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => true,
+ 'labels' => array(
+ 'name' => 'Tree Terms & ' . self::safe_text( $ctx->fork( 'tree-label' ), 2, 10 ),
+ 'all_items' => 'All generated tree terms',
+ 'most_used' => 'Most used generated tree terms',
+ 'add_new_item' => 'Add Tree Term',
+ 'new_item_name' => 'New generated tree term',
+ 'parent_item' => 'Parent generated tree term',
+ 'parent_item_colon' => 'Parent generated tree term:',
+ ),
+ 'capabilities' => array(
+ 'assign_terms' => 'assign_' . $tree_tax,
+ 'edit_terms' => $edit_cap,
+ ),
+ )
+ );
+
+ $post_id = self::insert_post( array( 'post_type' => 'post', 'post_status' => 'publish' ) );
+ $terms = array(
+ \wp_insert_term( 'Alpha ' . self::safe_text( $ctx->fork( 'flat-alpha' ), 2, 8 ), $flat_tax ),
+ \wp_insert_term( 'Beta ' . self::safe_text( $ctx->fork( 'flat-beta' ), 2, 8 ), $flat_tax ),
+ \wp_insert_term( 'Parent ' . self::safe_text( $ctx->fork( 'tree-parent' ), 2, 8 ), $tree_tax ),
+ );
+ $flat_term_ids = array();
+ foreach ( $terms as $term ) {
+ if ( is_array( $term ) && isset( $term['term_id'] ) ) {
+ $flat_term_ids[] = (int) $term['term_id'];
+ }
+ }
+ \wp_set_object_terms( $post_id, array_slice( $flat_term_ids, 0, 2 ), $flat_tax, false );
+
+ $post = \get_post( $post_id );
+ $flat_allowed = self::with_capability_denials(
+ array(),
+ static function () use ( $post, $flat_tax ): string {
+ return self::capture_output(
+ static function () use ( $post, $flat_tax ): void {
+ \post_tags_meta_box( $post, array( 'args' => array( 'taxonomy' => $flat_tax ) ) );
+ }
+ );
+ }
+ );
+ $flat_denied = self::with_capability_denials(
+ array( $assign_cap ),
+ static function () use ( $post, $flat_tax ): string {
+ return self::capture_output(
+ static function () use ( $post, $flat_tax ): void {
+ \post_tags_meta_box( $post, array( 'args' => array( 'taxonomy' => $flat_tax ) ) );
+ }
+ );
+ }
+ );
+
+ $dropdown_args_seen = array();
+ $dropdown_filter = static function ( array $args ) use ( &$dropdown_args_seen ): array {
+ $dropdown_args_seen[] = $args;
+ return $args;
+ };
+ \add_filter( 'post_edit_category_parent_dropdown_args', $dropdown_filter );
+ $tree_allowed = self::with_capability_denials(
+ array(),
+ static function () use ( $post, $tree_tax ): string {
+ return self::capture_output(
+ static function () use ( $post, $tree_tax ): void {
+ \post_categories_meta_box( $post, array( 'args' => array( 'taxonomy' => $tree_tax ) ) );
+ }
+ );
+ }
+ );
+ \remove_filter( 'post_edit_category_parent_dropdown_args', $dropdown_filter );
+
+ $tree_denied = self::with_capability_denials(
+ array( $edit_cap ),
+ static function () use ( $post, $tree_tax ): string {
+ return self::capture_output(
+ static function () use ( $post, $tree_tax ): void {
+ \post_categories_meta_box( $post, array( 'args' => array( 'taxonomy' => $tree_tax ) ) );
+ }
+ );
+ }
+ );
+
+ self::collect_failure(
+ $failures,
+ str_contains( $flat_allowed, 'class="tagsdiv"' )
+ && str_contains( $flat_allowed, 'id="' . \esc_attr( $flat_tax ) . '"' )
+ && str_contains( $flat_allowed, 'name="tax_input[' . $flat_tax . ']"' )
+ && str_contains( $flat_allowed, 'data-wp-taxonomy="' . $flat_tax . '"' )
+ && str_contains( $flat_allowed, 'tagcloud-link' )
+ && ! str_contains( $flat_allowed, ' disabled=' ),
+ 'post_tags_meta_box() renders assignable flat taxonomy controls and the JS tag adder',
+ array( 'taxonomy' => $flat_tax, 'html' => self::describe_string( $flat_allowed ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $flat_denied, ' disabled=' )
+ && ! str_contains( $flat_denied, 'class="newtag form-input-tip"' )
+ && ! str_contains( $flat_denied, 'tagcloud-link' ),
+ 'post_tags_meta_box() disables textarea input and hides add/cloud controls without assign_terms',
+ array( 'taxonomy' => $flat_tax, 'html' => self::describe_string( $flat_denied ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $tree_allowed, 'id="taxonomy-' . $tree_tax . '"' )
+ && str_contains( $tree_allowed, "name='tax_input[{$tree_tax}][]' value='0'" )
+ && str_contains( $tree_allowed, 'data-wp-lists="list:' . $tree_tax . '"' )
+ && str_contains( $tree_allowed, 'id="' . $tree_tax . '-adder"' )
+ && isset( $dropdown_args_seen[0]['taxonomy'] )
+ && $tree_tax === $dropdown_args_seen[0]['taxonomy']
+ && 'new' . $tree_tax . '_parent' === $dropdown_args_seen[0]['name'],
+ 'post_categories_meta_box() renders hierarchical checklist, zero sentinel, adder, and filtered parent dropdown args',
+ array(
+ 'taxonomy' => $tree_tax,
+ 'dropdownArgs' => $dropdown_args_seen,
+ 'html' => self::describe_string( $tree_allowed ),
+ )
+ );
+ self::collect_failure(
+ $failures,
+ ! str_contains( $tree_denied, 'id="' . $tree_tax . '-adder"' )
+ && str_contains( $tree_denied, "name='tax_input[{$tree_tax}][]' value='0'" ),
+ 'post_categories_meta_box() keeps submitted empty-set sentinel but hides the adder without edit_terms',
+ array( 'taxonomy' => $tree_tax, 'html' => self::describe_string( $tree_denied ) )
+ );
+
+ return self::result_from_failures(
+ $ctx,
+ 'admin-edit-metaboxes.taxonomy.flat-and-hierarchical-callbacks',
+ $failures,
+ array(
+ 'flatTaxonomy' => $flat_tax,
+ 'treeTaxonomy' => $tree_tax,
+ )
+ );
+ }
+
+ private static function check_content_meta_boxes( \ComponentFuzz\FuzzContext $ctx ): array {
+ $failures = array();
+ $user_id = self::seed_user( $ctx->fork( 'user' ) );
+ \wp_set_current_user( $user_id );
+ \set_current_screen( 'post' );
+
+ $excerpt = self::safe_text( $ctx->fork( 'excerpt' ), 8, 36 );
+ $slug = 'slug-' . self::key( $ctx->fork( 'slug' ), 'piece', 16 );
+ $post_id = self::insert_post(
+ array(
+ 'post_type' => 'post',
+ 'post_status' => 'publish',
+ 'post_title' => 'Content boxes',
+ 'post_excerpt' => $excerpt,
+ 'post_name' => $slug,
+ 'to_ping' => "https://one.example/a\nhttps://two.example/b",
+ 'pinged' => "https://pinged.example/a?x=\nhttps://pinged.example/b&y=1",
+ 'comment_status' => $ctx->bool() ? 'open' : 'closed',
+ 'ping_status' => $ctx->bool() ? 'open' : 'closed',
+ )
+ );
+ \add_post_meta( $post_id, 'visible_' . self::key( $ctx->fork( 'meta-key' ), 'key', 12 ), self::safe_text( $ctx->fork( 'meta-value' ), 4, 20 ) );
+ \add_post_meta( $post_id, '_protected_' . self::key( $ctx->fork( 'hidden-key' ), 'key', 12 ), 'hidden-value' );
+
+ $post = \get_post( $post_id );
+
+ $excerpt_html = self::capture_output(
+ static function () use ( $post ): void {
+ \post_excerpt_meta_box( $post );
+ }
+ );
+ $trackback_html = self::capture_output(
+ static function () use ( $post ): void {
+ \post_trackback_meta_box( $post );
+ }
+ );
+ $custom_html = self::with_capability_denials(
+ array(),
+ static function () use ( $post ): string {
+ return self::capture_output(
+ static function () use ( $post ): void {
+ \post_custom_meta_box( $post );
+ }
+ );
+ }
+ );
+ $comment_status_html = self::capture_output(
+ static function () use ( $post ): void {
+ \post_comment_status_meta_box( $post );
+ }
+ );
+
+ $filtered_slug = 'filtered-"' . self::key( $ctx->fork( 'filtered-slug' ), 'slug', 14 );
+ $slug_filter = static function () use ( $filtered_slug ): string {
+ return $filtered_slug;
+ };
+ \add_filter( 'editable_slug', $slug_filter );
+ $slug_html = self::capture_output(
+ static function () use ( $post ): void {
+ \post_slug_meta_box( $post );
+ }
+ );
+ \remove_filter( 'editable_slug', $slug_filter );
+
+ $thead = \post_comment_meta_box_thead(
+ array(
+ 'cb' => 'checkbox',
+ 'author' => 'Author',
+ 'comment' => 'Comment',
+ 'response' => 'Response',
+ 'date' => 'Date',
+ )
+ );
+
+ $comment_id = \wp_insert_comment(
+ array(
+ 'comment_post_ID' => $post_id,
+ 'comment_author' => 'Fuzzer',
+ 'comment_author_email' => 'fuzzer@example.test',
+ 'comment_content' => 'Generated comment ' . self::safe_text( $ctx->fork( 'comment' ), 4, 16 ),
+ 'comment_approved' => '1',
+ )
+ );
+ $comment_html = self::capture_output(
+ static function () use ( $post ): void {
+ \post_comment_meta_box( $post );
+ }
+ );
+
+ self::collect_failure(
+ $failures,
+ str_contains( $excerpt_html, 'name="excerpt" id="excerpt"' )
+ && str_contains( $excerpt_html, $excerpt ),
+ 'post_excerpt_meta_box() preserves generated excerpt content inside the textarea',
+ array( 'excerpt' => $excerpt, 'html' => self::describe_string( $excerpt_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $trackback_html, 'name="trackback_url" id="trackback_url"' )
+ && str_contains( $trackback_html, 'https://one.example/a https://two.example/b' )
+ && str_contains( $trackback_html, 'https://pinged.example/a?x=<tag>' )
+ && str_contains( $trackback_html, 'https://pinged.example/b&y=1' ),
+ 'post_trackback_meta_box() normalizes to_ping newlines and escapes pinged URLs',
+ array( 'html' => self::describe_string( $trackback_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $custom_html, 'id="postcustomstuff"' )
+ && str_contains( $custom_html, 'visible_' )
+ && ! str_contains( $custom_html, '_protected_' ),
+ 'post_custom_meta_box() lists editable custom fields while omitting protected meta keys',
+ array( 'html' => self::describe_string( $custom_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $comment_status_html, 'name="advanced_view" type="hidden" value="1"' )
+ && ( ( 'open' === $post->comment_status ) === self::input_is_checked( $comment_status_html, 'comment_status', 'open' ) )
+ && ( ( 'open' === $post->ping_status ) === self::input_is_checked( $comment_status_html, 'ping_status', 'open' ) ),
+ 'post_comment_status_meta_box() mirrors generated comment and ping openness in checkbox state',
+ array(
+ 'commentStatus' => $post->comment_status,
+ 'pingStatus' => $post->ping_status,
+ 'html' => self::describe_string( $comment_status_html ),
+ )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $slug_html, 'name="post_name" type="text"' )
+ && str_contains( $slug_html, 'value="' . \esc_attr( $filtered_slug ) . '"' ),
+ 'post_slug_meta_box() applies editable_slug and escapes the filtered value',
+ array( 'filteredSlug' => $filtered_slug, 'html' => self::describe_string( $slug_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ ! isset( $thead['cb'], $thead['response'] )
+ && isset( $thead['author'], $thead['comment'], $thead['date'] ),
+ 'post_comment_meta_box_thead() removes checkbox/response columns and keeps content columns',
+ array( 'thead' => $thead )
+ );
+ self::collect_failure(
+ $failures,
+ $comment_id > 0
+ && str_contains( $comment_html, 'id="add-new-comment"' )
+ && str_contains( $comment_html, 'comments-box' )
+ && str_contains( $comment_html, 'id="no-comments"' )
+ && ! str_contains( $comment_html, 'id="show-comments"' ),
+ 'post_comment_meta_box() renders add-comment controls, list table shell, and the empty-comments fallback under the stubbed comment count query',
+ array( 'commentId' => $comment_id, 'html' => self::describe_string( $comment_html ) )
+ );
+
+ return self::result_from_failures(
+ $ctx,
+ 'admin-edit-metaboxes.content-comment-and-custom-field-callbacks',
+ $failures,
+ array( 'postId' => $post_id )
+ );
+ }
+
+ private static function check_page_media_and_link_boxes( \ComponentFuzz\FuzzContext $ctx ): array {
+ $failures = array();
+ $user_id = self::seed_user( $ctx->fork( 'user' ) );
+ \wp_set_current_user( $user_id );
+
+ $page_type = self::key( $ctx->fork( 'page-type' ), 'cfz_page_type', 20 );
+ \register_post_type(
+ $page_type,
+ array(
+ 'label' => 'Generated Pages',
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => true,
+ 'supports' => array( 'title', 'page-attributes', 'author', 'thumbnail' ),
+ )
+ );
+ \set_current_screen( $page_type );
+
+ $parent_id = self::insert_post(
+ array(
+ 'post_type' => $page_type,
+ 'post_status' => 'publish',
+ 'post_title' => 'Parent page',
+ )
+ );
+ $page_id = self::insert_post(
+ array(
+ 'post_type' => $page_type,
+ 'post_status' => 'draft',
+ 'post_title' => 'Child page',
+ 'post_parent' => $parent_id,
+ 'menu_order' => $ctx->int( -5, 15 ),
+ )
+ );
+ $page = \get_post( $page_id );
+ $page->page_template = 'templates/generated.php';
+
+ $page_dropdown_args = array();
+ $page_dropdown_filter = static function ( array $args ) use ( &$page_dropdown_args ): array {
+ $page_dropdown_args[] = $args;
+ return $args;
+ };
+ $template_filter = static function ( array $templates ) use ( $page_type ): array {
+ $templates['templates/generated.php'] = 'Generated Template ' . $page_type;
+ return $templates;
+ };
+ $default_title_filter = static function (): string {
+ return 'Generated Default Template';
+ };
+ \add_filter( 'page_attributes_dropdown_pages_args', $page_dropdown_filter, 10, 2 );
+ \add_filter( 'theme_' . $page_type . '_templates', $template_filter, 10, 4 );
+ \add_filter( 'default_page_template_title', $default_title_filter, 10, 2 );
+ $page_html = self::capture_output(
+ static function () use ( $page ): void {
+ \page_attributes_meta_box( $page );
+ }
+ );
+ \remove_filter( 'page_attributes_dropdown_pages_args', $page_dropdown_filter, 10 );
+ \remove_filter( 'theme_' . $page_type . '_templates', $template_filter, 10 );
+ \remove_filter( 'default_page_template_title', $default_title_filter, 10 );
+
+ \add_theme_support( 'post-formats', array( 'aside', 'gallery', 'quote' ) );
+ \add_post_type_support( 'post', 'post-formats' );
+ $format_post_id = self::insert_post( array( 'post_type' => 'post', 'post_status' => 'draft' ) );
+ \set_post_format( $format_post_id, 'quote' );
+ $format_html = self::capture_output(
+ static function () use ( $format_post_id ): void {
+ \post_format_meta_box( \get_post( $format_post_id ), array( 'args' => array() ) );
+ }
+ );
+
+ $attachment_id = self::insert_post(
+ array(
+ 'post_type' => 'attachment',
+ 'post_status' => 'inherit',
+ 'post_title' => 'Generated audio',
+ 'post_mime_type' => 'audio/mpeg',
+ 'post_date' => '2025-02-03 04:05:06',
+ 'post_date_gmt' => '2025-02-03 04:05:06',
+ )
+ );
+ \update_post_meta(
+ $attachment_id,
+ '_wp_attachment_metadata',
+ array(
+ 'artist' => 'Artist ' . self::safe_text( $ctx->fork( 'artist' ), 2, 12 ),
+ 'album' => 'Album ' . self::safe_text( $ctx->fork( 'album' ), 2, 12 ),
+ 'length' => $ctx->int( 15, 300 ),
+ )
+ );
+ \update_post_meta( $attachment_id, '_thumbnail_id', $format_post_id );
+ $attachment = \get_post( $attachment_id );
+ $attachment_submit_html = self::with_capability_denials(
+ array(),
+ static function () use ( $attachment ): string {
+ return self::capture_output(
+ static function () use ( $attachment ): void {
+ \attachment_submit_meta_box( $attachment );
+ }
+ );
+ }
+ );
+ $id3_html = self::capture_output(
+ static function () use ( $attachment ): void {
+ \attachment_id3_data_meta_box( $attachment );
+ }
+ );
+ $thumbnail_html = self::capture_output(
+ static function () use ( $attachment ): void {
+ \post_thumbnail_meta_box( $attachment );
+ }
+ );
+
+ $link = (object) array(
+ 'link_id' => $ctx->int( 10, 999),
+ 'link_name' => 'Generated Link ' . self::safe_text( $ctx->fork( 'link-name' ), 2, 10 ),
+ 'link_url' => 'https://example.test/' . self::key( $ctx->fork( 'link-url' ), 'path', 12 ),
+ 'link_visible' => $ctx->bool() ? 'Y' : 'N',
+ 'link_target' => $ctx->choice( array( '_blank', '_top', '' ) ),
+ 'link_rel' => 'friend met co-worker ' . ( $ctx->bool() ? 'me' : '' ),
+ 'link_image' => 'https://example.test/image.png?x=1&y=2',
+ 'link_rss' => 'https://example.test/feed?x=1&y=2',
+ 'link_notes' => 'Notes ' . self::safe_text( $ctx->fork( 'notes' ), 4, 18 ),
+ 'link_rating' => $ctx->int( 0, 10 ),
+ );
+ $GLOBALS['link'] = $link;
+ $old_get = $_GET;
+ $_GET['action'] = 'edit';
+
+ $link_submit_html = self::with_capability_denials(
+ array(),
+ static function () use ( $link ): string {
+ return self::capture_output(
+ static function () use ( $link ): void {
+ \link_submit_meta_box( $link );
+ }
+ );
+ }
+ );
+ $link_target_html = self::capture_output(
+ static function () use ( $link ): void {
+ \link_target_meta_box( $link );
+ }
+ );
+ $link_xfn_html = self::capture_output(
+ static function () use ( $link ): void {
+ \link_xfn_meta_box( $link );
+ }
+ );
+ $link_advanced_html = self::capture_output(
+ static function () use ( $link ): void {
+ \link_advanced_meta_box( $link );
+ }
+ );
+ $_GET = $old_get;
+
+ self::collect_failure(
+ $failures,
+ isset( $page_dropdown_args[0]['post_type'], $page_dropdown_args[0]['exclude_tree'], $page_dropdown_args[0]['selected'] )
+ && $page_type === $page_dropdown_args[0]['post_type']
+ && $page_id === (int) $page_dropdown_args[0]['exclude_tree']
+ && $parent_id === (int) $page_dropdown_args[0]['selected']
+ && str_contains( $page_html, 'name="page_template" id="page_template"' )
+ && str_contains( $page_html, 'Generated Default Template' )
+ && self::option_is_selected( $page_html, 'templates/generated.php' )
+ && str_contains( $page_html, 'name="menu_order" type="text"' ),
+ 'page_attributes_meta_box() filters parent dropdown args and renders template/menu-order controls',
+ array(
+ 'pageType' => $page_type,
+ 'dropdownArgs' => $page_dropdown_args,
+ 'html' => self::describe_string( $page_html ),
+ )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $format_html, 'id="post-formats-select"' )
+ && self::input_is_checked( $format_html, 'post_format', 'quote' )
+ && str_contains( $format_html, 'value="aside"' )
+ && str_contains( $format_html, 'value="gallery"' ),
+ 'post_format_meta_box() renders supported formats and checks the generated current format',
+ array( 'html' => self::describe_string( $format_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $attachment_submit_html, 'Uploaded on:' )
+ && str_contains( $attachment_submit_html, 'id="publish" value="Update"' )
+ && str_contains( $attachment_submit_html, 'Delete permanently' ),
+ 'attachment_submit_meta_box() renders uploaded timestamp, update button, and current delete branch',
+ array( 'html' => self::describe_string( $attachment_submit_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $id3_html, 'name="id3_artist"' )
+ && str_contains( $id3_html, 'name="id3_album"' )
+ && str_contains( $id3_html, 'class="large-text"' ),
+ 'attachment_id3_data_meta_box() renders editable ID3 fields for generated audio metadata',
+ array( 'html' => self::describe_string( $id3_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $thumbnail_html, 'name="_thumbnail_id"' )
+ || str_contains( $thumbnail_html, 'set-post-thumbnail' )
+ || str_contains( $thumbnail_html, 'postimagediv' ),
+ 'post_thumbnail_meta_box() delegates to thumbnail HTML helper without dropping the thumbnail control shell',
+ array( 'html' => self::describe_string( $thumbnail_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ str_contains( $link_submit_html, 'id="submitlink"' )
+ && str_contains( $link_submit_html, 'Visit Link' )
+ && ! str_contains( $link_submit_html, '>Delete<' )
+ && str_contains( $link_submit_html, 'value="Update Link"' )
+ && ( ( 'N' === $link->link_visible ) === self::input_is_checked( $link_submit_html, 'link_visible', 'N' ) ),
+ 'link_submit_meta_box() renders visit/update/private branches and hides delete when manage_links maps to do_not_allow',
+ array( 'link' => self::object_summary( $link ), 'html' => self::describe_string( $link_submit_html ) )
+ );
+ self::collect_failure(
+ $failures,
+ self::input_is_checked( $link_target_html, 'link_target', $link->link_target )
+ && self::input_is_checked( $link_xfn_html, 'friendship', 'friend' )
+ && self::input_is_checked( $link_xfn_html, 'physical', 'met' )
+ && self::input_is_checked( $link_xfn_html, 'professional', 'co-worker' )
+ && str_contains( $link_advanced_html, 'name="link_image"')
+ && str_contains( $link_advanced_html, 'value="' . \esc_attr( $link->link_image ) . '"' )
+ && str_contains( $link_advanced_html, 'name="link_rating"')
+ && self::option_is_selected( $link_advanced_html, (string) $link->link_rating ),
+ 'link target, XFN, and advanced boxes mirror generated link fields and escape attributes',
+ array(
+ 'link' => self::object_summary( $link ),
+ 'targetHtml' => self::describe_string( $link_target_html ),
+ 'xfnHtml' => self::describe_string( $link_xfn_html ),
+ 'advancedHtml' => self::describe_string( $link_advanced_html ),
+ )
+ );
+
+ return self::result_from_failures(
+ $ctx,
+ 'admin-edit-metaboxes.page-media-format-and-link-callbacks',
+ $failures,
+ array( 'pageType' => $page_type, 'attachmentId' => $attachment_id )
+ );
+ }
+
+ private static function check_default_registration_matrix( \ComponentFuzz\FuzzContext $ctx ): array {
+ $failures = array();
+ $user_id = self::seed_user( $ctx->fork( 'user' ) );
+ \wp_set_current_user( $user_id );
+
+ $post_type = self::key( $ctx->fork( 'post-type' ), 'cfz_edit', 20 );
+ $flat_tax = self::key( $ctx->fork( 'flat-tax' ), 'cfz_reg_flat', 24 );
+ $tree_tax = self::key( $ctx->fork( 'tree-tax' ), 'cfz_reg_tree', 24 );
+ $supports = array( 'title', 'editor', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'author', 'page-attributes', 'thumbnail', 'revisions', 'post-formats' );
+
+ \register_post_type(
+ $post_type,
+ array(
+ 'label' => 'Generated Edit Surface',
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => true,
+ 'supports' => $supports,
+ )
+ );
+ \register_taxonomy(
+ $flat_tax,
+ $post_type,
+ array(
+ 'label' => 'Generated Flat Registration',
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => false,
+ )
+ );
+ \register_taxonomy(
+ $tree_tax,
+ $post_type,
+ array(
+ 'label' => 'Generated Tree Registration',
+ 'public' => false,
+ 'show_ui' => true,
+ 'hierarchical' => true,
+ )
+ );
+ \add_theme_support( 'post-thumbnails', array( $post_type ) );
+ \add_theme_support( 'post-formats', array( 'aside', 'quote' ) );
+ \set_current_screen( $post_type );
+
+ $post_id = self::insert_post(
+ array(
+ 'post_type' => $post_type,
+ 'post_status' => 'publish',
+ 'post_title' => 'Registration matrix',
+ 'post_name' => 'registration-matrix',
+ 'comment_status' => 'open',
+ 'ping_status' => 'open',
+ 'comment_count' => 1,
+ )
+ );
+ \wp_insert_comment(
+ array(
+ 'comment_post_ID' => $post_id,
+ 'comment_author' => 'Registration',
+ 'comment_content' => 'Registration matrix comment',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $events = array();
+ $generic_hook = static function ( string $object_type, $object ) use ( &$events ): void {
+ $events[] = array( 'hook' => 'add_meta_boxes', 'object_type' => $object_type, 'object_id' => is_object( $object ) && isset( $object->ID ) ? (int) $object->ID : null );
+ };
+ $specific_hook = static function ( $object ) use ( &$events, $post_type ): void {
+ $events[] = array( 'hook' => 'add_meta_boxes_' . $post_type, 'object_id' => is_object( $object ) && isset( $object->ID ) ? (int) $object->ID : null );
+ };
+ $do_hook = static function ( string $object_type, string $context, $object ) use ( &$events ): void {
+ $events[] = array( 'hook' => 'do_meta_boxes', 'object_type' => $object_type, 'context' => $context, 'object_id' => is_object( $object ) && isset( $object->ID ) ? (int) $object->ID : null );
+ };
+ \add_action( 'add_meta_boxes', $generic_hook, 10, 2 );
+ \add_action( 'add_meta_boxes_' . $post_type, $specific_hook, 10, 1 );
+ \add_action( 'do_meta_boxes', $do_hook, 10, 3 );
+
+ self::with_capability_denials(
+ array(),
+ static function () use ( $post_id ): void {
+ \register_and_do_post_meta_boxes( \get_post( $post_id ) );
+ }
+ );
+
+ \remove_action( 'add_meta_boxes', $generic_hook, 10 );
+ \remove_action( 'add_meta_boxes_' . $post_type, $specific_hook, 10 );
+ \remove_action( 'do_meta_boxes', $do_hook, 10 );
+
+ $boxes = self::flatten_meta_boxes( $GLOBALS['wp_meta_boxes'][ $post_type ] ?? array() );
+ $expected = array(
+ 'submitdiv' => array( 'context' => 'side', 'priority' => 'core', 'callback' => 'post_submit_meta_box' ),
+ 'tagsdiv-' . $flat_tax => array( 'context' => 'side', 'priority' => 'core', 'callback' => 'post_tags_meta_box' ),
+ $tree_tax . 'div' => array( 'context' => 'side', 'priority' => 'core', 'callback' => 'post_categories_meta_box' ),
+ 'pageparentdiv' => array( 'context' => 'side', 'priority' => 'core', 'callback' => 'page_attributes_meta_box' ),
+ 'postimagediv' => array( 'context' => 'side', 'priority' => 'low', 'callback' => 'post_thumbnail_meta_box' ),
+ 'postexcerpt' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_excerpt_meta_box' ),
+ 'trackbacksdiv' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_trackback_meta_box' ),
+ 'postcustom' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_custom_meta_box' ),
+ 'commentstatusdiv' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_comment_status_meta_box' ),
+ 'commentsdiv' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_comment_meta_box' ),
+ 'slugdiv' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_slug_meta_box' ),
+ 'authordiv' => array( 'context' => 'normal', 'priority' => 'core', 'callback' => 'post_author_meta_box' ),
+ 'formatdiv' => array( 'context' => 'side', 'priority' => 'core', 'callback' => 'post_format_meta_box' ),
+ );
+
+ foreach ( $expected as $id => $expect ) {
+ $actual = $boxes[ $id ] ?? null;
+ self::collect_failure(
+ $failures,
+ is_array( $actual )
+ && $expect['context'] === $actual['context']
+ && $expect['priority'] === $actual['priority']
+ && $expect['callback'] === $actual['callback']
+ && true === ( $actual['args']['__back_compat_meta_box'] ?? false ),
+ 'register_and_do_post_meta_boxes() registers expected built-in box ' . $id,
+ array( 'expected' => $expect, 'actual' => $actual )
+ );
+ }
+
+ self::collect_failure(
+ $failures,
+ self::event_seen( $events, 'add_meta_boxes', $post_type, null, $post_id )
+ && self::event_seen( $events, 'add_meta_boxes_' . $post_type, null, null, $post_id ),
+ 'register_and_do_post_meta_boxes() fires generic and post-type-specific add_meta_boxes hooks with the post object',
+ array( 'events' => $events )
+ );
+ self::collect_failure(
+ $failures,
+ self::event_seen( $events, 'do_meta_boxes', $post_type, 'normal', $post_id )
+ && self::event_seen( $events, 'do_meta_boxes', $post_type, 'advanced', $post_id )
+ && self::event_seen( $events, 'do_meta_boxes', $post_type, 'side', $post_id ),
+ 'register_and_do_post_meta_boxes() runs do_meta_boxes for normal, advanced, and side contexts',
+ array( 'events' => $events )
+ );
+
+ return self::result_from_failures(
+ $ctx,
+ 'admin-edit-metaboxes.registration.default-boxes-hooks-and-contexts',
+ $failures,
+ array(
+ 'postType' => $post_type,
+ 'boxes' => $boxes,
+ 'events' => $events,
+ )
+ );
+ }
+
+ private static function reset_runtime(): void {
+ if ( isset( $GLOBALS['wpdb'] ) && method_exists( $GLOBALS['wpdb'], 'component_fuzz_reset_content' ) ) {
+ $GLOBALS['wpdb']->component_fuzz_reset_content();
+ }
+
+ if ( function_exists( 'wp_cache_flush' ) ) {
+ \wp_cache_flush();
+ }
+ if ( function_exists( 'create_initial_post_types' ) ) {
+ \create_initial_post_types();
+ }
+ if ( function_exists( 'create_initial_taxonomies' ) ) {
+ \create_initial_taxonomies();
+ }
+
+ $GLOBALS['wp_meta_boxes'] = array();
+ $GLOBALS['typenow'] = '';
+ $GLOBALS['taxnow'] = '';
+ $GLOBALS['pagenow'] = 'post.php';
+ if ( class_exists( 'WP_Rewrite' ) ) {
+ $GLOBALS['wp_rewrite'] = new \WP_Rewrite();
+ }
+ $_GET = array();
+ $_POST = array();
+ $_REQUEST = array();
+
+ \set_current_screen( 'dashboard' );
+ }
+
+ private static function seed_user( \ComponentFuzz\FuzzContext $ctx ): int {
+ $id = \wp_insert_user(
+ array(
+ 'user_login' => 'cfz_admin_meta_' . self::key( $ctx, 'user', 12 ),
+ 'user_pass' => 'password',
+ 'user_email' => 'cfz-admin-meta-' . $ctx->int( 1000, 9999 ) . '@example.test',
+ 'display_name' => 'Meta Box Fuzzer ' . self::safe_text( $ctx, 2, 8 ),
+ 'role' => 'administrator',
+ )
+ );
+
+ return is_wp_error( $id ) ? 0 : (int) $id;
+ }
+
+ private static function insert_post( array $overrides ): int {
+ $postarr = array_merge(
+ array(
+ 'post_type' => 'post',
+ 'post_status' => 'draft',
+ 'post_title' => 'Generated Post',
+ 'post_content' => 'Generated content',
+ 'post_excerpt' => '',
+ 'post_author' => \get_current_user_id(),
+ 'post_date' => '2025-01-02 03:04:05',
+ 'post_date_gmt' => '2025-01-02 03:04:05',
+ 'comment_status' => 'closed',
+ 'ping_status' => 'closed',
+ ),
+ $overrides
+ );
+
+ $id = \wp_insert_post( \wp_slash( $postarr ), true, false );
+ if ( is_wp_error( $id ) ) {
+ throw new \RuntimeException( 'Could not insert generated post: ' . $id->get_error_message() );
+ }
+
+ return (int) $id;
+ }
+
+ private static function with_capability_denials( array $denied_caps, callable $callback ) {
+ $denied = array_fill_keys( $denied_caps, true );
+ $filter = static function ( array $allcaps, array $caps ) use ( $denied ): array {
+ foreach ( $caps as $cap ) {
+ $allcaps[ $cap ] = ! isset( $denied[ $cap ] );
+ }
+ foreach ( $denied as $cap => $_ ) {
+ $allcaps[ $cap ] = false;
+ }
+ return $allcaps;
+ };
+
+ \add_filter( 'user_has_cap', $filter, 1000, 4 );
+ try {
+ return $callback();
+ } finally {
+ \remove_filter( 'user_has_cap', $filter, 1000 );
+ }
+ }
+
+ private static function expected_visibility( string $status, string $password, bool $sticky ): string {
+ if ( 'private' === $status ) {
+ return 'Private';
+ }
+ if ( '' !== $password ) {
+ return 'Password protected';
+ }
+ if ( $sticky ) {
+ return 'Public, Sticky';
+ }
+ return 'Public';
+ }
+
+ private static function expected_publish_action( array $case ): string {
+ if ( ! in_array( $case['status'], array( 'publish', 'future', 'private' ), true ) ) {
+ if ( ! $case['canPublish'] ) {
+ return 'Submit for Review';
+ }
+ return $case['futureDate'] ? 'Schedule' : 'Publish';
+ }
+
+ return 'Update';
+ }
+
+ private static function key( \ComponentFuzz\FuzzContext $ctx, string $prefix, int $max = 24 ): string {
+ $key = strtolower( preg_replace( '/[^a-zA-Z0-9_]/', '_', $prefix . '_' . $ctx->identifier( 3, 12 ) ) );
+ $key = trim( preg_replace( '/_+/', '_', $key ), '_' );
+ return substr( $key, 0, $max );
+ }
+
+ private static function safe_text( \ComponentFuzz\FuzzContext $ctx, int $min, int $max ): string {
+ $text = preg_replace( '/[^A-Za-z0-9 _.,:&-]/', '', $ctx->text( $min, $max ) );
+ $text = trim( preg_replace( '/\s+/', ' ', (string) $text ) );
+ if ( strlen( $text ) < $min ) {
+ $text .= substr( ' generated value', 0, $min - strlen( $text ) );
+ }
+ return substr( $text, 0, $max );
+ }
+
+ private static function input_is_checked( string $html, string $name, string $value ): bool {
+ $quoted_name = preg_quote( $name, '/' );
+ $quoted_value = preg_quote( $value, '/' );
+ return 1 === preg_match(
+ '/]*\bname=(["\'])' . $quoted_name . '\1)(?=[^>]*\bvalue=(["\'])' . $quoted_value . '\2)(?=[^>]*\bchecked=(["\'])checked\3)[^>]*>/i',
+ $html
+ );
+ }
+
+ private static function option_is_selected( string $html, string $value ): bool {
+ $quoted_value = preg_quote( $value, '/' );
+ return 1 === preg_match(
+ '/