PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 All 51 releases
← 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 }