clientId = $clientId; $this->clientSecret = $clientSecret; $this->redirectUri = $redirectUri; } /** * Add scopes for the OAuth2 request * @param array $scopes Array of Google OAuth2 scopes * @return self */ public function addScopes(array $scopes): self { $this->scopes = array_merge($this->scopes, $scopes); return $this; } /** * Generate the authorization URL * @param array $additionalParams Additional URL parameters * @return string */ public function getAuthorizationUrl(array $additionalParams = []): string { $params = array_merge([ 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'response_type' => 'code', 'access_type' => 'offline', 'prompt' => 'consent', 'scope' => implode(' ', array_unique($this->scopes)) ], $additionalParams); return self::AUTH_URL . '?' . http_build_query($params); } /** * Exchange authorization code for tokens * @param string $code Authorization code * @return array Token response * @throws \Exception */ public function exchangeCode(string $code): array { $response = $this->makeTokenRequest([ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $this->redirectUri ]); $this->setTokens($response); return $response; } /** * Refresh the access token * @throws \Exception */ public function refreshAccessToken(): array { if (!$this->refreshToken) { throw new \Exception('No refresh token available'); } $response = $this->makeTokenRequest([ 'grant_type' => 'refresh_token', 'refresh_token' => $this->refreshToken ]); $this->setTokens($response); return $response; } /** * Make an authenticated request to Google APIs * @param string $url * @param string $method * @param array $headers * @param mixed $body * @param bool $rawResponse Whether to return raw response instead of JSON * @return array|string * @throws \Exception */ public function makeAuthenticatedRequest( string $url, string $method = 'GET', array $headers = [], $body = null, bool $rawResponse = false ){ if ($this->isTokenExpired()) { $this->refreshAccessToken(); } $headers = array_merge([ 'Authorization: Bearer ' . $this->accessToken, 'Accept: application/json' ], $headers); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, CURLOPT_CUSTOMREQUEST => $method ]); if ($body && in_array($method, ['POST', 'PUT', 'PATCH'])) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { throw new \Exception('Request failed with status ' . $httpCode . ': ' . $response); } return $rawResponse ? $response : json_decode($response, true); } /** * Set tokens from OAuth2 response * @param array $response */ private function setTokens(array $response): void { $this->accessToken = $response['access_token']; if (isset($response['refresh_token'])) { $this->refreshToken = $response['refresh_token']; } $this->tokenExpires = time() + ($response['expires_in'] ?? 3600); } /** * Check if the current access token is expired * @return bool */ private function isTokenExpired(): bool { return !$this->accessToken || !$this->tokenExpires || time() >= $this->tokenExpires; } /** * Make a token request to Google's OAuth2 server * @param array $params * @return array * @throws \Exception */ private function makeTokenRequest(array $params): array { $params = array_merge([ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret ], $params); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => self::TOKEN_URL, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'] ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { throw new \Exception('Token request failed with status ' . $httpCode . ': ' . $response); } return json_decode($response, true); } // Getters for tokens and expiration public function getAccessToken(): ?string { return $this->accessToken; } public function setAccessToken(string $accessToken): void { $this->accessToken = $accessToken; } public function getRefreshToken(): ?string { return $this->refreshToken; } public function setRefreshToken(string $refreshToken): void { $this->refreshToken = $refreshToken; } public function getTokenExpires(): ?int { return $this->tokenExpires; } }