This commit is contained in:
emmymayo
2025-02-04 23:06:08 +01:00
commit 77037e7e84
74 changed files with 33573 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
<?php
namespace Lib\Google;
include_once 'lib/google/oauth2.php';
include_once 'config.php';
class GoogleDrive {
private const DRIVE_API_BASE = 'https://www.googleapis.com/drive/v3';
private GoogleOAuth2 $oauth;
// Required scopes for Drive API
private const DRIVE_SCOPES = [
'https://www.googleapis.com/auth/drive.file'
];
public function __construct(GoogleOAuth2 $oauth) {
$this->oauth = $oauth;
$this->oauth->addScopes(self::DRIVE_SCOPES);
}
/**
* List files and folders in Google Drive
* @param string|null $folderId Parent folder ID (null for root)
* @param array $options Additional query parameters
* @return array
* @throws \Exception
*/
public function listFiles(?string $folderId = null, array $options = []): array {
try {
$query = [];
if ($folderId) {
$query[] = "'{$folderId}' in parents";
} else {
$query[] = "'root' in parents";
}
// Add trashed=false to exclude deleted files
$query[] = "trashed=false";
// Handle mime type filtering through options
if (isset($options['mimeTypes'])) {
$mimeTypes = array_map(function($type) {
return "mimeType='" . $type . "'";
}, $options['mimeTypes']);
$query[] = '(' . implode(' or ', $mimeTypes) . ')';
unset($options['mimeTypes']); // Remove from options to avoid duplication in params
}
$params = array_merge([
'fields' => 'files(id, name, mimeType, size, modifiedTime, parents)',
'orderBy' => 'folder,name', // Sort folders first, then by name
'q' => implode(' and ', $query)
], $options);
$response = $this->oauth->makeAuthenticatedRequest(
self::DRIVE_API_BASE . '/files?' . http_build_query($params)
);
return $response;
} catch (\Exception $e) {
if ($this->isAuthenticationError($e)) {
throw new \Exception('Authentication required. Use getAuthorizationUrl() to start the auth flow.', 401);
}
throw $e;
}
}
/**
* Download a file by its ID
* @param string $fileId
* @param string|null $mimeType Optional mime type for export (useful for Google Docs)
* @return string File content
* @throws \Exception
*/
public function downloadFile(string $fileId, ?string $mimeType = null): string {
try {
// First, get file metadata to determine if it's a Google Workspace file
$file = $this->getFile($fileId);
$isGoogleWorkspaceFile = substr($file['mimeType'], 0, 23) === 'application/vnd.google-apps';
// For Google Sheets, always use export
if ($file['mimeType'] === 'application/vnd.google-apps.spreadsheet') {
$endpoint = self::DRIVE_API_BASE . '/files/' . $fileId . '/export';
$endpoint .= '?mimeType=' . urlencode('text/csv');
return $this->oauth->makeAuthenticatedRequest(
$endpoint,
'GET',
['Accept: text/csv'],
null,
true
);
}
// For other files, use regular download
$endpoint = self::DRIVE_API_BASE . '/files/' . $fileId;
if ($isGoogleWorkspaceFile && $mimeType) {
$endpoint .= '/export';
$endpoint .= '?mimeType=' . urlencode($mimeType);
} else {
$endpoint .= '?alt=media';
}
return $this->oauth->makeAuthenticatedRequest(
$endpoint,
'GET',
['Accept: */*'],
null,
true
);
} catch (\Exception $e) {
if ($this->isAuthenticationError($e)) {
throw new \Exception('Authentication required. Use getAuthorizationUrl() to start the auth flow.', 401);
}
throw $e;
}
}
/**
* Get file metadata
* @param string $fileId
* @return array
* @throws \Exception
*/
public function getFile(string $fileId): array {
try {
return $this->oauth->makeAuthenticatedRequest(
self::DRIVE_API_BASE . '/files/' . $fileId . '?fields=id,name,mimeType,size,modifiedTime,parents'
);
} catch (\Exception $e) {
if ($this->isAuthenticationError($e)) {
throw new \Exception('Authentication required. Use getAuthorizationUrl() to start the auth flow.', 401);
}
throw $e;
}
}
/**
* Get authorization URL to start OAuth flow
* @param array $additionalParams
* @return string
*/
public function getAuthorizationUrl(array $additionalParams = []): string {
return $this->oauth->getAuthorizationUrl($additionalParams);
}
/**
* Check if the error is related to authentication
* @param \Exception $e
* @return bool
*/
private function isAuthenticationError(\Exception $e): bool {
return $e->getCode() === 401 || $e->getCode() === 403;
}
/**
* Get common MIME types for Google Workspace files
* @return array
*/
public static function getCommonExportMimeTypes(): array {
return [
'application/vnd.google-apps.spreadsheet' => [
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'csv' => 'text/csv',
'pdf' => 'application/pdf'
],
'application/vnd.google-apps.document' => [
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'pdf' => 'application/pdf',
'txt' => 'text/plain'
],
'application/vnd.google-apps.presentation' => [
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'pdf' => 'application/pdf'
]
];
}
}
// Initialize the service
// $oauth = new GoogleOAuth2($clientId, $clientSecret, $redirectUri);
// $drive = new GoogleDrive($oauth);
+207
View File
@@ -0,0 +1,207 @@
<?php
namespace Lib\Google;
class GoogleOAuth2 {
private string $clientId;
private string $clientSecret;
private string $redirectUri;
private array $scopes = [];
private ?string $accessToken = null;
private ?string $refreshToken = null;
private ?int $tokenExpires = null;
private const AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
private const TOKEN_URL = 'https://oauth2.googleapis.com/token';
public function __construct(string $clientId, string $clientSecret, string $redirectUri) {
$this->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;
}
}