← All changes
|
includes/seo/email-report-sections/class-top-posts-renderer.php
+62
-85
2.4.0
→
2.9.0
View file →
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * Top Posts Renderer |
| 4 | 4 | * |
| 5 | - * Shared HTML helper used by Top Winning Posts and Top Losing Posts so | |
| 6 | - * both render identically. Not a section itself — purely a stateless | |
| 7 | - * presentation helper. | |
| 5 | + * Shared row-shaping helper used by the four growing / losing sections so | |
| 6 | + * pages and queries read the same way in every card. Not a section itself | |
| 7 | + * — purely a stateless presentation helper on top of Email_Report_Html. | |
| 8 | 8 | * |
| 9 | 9 | * @package ThinkRank |
| 10 | 10 | * @subpackage SEO\Email_Report_Sections |
| 11 | 11 | * @since 1.9.0 |
| @@ -21,113 +21,90 @@ | ||
| 21 | 21 | |
| 22 | 22 | final class Top_Posts_Renderer { |
| 23 | 23 | |
| 24 | 24 | /** |
| 25 | - * Render a page table (page, clicks, click change vs previous period). | |
| 25 | + * Shape page entries into list rows: title (resolved from the URL when | |
| 26 | + * it is one of this site's posts), the path underneath, a ±N clicks pill. | |
| 26 | 27 | * |
| 27 | - * @param array $rows Each row: ['url'|'page' => string, 'clicks' => int, 'change' => ?int, 'title' => ?string] | |
| 28 | - * @param string $variant 'gain' | 'loss' — controls accent color only. | |
| 28 | + * @param array $rows Each row: ['url' => string, 'clicks' => int, 'change' => int] | |
| 29 | + * @return array<int,array{title:string,subtitle:string,pill:string,href:string}> | |
| 29 | 30 | */ |
| 30 | - public static function render(array $rows, string $variant = 'gain'): string { | |
| 31 | - if (empty($rows)) { | |
| 32 | - return ''; | |
| 33 | - } | |
| 34 | - | |
| 35 | - $accent = $variant === 'loss' ? '#dc2626' : '#16a34a'; | |
| 36 | - | |
| 37 | - $html = '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">'; | |
| 38 | - $html .= '<tr>' | |
| 39 | - . self::th(__('Page', 'thinkrank'), 'left') | |
| 40 | - . self::th(__('Clicks', 'thinkrank'), 'right') | |
| 41 | - . self::th(__('Change', 'thinkrank'), 'right') | |
| 42 | - . '</tr>'; | |
| 43 | - | |
| 31 | + public static function page_rows(array $rows): array { | |
| 32 | + $out = []; | |
| 44 | 33 | foreach ($rows as $row) { |
| 45 | 34 | $url = (string) ($row['url'] ?? $row['page'] ?? ''); |
| 35 | + if ($url === '') { | |
| 36 | + continue; | |
| 37 | + } | |
| 46 | 38 | $title = (string) ($row['title'] ?? ''); |
| 47 | - $clicks = (int) ($row['clicks'] ?? 0); | |
| 48 | - $display = $title !== '' ? $title : $url; | |
| 49 | - | |
| 50 | - $html .= '<tr>' | |
| 51 | - . '<td style="padding:10px;border-bottom:1px solid #f3f4f6;">' | |
| 52 | - . ($url !== '' | |
| 53 | - ? '<a href="' . esc_url($url) . '" style="color:#111827;text-decoration:none;">' . esc_html($display) . '</a>' | |
| 54 | - : '<span style="color:#111827;">' . esc_html($display) . '</span>') | |
| 55 | - . '</td>' | |
| 56 | - . '<td style="padding:10px;border-bottom:1px solid #f3f4f6;text-align:right;color:#374151;font:600 13px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;">' | |
| 57 | - . esc_html(number_format_i18n($clicks)) | |
| 58 | - . '</td>' | |
| 59 | - . self::change_cell($row['change'] ?? null, $accent) | |
| 60 | - . '</tr>'; | |
| 39 | + if ($title === '') { | |
| 40 | + $title = Email_Report_Html::page_title($url); | |
| 41 | + } | |
| 42 | + $path = Email_Report_Html::display_path($url); | |
| 43 | + $out[] = [ | |
| 44 | + 'title' => $title, | |
| 45 | + 'subtitle' => $path !== $title ? $path : '', | |
| 46 | + 'pill' => self::clicks_pill((int) ($row['change'] ?? 0)), | |
| 47 | + 'href' => $url, | |
| 48 | + ]; | |
| 61 | 49 | } |
| 62 | - | |
| 63 | - $html .= '</table>'; | |
| 64 | - return $html; | |
| 50 | + return $out; | |
| 65 | 51 | } |
| 66 | 52 | |
| 67 | 53 | /** |
| 68 | - * Render a keyword table (query, position, clicks, click change). | |
| 54 | + * Shape query entries into list rows: the query, its average position | |
| 55 | + * (and the previous one when it moved), a ±N clicks pill. | |
| 69 | 56 | * |
| 70 | - * @param array $rows Each row: ['query' => string, 'position' => ?float, 'clicks' => int, 'change' => ?int] | |
| 71 | - * @param string $variant 'gain' | 'loss' | |
| 57 | + * @param array $rows Each row: ['query' => string, 'position' => ?float, 'prev_position' => ?float, 'change' => int] | |
| 58 | + * @return array<int,array{title:string,subtitle:string,pill:string}> | |
| 72 | 59 | */ |
| 73 | - public static function render_keywords(array $rows, string $variant = 'gain'): string { | |
| 74 | - if (empty($rows)) { | |
| 75 | - return ''; | |
| 76 | - } | |
| 77 | - | |
| 78 | - $accent = $variant === 'loss' ? '#dc2626' : '#16a34a'; | |
| 79 | - | |
| 80 | - $html = '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">'; | |
| 81 | - $html .= '<tr>' | |
| 82 | - . self::th(__('Keyword', 'thinkrank'), 'left') | |
| 83 | - . self::th(__('Position', 'thinkrank'), 'right') | |
| 84 | - . self::th(__('Clicks', 'thinkrank'), 'right') | |
| 85 | - . self::th(__('Change', 'thinkrank'), 'right') | |
| 86 | - . '</tr>'; | |
| 87 | - | |
| 60 | + public static function keyword_rows(array $rows): array { | |
| 61 | + $out = []; | |
| 88 | 62 | foreach ($rows as $row) { |
| 89 | 63 | $query = (string) ($row['query'] ?? ($row['keys'][0] ?? '')); |
| 90 | - // Null position means "no data this period" (e.g. a keyword that | |
| 91 | - // dropped out entirely) — a literal 0.0 would read as rank #1. | |
| 92 | - $position = isset($row['position']) | |
| 93 | - ? number_format_i18n((float) $row['position'], 1) | |
| 94 | - : '—'; | |
| 95 | - $clicks = (int) ($row['clicks'] ?? 0); | |
| 96 | 64 | if ($query === '') { |
| 97 | 65 | continue; |
| 98 | 66 | } |
| 99 | - $html .= '<tr>' | |
| 100 | - . '<td style="padding:10px;border-bottom:1px solid #f3f4f6;color:#111827;">' . esc_html($query) . '</td>' | |
| 101 | - . '<td style="padding:10px;border-bottom:1px solid #f3f4f6;text-align:right;color:#374151;font:600 13px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;">' . esc_html($position) . '</td>' | |
| 102 | - . '<td style="padding:10px;border-bottom:1px solid #f3f4f6;text-align:right;color:#374151;">' . esc_html(number_format_i18n($clicks)) . '</td>' | |
| 103 | - . self::change_cell($row['change'] ?? null, $accent) | |
| 104 | - . '</tr>'; | |
| 67 | + $out[] = [ | |
| 68 | + 'title' => $query, | |
| 69 | + 'subtitle' => self::position_line($row['position'] ?? null, $row['prev_position'] ?? null), | |
| 70 | + 'pill' => self::clicks_pill((int) ($row['change'] ?? 0)), | |
| 71 | + ]; | |
| 105 | 72 | } |
| 106 | - | |
| 107 | - $html .= '</table>'; | |
| 108 | - return $html; | |
| 73 | + return $out; | |
| 109 | 74 | } |
| 110 | 75 | |
| 111 | 76 | /** |
| 112 | - * Table header cell. | |
| 77 | + * "Avg. position 9.4 (was 5.1)". A null current position means the query | |
| 78 | + * had no impressions this period — say so rather than print 0.0. | |
| 113 | 79 | */ |
| 114 | - private static function th(string $label, string $align): string { | |
| 115 | - return '<th style="text-align:' . esc_attr($align) . ';padding:8px 10px;border-bottom:1px solid #e5e7eb;font:600 12px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;color:#6b7280;text-transform:uppercase;letter-spacing:.04em;">' | |
| 116 | - . esc_html($label) . '</th>'; | |
| 80 | + private static function position_line($position, $previous): string { | |
| 81 | + if ($position === null) { | |
| 82 | + return __('No impressions this period', 'thinkrank'); | |
| 83 | + } | |
| 84 | + $line = sprintf( | |
| 85 | + /* translators: %s: average position, one decimal. */ | |
| 86 | + __('Avg. position %s', 'thinkrank'), | |
| 87 | + number_format_i18n((float) $position, 1) | |
| 88 | + ); | |
| 89 | + if ($previous !== null && abs((float) $previous - (float) $position) >= 0.1) { | |
| 90 | + $line .= ' ' . sprintf( | |
| 91 | + /* translators: %s: previous average position, one decimal. */ | |
| 92 | + __('(was %s)', 'thinkrank'), | |
| 93 | + number_format_i18n((float) $previous, 1) | |
| 94 | + ); | |
| 95 | + } | |
| 96 | + return $line; | |
| 117 | 97 | } |
| 118 | 98 | |
| 119 | 99 | /** |
| 120 | - * Signed click-change cell. Positive renders green with ▲, negative red | |
| 121 | - * with ▼. A null/zero change renders a neutral dash. | |
| 100 | + * "+412 clicks" / "−188 clicks". | |
| 122 | 101 | */ |
| 123 | - private static function change_cell($change, string $accent): string { | |
| 124 | - $td = '<td style="padding:10px;border-bottom:1px solid #f3f4f6;text-align:right;font:600 13px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;color:%s;">%s</td>'; | |
| 125 | - | |
| 126 | - if ($change === null || (int) $change === 0) { | |
| 127 | - return sprintf($td, '#9ca3af', '—'); | |
| 128 | - } | |
| 129 | - $change = (int) $change; | |
| 130 | - $arrow = $change > 0 ? '▲' : '▼'; | |
| 131 | - return sprintf($td, esc_attr($accent), esc_html($arrow . ' ' . number_format_i18n(abs($change)))); | |
| 102 | + private static function clicks_pill(int $change): string { | |
| 103 | + $sign = $change < 0 ? '−' : '+'; | |
| 104 | + return $sign . sprintf( | |
| 105 | + /* translators: %s: number of clicks. */ | |
| 106 | + _n('%s click', '%s clicks', abs($change), 'thinkrank'), | |
| 107 | + number_format_i18n(abs($change)) | |
| 108 | + ); | |
| 132 | 109 | } |
| 133 | 110 | } |