list_sites(); return [ 'success' => true, 'message' => 'Google Search Console API connection successful', 'sites_count' => count($result['siteEntry'] ?? []) ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * List verified sites in Search Console * * @return array List of verified sites * @throws \Exception If API request fails */ public function list_sites(): array { $endpoint = '/sites'; $params = ['key' => $this->api_key]; $full_url = self::API_BASE_URL . $endpoint; return $this->make_request($full_url, $params, 'GET'); } /** * Verify site ownership in Search Console * * @param string $site_url Site URL to verify * @param string $verification_method Verification method used * @return array Verification results */ public function verify_site(string $site_url, string $verification_method = 'meta'): array { try { // Check if the site is already verified by listing sites $sites = $this->list_sites(); $site_verified = false; foreach ($sites['siteEntry'] ?? [] as $site) { if ($site['siteUrl'] === $site_url) { $site_verified = true; break; } } return [ 'success' => $site_verified, 'message' => $site_verified ? 'Site is verified in Search Console' : 'Site not found in Search Console', 'site_url' => $site_url, 'verification_method' => $verification_method ]; } catch (\Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Get search performance data with flexible dimensions * * @param string $site_url Site URL to get data for * @param string $date_range Date range ('7d', '30d', '90d') * @param array $dimensions Dimensions to group by (query, page, country, device, searchAppearance) * @param int $row_limit Maximum number of rows to return * @return array Search performance data * @throws \Exception If API request fails */ public function get_search_performance(string $site_url, string $date_range = '30d', array $dimensions = ['query'], int $row_limit = 100): array { // Calculate date range $end_date = gmdate('Y-m-d'); $days = (int) str_replace('d', '', $date_range); $start_date = gmdate('Y-m-d', strtotime("-{$days} days")); $endpoint = '/sites/' . urlencode($site_url) . '/searchAnalytics/query'; $request_body = [ 'startDate' => $start_date, 'endDate' => $end_date, 'dimensions' => $dimensions, 'rowLimit' => $row_limit ]; $full_url = self::API_BASE_URL . $endpoint . '?key=' . $this->api_key; return $this->make_request($full_url, $request_body, 'POST'); } /** * Get top search queries * * @param string $site_url Site URL to get data for * @param int $limit Number of queries to retrieve * @return array Top search queries * @throws \Exception If API request fails */ public function get_top_queries(string $site_url, int $limit = 10): array { try { $performance_data = $this->get_search_performance($site_url, '30d'); $queries = []; foreach ($performance_data['rows'] ?? [] as $row) { if (count($queries) >= $limit) { break; } $queries[] = [ 'query' => $row['keys'][0] ?? '', 'clicks' => $row['clicks'] ?? 0, 'impressions' => $row['impressions'] ?? 0, 'ctr' => $row['ctr'] ?? 0, 'position' => $row['position'] ?? 0 ]; } return [ 'queries' => $queries, 'site_url' => $site_url, 'limit' => $limit ]; } catch (\Exception $e) { return [ 'queries' => [], 'site_url' => $site_url, 'limit' => $limit, 'error' => $e->getMessage() ]; } } /** * Get page performance data for SEO analytics * * @param string $site_url Site URL to get data for * @param string $date_range Date range for data * @param int $limit Number of pages to retrieve * @return array Page performance data * @throws \Exception If API request fails */ public function get_page_performance(string $site_url, string $date_range = '30d', int $limit = 25): array { $result = $this->get_search_performance($site_url, $date_range, ['page'], $limit); $pages = []; $rows = $result['rows'] ?? []; foreach ($rows as $row) { $pages[] = [ 'page' => $row['keys'][0] ?? '', 'clicks' => $row['clicks'] ?? 0, 'impressions' => $row['impressions'] ?? 0, 'ctr' => round(($row['ctr'] ?? 0) * 100, 2), // Convert to percentage 'position' => round($row['position'] ?? 0, 1) ]; } return [ 'pages' => $pages, 'site_url' => $site_url, 'date_range' => $date_range, 'total_pages' => count($pages) ]; } /** * Get device performance breakdown for mobile SEO insights * * @param string $site_url Site URL to get data for * @param string $date_range Date range for data * @return array Device performance data * @throws \Exception If API request fails */ public function get_device_performance(string $site_url, string $date_range = '30d'): array { $result = $this->get_search_performance($site_url, $date_range, ['device'], 10); $devices = []; $rows = $result['rows'] ?? []; foreach ($rows as $row) { $device = $row['keys'][0] ?? ''; $devices[$device] = [ 'clicks' => $row['clicks'] ?? 0, 'impressions' => $row['impressions'] ?? 0, 'ctr' => round(($row['ctr'] ?? 0) * 100, 2), 'position' => round($row['position'] ?? 0, 1) ]; } return [ 'devices' => $devices, 'site_url' => $site_url, 'date_range' => $date_range ]; } /** * Get search appearance data for rich results tracking * * @param string $site_url Site URL to get data for * @param string $date_range Date range for data * @return array Search appearance data * @throws \Exception If API request fails */ public function get_search_appearance(string $site_url, string $date_range = '30d'): array { $result = $this->get_search_performance($site_url, $date_range, ['searchAppearance'], 20); $appearances = []; $rows = $result['rows'] ?? []; foreach ($rows as $row) { $appearance = $row['keys'][0] ?? ''; $appearances[$appearance] = [ 'clicks' => $row['clicks'] ?? 0, 'impressions' => $row['impressions'] ?? 0, 'ctr' => round(($row['ctr'] ?? 0) * 100, 2), 'position' => round($row['position'] ?? 0, 1) ]; } return [ 'appearances' => $appearances, 'site_url' => $site_url, 'date_range' => $date_range ]; } /** * Get site indexing status and coverage data * * @param string $site_url Site URL to check * @return array Indexing status and coverage data * @throws \Exception If API request fails */ public function get_indexing_status(string $site_url): array { try { // Get overall search performance to estimate indexed pages $performance = $this->get_search_performance($site_url, '30d', ['page'], 1000); $indexed_pages = count($performance['rows'] ?? []); // Get basic site info $sites = $this->list_sites(); $site_info = null; foreach ($sites['siteEntry'] ?? [] as $site) { if ($site['siteUrl'] === $site_url) { $site_info = $site; break; } } return [ 'site_url' => $site_url, 'is_verified' => !is_null($site_info), 'indexed_pages_estimate' => $indexed_pages, 'permission_level' => $site_info['permissionLevel'] ?? 'none', 'last_updated' => gmdate('Y-m-d H:i:s') ]; } catch (\Exception $e) { return [ 'site_url' => $site_url, 'is_verified' => false, 'indexed_pages_estimate' => 0, 'permission_level' => 'none', 'error' => $e->getMessage(), 'last_updated' => gmdate('Y-m-d H:i:s') ]; } } /** * Get keyword opportunities for SEO insights * Identifies queries with high impressions but low CTR or position * * @param string $site_url Site URL to analyze * @param string $date_range Date range for analysis * @param int $min_impressions Minimum impressions threshold * @return array Keyword opportunities * @throws \Exception If API request fails */ public function get_keyword_opportunities(string $site_url, string $date_range = '30d', int $min_impressions = 100): array { $result = $this->get_search_performance($site_url, $date_range, ['query'], 500); $opportunities = []; $rows = $result['rows'] ?? []; foreach ($rows as $row) { $impressions = $row['impressions'] ?? 0; $ctr = $row['ctr'] ?? 0; $position = $row['position'] ?? 0; $clicks = $row['clicks'] ?? 0; // Identify opportunities: high impressions, low CTR, or position 4-10 if ($impressions >= $min_impressions) { $opportunity_score = 0; $opportunity_reasons = []; // Low CTR opportunity if ($ctr < 0.05 && $position <= 10) { // Less than 5% CTR in top 10 $opportunity_score += 30; $opportunity_reasons[] = 'Low CTR for top 10 position'; } // Position 4-10 opportunity (could reach top 3) if ($position >= 4 && $position <= 10) { $opportunity_score += 40; $opportunity_reasons[] = 'Ranking 4-10, potential for top 3'; } // High impressions, low clicks if ($impressions > 500 && $clicks < 25) { $opportunity_score += 20; $opportunity_reasons[] = 'High impressions but low clicks'; } if ($opportunity_score > 0) { $opportunities[] = [ 'query' => $row['keys'][0] ?? '', 'clicks' => $clicks, 'impressions' => $impressions, 'ctr' => round($ctr * 100, 2), 'position' => round($position, 1), 'opportunity_score' => $opportunity_score, 'reasons' => $opportunity_reasons ]; } } } // Sort by opportunity score (highest first) usort($opportunities, function($a, $b) { return $b['opportunity_score'] <=> $a['opportunity_score']; }); return [ 'opportunities' => array_slice($opportunities, 0, 50), // Top 50 opportunities 'site_url' => $site_url, 'date_range' => $date_range, 'total_opportunities' => count($opportunities) ]; } /** * Get rate limit configuration * Following ThinkRank rate limiting patterns * * @return array Rate limit configuration */ protected function get_rate_limits(): array { return [ 'max_requests_per_day' => self::MAX_REQUESTS_PER_DAY, 'reset_time' => get_transient(self::RATE_LIMIT_KEY . '_reset') ?: strtotime('tomorrow') ]; } /** * Get rate limit transient key * Following ThinkRank option naming patterns * * @return string Rate limit key */ protected function get_rate_limit_key(): string { return self::RATE_LIMIT_KEY; } /** * Get rate limit error message * * @return string Error message */ protected function get_rate_limit_error_message(): string { return 'Google Search Console API rate limit exceeded. Try again tomorrow.'; } }