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