PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.1
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.1
2.14.1 2.14.0 2.13.0 2.12.0 2.11.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 All 56 releases
thinkrank / includes / seo / email-report-sections / class-position-summary-section.php

class-position-summary-section.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.14.1, at includes/seo/email-report-sections/class-position-summary-section.php

131 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Position Summary Section — "Where your keywords rank".
4 *
5 * Buckets the period's queries into Top 3 / 4–10 / 11–50 / 51–100 and draws
6 * them as bars. Source: GSC `position_distribution` already computed by
7 * Analytics_Manager over the whole property (#913), with queries ranked
8 * past 100 in `over_100`.
9 *
10 * @package ThinkRank
11 * @subpackage SEO\Email_Report_Sections
12 * @since 1.9.0
13 */
14
15 declare(strict_types=1);
16
17 namespace ThinkRank\SEO\Email_Report_Sections;
18
19 if (!defined('ABSPATH')) {
20 exit;
21 }
22
23 final class Position_Summary_Section implements Email_Report_Section_Interface {
24
25 public function key(): string {
26 return 'position_summary';
27 }
28
29 public function label(): string {
30 return __('Where your keywords rank', 'thinkrank');
31 }
32
33 public function default_enabled(): bool {
34 return true;
35 }
36
37 public function requires_capability(): ?string {
38 return null;
39 }
40
41 public function renders_own_heading(): bool {
42 return true;
43 }
44
45 public function collect(array $context): array {
46 $shared = $context['shared'] ?? [];
47 if (empty($shared['available'])) {
48 return [];
49 }
50 $dist = $shared['current']['search_performance']['position_distribution'] ?? [];
51 if (!is_array($dist) || empty($dist)) {
52 return [];
53 }
54 return [
55 'top_3' => (int) ($dist['top_3'] ?? 0),
56 '4_10' => (int) ($dist['4_10'] ?? 0),
57 '11_50' => (int) ($dist['10_50'] ?? 0), // GSC's `10_50` bucket starts at 11
58 '51_100' => (int) ($dist['51_100'] ?? 0),
59 // Counted over the whole property since #913. A payload cached
60 // before then has neither key and reads as before.
61 'over_100' => (int) ($dist['over_100'] ?? 0),
62 'complete' => (bool) ($dist['complete'] ?? true),
63 ];
64 }
65
66 public function has_data(array $payload): bool {
67 $counts = array_intersect_key($payload, array_flip(['top_3', '4_10', '11_50', '51_100', 'over_100']));
68 return array_sum(array_map('intval', $counts)) > 0;
69 }
70
71 public function render(array $payload): string {
72 $counts = [
73 'top_3' => (int) ($payload['top_3'] ?? 0),
74 '4_10' => (int) ($payload['4_10'] ?? 0),
75 '11_50' => (int) ($payload['11_50'] ?? 0),
76 '51_100' => (int) ($payload['51_100'] ?? 0),
77 ];
78 // The bars share out the top 100; queries ranked past it still had
79 // an impression, so they count toward the headline total (#913).
80 $ranked = array_sum($counts);
81 $over_100 = max(0, (int) ($payload['over_100'] ?? 0));
82 $total = $ranked + $over_100;
83 if ($total === 0) {
84 return '';
85 }
86 $page_one = $counts['top_3'] + $counts['4_10'];
87
88 // Incomplete means Search Console could not be paged to the end, so
89 // the count covers the top of the property by clicks, not all of it.
90 $subtitle = (bool) ($payload['complete'] ?? true)
91 ? sprintf(
92 /* translators: %s: number of queries. */
93 _n('%s query got at least one impression this period', '%s queries got at least one impression this period', $total, 'thinkrank'),
94 number_format_i18n($total)
95 )
96 : sprintf(
97 /* translators: %s: number of queries counted. */
98 _n('Counted over your top %s query by clicks this period', 'Counted over your top %s queries by clicks this period', $total, 'thinkrank'),
99 number_format_i18n($total)
100 );
101
102 $html = Email_Report_Html::heading($this->label(), $subtitle);
103 $html .= '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin-top:14px;">'
104 . Email_Report_Html::bar_row(__('Top 3', 'thinkrank'), $counts['top_3'], $ranked, Email_Report_Html::PRIMARY)
105 . Email_Report_Html::bar_row(__('Positions 4–10', 'thinkrank'), $counts['4_10'], $ranked, '#6e79ff')
106 . Email_Report_Html::bar_row(__('Positions 11–50', 'thinkrank'), $counts['11_50'], $ranked, '#b3b9ff')
107 . Email_Report_Html::bar_row(__('Positions 51–100', 'thinkrank'), $counts['51_100'], $ranked, '#dcdfff')
108 . '</table>';
109 if ($over_100 > 0) {
110 $html .= Email_Report_Html::note(sprintf(
111 /* translators: 1: queries ranking in the top 100, 2: queries ranking beyond position 100. */
112 _n('%1$s rank in the top 100 and %2$s ranks beyond position 100.', '%1$s rank in the top 100 and %2$s rank beyond position 100.', $over_100, 'thinkrank'),
113 number_format_i18n($ranked),
114 number_format_i18n($over_100)
115 ));
116 }
117 $html .= Email_Report_Html::note(sprintf(
118 /* translators: %s: number of queries ranking in positions 1–10. */
119 _n('%s query is on page one.', '%s queries are on page one.', $page_one, 'thinkrank'),
120 number_format_i18n($page_one)
121 ));
122 $html .= Email_Report_Html::link(__('See all keywords', 'thinkrank'), Email_Report_Html::admin_link('analytics', 'keywords'));
123
124 return $html;
125 }
126
127 public function fallback_html(): string {
128 return '';
129 }
130 }
131