'; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only admin view. $post_id = absint( wp_unslash( (string) ( $_GET['forwp_post'] ?? '' ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended ?>

0 ) { self::render_detail( $post_id ); } else { self::render_list(); } ?>
Menu::DYNAMICS_PAGE_SLUG, 'forwp_post' => absint( $post_id ), ], admin_url( 'admin.php' ) ); } public static function list_url(): string { return admin_url( 'admin.php?page=' . Menu::DYNAMICS_PAGE_SLUG ); } private static function render_list(): void { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only list filters. $paged = max( 1, absint( wp_unslash( (string) ( $_GET['paged'] ?? '1' ) ) ) ); $search = sanitize_text_field( wp_unslash( (string) ( $_GET['s'] ?? '' ) ) ); // phpcs:enable WordPress.Security.NonceVerification.Recommended $per_page = 20; $result = History::listed_posts( $paged, $per_page, $search ); $items = $result['items']; $total = $result['total']; $pages = max( 1, (int) ceil( $total / $per_page ) ); $waiting = History::waiting_for_crawl( 8 ); $ids = array_map( static fn( array $row ): int => (int) $row['post_id'], $items ); $typed = []; foreach ( array_unique( array_filter( array_column( $items, 'last_type' ) ) ) as $type ) { $typed[ (string) $type ] = History::latest_typed( $ids, (string) $type, 2 ); } ?>

%2$s', esc_url( self::url_for_post( (int) $row['post_id'] ) ), esc_html( self::post_label( (int) $row['post_id'] ) ) ); } echo wp_kses_post( implode( ', ', $wait_links ) ); ?>

| |
'; echo '' . esc_html( sprintf( /* translators: %s: number of pages */ _n( '%s page', '%s pages', $total, '4wp-seo-helper' ), number_format_i18n( $total ) ) ) . ''; if ( $pages > 1 ) { $base = add_query_arg( array_filter( [ 'page' => Menu::DYNAMICS_PAGE_SLUG, 's' => $search, ] ), admin_url( 'admin.php' ) ); echo ''; echo wp_kses_post( (string) paginate_links( [ 'base' => add_query_arg( 'paged', '%#%', $base ), 'format' => '', 'total' => $pages, 'current' => min( $paged, $pages ), 'mid_size' => 2, 'prev_text' => '«', 'next_text' => '»', ] ) ); echo ''; } echo '
'; ?>

' . esc_html__( 'Post not found.', '4wp-seo-helper' ) . '

'; return; } $events = History::for_post( $post_id ); if ( [] === $events ) { HistoryLogger::seed_existing( $post ); $events = History::for_post( $post_id ); } $latest = History::latest_by_type( $post_id ); $inspect = Indexing::inventory_fields( $post_id ); $chars = HistoryLogger::character_count( $post ); $edit = get_edit_post_link( $post_id, 'raw' ); ?>

← · · ·

0 ? gmdate( 'Y-m-d H:i:s', $inspect['gsc_index_requested_at'] ) : '' ); self::render_check_item( __( 'Robot crawled', '4wp-seo-helper' ), $latest[ History::TYPE_CRAWL ], (string) $inspect['gsc_last_crawl'] ); self::render_check_item( __( 'Content length', '4wp-seo-helper' ), $latest[ History::TYPE_CONTENT ], '', sprintf( /* translators: %s: character count */ __( 'Now: %s characters', '4wp-seo-helper' ), number_format_i18n( $chars ) ) ); self::render_check_item( __( 'SEO fields', '4wp-seo-helper' ), $latest[ History::TYPE_SEO ] ); self::render_check_item( __( 'Search metrics', '4wp-seo-helper' ), $latest[ History::TYPE_GSC ] ); ?>

