(int) ($dash['clicks'] ?? 0), 'impressions' => (int) ($dash['impressions'] ?? 0), 'position' => (float) ($dash['position'] ?? 0.0), ]; } $has_previous = (int) ($previous['clicks'] ?? 0) > 0 || (int) ($previous['impressions'] ?? 0) > 0; return [ 'clicks' => (int) ($current['clicks'] ?? 0), 'impressions' => (int) ($current['impressions'] ?? 0), 'position' => round((float) ($current['position'] ?? 0.0), 1), 'prev_clicks' => $has_previous ? (int) ($previous['clicks'] ?? 0) : null, 'prev_impressions' => $has_previous ? (int) ($previous['impressions'] ?? 0) : null, 'prev_position' => $has_previous ? round((float) ($previous['position'] ?? 0.0), 1) : null, 'period_days' => (int) ($context['frequency_days'] ?? 30), 'period_label' => (string) ($context['period_label'] ?? ''), ]; } /** * Zero clicks and zero impressions is a connected property with no * traffic — a placeholder, not a report. */ public function has_data(array $payload): bool { return (int) ($payload['clicks'] ?? 0) > 0 || (int) ($payload['impressions'] ?? 0) > 0; } public function render(array $payload): string { $days = (int) ($payload['period_days'] ?? 30); $ctr = self::ctr($payload['clicks'], $payload['impressions']); $clicks_change = ''; $imps_change = ''; $ctr_change = ''; $pos_change = ''; $compared = ''; if ($payload['prev_clicks'] !== null) { $c = Email_Report_Html::pct_change((float) $payload['clicks'], (float) $payload['prev_clicks']); $i = Email_Report_Html::pct_change((float) $payload['impressions'], (float) $payload['prev_impressions']); $clicks_change = $c ? Email_Report_Html::change($c['text'], $c['direction'], $c['direction'] !== 'down') : ''; $imps_change = $i ? Email_Report_Html::change($i['text'], $i['direction'], $i['direction'] !== 'down') : ''; $prev_ctr = self::ctr((int) $payload['prev_clicks'], (int) $payload['prev_impressions']); $pts = round(($ctr - $prev_ctr) * 100, 1); if (abs($pts) >= 0.1) { $ctr_change = Email_Report_Html::change( number_format_i18n(abs($pts), 1), $pts > 0 ? 'up' : 'down', $pts > 0, __('pts', 'thinkrank') ); } elseif ($prev_ctr > 0) { $ctr_change = Email_Report_Html::change('0', 'flat', true); } if ((float) $payload['prev_position'] > 0 && (float) $payload['position'] > 0) { $diff = round((float) $payload['prev_position'] - (float) $payload['position'], 1); if (abs($diff) >= 0.1) { // A smaller position number is better, so "up" here means improved. $pos_change = Email_Report_Html::change( number_format_i18n(abs($diff), 1), $diff > 0 ? 'up' : 'down', $diff > 0, $diff > 0 ? __('better', 'thinkrank') : __('worse', 'thinkrank') ); } else { $pos_change = Email_Report_Html::change('0', 'flat', true); } } $compared = ' · ' . sprintf( /* translators: %d: number of days in the comparison window. */ _n('compared with the previous %d day', 'compared with the previous %d days', $days, 'thinkrank'), $days ); } $eyebrow = '
' . esc_html((string) get_bloginfo('name')) . '
'; $title = '
' . esc_html(sprintf( /* translators: %d: number of days the report covers. */ _n('Your search performance for the last %d day', 'Your search performance for the last %d days', $days, 'thinkrank'), $days )) . '
'; $sub = '
' . esc_html((string) $payload['period_label'] . $compared) . '
'; $tiles = [ Email_Report_Html::stat([ 'label' => __('Clicks from Google', 'thinkrank'), 'value' => number_format_i18n((int) $payload['clicks']), 'change' => $clicks_change, 'icon' => 'clicks', ]), Email_Report_Html::stat([ 'label' => __('Impressions', 'thinkrank'), 'value' => number_format_i18n((int) $payload['impressions']), 'change' => $imps_change, 'icon' => 'impressions', ]), Email_Report_Html::stat([ 'label' => __('Average CTR', 'thinkrank'), 'value' => number_format_i18n($ctr * 100, 1) . '%', 'change' => $ctr_change, 'icon' => 'ctr', ]), Email_Report_Html::stat([ 'label' => __('Average position', 'thinkrank'), 'value' => (float) $payload['position'] > 0 ? number_format_i18n((float) $payload['position'], 1) : '—', 'change' => $pos_change, 'icon' => 'position', ]), ]; return $eyebrow . $title . $sub . Email_Report_Html::stat_grid($tiles) . Email_Report_Html::note(__('Source: Google Search Console. Data is complete up to 2 days before this report.', 'thinkrank'), '8px'); } public function fallback_html(): string { return ''; } private static function ctr(int $clicks, int $impressions): float { return $impressions > 0 ? $clicks / $impressions : 0.0; } }