← 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; |