|null $event */ private static function render_check_item( string $label, ?array $event, string $fallback_date = '', string $extra = '' ): void { $done = null !== $event || '' !== $fallback_date; $when = ''; if ( is_array( $event ) ) { $when = self::format_gmt( (string) $event['occurred_at'] ); } elseif ( '' !== $fallback_date ) { $when = self::format_gmt( $fallback_date ); } ?>

$row * @param array|null $previous */ private static function render_event_item( array $row, ?array $previous = null ): void { $type = (string) $row['event_type']; $snap = (array) ( $row['snapshot'] ?? [] ); $delta = ''; if ( is_array( $previous ) ) { $delta = self::snapshot_delta( (array) ( $previous['snapshot'] ?? [] ), $snap, $type ); } ?>
  • $snapshot */ private static function snapshot_summary( array $snapshot ): string { $parts = []; if ( isset( $snapshot['chars'] ) ) { $parts[] = sprintf( /* translators: %s: character count */ __( '%s chars', '4wp-seo-helper' ), number_format_i18n( (int) $snapshot['chars'] ) ); } if ( isset( $snapshot['score'] ) ) { $parts[] = sprintf( /* translators: %d: SEO score */ __( 'score %d', '4wp-seo-helper' ), (int) $snapshot['score'] ); } if ( ! empty( $snapshot['keys'] ) && is_array( $snapshot['keys'] ) ) { $parts[] = implode( ', ', array_map( 'strval', array_slice( $snapshot['keys'], 0, 5 ) ) ); } if ( isset( $snapshot['clicks'] ) || isset( $snapshot['impr'] ) ) { $parts[] = sprintf( /* translators: 1: clicks, 2: impressions, 3: average position */ __( '%1$s clicks / %2$s impr / pos %3$s', '4wp-seo-helper' ), number_format_i18n( (int) ( $snapshot['clicks'] ?? 0 ) ), number_format_i18n( (int) ( $snapshot['impr'] ?? 0 ) ), isset( $snapshot['pos'] ) ? (string) $snapshot['pos'] : '—' ); } if ( ! empty( $snapshot['sugg'] ) && is_array( $snapshot['sugg'] ) ) { $queries = []; foreach ( array_slice( $snapshot['sugg'], 0, 3 ) as $row ) { if ( is_array( $row ) && ! empty( $row['q'] ) ) { $queries[] = (string) $row['q']; } } if ( [] !== $queries ) { $parts[] = sprintf( /* translators: %s: comma-separated search queries */ __( 'suggest: %s', '4wp-seo-helper' ), implode( ', ', $queries ) ); } } if ( ! empty( $snapshot['cov'] ) ) { $parts[] = (string) $snapshot['cov']; } return implode( ' · ', $parts ); } /** * @param array $from * @param array $to */ private static function snapshot_delta( array $from, array $to, string $type ): string { if ( History::TYPE_CONTENT === $type && isset( $from['chars'], $to['chars'] ) && (int) $from['chars'] !== (int) $to['chars'] ) { return sprintf( /* translators: 1: previous character count, 2: new character count */ __( '%1$s → %2$s characters', '4wp-seo-helper' ), number_format_i18n( (int) $from['chars'] ), number_format_i18n( (int) $to['chars'] ) ); } if ( History::TYPE_GSC === $type && ( isset( $from['clicks'] ) || isset( $to['clicks'] ) ) ) { return sprintf( /* translators: 1: previous clicks, 2: new clicks, 3: previous position, 4: new position */ __( 'clicks %1$s → %2$s · pos %3$s → %4$s', '4wp-seo-helper' ), number_format_i18n( (int) ( $from['clicks'] ?? 0 ) ), number_format_i18n( (int) ( $to['clicks'] ?? 0 ) ), isset( $from['pos'] ) ? (string) $from['pos'] : '—', isset( $to['pos'] ) ? (string) $to['pos'] : '—' ); } if ( History::TYPE_CRAWL === $type && ! empty( $from['crawl'] ) && ! empty( $to['crawl'] ) && $from['crawl'] !== $to['crawl'] ) { return sprintf( /* translators: 1: previous crawl datetime, 2: new crawl datetime */ __( 'previous crawl %1$s → %2$s', '4wp-seo-helper' ), self::format_gmt( (string) $from['crawl'] ), self::format_gmt( (string) $to['crawl'] ) ); } return ''; } /** * @param array $current Latest snapshot. * @param array|null $previous Previous snapshot of the same type. */ private static function change_summary( string $type, array $current, ?array $previous ): string { if ( History::TYPE_GSC === $type ) { $clicks = (int) ( $current['clicks'] ?? 0 ); $impr = (int) ( $current['impr'] ?? 0 ); if ( is_array( $previous ) ) { $dc = $clicks - (int) ( $previous['clicks'] ?? 0 ); $di = $impr - (int) ( $previous['impr'] ?? 0 ); $parts = []; if ( 0 !== $dc ) { $parts[] = sprintf( '%+d clicks', $dc ); } if ( 0 !== $di ) { $parts[] = sprintf( '%+d impr', $di ); } if ( [] !== $parts ) { return implode( ' · ', $parts ); } } return sprintf( /* translators: 1: clicks, 2: impressions */ __( '%1$s clicks · %2$s impr', '4wp-seo-helper' ), number_format_i18n( $clicks ), number_format_i18n( $impr ) ); } if ( History::TYPE_CONTENT === $type ) { $chars = (int) ( $current['chars'] ?? 0 ); if ( is_array( $previous ) && (int) ( $previous['chars'] ?? 0 ) !== $chars ) { return sprintf( /* translators: 1: previous character count, 2: new character count */ __( '%1$s → %2$s characters', '4wp-seo-helper' ), number_format_i18n( (int) $previous['chars'] ), number_format_i18n( $chars ) ); } return sprintf( /* translators: %s: character count */ __( '%s characters', '4wp-seo-helper' ), number_format_i18n( $chars ) ); } if ( History::TYPE_SEO === $type ) { $score = (int) ( $current['score'] ?? 0 ); if ( is_array( $previous ) && (int) ( $previous['score'] ?? 0 ) !== $score ) { return sprintf( /* translators: 1: previous score, 2: new score */ __( 'Score %1$d → %2$d', '4wp-seo-helper' ), (int) $previous['score'], $score ); } return sprintf( /* translators: %d: SEO score */ __( 'Score %d', '4wp-seo-helper' ), $score ); } if ( History::TYPE_CRAWL === $type ) { return __( 'Google crawled this URL', '4wp-seo-helper' ); } if ( History::TYPE_INDEX_REQUEST === $type ) { return __( 'Indexing requested', '4wp-seo-helper' ); } if ( History::TYPE_INSPECT === $type ) { return __( 'URL inspected in Search Console', '4wp-seo-helper' ); } return self::event_label( $type ); } private static function event_label( string $type ): string { $labels = [ History::TYPE_INDEX_REQUEST => __( 'Index request', '4wp-seo-helper' ), History::TYPE_INSPECT => __( 'Inspect', '4wp-seo-helper' ), History::TYPE_CRAWL => __( 'Crawl', '4wp-seo-helper' ), History::TYPE_CONTENT => __( 'Content', '4wp-seo-helper' ), History::TYPE_SEO => __( 'SEO', '4wp-seo-helper' ), History::TYPE_GSC => __( 'Search', '4wp-seo-helper' ), ]; return $labels[ $type ] ?? ( '' !== $type ? $type : '—' ); } private static function post_label( int $post_id ): string { $title = get_the_title( $post_id ); return '' !== $title ? $title : '#' . $post_id; } private static function format_relative( string $gmt ): string { $absolute = self::format_gmt( $gmt ); if ( '—' === $absolute ) { return $absolute; } $ts = strtotime( $gmt . ' UTC' ); if ( false === $ts ) { return $absolute; } return sprintf( /* translators: %s: human-readable time difference */ __( '%s ago', '4wp-seo-helper' ), human_time_diff( $ts ) ); } private static function format_gmt( string $gmt ): string { if ( '' === $gmt ) { return '—'; } $local = get_date_from_gmt( $gmt ); if ( ! is_string( $local ) || '' === $local || '0000-00-00 00:00:00' === $local ) { $ts = strtotime( $gmt ); if ( false === $ts ) { return $gmt; } $local = wp_date( 'Y-m-d H:i:s', $ts ); } $formatted = mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $local, true ); return is_string( $formatted ) && '' !== $formatted ? $formatted : $gmt; } }