PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.2
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.2
2.14.2 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 All 57 releases
← All changes | includes/integrations/class-google-search-console-client.php +27 -83 1.28.0 → 2.14.2 View file →
@@ -61,10 +61,16 @@
61 61 'message' => 'Google Search Console API connection successful',
62 62 'sites_count' => count($result['siteEntry'] ?? [])
63 63 ];
64 64 } catch (\Exception $e) {
65 + // `message` as well as `error`: the success branch above reports
66 + // its outcome as `message`, and every consumer reads that key, so
67 + // a failure that only set `error` was reported to the admin as an
68 + // error with no reason at all (#852). `error` is kept for any
69 + // caller that already reads it.
65 70 return [
66 71 'success' => false,
72 + 'message' => $e->getMessage(),
67 73 'error' => $e->getMessage()
68 74 ];
69 75 }
70 76 }
@@ -95,10 +101,15 @@
95 101 // Check if the site is already verified by listing sites
96 102 $sites = $this->list_sites();
97 103 $site_verified = false;
98 104
105 + // A domain property is compared in its punycode form, so an IDN
106 + // matches whichever form the account lists it in.
107 + $wanted = \ThinkRank\Core\Url_Validator::search_console_domain_property($site_url) ?? $site_url;
108 +
99 109 foreach ($sites['siteEntry'] ?? [] as $site) {
100 - if ($site['siteUrl'] === $site_url) {
110 + $listed = (string) ($site['siteUrl'] ?? '');
111 + if ($listed === $site_url || (\ThinkRank\Core\Url_Validator::search_console_domain_property($listed) ?? $listed) === $wanted) {
101 112 $site_verified = true;
102 113 break;
103 114 }
104 115 }
@@ -133,9 +144,9 @@
133 144 $days = (int) str_replace('d', '', $date_range);
134 145 $end_date = gmdate('Y-m-d', strtotime('-2 days'));
135 146 $start_date = gmdate('Y-m-d', strtotime('-' . ($days - 1) . ' days', strtotime($end_date)));
136 147
137 - $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query';
148 + $endpoint = '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query';
138 149
139 150 $request_body = [
140 151 'startDate' => $start_date,
141 152 'endDate' => $end_date,
@@ -161,9 +172,9 @@
161 172 $days = (int) str_replace('d', '', $date_range);
162 173 $end_date = gmdate('Y-m-d', strtotime('-2 days'));
163 174 $start_date = gmdate('Y-m-d', strtotime('-' . ($days - 1) . ' days', strtotime($end_date)));
164 175
165 - $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query';
176 + $endpoint = '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query';
166 177
167 178 // diverse from get_search_performance: no dimensions, just totals
168 179 $request_body = [
169 180 'startDate' => $start_date,
@@ -209,9 +220,9 @@
209 220 * @return array Aggregated totals
210 221 * @throws \Exception If API request fails
211 222 */
212 223 public function get_search_totals_by_dates(string $site_url, string $start_date, string $end_date): array {
213 - $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query';
224 + $endpoint = '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query';
214 225
215 226 $request_body = [
216 227 'startDate' => $start_date,
217 228 'endDate' => $end_date,
@@ -242,13 +253,16 @@
242 253 * @param string $site_url Site URL to get data for
243 254 * @param string $start_date Start date (Y-m-d)
244 255 * @param string $end_date End date (Y-m-d)
245 256 * @param int $row_limit Maximum rows to return
257 + * @param array $dimensions Dimensions to group by
258 + * @param array $filters Optional dimension filters (ANDed), each
259 + * {dimension, operator, expression}. @since 2.15.0
246 260 * @return array Raw rows from GSC API
247 261 * @throws \Exception If API request fails
248 262 */
249 - public function get_search_performance_by_dates(string $site_url, string $start_date, string $end_date, int $row_limit = 500, array $dimensions = ['query']): array {
250 - $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query';
263 + public function get_search_performance_by_dates(string $site_url, string $start_date, string $end_date, int $row_limit = 500, array $dimensions = ['query'], array $filters = []): array {
264 + $endpoint = '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query';
251 265
252 266 $request_body = [
253 267 'startDate' => $start_date,
254 268 'endDate' => $end_date,
@@ -259,8 +273,14 @@
259 273 // last 2-4 days aren't missing.
260 274 'dataState' => 'all',
261 275 ];
262 276
277 + // Optional dimension filters, ANDed, e.g. one page's queries:
278 + // [['dimension' => 'page', 'operator' => 'equals', 'expression' => $url]].
279 + if (!empty($filters)) {
280 + $request_body['dimensionFilterGroups'] = [['filters' => array_values($filters)]];
281 + }
282 +
263 283 $full_url = self::API_BASE_URL . $endpoint;
264 284 $response = $this->make_request($full_url, $request_body, 'POST');
265 285
266 286 return $response['rows'] ?? [];
@@ -308,41 +328,8 @@
308 328 }
309 329 }
310 330
311 331 /**
312 - * Get page performance data for SEO analytics
313 - *
314 - * @param string $site_url Site URL to get data for
315 - * @param string $date_range Date range for data
316 - * @param int $limit Number of pages to retrieve
317 - * @return array Page performance data
318 - * @throws \Exception If API request fails
319 - */
320 - public function get_page_performance(string $site_url, string $date_range = '30d', int $limit = 25): array {
321 - $result = $this->get_search_performance($site_url, $date_range, ['page'], $limit);
322 -
323 - $pages = [];
324 - $rows = $result['rows'] ?? [];
325 -
326 - foreach ($rows as $row) {
327 - $pages[] = [
328 - 'page' => $row['keys'][0] ?? '',
329 - 'clicks' => $row['clicks'] ?? 0,
330 - 'impressions' => $row['impressions'] ?? 0,
331 - 'ctr' => round(($row['ctr'] ?? 0) * 100, 2), // Convert to percentage
332 - 'position' => round($row['position'] ?? 0, 1)
333 - ];
334 - }
335 -
336 - return [
337 - 'pages' => $pages,
338 - 'site_url' => $site_url,
339 - 'date_range' => $date_range,
340 - 'total_pages' => count($pages)
341 - ];
342 - }
343 -
344 - /**
345 332 * Get device performance breakdown for mobile SEO insights
346 333 *
347 334 * @param string $site_url Site URL to get data for
348 335 * @param string $date_range Date range for data
@@ -403,51 +390,8 @@
403 390 ];
404 391 }
405 392
406 393 /**
407 - * Get site indexing status and coverage data
408 - *
409 - * @param string $site_url Site URL to check
410 - * @return array Indexing status and coverage data
411 - * @throws \Exception If API request fails
412 - */
413 - public function get_indexing_status(string $site_url): array {
414 - try {
415 - // Get overall search performance to estimate indexed pages
416 - $performance = $this->get_search_performance($site_url, '30d', ['page'], 1000);
417 - $indexed_pages = count($performance['rows'] ?? []);
418 -
419 - // Get basic site info
420 - $sites = $this->list_sites();
421 - $site_info = null;
422 -
423 - foreach ($sites['siteEntry'] ?? [] as $site) {
424 - if ($site['siteUrl'] === $site_url) {
425 - $site_info = $site;
426 - break;
427 - }
428 - }
429 -
430 - return [
431 - 'site_url' => $site_url,
432 - 'is_verified' => !is_null($site_info),
433 - 'indexed_pages_estimate' => $indexed_pages,
434 - 'permission_level' => $site_info['permissionLevel'] ?? 'none',
435 - 'last_updated' => gmdate('Y-m-d H:i:s')
436 - ];
437 - } catch (\Exception $e) {
438 - return [
439 - 'site_url' => $site_url,
440 - 'is_verified' => false,
441 - 'indexed_pages_estimate' => 0,
442 - 'permission_level' => 'none',
443 - 'error' => $e->getMessage(),
444 - 'last_updated' => gmdate('Y-m-d H:i:s')
445 - ];
446 - }
447 - }
448 -
449 - /**
450 394 * Get keyword opportunities for SEO insights
451 395 * Identifies queries with high impressions but low CTR or position
452 396 *
453 397 * @param string $site_url Site URL to analyze
@@ -597,9 +541,9 @@
597 541 * @param string[] $brand_terms Brand keywords to match (substring, case-insensitive)
598 542 * @return array { branded: {...}, non_branded: {...}, total_clicks: int }
599 543 */
600 544 private function gsc_split_by_brand(string $site_url, string $start, string $end, array $brand_terms): array {
601 - $url = self::API_BASE_URL . '/sites/' . urlencode($site_url) . '/searchAnalytics/query';
545 + $url = self::API_BASE_URL . '/sites/' . rawurlencode($site_url) . '/searchAnalytics/query';
602 546
603 547 $page_size = 25000;
604 548 $start_row = 0;
605 549 $total_clicks = 0;