| 1 |
<?php |
| 2 |
/** |
| 3 |
* Key Metrics Section |
| 4 |
* |
| 5 |
* Renders traffic, impressions, average position, and total tracked keywords |
| 6 |
* for the period. PRD §3, Sections-to-Include item 1. |
| 7 |
* |
| 8 |
* @package ThinkRank |
| 9 |
* @subpackage SEO\Email_Report_Sections |
| 10 |
* @since 1.9.0 |
| 11 |
*/ |
| 12 |
|
| 13 |
declare(strict_types=1); |
| 14 |
|
| 15 |
namespace ThinkRank\SEO\Email_Report_Sections; |
| 16 |
|
| 17 |
if (!defined('ABSPATH')) { |
| 18 |
exit; |
| 19 |
} |
| 20 |
|
| 21 |
final class Key_Metrics_Section implements Email_Report_Section_Interface { |
| 22 |
|
| 23 |
public function key(): string { |
| 24 |
return 'key_metrics'; |
| 25 |
} |
| 26 |
|
| 27 |
public function label(): string { |
| 28 |
return __('Key Metrics', 'thinkrank'); |
| 29 |
} |
| 30 |
|
| 31 |
public function default_enabled(): bool { |
| 32 |
return true; |
| 33 |
} |
| 34 |
|
| 35 |
public function requires_capability(): ?string { |
| 36 |
return null; |
| 37 |
} |
| 38 |
|
| 39 |
public function collect(array $context): array { |
| 40 |
$shared = $context['shared'] ?? []; |
| 41 |
if (empty($shared['available'])) { |
| 42 |
return []; |
| 43 |
} |
| 44 |
$current = $shared['current'] ?? []; |
| 45 |
$traffic = $current['traffic'] ?? []; |
| 46 |
$search = $current['search_performance'] ?? []; |
| 47 |
$totals = $search['totals'] ?? []; |
| 48 |
|
| 49 |
return [ |
| 50 |
'sessions' => (int) ($traffic['sessions'] ?? 0), |
| 51 |
'users' => (int) ($traffic['users'] ?? 0), |
| 52 |
'impressions' => (int) ($totals['impressions'] ?? 0), |
| 53 |
'clicks' => (int) ($totals['clicks'] ?? 0), |
| 54 |
'avg_position' => (float) ($totals['position'] ?? 0.0), |
| 55 |
'total_keywords' => (int) (count($search['rows'] ?? [])), |
| 56 |
]; |
| 57 |
} |
| 58 |
|
| 59 |
public function render(array $payload): string { |
| 60 |
$cells = [ |
| 61 |
__('Sessions', 'thinkrank') => number_format_i18n((int) $payload['sessions']), |
| 62 |
__('Users', 'thinkrank') => number_format_i18n((int) $payload['users']), |
| 63 |
__('Impressions', 'thinkrank') => number_format_i18n((int) $payload['impressions']), |
| 64 |
__('Clicks', 'thinkrank') => number_format_i18n((int) $payload['clicks']), |
| 65 |
__('Avg. Position', 'thinkrank') => number_format_i18n((float) $payload['avg_position'], 1), |
| 66 |
__('Tracked Keywords', 'thinkrank') => number_format_i18n((int) $payload['total_keywords']), |
| 67 |
]; |
| 68 |
|
| 69 |
$html = '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">'; |
| 70 |
$i = 0; |
| 71 |
foreach ($cells as $label => $value) { |
| 72 |
if ($i % 3 === 0) { |
| 73 |
$html .= ($i === 0 ? '<tr>' : '</tr><tr>'); |
| 74 |
} |
| 75 |
$html .= '<td style="width:33%;padding:10px;border:1px solid #e5e7eb;background:#f9fafb;text-align:center;">' |
| 76 |
. '<div style="font:500 12px/1.2 -apple-system,Segoe UI,Roboto,sans-serif;color:#6b7280;text-transform:uppercase;letter-spacing:.04em;">' . esc_html($label) . '</div>' |
| 77 |
. '<div style="font:700 20px/1.2 -apple-system,Segoe UI,Roboto,sans-serif;color:#111827;margin-top:4px;">' . esc_html($value) . '</div>' |
| 78 |
. '</td>'; |
| 79 |
$i++; |
| 80 |
} |
| 81 |
$html .= '</tr></table>'; |
| 82 |
return $html; |
| 83 |
} |
| 84 |
|
| 85 |
public function fallback_html(): string { |
| 86 |
return '<p style="color:#6b7280;font-style:italic;">' |
| 87 |
. esc_html__('Data temporarily unavailable. Please check your connected integrations.', 'thinkrank') |
| 88 |
. '</p>'; |
| 89 |
} |
| 90 |
} |
| 91 |
|