🚨 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;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |