admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('hvac_zoho_nonce') )); // Add inline test script for debugging wp_add_inline_script('hvac-zoho-admin', ' console.log("Zoho admin script loaded"); jQuery(document).ready(function($) { console.log("DOM ready, setting up click handler"); $(document).on("click", "#test-zoho-connection", function() { console.log("Test button clicked - inline script"); }); }); '); wp_enqueue_style( 'hvac-zoho-admin', HVAC_PLUGIN_URL . 'assets/css/zoho-admin.css', array(), HVAC_PLUGIN_VERSION ); } /** * Render admin page */ public function render_admin_page() { $site_url = get_site_url(); // Debug logging // More robust production detection $parsed_url = parse_url($site_url); $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; // Remove www prefix for comparison $clean_host = preg_replace('/^www\./', '', $host); // Check if this is production $is_production = ($clean_host === 'upskillhvac.com'); // Double-check with string comparison as fallback if (!$is_production) { $is_production = (strpos($site_url, 'upskillhvac.com') !== false && strpos($site_url, 'staging') === false && strpos($site_url, 'test') === false && strpos($site_url, 'dev') === false && strpos($site_url, 'cloudwaysapps.com') === false); } // Set staging as opposite of production $is_staging = !$is_production; // Get stored credentials $client_id = get_option('hvac_zoho_client_id', ''); $client_secret = get_option('hvac_zoho_client_secret', ''); $stored_refresh_token = get_option('hvac_zoho_refresh_token', ''); $has_credentials = !empty($client_id) && !empty($client_secret); // Handle form submission if (isset($_GET['credentials_saved'])) { echo '

Zoho CRM credentials saved successfully!

'; } if (isset($_GET['oauth_success'])) { echo '

OAuth authorization completed successfully!

'; } if (isset($_GET['oauth_error'])) { echo '

OAuth authorization failed. Please try again.

'; } ?>

Zoho CRM Sync

🔧 STAGING MODE ACTIVE

Current site:

Staging mode is active. Data sync will be simulated only. No actual data will be sent to Zoho CRM.

Production sync is only enabled on upskillhvac.com

OAuth Redirect URI:

Use this redirect URI in your Zoho OAuth app configuration.

🔑 Zoho CRM Credentials

Your Zoho OAuth Client ID from the Zoho Developer Console. Get your Client ID

Your Zoho OAuth Client Secret from the Zoho Developer Console.

OAuth Redirect URI

Use this exact URL in your Zoho OAuth app configuration.

OAuth rewrite rule:

Connection Status No credentials configured Credentials set, OAuth authorization required Connected and authorized

Connection Test

Test your Zoho CRM connection to ensure everything is working properly.

⚠️ OAuth Authorization Required

You have saved credentials but need to authorize the application with Zoho CRM.

Click "Authorize with Zoho" above to complete the setup.

Data Sync

Events → Campaigns

Sync events from The Events Calendar to Zoho CRM Campaigns

Users → Contacts

Sync trainers and attendees to Zoho CRM Contacts

Purchases → Invoices

Sync ticket purchases to Zoho CRM Invoices

Sync Settings





