| | |
| | | |
| | | class GoogleMyBusiness extends Integrations |
| | | { |
| | | private string $access_token; |
| | | private ?string $access_token = null; |
| | | protected string $readMask = 'name,title,storefrontAddress,metadata,openInfo,storeCode,categories,phoneNumbers,labels,specialHours'; |
| | | private ?string $location = null; |
| | | private ?string $refresh_token = null; |
| | | private ?string $client_id = null; |
| | | private ?string $client_secret = null; |
| | | private ?string $account_id = null; |
| | | |
| | | public function __construct(?int $userID = null) |
| | |
| | | ]; |
| | | |
| | | $this->apiEndpoints = [ |
| | | '/accounts/[^/]+/locations/[^/]+/reviews', |
| | | '/accounts/[^/]+/locations/[^/]+/foodMenus', |
| | | '/v4/accounts/[^/]+/locations/[^/]+/media', |
| | | '/v4/accounts/[^/]+/locations/[^/]+/localPosts', |
| | |
| | | 'label' => 'OAuth Client Secret', |
| | | 'required' => true, |
| | | ], |
| | | 'access_token' => [ |
| | | 'type' => 'text', |
| | | 'subtype' => 'password', |
| | | 'label' => 'Access Token', |
| | | 'hint' => 'Generated automagically after OAuth authorization.' |
| | | ], |
| | | 'refresh_token' => [ |
| | | 'type' => 'text', |
| | | 'subtype' => 'password', |
| | | 'label' => 'Refresh Token', |
| | | 'hint' => 'Generated automagically after OAuth authorization.' |
| | | ] |
| | | // 'access_token' => [ |
| | | // 'type' => 'text', |
| | | // 'subtype' => 'password', |
| | | // 'label' => 'Access Token', |
| | | // 'hint' => 'Generated automagically after OAuth authorization.' |
| | | // ], |
| | | // 'refresh_token' => [ |
| | | // 'type' => 'text', |
| | | // 'subtype' => 'password', |
| | | // 'label' => 'Refresh Token', |
| | | // 'hint' => 'Generated automagically after OAuth authorization.' |
| | | // ] |
| | | ]; |
| | | |
| | | $this->advanced = [ |
| | |
| | | 'check_oauth_status' => 'Check OAuth Status' |
| | | ] |
| | | ); |
| | | |
| | | // $this->cache->clear(); |
| | | } |
| | | |
| | | protected function initialize(): void |
| | |
| | | if (empty($this->credentials)) { |
| | | $this->loadCredentials(); |
| | | } |
| | | $this->access_token = $this->credentials['access_token'] ?? ''; |
| | | $this->refresh_token = $this->credentials['refresh_token'] ?? ''; |
| | | $this->client_id = $this->credentials['client_id'] ?? ''; |
| | | $this->client_secret = $this->credentials['client_secret'] ?? ''; |
| | | $this->location = $this->credentials['location'] ?? null; |
| | | $this->account_id = $this->credentials['account'] ?? null; |
| | | $this->access_token = (array_key_exists('access_token', $this->credentials)) ? $this->credentials['access_token'] : null; |
| | | $this->refresh_token = (array_key_exists('refresh_token', $this->credentials)) ? $this->credentials['refresh_token'] : null; |
| | | $this->client_id = (array_key_exists('client_id', $this->credentials)) ? $this->credentials['client_id'] : null; |
| | | $this->client_secret = (array_key_exists('client_secret', $this->credentials)) ? $this->credentials['client_secret'] : null; |
| | | $this->location = (array_key_exists('location', $this->credentials)) ? $this->credentials['location'] : null; |
| | | $this->account_id = (array_key_exists('account', $this->credentials)) ? $this->credentials['account'] : null; |
| | | |
| | | if ($this->account_id) { |
| | | $this->apiEndpoints[] = "/v1/{$this->account_id}/locations"; |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check if response contains an error - Google-specific |
| | | */ |
| | | protected function isErrorResponse(array $response): bool |
| | | { |
| | | // Google APIs return errors in this format: |
| | | // {"error": {"code": 401, "message": "...", "status": "UNAUTHENTICATED"}} |
| | | return isset($response['error']) && isset($response['error']['code']); |
| | | } |
| | | |
| | | protected function getRequestHeaders(): array |
| | | { |
| | | return [ |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get reviews for the current location |
| | | * @param int $page_size Number of reviews to fetch (max 50) |
| | | * @return array|null |
| | | */ |
| | | public function getReviews(int $page_size = 5): ?array |
| | | { |
| | | $this->ensureInitialized(); |
| | | if (!$this->location) { |
| | | throw new \Exception('No location selected'); |
| | | } |
| | | if (!$this->account_id) { |
| | | throw new \Exception('No account configured'); |
| | | } |
| | | |
| | | $location = $this->getSelectedLocationResourceName(); |
| | | $account = $this->account_id; |
| | | |
| | | |
| | | // Check cache first (weekly refresh = 604800 seconds) |
| | | $cache_key = ['reviews', $location, $page_size]; |
| | | $cached = $this->cache->get($cache_key); |
| | | if ($cached !== false) { |
| | | return $cached; |
| | | } |
| | | |
| | | try { |
| | | // Reviews endpoint from My Business Account Management API |
| | | $response = $this->getRequest( |
| | | "/{$account}/{$location}/reviews", |
| | | [ |
| | | 'orderBy' => 'updateTime desc' |
| | | ], |
| | | 'v4' |
| | | ); |
| | | |
| | | error_log('Review response: '.print_r($response, true)); |
| | | $reviews = $response ?? []; |
| | | |
| | | // Cache for 1 week (604800 seconds) |
| | | $this->cache->set($cache_key, $reviews, WEEK_IN_SECONDS); |
| | | |
| | | return $reviews; |
| | | |
| | | } catch (\Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'getReviews' |
| | | ]); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the URL to view all Google reviews for the current location |
| | | * @return string|null The reviews viewing URL or null if not available |
| | | */ |
| | | public function getReviewsViewUrl(): ?string |
| | | { |
| | | $this->ensureInitialized(); |
| | | try { |
| | | $location = $this->getLocation(); |
| | | |
| | | if (empty($location)) { |
| | | return null; |
| | | } |
| | | |
| | | // Prefer maps URL as it shows all reviews directly |
| | | if (!empty($location['metadata']['mapsUrl'])) { |
| | | return $location['metadata']['mapsUrl']; |
| | | } |
| | | |
| | | // Fallback: construct from Place ID |
| | | if (!empty($location['metadata']['placeId'])) { |
| | | return 'https://search.google.com/local/reviews?placeid=' . |
| | | urlencode($location['metadata']['placeId']); |
| | | } |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | $this->logError('Failed to get reviews view URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewsViewUrl' |
| | | ]); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get the URL to leave a review for the current location |
| | | * @return string|null The review URL or null if not available |
| | | */ |
| | | public function getReviewUrl(): ?string |
| | | { |
| | | $this->ensureInitialized(); |
| | | try { |
| | | $location = $this->getLocation(); |
| | | |
| | | if (empty($location)) { |
| | | return null; |
| | | } |
| | | |
| | | // Try to use Place ID for write review |
| | | if (!empty($location['metadata']['placeId'])) { |
| | | return 'https://search.google.com/local/writereview?placeid=' . |
| | | urlencode($location['metadata']['placeId']); |
| | | } |
| | | |
| | | // Fallback to maps URL |
| | | if (!empty($location['metadata']['mapsUrl'])) { |
| | | return $location['metadata']['mapsUrl'] . '/reviews'; |
| | | } |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | $this->logError('Failed to get review URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewUrl' |
| | | ]); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get locations for an account (with persistent storage) |
| | | * Allowed Fields: https://developers.google.com/my-business/content/location-data#list_of_all_supported_filter_fields |
| | | */ |
| | |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | |
| | | } |