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
+84
View File
@@ -0,0 +1,84 @@
<?php
include_once 'lib/ghl/oauth2.php';
include_once 'config.php';
include_once 'project-model.php';
$config = MkdConfig::get_instance()->get_config();
$oauth = new GHLOAuth2($config);
class GHLCalendar {
private $access_token;
private $refresh_token;
private $token_expiry;
private $oauth;
private $project_id;
public function __construct($project_id, $access_token, $refresh_token) {
$this->project_id = $project_id;
$this->access_token = $access_token;
$this->refresh_token = $refresh_token;
$this->oauth = $GLOBALS['oauth'];
}
public function refreshToken() {
$result = $this->oauth->refreshToken($this->refresh_token);
if ($result['success']) {
$this->access_token = $result['data']['access_token'];
$this->refresh_token = $result['data']['refresh_token'] ?? $this->refresh_token;
$projectModel = new ProjectModel();
$projectModel->edit([
'access_token' => $this->access_token,
'refresh_token' => $this->refresh_token
], $this->project_id );
return [
'code' => 200,
'message' => 'Access token refreshed successfully',
'data' => $result['data']
];
} else {
return [
'code' => 400,
'message' => 'Error: ' . $result['error'] . '. Please authorize',
'data' => null
];
}
}
public function getFreeSlots($calendar_id, $start_date, $end_date) {
$url = "https://services.leadconnectorhq.com/calendars/$calendar_id/free-slots?startDate=$start_date&endDate=$end_date";
$headers = [
"Accept: application/json",
"Authorization: Bearer " . $this->access_token,
"Version: 2021-04-15"
];
// Initialize cURL session
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Execute cURL request
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); // Get the HTTP response code
curl_close($ch);
// Decode the response
$responseData = json_decode($response, true);
// Prepare the return array
return [
'code' => $httpCode,
'message' => $responseData['message'] ?? 'Success', // Default message if not present
'data' => $responseData
];
}
}
+138
View File
@@ -0,0 +1,138 @@
<?php
class GHLOAuth2 {
private $config;
private $baseUrl = 'https://services.leadconnectorhq.com';
private $authorizeUrl = 'https://marketplace.gohighlevel.com/oauth/chooselocation';
private $scopes = ['calendars.readonly', 'calendars/events.readonly'];
private $redirectUri = 'https://ghltool.team-followup.com/ghl/callback';
private $clientId = 'xxx';
private $clientSecret = 'xxx';
public function __construct(array $config) {
if (!isset($config['gohighlevel_client_id']) || !isset($config['gohighlevel_client_secret'])) {
throw new Exception('Client ID and Client Secret are required.');
}
$this->config = $config;
}
public function getAuthorizationUrl() {
$params = [
'response_type' => 'code',
'redirect_uri' => $this->config['gohighlevel_redirect_uri'] ?? $this->redirectUri,
'client_id' => $this->config['gohighlevel_client_id'] ?? $this->clientId,
'scope' => implode(' ', $this->scopes),
'user_type' => 'Location'
];
return $this->authorizeUrl . '?' . http_build_query($params);
}
/*
Gohighlevel returns the code in the redirect uri ?code=xxxx
*/
public function getAccessToken($code, $redirectUri = null) {
$data = [
'client_id' => $this->config['gohighlevel_client_id'] ?? $this->clientId,
'client_secret' => $this->config['gohighlevel_client_secret'] ?? $this->clientSecret,
'grant_type' => 'authorization_code',
'code' => $code,
'user_type' => 'Location'
];
if ($redirectUri) {
$data['redirect_uri'] = $redirectUri;
}
return $this->makeTokenRequest($data);
}
public function refreshToken($refreshToken) {
if (empty($refreshToken)) {
throw new Exception('Refresh token is required');
}
$data = [
'client_id' => $this->config['gohighlevel_client_id'] ?? $this->clientId,
'client_secret' => $this->config['gohighlevel_client_secret'] ?? $this->clientSecret,
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'user_type' => 'Location'
];
return $this->makeTokenRequest($data);
}
private function makeTokenRequest($data) {
$curl = curl_init();
$options = [
CURLOPT_URL => $this->baseUrl . '/oauth/token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/x-www-form-urlencoded'
],
];
curl_setopt_array($curl, $options);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);
curl_close($curl);
if ($error) {
return [
'success' => false,
'error' => 'Curl error: ' . $error
];
}
$responseData = json_decode($response, true);
if ($responseData === null) {
return [
'success' => false,
'error' => 'Error decoding JSON response'
];
}
if ($httpCode !== 200) {
return [
'success' => false,
'error' => $responseData['message'] ?? 'Unknown error occurred',
'code' => $httpCode,
'raw_response' => $responseData
];
}
// Ensure we always return refresh_token if it exists in the response
return [
'success' => true,
'data' => [
'access_token' => $responseData['access_token'] ?? null,
'refresh_token' => $responseData['refresh_token'] ?? null,
'expires_in' => $responseData['expires_in'] ?? null,
'locationId' => $responseData['locationId'] ?? null,
'companyId' => $responseData['companyId'] ?? null,
'userId' => $responseData['userId'] ?? null,
'scope' => $responseData['scope'] ?? null,
'token_type' => $responseData['token_type'] ?? null
],
'raw_response' => $responseData
];
}
}
+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;
}
}
File diff suppressed because it is too large Load Diff