'Simple test works!')); } /** * Save Zoho CRM credentials */ public function save_credentials() { if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized access')); return; } if (!check_ajax_referer('hvac_zoho_credentials', 'nonce', false)) { wp_send_json_error(array('message' => 'Invalid nonce')); return; } $client_id = sanitize_text_field($_POST['zoho_client_id']); $client_secret = sanitize_text_field($_POST['zoho_client_secret']); if (empty($client_id) || empty($client_secret)) { wp_send_json_error(array('message' => 'Client ID and Client Secret are required')); return; } // Validate Client ID format (should start with "1000.") if (!preg_match('/^1000\.[A-Z0-9]+$/', $client_id)) { wp_send_json_error(array('message' => 'Invalid Client ID format. Should start with "1000."')); return; } // Save credentials update_option('hvac_zoho_client_id', $client_id); update_option('hvac_zoho_client_secret', $client_secret); // Clear any existing refresh token since credentials changed delete_option('hvac_zoho_refresh_token'); wp_send_json_success(array( 'message' => 'Credentials saved successfully', 'client_id_preview' => substr($client_id, 0, 10) . '...' )); } /** * Flush rewrite rules via AJAX */ public function flush_rewrite_rules_ajax() { if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized access')); return; } // Clear any cached rules first wp_cache_delete('rewrite_rules', 'options'); // Add OAuth rewrite rules multiple ways add_rewrite_rule('^oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top'); add_rewrite_rule('oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top'); // Force hard flush flush_rewrite_rules(true); // Clear cache again wp_cache_delete('rewrite_rules', 'options'); // Add rules again and soft flush $this->add_oauth_rewrite_rule(); flush_rewrite_rules(false); // Force WordPress to regenerate rules delete_option('rewrite_rules'); wp_cache_delete('rewrite_rules', 'options'); $wp_rewrite = $GLOBALS['wp_rewrite']; $wp_rewrite->flush_rules(true); // Verify the rule exists after flush $rewrite_rules = get_option('rewrite_rules', array()); $oauth_rule_exists = isset($rewrite_rules['^oauth/callback/?$']) || isset($rewrite_rules['oauth/callback/?$']); // Log for debugging wp_send_json_success(array( 'message' => 'Rewrite rules flushed successfully', 'oauth_rule_exists' => $oauth_rule_exists, 'total_rules' => count($rewrite_rules), 'rules_sample' => array_slice(array_keys($rewrite_rules), 0, 5) )); } /** * Flush rewrite rules on plugin activation */ public function flush_rewrite_rules_on_activation() { $this->add_oauth_rewrite_rule(); flush_rewrite_rules(); } /** * Add OAuth query vars */ public function add_oauth_query_vars($vars) { // Only add if not already present to avoid duplicates if (!in_array('hvac_oauth_callback', $vars)) { $vars[] = 'hvac_oauth_callback'; } return $vars; } /** * Add OAuth query vars to public query vars */ public function add_public_query_vars() { global $wp; // Check if already added to avoid duplicates if (!in_array('hvac_oauth_callback', $wp->public_query_vars)) { $wp->add_query_var('hvac_oauth_callback'); } } /** * Parse OAuth request using parse_request hook */ public function parse_oauth_request($wp) { // Check if this is an OAuth callback request if (preg_match('#^/oauth/callback/?#', $_SERVER['REQUEST_URI'])) { // Check if we have the code parameter if (isset($_GET['code'])) { $this->process_oauth_callback(); exit; } else { wp_die('OAuth callback missing authorization code'); } } } /** * Add OAuth callback rewrite rule */ public function add_oauth_rewrite_rule() { add_rewrite_rule('^oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top'); // Also add alternative rule patterns add_rewrite_rule('oauth/callback/?$', 'index.php?hvac_oauth_callback=1', 'top'); // Force flush if the rule doesn't exist $rewrite_rules = get_option('rewrite_rules'); if (!$rewrite_rules || !isset($rewrite_rules['^oauth/callback/?$'])) { // Set a flag to flush rules on next page load update_option('hvac_oauth_rules_need_flush', true); // Also try to flush immediately if we're in admin if (is_admin()) { flush_rewrite_rules(false); } } // Check if we need to flush based on flag if (get_option('hvac_oauth_rules_need_flush')) { flush_rewrite_rules(false); delete_option('hvac_oauth_rules_need_flush'); } } /** * Handle OAuth template redirect */ public function handle_oauth_template_redirect() { if (get_query_var('hvac_oauth_callback')) { $this->process_oauth_callback(); } } /** * Process OAuth callback from Zoho */ public function process_oauth_callback() { if (!isset($_GET['code'])) { wp_die('OAuth callback missing authorization code'); } // Get credentials from WordPress options $client_id = get_option('hvac_zoho_client_id', ''); $client_secret = get_option('hvac_zoho_client_secret', ''); if (empty($client_id) || empty($client_secret)) { wp_die('OAuth callback error: Missing client credentials. Please configure your Zoho CRM credentials first.'); } // Exchange authorization code for tokens $token_url = 'https://accounts.zoho.com/oauth/v2/token'; $redirect_uri = get_site_url() . '/oauth/callback'; $token_params = array( 'grant_type' => 'authorization_code', 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'code' => $_GET['code'] ); $response = wp_remote_post($token_url, array( 'body' => $token_params, 'timeout' => 30 )); if (is_wp_error($response)) { wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode($response->get_error_message()))); exit; } $body = wp_remote_retrieve_body($response); $token_data = json_decode($body, true); // Check for errors in response if (isset($token_data['error'])) { wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode($token_data['error']))); exit; } if (!isset($token_data['access_token'])) { wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_error=1&error_msg=' . urlencode('No access token received'))); exit; } // Save tokens update_option('hvac_zoho_access_token', $token_data['access_token']); update_option('hvac_zoho_token_expires', time() + ($token_data['expires_in'] ?? 3600)); // Refresh token might not be returned on subsequent authorizations if (isset($token_data['refresh_token']) && !empty($token_data['refresh_token'])) { update_option('hvac_zoho_refresh_token', $token_data['refresh_token']); } else { $existing_refresh = get_option('hvac_zoho_refresh_token'); if (empty($existing_refresh)) { // This is critical - we need a refresh token for long-term access // Store a warning but still complete the flow update_option('hvac_zoho_missing_refresh_token', true); } else { } } // Success - redirect to admin page with success message wp_redirect(admin_url('admin.php?page=hvac-zoho-sync&oauth_success=1')); exit; } /** * Handle OAuth callback from Zoho (legacy method) */ public function handle_oauth_callback() { // This method is kept for backwards compatibility // The main handling is now done in template_redirect return; } /** * Test Zoho connection */ public function test_connection() { try { check_ajax_referer('hvac_zoho_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized access')); return; } // Get credentials from WordPress options $client_id = get_option('hvac_zoho_client_id', ''); $client_secret = get_option('hvac_zoho_client_secret', ''); // Check configuration before attempting connection if (empty($client_id)) { wp_send_json_error(array( 'message' => 'Configuration Error', 'error' => 'Client ID is not configured', 'details' => 'Please enter your Zoho CRM Client ID in the form above', 'help' => 'Get your Client ID from the Zoho Developer Console' )); return; } if (empty($client_secret)) { wp_send_json_error(array( 'message' => 'Configuration Error', 'error' => 'Client Secret is not configured', 'details' => 'Please enter your Zoho CRM Client Secret in the form above', 'help' => 'Get your Client Secret from the Zoho Developer Console' )); return; } // Check if we have stored refresh token from previous OAuth $stored_refresh_token = get_option('hvac_zoho_refresh_token'); if (empty($stored_refresh_token)) { $site_url = get_site_url(); $redirect_uri = $site_url . '/oauth/callback'; $scopes = 'ZohoCRM.settings.ALL,ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.org.ALL,ZohoCRM.bulk.READ'; $auth_url = 'https://accounts.zoho.com/oauth/v2/auth?' . http_build_query(array( 'scope' => $scopes, 'client_id' => $client_id, 'response_type' => 'code', 'access_type' => 'offline', 'redirect_uri' => $redirect_uri, 'prompt' => 'consent' )); wp_send_json_error(array( 'message' => 'OAuth Authorization Required', 'error' => 'No stored refresh token found', 'details' => 'You have valid credentials but need to complete OAuth authorization to get a fresh token', 'help' => 'Click the "Authorize with Zoho" button above to complete setup', 'next_steps' => array( '1. Click the "Authorize with Zoho" button above', '2. Sign in to your Zoho account', '3. Grant permissions to the application', '4. You will be redirected back and the refresh token will be saved automatically' ), 'auth_url' => $auth_url, 'credentials_status' => array( 'client_id' => substr($client_id, 0, 10) . '...', 'client_secret_exists' => true, 'refresh_token_exists' => false ) )); return; } // We have a refresh token - test the actual API connection require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php'; $auth = new HVAC_Zoho_CRM_Auth(); // Test API call $response = $auth->make_api_request('/settings/modules', 'GET'); if (is_wp_error($response)) { wp_send_json_error(array( 'message' => 'API Connection Failed', 'error' => $response->get_error_message(), 'details' => 'WordPress HTTP error occurred' )); return; } if (isset($response['error'])) { // Check if it's an invalid token error if (strpos($response['error'], 'invalid') !== false || strpos($response['error'], 'expired') !== false) { // Clear the invalid token and trigger OAuth delete_option('hvac_zoho_refresh_token'); $auth_url = $auth->get_authorization_url(); $site_url = get_site_url(); $redirect_uri = $site_url . '/oauth/callback'; $scopes = 'ZohoCRM.settings.ALL,ZohoCRM.modules.ALL,ZohoCRM.users.ALL,ZohoCRM.org.ALL,ZohoCRM.bulk.READ'; $new_auth_url = 'https://accounts.zoho.com/oauth/v2/auth?' . http_build_query(array( 'scope' => $scopes, 'client_id' => $client_id, 'response_type' => 'code', 'access_type' => 'offline', 'redirect_uri' => $redirect_uri, 'prompt' => 'consent' )); wp_send_json_error(array( 'message' => 'OAuth Authorization Required', 'error' => 'Refresh token expired or invalid', 'details' => 'The stored refresh token is no longer valid. Please re-authorize.', 'help' => 'Refresh the page and click "Authorize with Zoho" again', 'next_steps' => array( '1. Refresh this page', '2. Click the "Authorize with Zoho" button', '3. Sign in to your Zoho account', '4. Grant permissions to the application', '5. You will be redirected back and the refresh token will be saved automatically' ), 'auth_url' => $new_auth_url, 'credentials_status' => array( 'client_id' => substr($client_id, 0, 10) . '...', 'client_secret_exists' => true, 'refresh_token_exists' => false ) )); return; } wp_send_json_error(array( 'message' => 'Zoho API Error', 'error' => $response['error'], 'details' => isset($response['details']) ? $response['details'] : 'No additional details' )); return; } // Success! wp_send_json_success(array( 'message' => 'Connection successful!', 'modules' => isset($response['modules']) ? count($response['modules']) . ' modules available' : 'API connected', 'credentials_status' => array( 'client_id' => substr($client_id, 0, 10) . '...', 'client_secret_exists' => true, 'refresh_token_exists' => true, 'api_working' => true ) )); } catch (Exception $e) { wp_send_json_error(array( 'message' => 'Connection test failed due to exception', 'error' => $e->getMessage(), 'file' => $e->getFile() . ':' . $e->getLine() )); } catch (Error $e) { wp_send_json_error(array( 'message' => 'Connection test failed due to PHP error', 'error' => $e->getMessage(), 'file' => $e->getFile() . ':' . $e->getLine() )); } catch (Throwable $e) { wp_send_json_error(array( 'message' => 'Connection test failed due to fatal error', 'error' => $e->getMessage(), 'file' => $e->getFile() . ':' . $e->getLine() )); } } /** * Sync data to Zoho */ public function sync_data() { check_ajax_referer('hvac_zoho_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('Unauthorized'); } $type = sanitize_text_field($_POST['type']); try { require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-sync.php'; $sync = new HVAC_Zoho_Sync(); switch ($type) { case 'events': $result = $sync->sync_events(); break; case 'users': $result = $sync->sync_users(); break; case 'purchases': $result = $sync->sync_purchases(); break; default: throw new Exception('Invalid sync type'); } wp_send_json_success($result); } catch (Exception $e) { wp_send_json_error(array( 'message' => 'Sync failed', 'error' => $e->getMessage() )); } } } ?>