init
This commit is contained in:
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user