|null */ private static ?array $stats = null; public static function on_load(): void { if ( ! current_user_can( 'manage_options' ) ) { return; } ReportPeriod::maybe_save_from_request(); wp_enqueue_script( 'dashboard' ); wp_enqueue_style( 'dashboard' ); if ( wp_is_mobile() ) { wp_enqueue_script( 'jquery-touch-punch' ); } wp_enqueue_style( 'forwp-seo-admin-settings', FORWP_SEO_HELPER_URL . 'assets/css/admin-settings.css', [], FORWP_SEO_HELPER_VERSION ); wp_enqueue_style( 'forwp-seo-admin-dashboard', FORWP_SEO_HELPER_URL . 'assets/css/admin-dashboard.css', [ 'dashboard', 'forwp-seo-admin-settings' ], FORWP_SEO_HELPER_VERSION ); wp_enqueue_script( 'forwp-seo-admin-tabs', FORWP_SEO_HELPER_URL . 'assets/js/admin-tabs.js', [], FORWP_SEO_HELPER_VERSION, true ); add_filter( 'admin_body_class', [ self::class, 'body_class' ] ); add_filter( 'screen_settings', [ self::class, 'filter_screen_settings' ], 10, 2 ); $screen = get_current_screen(); if ( ! $screen ) { return; } add_meta_box( 'forwp_seo_dash_metrics', __( 'Key metrics', '4wp-seo-helper' ), [ self::class, 'render_metrics_box' ], $screen->id, 'normal', 'high' ); add_meta_box( 'forwp_seo_dash_weakest', __( 'Weakest pages', '4wp-seo-helper' ), [ self::class, 'render_weakest_box' ], $screen->id, 'normal', 'default' ); add_meta_box( 'forwp_seo_dash_queries', __( 'Top queries', '4wp-seo-helper' ), [ self::class, 'render_queries_box' ], $screen->id, 'side', 'high' ); add_meta_box( 'forwp_seo_dash_search', __( 'Search & growth', '4wp-seo-helper' ), [ self::class, 'render_search_box' ], $screen->id, 'side', 'default' ); add_meta_box( 'forwp_seo_dash_gaps', __( 'SEO gaps by field', '4wp-seo-helper' ), [ self::class, 'render_gaps_box' ], $screen->id, 'column3', 'default' ); } /** * @param string $classes Body classes. */ public static function body_class( string $classes ): string { return $classes . ' forwp-seo-dashboard-page'; } /** * @param string $settings Existing extra settings HTML. * @param \WP_Screen $screen Current screen. */ public static function filter_screen_settings( $settings, $screen ): string { if ( ! $screen instanceof \WP_Screen || 'toplevel_page_' . Menu::PAGE_SLUG !== $screen->id ) { return (string) $settings; } $columns = self::layout_columns( $screen ); ob_start(); ?>
'; ?>

id, 'normal', '' ); ?>
id, 'side', '' ); ?>
id, 'column3', '' ); ?>
id, 'column4', '' ); ?>
0 ? 'is-alert' : 'is-ok', (string) $stats['weak_url'] ); self::render_metric_card( (int) $stats['avg'] . '%', __( 'Average SEO score', '4wp-seo-helper' ), (array) ( $stats['delta']['avg'] ?? [] ) ); self::render_metric_card( number_format_i18n( (int) $stats['gaps'] ), __( 'Pages with gaps', '4wp-seo-helper' ), (array) ( $stats['delta']['gaps'] ?? [] ), (int) $stats['gaps'] > 0 ? 'is-warn' : '', (string) $stats['gaps_url'] ); self::render_metric_card( number_format_i18n( (int) ( $growth['week'] ?? 0 ) ), __( 'New pages this week', '4wp-seo-helper' ), (array) ( $growth['week_delta'] ?? [] ), (int) ( $growth['week'] ?? 0 ) > 0 ? 'is-up' : '' ); if ( ! empty( $gsc['enabled'] ) && ! empty( $gsc['connected'] ) ) { self::render_metric_card( number_format_i18n( (int) ( $gsc['clicks'] ?? 0 ) ), __( 'Search clicks', '4wp-seo-helper' ), is_array( $gsc['change']['clicks'] ?? null ) ? (array) $gsc['change']['clicks'] : [], '', (string) $stats['gsc_url'] ); self::render_metric_card( number_format_i18n( (int) ( $gsc['impressions'] ?? 0 ) ), __( 'Search impressions', '4wp-seo-helper' ), is_array( $gsc['change']['impressions'] ?? null ) ? (array) $gsc['change']['impressions'] : [], '', (string) $stats['gsc_url'] ); } ?>
*/ private static function stats(): array { if ( null === self::$stats ) { self::$stats = DashboardStats::collect(); } return self::$stats; } private static function layout_columns( \WP_Screen $screen ): int { $columns = (int) get_user_option( 'screen_layout_' . $screen->id ); if ( $columns < 2 || $columns > 4 ) { return 2; } return $columns; } private static function render_period_select(): void { if ( ! GscModule::get_instance()->is_enabled() ) { return; } $days = ReportPeriod::resolve_from_request(); ?>

> $weakest Weakest inventory rows. */ private static function render_weakest_pane( array $weakest, string $weak_url ): void { if ( [] === $weakest ) { echo '

' . esc_html__( 'No inventory URLs yet.', '4wp-seo-helper' ) . '

'; return; } ?> $gsc GSC payload from DashboardStats. */ private static function render_queries_pane( array $gsc, string $gsc_url, string $settings_url ): void { $queries = is_array( $gsc['top_queries'] ?? null ) ? $gsc['top_queries'] : []; if ( empty( $gsc['enabled'] ) ) { echo '

'; esc_html_e( 'Search Console is turned off.', '4wp-seo-helper' ); echo ' ' . esc_html__( 'Settings', '4wp-seo-helper' ) . ''; echo '

'; return; } if ( empty( $gsc['connected'] ) ) { echo '

'; esc_html_e( 'Connect Google and pick a property to load queries.', '4wp-seo-helper' ); echo ' ' . esc_html__( 'GSC settings', '4wp-seo-helper' ) . ''; echo '

'; return; } if ( ! empty( $gsc['needs_sync'] ) || [] === $queries ) { echo '

'; esc_html_e( 'No synced query data for this period yet.', '4wp-seo-helper' ); echo ' ' . esc_html__( 'Open Search Console', '4wp-seo-helper' ) . ''; echo '

'; return; } ?> $gsc GSC payload. * @param array $growth Growth stats. * @param array> $types By post type. */ private static function render_search_summary_pane( array $gsc, array $growth, array $types, string $gsc_url, string $settings_url ): void { $pages = is_array( $gsc['top_pages'] ?? null ) ? $gsc['top_pages'] : []; if ( ! empty( $gsc['enabled'] ) && ! empty( $gsc['connected'] ) && empty( $gsc['needs_sync'] ) ) { ?>
  • 0 ) : ?>

'; esc_html_e( 'Search Console is not ready yet.', '4wp-seo-helper' ); echo ' ' . esc_html__( 'Connect property', '4wp-seo-helper' ) . ''; echo '

'; } elseif ( ! empty( $gsc['enabled'] ) ) { echo '

'; esc_html_e( 'Run a Search Console sync to load period totals.', '4wp-seo-helper' ); echo ' ' . esc_html__( 'Open Search Console', '4wp-seo-helper' ) . ''; echo '

'; } ?>

    $type ) : ?>
$missing Missing field counts. */ private static function render_gaps_pane( array $missing, string $gaps_url ): void { $field_labels = [ 'title' => __( 'SEO title', '4wp-seo-helper' ), 'description' => __( 'Meta description', '4wp-seo-helper' ), 'focus_keyword' => __( 'Focus keyphrases', '4wp-seo-helper' ), 'og_image' => __( 'OG image', '4wp-seo-helper' ), ]; ?>
    $label ) : ?>

0 ? 'up' : ( $value < 0 ? 'down' : 'flat' ); $text = is_numeric( $pct ) ? sprintf( '%+s%%', (string) $pct ) : sprintf( '%+d', $value ); return '' . esc_html( $text ) . ''; } }