*/ private static array $cell_context = []; /** * @param array $extra_query */ public static function render_period_selector( string $tab, int $days, array $extra_query = [] ): void { $hidden = in_array( $tab, [ Admin::TAB_INSPECTION, Admin::TAB_SYNC ], true ); ?>
>
$value ) : ?>

$overview */ public static function render_overview_tab( array $overview ): void { $current = $overview['current'] ?? []; $change = $overview['change'] ?? []; $period = $overview['period'] ?? []; $daily = $overview['daily'] ?? []; ?>

%
$cards */ public static function render_insights_tab( array $cards ): void { $period_label = (string) ( $cards['period_label'] ?? ReportPeriod::label() ); $needs_sync = ! empty( $cards['needs_sync'] ); ?>

(string) ( $cards['property'] ?? '' ), 'period_key' => (string) ( $cards['period_key'] ?? '' ), ] ); ?>
*/ public static function performance_dimensions(): array { return [ 'query' => __( 'Queries', '4wp-seo-helper' ), 'page' => __( 'Pages', '4wp-seo-helper' ), 'country' => __( 'Countries', '4wp-seo-helper' ), 'device' => __( 'Devices', '4wp-seo-helper' ), 'searchAppearance' => __( 'Search appearance', '4wp-seo-helper' ), 'date' => __( 'Days', '4wp-seo-helper' ), ]; } public static function resolve_performance_dimension( string $raw ): string { $raw = trim( $raw ); if ( '' === $raw ) { return 'query'; } $dimensions = self::performance_dimensions(); if ( isset( $dimensions[ $raw ] ) ) { return $raw; } // sanitize_key() lowercases camelCase GSC dimensions (searchAppearance → searchappearance). foreach ( array_keys( $dimensions ) as $key ) { if ( strtolower( $key ) === strtolower( $raw ) ) { return $key; } } return 'query'; } public static function render_performance_tab( string $property, string $dimension, int $days ): void { $dimensions = self::performance_dimensions(); $dimension = self::resolve_performance_dimension( $dimension ); $repo = new Repository(); [ $start, $end ] = ReportPeriod::ranges( $days ); $page_url = Admin::get_instance()->get_page_url( Admin::TAB_PERFORMANCE ); $dim_base_url = remove_query_arg( [ 'orderby', 'order' ], $page_url ); ?>

$label ) : ?>
get_daily_range( $property, 'web', $start, $end ); $rows = self::sort_metric_rows( $rows, $orderby, $order ); self::render_metric_table( [ [ 'key' => 'metric_date', 'label' => __( 'Date', '4wp-seo-helper' ) ], [ 'key' => 'clicks', 'label' => __( 'Clicks', '4wp-seo-helper' ) ], [ 'key' => 'impressions', 'label' => __( 'Impressions', '4wp-seo-helper' ) ], [ 'key' => 'ctr', 'label' => __( 'CTR', '4wp-seo-helper' ), 'format' => 'pct' ], [ 'key' => 'position', 'label' => __( 'Position', '4wp-seo-helper' ), 'format' => 'float' ], ], $rows, $orderby, $order, [ 'page' => Menu::GSC_PAGE_SLUG, 'tab' => Admin::TAB_PERFORMANCE, 'range' => $days, 'dimension' => $dimension, ] ); ?> get_facts( $property, 'web', $dimension, ReportPeriod::period_key_current( $days ), 25000 ); if ( 'country' === $dimension && 'dim_key' === $orderby ) { $rows = self::sort_country_rows( $rows, $order ); } else { $rows = self::sort_metric_rows( $rows, $orderby, $order ); } $dim_column = [ 'key' => 'dim_key', 'label' => $dimensions[ $dimension ], ]; if ( 'country' === $dimension ) { $dim_column['format'] = 'country'; } if ( 'query' === $dimension ) { $dim_column['format'] = 'query'; } $cell_context = []; if ( 'query' === $dimension ) { $lookup = new QueryPageLookup(); $cell_context['landing_pages'] = $lookup->top_pages_for_queries( $property, ReportPeriod::period_key_current( $days ), array_map( static function ( array $row ): string { return (string) ( $row['dim_key'] ?? '' ); }, $rows ) ); } self::render_metric_table( [ $dim_column, [ 'key' => 'clicks', 'label' => __( 'Clicks', '4wp-seo-helper' ) ], [ 'key' => 'impressions', 'label' => __( 'Impressions', '4wp-seo-helper' ) ], [ 'key' => 'ctr', 'label' => __( 'CTR', '4wp-seo-helper' ), 'format' => 'pct' ], [ 'key' => 'position', 'label' => __( 'Position', '4wp-seo-helper' ), 'format' => 'float' ], ], $rows, $orderby, $order, [ 'page' => Menu::GSC_PAGE_SLUG, 'tab' => Admin::TAB_PERFORMANCE, 'range' => $days, 'dimension' => $dimension, ], $cell_context ); ?>
> $logs */ public static function render_sync_tab( string $property, bool $sync_running, array $logs, ?string $last_sync ): void { $brand_terms = (string) get_option( 'forwp_seo_gsc_brand_terms', '' ); $cron_enabled = Module::get_instance()->is_cron_enabled(); $next_cron = wp_next_scheduled( Sync::CRON_HOOK ); ?>

'disabled' ] : [] ); ?>

'disabled' ] : [] ); ?>

