🚨 CRITICAL: Fixed deployment blockers by adding missing core directories: **Community System (CRITICAL)** - includes/community/ - Login_Handler and all community classes - templates/community/ - Community login forms **Certificate System (CRITICAL)** - includes/certificates/ - 8+ certificate classes and handlers - templates/certificates/ - Certificate reports and generation templates **Core Individual Classes (CRITICAL)** - includes/class-hvac-event-summary.php - includes/class-hvac-trainer-profile-manager.php - includes/class-hvac-master-dashboard-data.php - Plus 40+ other individual HVAC classes **Major Feature Systems (HIGH)** - includes/database/ - Training leads database tables - includes/find-trainer/ - Find trainer directory and MapGeo integration - includes/google-sheets/ - Google Sheets integration system - includes/zoho/ - Complete Zoho CRM integration - includes/communication/ - Communication templates system **Template Infrastructure** - templates/attendee/, templates/email-attendees/ - templates/event-summary/, templates/status/ - templates/template-parts/ - Shared template components **Impact:** - 70+ files added covering 10+ missing directories - Resolves ALL deployment blockers and feature breakdowns - Plugin activation should now work correctly - Multi-machine deployment fully supported 🔧 Generated with Claude Code Co-Authored-By: Ben Reed <ben@tealmaker.com>
435 lines
No EOL
16 KiB
PHP
435 lines
No EOL
16 KiB
PHP
<?php
|
|
/**
|
|
* Google Sheets Authentication Handler
|
|
*
|
|
* Handles OAuth token management and Google Sheets API authentication
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Google_Sheets_Integration
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class HVAC_Google_Sheets_Auth {
|
|
|
|
private $client_id;
|
|
private $client_secret;
|
|
private $refresh_token;
|
|
private $redirect_uri;
|
|
private $access_token;
|
|
private $token_expiry;
|
|
private $folder_id;
|
|
private $last_error = null;
|
|
|
|
// Google API endpoints
|
|
private $auth_url = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
private $token_url = 'https://oauth2.googleapis.com/token';
|
|
private $sheets_api_url = 'https://sheets.googleapis.com/v4/spreadsheets';
|
|
private $drive_api_url = 'https://www.googleapis.com/drive/v3/files';
|
|
|
|
public function __construct() {
|
|
// Load configuration if available
|
|
$config_file = plugin_dir_path(__FILE__) . 'google-sheets-config.php';
|
|
if (file_exists($config_file)) {
|
|
require_once $config_file;
|
|
|
|
$this->client_id = defined('GOOGLE_SHEETS_CLIENT_ID') ? GOOGLE_SHEETS_CLIENT_ID : '';
|
|
$this->client_secret = defined('GOOGLE_SHEETS_CLIENT_SECRET') ? GOOGLE_SHEETS_CLIENT_SECRET : '';
|
|
$this->refresh_token = defined('GOOGLE_SHEETS_REFRESH_TOKEN') ? GOOGLE_SHEETS_REFRESH_TOKEN : '';
|
|
$this->redirect_uri = defined('GOOGLE_SHEETS_REDIRECT_URI') ? GOOGLE_SHEETS_REDIRECT_URI : 'http://localhost:8080/callback';
|
|
$this->folder_id = defined('GOOGLE_SHEETS_FOLDER_ID') ? GOOGLE_SHEETS_FOLDER_ID : '';
|
|
}
|
|
|
|
// Load stored access token from WordPress options
|
|
$this->load_access_token();
|
|
|
|
// Register callback handler - use template_redirect to catch it before page rendering
|
|
add_action('template_redirect', array($this, 'handle_oauth_callback'));
|
|
}
|
|
|
|
/**
|
|
* Generate authorization URL for initial setup
|
|
*/
|
|
public function get_authorization_url() {
|
|
$params = array(
|
|
'client_id' => $this->client_id,
|
|
'redirect_uri' => $this->redirect_uri,
|
|
'scope' => 'https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.file',
|
|
'response_type' => 'code',
|
|
'access_type' => 'offline',
|
|
'prompt' => 'consent',
|
|
'include_granted_scopes' => 'true'
|
|
);
|
|
|
|
return $this->auth_url . '?' . http_build_query($params);
|
|
}
|
|
|
|
/**
|
|
* Exchange authorization code for tokens
|
|
*/
|
|
public function exchange_code_for_tokens($auth_code) {
|
|
$params = array(
|
|
'client_id' => $this->client_id,
|
|
'client_secret' => $this->client_secret,
|
|
'redirect_uri' => $this->redirect_uri,
|
|
'grant_type' => 'authorization_code',
|
|
'code' => $auth_code
|
|
);
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Token exchange request params: " . json_encode(array(
|
|
'client_id' => substr($this->client_id, 0, 20) . '...',
|
|
'redirect_uri' => $this->redirect_uri,
|
|
'grant_type' => 'authorization_code',
|
|
'code' => substr($auth_code, 0, 20) . '...'
|
|
)), 'GoogleSheets');
|
|
}
|
|
|
|
$response = wp_remote_post($this->token_url, array(
|
|
'body' => $params,
|
|
'headers' => array(
|
|
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
),
|
|
'timeout' => 30
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
$this->log_error('Failed to exchange code: ' . $response->get_error_message());
|
|
return false;
|
|
}
|
|
|
|
$response_code = wp_remote_retrieve_response_code($response);
|
|
$body = wp_remote_retrieve_body($response);
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Token exchange response code: " . $response_code, 'GoogleSheets');
|
|
HVAC_Logger::info("Token exchange response body: " . $body, 'GoogleSheets');
|
|
}
|
|
|
|
$data = json_decode($body, true);
|
|
|
|
if (isset($data['access_token'])) {
|
|
$this->access_token = $data['access_token'];
|
|
if (isset($data['refresh_token'])) {
|
|
$this->refresh_token = $data['refresh_token'];
|
|
}
|
|
$this->token_expiry = time() + (int)$data['expires_in'];
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Successfully received tokens. Access token: " . substr($this->access_token, 0, 20) . "...", 'GoogleSheets');
|
|
HVAC_Logger::info("Refresh token: " . ($this->refresh_token ? 'RECEIVED' : 'NOT RECEIVED'), 'GoogleSheets');
|
|
HVAC_Logger::info("Token expires at: " . date('Y-m-d H:i:s', $this->token_expiry), 'GoogleSheets');
|
|
}
|
|
|
|
// Save tokens
|
|
$this->save_tokens();
|
|
|
|
return true;
|
|
}
|
|
|
|
$this->log_error('Invalid token response (status ' . $response_code . '): ' . $body);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get valid access token (refresh if needed)
|
|
*/
|
|
public function get_access_token() {
|
|
// Check if token is expired or will expire in next 5 minutes
|
|
if ($this->token_expiry && ($this->token_expiry - 300) < time()) {
|
|
$this->refresh_access_token();
|
|
}
|
|
|
|
return $this->access_token;
|
|
}
|
|
|
|
/**
|
|
* Refresh access token using refresh token
|
|
*/
|
|
private function refresh_access_token() {
|
|
if (empty($this->refresh_token)) {
|
|
$this->log_error('No refresh token available');
|
|
return false;
|
|
}
|
|
|
|
$params = array(
|
|
'client_id' => $this->client_id,
|
|
'client_secret' => $this->client_secret,
|
|
'refresh_token' => $this->refresh_token,
|
|
'grant_type' => 'refresh_token'
|
|
);
|
|
|
|
$response = wp_remote_post($this->token_url, array(
|
|
'body' => $params,
|
|
'headers' => array(
|
|
'Content-Type' => 'application/x-www-form-urlencoded'
|
|
)
|
|
));
|
|
|
|
if (is_wp_error($response)) {
|
|
$this->log_error('Failed to refresh token: ' . $response->get_error_message());
|
|
return false;
|
|
}
|
|
|
|
$body = wp_remote_retrieve_body($response);
|
|
$data = json_decode($body, true);
|
|
|
|
if (isset($data['access_token'])) {
|
|
$this->access_token = $data['access_token'];
|
|
$this->token_expiry = time() + $data['expires_in'];
|
|
|
|
// Update refresh token if provided
|
|
if (isset($data['refresh_token'])) {
|
|
$this->refresh_token = $data['refresh_token'];
|
|
}
|
|
|
|
// Save updated tokens
|
|
$this->save_tokens();
|
|
|
|
return true;
|
|
}
|
|
|
|
$this->log_error('Failed to refresh token: ' . $body);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Make authenticated API request to Google Sheets/Drive
|
|
*/
|
|
public function make_api_request($method, $endpoint, $data = null, $api_type = 'sheets') {
|
|
$access_token = $this->get_access_token();
|
|
if (!$access_token) {
|
|
throw new Exception('No valid access token available');
|
|
}
|
|
|
|
$base_url = ($api_type === 'drive') ? $this->drive_api_url : $this->sheets_api_url;
|
|
$url = $base_url . $endpoint;
|
|
|
|
// Handle valueInputOption as query parameter for Sheets API
|
|
if ($data && isset($data['valueInputOption'])) {
|
|
$url .= '?valueInputOption=' . urlencode($data['valueInputOption']);
|
|
unset($data['valueInputOption']); // Remove from body data
|
|
}
|
|
|
|
$args = array(
|
|
'method' => $method,
|
|
'headers' => array(
|
|
'Authorization' => 'Bearer ' . $access_token,
|
|
'Content-Type' => 'application/json'
|
|
),
|
|
'timeout' => 30
|
|
);
|
|
|
|
if ($data && in_array($method, ['POST', 'PUT', 'PATCH'])) {
|
|
$args['body'] = json_encode($data);
|
|
}
|
|
|
|
$response = wp_remote_request($url, $args);
|
|
|
|
if (is_wp_error($response)) {
|
|
throw new Exception('API request failed: ' . $response->get_error_message());
|
|
}
|
|
|
|
$response_code = wp_remote_retrieve_response_code($response);
|
|
$body = wp_remote_retrieve_body($response);
|
|
|
|
if ($response_code >= 400) {
|
|
$error_data = json_decode($body, true);
|
|
$error_message = isset($error_data['error']['message']) ? $error_data['error']['message'] : 'Unknown API error';
|
|
throw new Exception("API error {$response_code}: {$error_message}");
|
|
}
|
|
|
|
return json_decode($body, true);
|
|
}
|
|
|
|
/**
|
|
* Test API connection
|
|
*/
|
|
public function test_connection() {
|
|
try {
|
|
// Try to create a test spreadsheet in the designated folder
|
|
$spreadsheet_data = array(
|
|
'properties' => array(
|
|
'title' => 'HVAC Test Connection - ' . date('Y-m-d H:i:s')
|
|
)
|
|
);
|
|
|
|
$response = $this->make_api_request('POST', '', $spreadsheet_data);
|
|
|
|
if (isset($response['spreadsheetId'])) {
|
|
// Move to designated folder if specified
|
|
if ($this->folder_id) {
|
|
$this->make_api_request(
|
|
'PATCH',
|
|
'/' . $response['spreadsheetId'] . '?addParents=' . $this->folder_id,
|
|
null,
|
|
'drive'
|
|
);
|
|
}
|
|
|
|
// Delete test spreadsheet
|
|
$this->make_api_request(
|
|
'DELETE',
|
|
'/' . $response['spreadsheetId'],
|
|
null,
|
|
'drive'
|
|
);
|
|
|
|
return array('success' => true, 'message' => 'Connection successful');
|
|
}
|
|
|
|
return array('success' => false, 'message' => 'Unexpected response format');
|
|
|
|
} catch (Exception $e) {
|
|
return array('success' => false, 'message' => $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load access token from WordPress options
|
|
*/
|
|
private function load_access_token() {
|
|
$token_data = get_option('hvac_google_sheets_tokens', array());
|
|
|
|
if (!empty($token_data)) {
|
|
$this->access_token = $token_data['access_token'] ?? '';
|
|
$this->refresh_token = $token_data['refresh_token'] ?? $this->refresh_token;
|
|
$this->token_expiry = $token_data['expires_at'] ?? 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save tokens to WordPress options
|
|
*/
|
|
private function save_tokens() {
|
|
$token_data = array(
|
|
'access_token' => $this->access_token,
|
|
'refresh_token' => $this->refresh_token,
|
|
'expires_at' => $this->token_expiry,
|
|
'created_at' => time()
|
|
);
|
|
|
|
$result = update_option('hvac_google_sheets_tokens', $token_data);
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Saving tokens to database: " . ($result ? 'SUCCESS' : 'FAILED'), 'GoogleSheets');
|
|
HVAC_Logger::info("Token data: " . json_encode(array(
|
|
'access_token' => substr($this->access_token, 0, 20) . '...',
|
|
'refresh_token' => $this->refresh_token ? 'SET' : 'NOT SET',
|
|
'expires_at' => date('Y-m-d H:i:s', $this->token_expiry),
|
|
'created_at' => date('Y-m-d H:i:s', time())
|
|
)), 'GoogleSheets');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear stored tokens
|
|
*/
|
|
public function clear_tokens() {
|
|
delete_option('hvac_google_sheets_tokens');
|
|
$this->access_token = '';
|
|
$this->refresh_token = '';
|
|
$this->token_expiry = 0;
|
|
}
|
|
|
|
/**
|
|
* Check if we have valid credentials
|
|
*/
|
|
public function has_valid_credentials() {
|
|
return !empty($this->client_id) && !empty($this->client_secret);
|
|
}
|
|
|
|
/**
|
|
* Check if we have an access token
|
|
*/
|
|
public function is_authenticated() {
|
|
return !empty($this->access_token) || !empty($this->refresh_token);
|
|
}
|
|
|
|
/**
|
|
* Get last error message
|
|
*/
|
|
public function get_last_error() {
|
|
return $this->last_error;
|
|
}
|
|
|
|
/**
|
|
* Log error message
|
|
*/
|
|
private function log_error($message) {
|
|
$this->last_error = $message;
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::error("Google Sheets Auth: {$message}", 'GoogleSheets');
|
|
}
|
|
error_log("HVAC Google Sheets Auth Error: {$message}");
|
|
}
|
|
|
|
/**
|
|
* Get configuration status
|
|
*/
|
|
public function get_config_status() {
|
|
return array(
|
|
'has_credentials' => $this->has_valid_credentials(),
|
|
'is_authenticated' => $this->is_authenticated(),
|
|
'client_id' => !empty($this->client_id) ? substr($this->client_id, 0, 10) . '...' : '',
|
|
'has_refresh_token' => !empty($this->refresh_token),
|
|
'token_expires' => $this->token_expiry ? date('Y-m-d H:i:s', $this->token_expiry) : 'Unknown',
|
|
'folder_id' => $this->folder_id
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle OAuth callback from Google
|
|
*/
|
|
public function handle_oauth_callback() {
|
|
// Debug: Log all OAuth callback attempts
|
|
if (isset($_GET['code']) && isset($_GET['scope'])) {
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("OAuth callback detected - URI: " . $_SERVER['REQUEST_URI'], 'GoogleSheets');
|
|
HVAC_Logger::info("OAuth callback - code param: " . substr($_GET['code'], 0, 20) . "...", 'GoogleSheets');
|
|
}
|
|
error_log("HVAC Google OAuth callback detected - URI: " . $_SERVER['REQUEST_URI']);
|
|
error_log("HVAC Google OAuth callback - code: " . substr($_GET['code'], 0, 20) . "...");
|
|
}
|
|
|
|
// Check if this is an OAuth callback request to the Google Sheets page
|
|
if (isset($_GET['code']) && isset($_GET['scope']) &&
|
|
(strpos($_SERVER['REQUEST_URI'], '/google-sheets/') !== false ||
|
|
strpos($_SERVER['REQUEST_URI'], 'google-sheets') !== false)) {
|
|
|
|
$auth_code = sanitize_text_field($_GET['code']);
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Processing OAuth callback with code: " . substr($auth_code, 0, 20) . "...", 'GoogleSheets');
|
|
HVAC_Logger::info("Current redirect URI: " . $this->redirect_uri, 'GoogleSheets');
|
|
}
|
|
|
|
// Exchange the authorization code for tokens
|
|
$success = $this->exchange_code_for_tokens($auth_code);
|
|
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info("Token exchange result: " . ($success ? 'SUCCESS' : 'FAILED'), 'GoogleSheets');
|
|
if (!$success) {
|
|
HVAC_Logger::error("Token exchange error: " . $this->get_last_error(), 'GoogleSheets');
|
|
}
|
|
}
|
|
|
|
if ($success) {
|
|
// Redirect to Google Sheets admin page with success message (clean URL)
|
|
wp_redirect(add_query_arg(array(
|
|
'auth_success' => '1'
|
|
), home_url('/google-sheets/')));
|
|
exit;
|
|
} else {
|
|
// Redirect to Google Sheets admin page with error message
|
|
wp_redirect(add_query_arg(array(
|
|
'auth_error' => '1',
|
|
'message' => urlencode($this->get_last_error())
|
|
), home_url('/google-sheets/')));
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
} |