> $rows * @param array $context */ private static function render_rank_table( string $title, array $rows, string $type, array $context = [] ): void { $landing_pages = []; if ( 'query' === $type && ! empty( $context['property'] ) && ! empty( $context['period_key'] ) ) { $lookup = new QueryPageLookup(); $landing_pages = $lookup->top_pages_for_queries( (string) $context['property'], (string) $context['period_key'], array_map( static function ( array $row ): string { return (string) ( $row['dim_key'] ?? '' ); }, $rows ) ); } self::$cell_context = [ 'landing_pages' => $landing_pages ]; ?>

>,down?:list>} $trending */ private static function render_trend_table( string $title, array $trending ): void { ?>

> $rows */ private static function render_change_rows( array $rows ): void { if ( empty( $rows ) ) { echo '

' . esc_html__( 'None', '4wp-seo-helper' ) . '

'; return; } echo '
    '; foreach ( $rows as $row ) { $pct = $row['change_pct'] ?? null; $label = null === $pct ? sprintf( '%+d', (int) ( $row['change'] ?? 0 ) ) : sprintf( '%+d (%s%%)', (int) ( $row['change'] ?? 0 ), (string) $pct ); printf( '
  • %s %s
  • ', esc_html( mb_strimwidth( (string) ( $row['dim_key'] ?? '' ), 0, 70, '…' ) ), esc_html( $label ) ); } echo '
'; } /** * @param list> $rows */ private static function render_traffic_sources( array $rows ): void { ?>

$split */ private static function render_brand_split( array $split ): void { $brand = $split['branded'] ?? [ 'clicks' => 0, 'impressions' => 0 ]; $other = $split['non_branded'] ?? [ 'clicks' => 0, 'impressions' => 0 ]; ?>

$columns * @param list> $rows * @param array $base_query * @param array $context */ private static function render_metric_table( array $columns, array $rows, string $orderby, string $order, array $base_query, array $context = [] ): void { self::$cell_context = $context; if ( empty( $rows ) ) { echo '

' . esc_html__( 'No rows for this dimension. Run sync first.', '4wp-seo-helper' ) . '

'; self::$cell_context = []; return; } ?> $key, 'order' => $next_order, ] ), admin_url( 'admin.php' ) ); ?>
$allowed * @return array{0:string,1:string} */ private static function resolve_performance_sort( array $allowed, string $default_key, string $default_order ): array { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only table sort. $orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( (string) $_GET['orderby'] ) ) : $default_key; $order = isset( $_GET['order'] ) ? strtolower( sanitize_key( wp_unslash( (string) $_GET['order'] ) ) ) : $default_order; // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( ! in_array( $orderby, $allowed, true ) ) { $orderby = $default_key; } if ( ! in_array( $order, [ 'asc', 'desc' ], true ) ) { $order = $default_order; } return [ $orderby, $order ]; } private static function next_sort_order( string $column_key, string $current_orderby, string $current_order ): string { if ( $current_orderby !== $column_key ) { return in_array( $column_key, [ 'dim_key', 'metric_date' ], true ) ? 'asc' : 'desc'; } return 'asc' === $current_order ? 'desc' : 'asc'; } /** * @param list> $rows * @return list> */ private static function sort_metric_rows( array $rows, string $orderby, string $order ): array { usort( $rows, static function ( array $left, array $right ) use ( $orderby, $order ): int { $left_value = $left[ $orderby ] ?? ''; $right_value = $right[ $orderby ] ?? ''; if ( in_array( $orderby, [ 'clicks', 'impressions', 'ctr', 'position' ], true ) ) { $cmp = (float) $left_value <=> (float) $right_value; } else { $cmp = strcasecmp( (string) $left_value, (string) $right_value ); } return 'asc' === $order ? $cmp : -$cmp; } ); return $rows; } /** * @param list> $rows * @return list> */ private static function sort_country_rows( array $rows, string $order ): array { usort( $rows, static function ( array $left, array $right ) use ( $order ): int { $left_name = CountryCatalog::resolve( (string) ( $left['dim_key'] ?? '' ) )['name']; $right_name = CountryCatalog::resolve( (string) ( $right['dim_key'] ?? '' ) )['name']; $cmp = strcasecmp( $left_name, $right_name ); return 'asc' === $order ? $cmp : -$cmp; } ); return $rows; } private static function render_dim_cell( string $value, string $type ): void { if ( 'country' === $type ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in CountryCatalog::render(). echo CountryCatalog::render( $value ); return; } if ( 'query' === $type ) { $landing = self::$cell_context['landing_pages'][ $value ] ?? null; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in QueryCell::render(). echo QueryCell::render( $value, is_array( $landing ) ? $landing : null ); return; } echo esc_html( mb_strimwidth( $value, 0, 80, '…' ) ); } /** * @param array $row * @param array{key:string,format?:string} $col */ private static function render_cell( array $row, array $col ): void { if ( 'country' === ( $col['format'] ?? '' ) ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in CountryCatalog::render(). echo CountryCatalog::render( (string) ( $row[ $col['key'] ] ?? '' ) ); return; } if ( 'query' === ( $col['format'] ?? '' ) ) { $query = (string) ( $row[ $col['key'] ] ?? '' ); $landing = self::$cell_context['landing_pages'][ $query ] ?? null; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in QueryCell::render(). echo QueryCell::render( $query, is_array( $landing ) ? $landing : null ); return; } echo esc_html( self::format_cell( $row, $col ) ); } /** * @param array $row * @param array{key:string,format?:string} $col */ private static function format_cell( array $row, array $col ): string { $value = $row[ $col['key'] ] ?? ''; if ( 'pct' === ( $col['format'] ?? '' ) ) { return (string) round( (float) $value * 100, 2 ) . '%'; } if ( 'float' === ( $col['format'] ?? '' ) ) { return (string) round( (float) $value, 1 ); } return (string) $value; } }