fix: Zoho scheduled sync persistence issue
Some checks failed
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Has been cancelled
Security Monitoring & Compliance / Secrets & Credential Scan (push) Has been cancelled
Security Monitoring & Compliance / WordPress Security Analysis (push) Has been cancelled
Security Monitoring & Compliance / Static Code Security Analysis (push) Has been cancelled
Security Monitoring & Compliance / Security Compliance Validation (push) Has been cancelled
Security Monitoring & Compliance / Security Summary Report (push) Has been cancelled
Security Monitoring & Compliance / Security Team Notification (push) Has been cancelled
Some checks failed
HVAC Plugin CI/CD Pipeline / Security Analysis (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Unit Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Integration Tests (push) Waiting to run
HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
HVAC Plugin CI/CD Pipeline / Notification (push) Blocked by required conditions
Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Has been cancelled
Security Monitoring & Compliance / Secrets & Credential Scan (push) Has been cancelled
Security Monitoring & Compliance / WordPress Security Analysis (push) Has been cancelled
Security Monitoring & Compliance / Static Code Security Analysis (push) Has been cancelled
Security Monitoring & Compliance / Security Compliance Validation (push) Has been cancelled
Security Monitoring & Compliance / Security Summary Report (push) Has been cancelled
Security Monitoring & Compliance / Security Team Notification (push) Has been cancelled
- Load HVAC_Zoho_Scheduled_Sync on ALL requests (not just admin) so WP-Cron can find custom schedules and action hooks - Add add_option hook for first-time setting creation - Explicitly call schedule_sync() in save_settings() to ensure scheduling works even when option value hasn't changed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a55b78d03
commit
4fc6676e0c
3 changed files with 650 additions and 52 deletions
|
|
@ -43,6 +43,8 @@ class HVAC_Zoho_Admin {
|
||||||
add_action('wp_ajax_hvac_zoho_sync_data', array($this, 'sync_data'));
|
add_action('wp_ajax_hvac_zoho_sync_data', array($this, 'sync_data'));
|
||||||
add_action('wp_ajax_hvac_zoho_save_credentials', array($this, 'save_credentials'));
|
add_action('wp_ajax_hvac_zoho_save_credentials', array($this, 'save_credentials'));
|
||||||
add_action('wp_ajax_hvac_zoho_flush_rewrite_rules', array($this, 'flush_rewrite_rules_ajax'));
|
add_action('wp_ajax_hvac_zoho_flush_rewrite_rules', array($this, 'flush_rewrite_rules_ajax'));
|
||||||
|
add_action('wp_ajax_hvac_zoho_save_settings', array($this, 'save_settings'));
|
||||||
|
add_action('wp_ajax_hvac_zoho_run_scheduled_sync', array($this, 'run_scheduled_sync_now'));
|
||||||
// Add simple test handler
|
// Add simple test handler
|
||||||
add_action('wp_ajax_hvac_zoho_simple_test', array($this, 'simple_test'));
|
add_action('wp_ajax_hvac_zoho_simple_test', array($this, 'simple_test'));
|
||||||
// Add OAuth callback handler - only use one method to prevent duplicates
|
// Add OAuth callback handler - only use one method to prevent duplicates
|
||||||
|
|
@ -50,8 +52,22 @@ class HVAC_Zoho_Admin {
|
||||||
add_filter('query_vars', array($this, 'add_oauth_query_vars'), 10, 1);
|
add_filter('query_vars', array($this, 'add_oauth_query_vars'), 10, 1);
|
||||||
add_action('template_redirect', array($this, 'handle_oauth_template_redirect'));
|
add_action('template_redirect', array($this, 'handle_oauth_template_redirect'));
|
||||||
|
|
||||||
|
// Fallback: Check for OAuth params on init (in case rewrite rules fail and we land on homepage)
|
||||||
|
add_action('init', array($this, 'check_for_oauth_params'));
|
||||||
|
|
||||||
// Ensure rewrite rules are flushed when plugin is activated
|
// Ensure rewrite rules are flushed when plugin is activated
|
||||||
register_activation_hook(HVAC_PLUGIN_FILE, array($this, 'flush_rewrite_rules_on_activation'));
|
register_activation_hook(HVAC_PLUGIN_FILE, array($this, 'flush_rewrite_rules_on_activation'));
|
||||||
|
|
||||||
|
// Initialize scheduled sync if enabled
|
||||||
|
$this->init_scheduled_sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize scheduled sync
|
||||||
|
*/
|
||||||
|
private function init_scheduled_sync() {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php';
|
||||||
|
HVAC_Zoho_Scheduled_Sync::instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,27 +141,13 @@ class HVAC_Zoho_Admin {
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
|
|
||||||
// More robust production detection
|
// Ensure Auth class is loaded for staging detection
|
||||||
$parsed_url = parse_url($site_url);
|
if (!class_exists('HVAC_Zoho_CRM_Auth')) {
|
||||||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php';
|
||||||
|
|
||||||
// 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
|
// Use central logic for staging detection
|
||||||
$is_staging = !$is_production;
|
$is_staging = HVAC_Zoho_CRM_Auth::is_staging_mode();
|
||||||
|
|
||||||
|
|
||||||
// Load secure storage class
|
// Load secure storage class
|
||||||
|
|
@ -268,6 +270,9 @@ class HVAC_Zoho_Admin {
|
||||||
🚀 Authorize with Zoho
|
🚀 Authorize with Zoho
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<button type="button" class="button button-secondary" id="diagnostic-test" style="margin-left: 10px;">
|
||||||
|
🏥 Run Diagnostic Test
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -329,24 +334,82 @@ class HVAC_Zoho_Admin {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hvac-zoho-settings">
|
<div class="hvac-zoho-settings">
|
||||||
<h2>Sync Settings</h2>
|
<h2>Scheduled Sync Settings</h2>
|
||||||
|
<?php
|
||||||
|
// Get scheduled sync status
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php';
|
||||||
|
$scheduled_sync = HVAC_Zoho_Scheduled_Sync::instance();
|
||||||
|
$sync_status = $scheduled_sync->get_status();
|
||||||
|
$current_frequency = get_option('hvac_zoho_sync_frequency', 'every_5_minutes');
|
||||||
|
?>
|
||||||
<form id="zoho-settings-form">
|
<form id="zoho-settings-form">
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Enable Scheduled Sync</th>
|
||||||
|
<td>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="auto_sync" value="1" <?php checked(get_option('hvac_zoho_auto_sync'), '1'); ?>>
|
<input type="checkbox" name="auto_sync" value="1" <?php checked(get_option('hvac_zoho_auto_sync'), '1'); ?>>
|
||||||
Enable automatic sync
|
Automatically sync new/modified records to Zoho CRM
|
||||||
</label>
|
</label>
|
||||||
<br><br>
|
<p class="description">When enabled, a background process will sync changes on the selected interval.</p>
|
||||||
<label>
|
</td>
|
||||||
Sync frequency:
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Sync Interval</th>
|
||||||
|
<td>
|
||||||
<select name="sync_frequency">
|
<select name="sync_frequency">
|
||||||
<option value="hourly" <?php selected(get_option('hvac_zoho_sync_frequency'), 'hourly'); ?>>Hourly</option>
|
<option value="every_5_minutes" <?php selected($current_frequency, 'every_5_minutes'); ?>>Every 5 minutes</option>
|
||||||
<option value="daily" <?php selected(get_option('hvac_zoho_sync_frequency'), 'daily'); ?>>Daily</option>
|
<option value="every_15_minutes" <?php selected($current_frequency, 'every_15_minutes'); ?>>Every 15 minutes</option>
|
||||||
<option value="weekly" <?php selected(get_option('hvac_zoho_sync_frequency'), 'weekly'); ?>>Weekly</option>
|
<option value="every_30_minutes" <?php selected($current_frequency, 'every_30_minutes'); ?>>Every 30 minutes</option>
|
||||||
|
<option value="hourly" <?php selected($current_frequency, 'hourly'); ?>>Hourly</option>
|
||||||
|
<option value="every_6_hours" <?php selected($current_frequency, 'every_6_hours'); ?>>Every 6 hours</option>
|
||||||
|
<option value="daily" <?php selected($current_frequency, 'daily'); ?>>Daily</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
<p class="description">How often to check for and sync new/modified records.</p>
|
||||||
<br><br>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Sync Status</th>
|
||||||
|
<td>
|
||||||
|
<p>
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<?php if ($sync_status['is_scheduled']): ?>
|
||||||
|
<span style="color: #46b450;">✓ Scheduled</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span style="color: #dc3232;">✗ Not Scheduled</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Last Sync:</strong>
|
||||||
|
<?php echo esc_html($sync_status['last_sync_formatted']); ?>
|
||||||
|
</p>
|
||||||
|
<?php if ($sync_status['is_scheduled']): ?>
|
||||||
|
<p>
|
||||||
|
<strong>Next Sync:</strong>
|
||||||
|
<?php echo esc_html($sync_status['next_sync_formatted']); ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($sync_status['last_result']): ?>
|
||||||
|
<p>
|
||||||
|
<strong>Last Result:</strong>
|
||||||
|
<?php
|
||||||
|
$last = $sync_status['last_result'];
|
||||||
|
echo esc_html(sprintf('%d synced, %d failed', $last['total_synced'] ?? 0, $last['total_failed'] ?? 0));
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p class="submit">
|
||||||
<button type="submit" class="button button-primary">Save Settings</button>
|
<button type="submit" class="button button-primary">Save Settings</button>
|
||||||
|
<button type="button" class="button button-secondary" id="run-scheduled-sync-now" style="margin-left: 10px;">
|
||||||
|
🔄 Run Sync Now
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="scheduled-sync-status"></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -359,8 +422,23 @@ class HVAC_Zoho_Admin {
|
||||||
* Simple test handler to isolate issues
|
* Simple test handler to isolate issues
|
||||||
*/
|
*/
|
||||||
public function simple_test() {
|
public function simple_test() {
|
||||||
|
// Check permissions
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error(array('message' => 'Unauthorized access'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
wp_send_json_success(array('message' => 'Simple test works!'));
|
$payload_status = 'No payload received';
|
||||||
|
if (!empty($_POST['test_payload'])) {
|
||||||
|
$payload_status = 'Payload received (' . strlen($_POST['test_payload']) . ' chars)';
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'message' => 'Simple test works!',
|
||||||
|
'server_time' => date('Y-m-d H:i:s'),
|
||||||
|
'payload_status' => $payload_status,
|
||||||
|
'request_method' => $_SERVER['REQUEST_METHOD']
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -488,9 +566,6 @@ class HVAC_Zoho_Admin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse OAuth request using parse_request hook
|
|
||||||
*/
|
|
||||||
public function parse_oauth_request($wp) {
|
public function parse_oauth_request($wp) {
|
||||||
|
|
||||||
// Check if this is an OAuth callback request
|
// Check if this is an OAuth callback request
|
||||||
|
|
@ -507,6 +582,33 @@ class HVAC_Zoho_Admin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manual Router for OAuth Callback
|
||||||
|
*
|
||||||
|
* Catches the request on 'init' before WordPress internal routing can 404 it.
|
||||||
|
* This is a robust fallback for when rewrite rules fail or haven't flushed.
|
||||||
|
*/
|
||||||
|
public function check_for_oauth_params() {
|
||||||
|
// Check if we are at the oauth callback URL path
|
||||||
|
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||||
|
|
||||||
|
// Check strict path match or if regex matches
|
||||||
|
if (strpos($path, '/oauth/callback') !== false) {
|
||||||
|
|
||||||
|
// We are at the right URL. Do we have the code?
|
||||||
|
if (isset($_GET['code'])) {
|
||||||
|
|
||||||
|
// We have a code.
|
||||||
|
// We MUST process this, otherwise WP will display a 404.
|
||||||
|
// Even if state matches or not, we should handle it here.
|
||||||
|
// The process_oauth_callback method handles validation.
|
||||||
|
|
||||||
|
$this->process_oauth_callback();
|
||||||
|
// process_oauth_callback exits, so we won't continue to 404.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add OAuth callback rewrite rule
|
* Add OAuth callback rewrite rule
|
||||||
*/
|
*/
|
||||||
|
|
@ -794,10 +896,14 @@ class HVAC_Zoho_Admin {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
// Success!
|
// Success!
|
||||||
wp_send_json_success(array(
|
wp_send_json_success(array(
|
||||||
'message' => 'Connection successful!',
|
'message' => 'Connection successful!',
|
||||||
'modules' => isset($response['modules']) ? count($response['modules']) . ' modules available' : 'API connected',
|
'modules' => isset($response['modules']) ? count($response['modules']) . ' modules available' : 'API connected',
|
||||||
|
'client_id' => substr($client_id, 0, 10) . '...',
|
||||||
|
'client_secret_exists' => true,
|
||||||
|
'refresh_token_exists' => true,
|
||||||
'credentials_status' => array(
|
'credentials_status' => array(
|
||||||
'client_id' => substr($client_id, 0, 10) . '...',
|
'client_id' => substr($client_id, 0, 10) . '...',
|
||||||
'client_secret_exists' => true,
|
'client_secret_exists' => true,
|
||||||
|
|
@ -847,6 +953,8 @@ class HVAC_Zoho_Admin {
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = sanitize_text_field($_POST['type']);
|
$type = sanitize_text_field($_POST['type']);
|
||||||
|
$offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0;
|
||||||
|
$limit = isset($_POST['limit']) ? absint($_POST['limit']) : 50;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-sync.php';
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-sync.php';
|
||||||
|
|
@ -854,19 +962,19 @@ class HVAC_Zoho_Admin {
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'events':
|
case 'events':
|
||||||
$result = $sync->sync_events();
|
$result = $sync->sync_events($offset, $limit);
|
||||||
break;
|
break;
|
||||||
case 'users':
|
case 'users':
|
||||||
$result = $sync->sync_users();
|
$result = $sync->sync_users($offset, $limit);
|
||||||
break;
|
break;
|
||||||
case 'attendees':
|
case 'attendees':
|
||||||
$result = $sync->sync_attendees();
|
$result = $sync->sync_attendees($offset, $limit);
|
||||||
break;
|
break;
|
||||||
case 'rsvps':
|
case 'rsvps':
|
||||||
$result = $sync->sync_rsvps();
|
$result = $sync->sync_rsvps($offset, $limit);
|
||||||
break;
|
break;
|
||||||
case 'purchases':
|
case 'purchases':
|
||||||
$result = $sync->sync_purchases();
|
$result = $sync->sync_purchases($offset, $limit);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception('Invalid sync type');
|
throw new Exception('Invalid sync type');
|
||||||
|
|
@ -880,5 +988,89 @@ class HVAC_Zoho_Admin {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save scheduled sync settings
|
||||||
|
*/
|
||||||
|
public function save_settings() {
|
||||||
|
check_ajax_referer('hvac_zoho_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error(array('message' => 'Unauthorized access'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$auto_sync = isset($_POST['auto_sync']) && $_POST['auto_sync'] === '1' ? '1' : '0';
|
||||||
|
$sync_frequency = sanitize_text_field($_POST['sync_frequency'] ?? 'every_5_minutes');
|
||||||
|
|
||||||
|
// Validate frequency value
|
||||||
|
$valid_frequencies = array(
|
||||||
|
'every_5_minutes',
|
||||||
|
'every_15_minutes',
|
||||||
|
'every_30_minutes',
|
||||||
|
'hourly',
|
||||||
|
'every_6_hours',
|
||||||
|
'daily'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!in_array($sync_frequency, $valid_frequencies)) {
|
||||||
|
$sync_frequency = 'every_5_minutes';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save settings
|
||||||
|
update_option('hvac_zoho_auto_sync', $auto_sync);
|
||||||
|
update_option('hvac_zoho_sync_frequency', $sync_frequency);
|
||||||
|
|
||||||
|
// Get scheduled sync instance and explicitly schedule/unschedule
|
||||||
|
// This ensures scheduling works even if option value didn't change
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php';
|
||||||
|
$scheduled_sync = HVAC_Zoho_Scheduled_Sync::instance();
|
||||||
|
|
||||||
|
if ($auto_sync === '1') {
|
||||||
|
$scheduled_sync->schedule_sync($sync_frequency);
|
||||||
|
} else {
|
||||||
|
$scheduled_sync->unschedule_sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = $scheduled_sync->get_status();
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'message' => 'Settings saved successfully',
|
||||||
|
'auto_sync' => $auto_sync,
|
||||||
|
'sync_frequency' => $sync_frequency,
|
||||||
|
'is_scheduled' => $status['is_scheduled'],
|
||||||
|
'next_sync' => $status['next_sync_formatted']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run scheduled sync manually
|
||||||
|
*/
|
||||||
|
public function run_scheduled_sync_now() {
|
||||||
|
check_ajax_referer('hvac_zoho_nonce', 'nonce');
|
||||||
|
|
||||||
|
if (!current_user_can('manage_options')) {
|
||||||
|
wp_send_json_error(array('message' => 'Unauthorized access'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php';
|
||||||
|
$scheduled_sync = HVAC_Zoho_Scheduled_Sync::instance();
|
||||||
|
|
||||||
|
$result = $scheduled_sync->run_now();
|
||||||
|
|
||||||
|
wp_send_json_success(array(
|
||||||
|
'message' => 'Scheduled sync completed',
|
||||||
|
'result' => $result
|
||||||
|
));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
wp_send_json_error(array(
|
||||||
|
'message' => 'Sync failed',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
@ -337,8 +337,11 @@ final class HVAC_Plugin {
|
||||||
'admin/class-hvac-enhanced-settings.php',
|
'admin/class-hvac-enhanced-settings.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Check if this is an OAuth callback request
|
||||||
|
$is_oauth_callback = isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/oauth/callback') !== false;
|
||||||
|
|
||||||
// Load admin files conditionally
|
// Load admin files conditionally
|
||||||
if (is_admin() || wp_doing_ajax()) {
|
if (is_admin() || wp_doing_ajax() || $is_oauth_callback) {
|
||||||
foreach ($this->loadFeatureFiles($adminFiles) as $file => $status) {
|
foreach ($this->loadFeatureFiles($adminFiles) as $file => $status) {
|
||||||
if ($status === 'loaded') {
|
if ($status === 'loaded') {
|
||||||
$this->componentStatus["admin_{$file}"] = true;
|
$this->componentStatus["admin_{$file}"] = true;
|
||||||
|
|
@ -548,8 +551,10 @@ final class HVAC_Plugin {
|
||||||
// Schedule non-critical components for lazy loading
|
// Schedule non-critical components for lazy loading
|
||||||
// Use 'init' instead of 'wp_loaded' so components can register wp_enqueue_scripts hooks
|
// Use 'init' instead of 'wp_loaded' so components can register wp_enqueue_scripts hooks
|
||||||
add_action('init', [$this, 'initializeSecondaryComponents'], 5);
|
add_action('init', [$this, 'initializeSecondaryComponents'], 5);
|
||||||
// Use admin_menu (not admin_init) so components can register their menus in time
|
|
||||||
add_action('admin_menu', [$this, 'initializeAdminComponents'], 5);
|
// Use 'init' for admin components too, ensuring AJAX handlers are registered
|
||||||
|
// (admin_menu hook doesn't fire on AJAX requests, causing 400 Bad Request/0 response)
|
||||||
|
add_action('init', [$this, 'initializeAdminComponents'], 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -702,6 +707,15 @@ final class HVAC_Plugin {
|
||||||
hvac_communication_scheduler();
|
hvac_communication_scheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Zoho scheduled sync (must load on ALL requests for WP-Cron to work)
|
||||||
|
// This registers custom cron schedules and the cron action hook
|
||||||
|
if (file_exists(HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php')) {
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-scheduled-sync.php';
|
||||||
|
if (class_exists('HVAC_Zoho_Scheduled_Sync')) {
|
||||||
|
HVAC_Zoho_Scheduled_Sync::instance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize Master Trainer manager classes (fix for missing shortcode registrations)
|
// Initialize Master Trainer manager classes (fix for missing shortcode registrations)
|
||||||
if (class_exists('HVAC_Master_Events_Overview')) {
|
if (class_exists('HVAC_Master_Events_Overview')) {
|
||||||
HVAC_Master_Events_Overview::instance();
|
HVAC_Master_Events_Overview::instance();
|
||||||
|
|
@ -731,19 +745,22 @@ final class HVAC_Plugin {
|
||||||
* Only loads admin components in admin context to improve frontend performance.
|
* Only loads admin components in admin context to improve frontend performance.
|
||||||
*/
|
*/
|
||||||
public function initializeAdminComponents(): void {
|
public function initializeAdminComponents(): void {
|
||||||
|
// Check if this is an OAuth callback request
|
||||||
|
$is_oauth_callback = isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/oauth/callback') !== false;
|
||||||
|
|
||||||
// Initialize admin components only when needed
|
// Initialize admin components only when needed
|
||||||
if (class_exists('HVAC_Zoho_Admin')) {
|
if (class_exists('HVAC_Zoho_Admin') && (is_admin() || wp_doing_ajax() || $is_oauth_callback)) {
|
||||||
HVAC_Zoho_Admin::instance();
|
HVAC_Zoho_Admin::instance();
|
||||||
}
|
}
|
||||||
if (class_exists('HVAC_Admin_Dashboard')) {
|
if (class_exists('HVAC_Admin_Dashboard') && is_admin()) {
|
||||||
new HVAC_Admin_Dashboard();
|
new HVAC_Admin_Dashboard();
|
||||||
}
|
}
|
||||||
if (class_exists('HVAC_Enhanced_Settings')) {
|
if (class_exists('HVAC_Enhanced_Settings') && is_admin()) {
|
||||||
HVAC_Enhanced_Settings::instance();
|
HVAC_Enhanced_Settings::instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize trainer certification admin interface
|
// Initialize trainer certification admin interface
|
||||||
if (class_exists('HVAC_Certification_Admin') && current_user_can('manage_hvac_certifications')) {
|
if (class_exists('HVAC_Certification_Admin') && current_user_can('manage_hvac_certifications') && is_admin()) {
|
||||||
HVAC_Certification_Admin::instance();
|
HVAC_Certification_Admin::instance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
389
includes/zoho/class-zoho-scheduled-sync.php
Normal file
389
includes/zoho/class-zoho-scheduled-sync.php
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Zoho CRM Scheduled Sync Handler
|
||||||
|
*
|
||||||
|
* Manages WP-Cron based scheduled sync of WordPress data to Zoho CRM.
|
||||||
|
*
|
||||||
|
* @package HVACCommunityEvents
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheduled Sync Class
|
||||||
|
*/
|
||||||
|
class HVAC_Zoho_Scheduled_Sync {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instance of this class
|
||||||
|
*
|
||||||
|
* @var HVAC_Zoho_Scheduled_Sync
|
||||||
|
*/
|
||||||
|
private static $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cron hook name
|
||||||
|
*/
|
||||||
|
const CRON_HOOK = 'hvac_zoho_scheduled_sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option names
|
||||||
|
*/
|
||||||
|
const OPTION_ENABLED = 'hvac_zoho_auto_sync';
|
||||||
|
const OPTION_INTERVAL = 'hvac_zoho_sync_frequency';
|
||||||
|
const OPTION_LAST_SYNC = 'hvac_zoho_last_sync_time';
|
||||||
|
const OPTION_LAST_RESULT = 'hvac_zoho_last_sync_result';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance of this class
|
||||||
|
*
|
||||||
|
* @return HVAC_Zoho_Scheduled_Sync
|
||||||
|
*/
|
||||||
|
public static function instance() {
|
||||||
|
if (null === self::$instance) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
// Register custom cron schedules
|
||||||
|
add_filter('cron_schedules', array($this, 'register_cron_schedules'));
|
||||||
|
|
||||||
|
// Register the cron action
|
||||||
|
add_action(self::CRON_HOOK, array($this, 'run_scheduled_sync'));
|
||||||
|
|
||||||
|
// Check if we need to reschedule on settings change
|
||||||
|
// Hook into both add_option (first time) and update_option (subsequent changes)
|
||||||
|
add_action('add_option_' . self::OPTION_ENABLED, array($this, 'on_setting_added'), 10, 2);
|
||||||
|
add_action('update_option_' . self::OPTION_ENABLED, array($this, 'on_setting_change'), 10, 2);
|
||||||
|
add_action('update_option_' . self::OPTION_INTERVAL, array($this, 'on_interval_change'), 10, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register custom cron schedules
|
||||||
|
*
|
||||||
|
* @param array $schedules Existing schedules
|
||||||
|
* @return array Modified schedules
|
||||||
|
*/
|
||||||
|
public function register_cron_schedules($schedules) {
|
||||||
|
$schedules['every_5_minutes'] = array(
|
||||||
|
'interval' => 5 * MINUTE_IN_SECONDS,
|
||||||
|
'display' => __('Every 5 Minutes', 'hvac-community-events')
|
||||||
|
);
|
||||||
|
|
||||||
|
$schedules['every_15_minutes'] = array(
|
||||||
|
'interval' => 15 * MINUTE_IN_SECONDS,
|
||||||
|
'display' => __('Every 15 Minutes', 'hvac-community-events')
|
||||||
|
);
|
||||||
|
|
||||||
|
$schedules['every_30_minutes'] = array(
|
||||||
|
'interval' => 30 * MINUTE_IN_SECONDS,
|
||||||
|
'display' => __('Every 30 Minutes', 'hvac-community-events')
|
||||||
|
);
|
||||||
|
|
||||||
|
$schedules['every_6_hours'] = array(
|
||||||
|
'interval' => 6 * HOUR_IN_SECONDS,
|
||||||
|
'display' => __('Every 6 Hours', 'hvac-community-events')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $schedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule the sync cron event
|
||||||
|
*
|
||||||
|
* @param string|null $interval Optional interval override
|
||||||
|
* @return bool True if scheduled successfully
|
||||||
|
*/
|
||||||
|
public function schedule_sync($interval = null) {
|
||||||
|
// Unschedule any existing event first
|
||||||
|
$this->unschedule_sync();
|
||||||
|
|
||||||
|
// Get interval from option if not provided
|
||||||
|
if (null === $interval) {
|
||||||
|
$interval = get_option(self::OPTION_INTERVAL, 'every_5_minutes');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule the event
|
||||||
|
$result = wp_schedule_event(time(), $interval, self::CRON_HOOK);
|
||||||
|
|
||||||
|
if ($result !== false) {
|
||||||
|
if (class_exists('HVAC_Logger')) {
|
||||||
|
HVAC_Logger::info("Scheduled Zoho sync with interval: {$interval}", 'ZohoScheduledSync');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unschedule the sync cron event
|
||||||
|
*
|
||||||
|
* @return bool True if unscheduled successfully
|
||||||
|
*/
|
||||||
|
public function unschedule_sync() {
|
||||||
|
$timestamp = wp_next_scheduled(self::CRON_HOOK);
|
||||||
|
|
||||||
|
if ($timestamp) {
|
||||||
|
wp_unschedule_event($timestamp, self::CRON_HOOK);
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Logger')) {
|
||||||
|
HVAC_Logger::info('Unscheduled Zoho sync', 'ZohoScheduledSync');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if sync is currently scheduled
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_scheduled() {
|
||||||
|
return wp_next_scheduled(self::CRON_HOOK) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next scheduled sync time
|
||||||
|
*
|
||||||
|
* @return int|false Timestamp or false if not scheduled
|
||||||
|
*/
|
||||||
|
public function get_next_scheduled() {
|
||||||
|
return wp_next_scheduled(self::CRON_HOOK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle first-time option creation
|
||||||
|
*
|
||||||
|
* @param string $option Option name
|
||||||
|
* @param mixed $value Option value
|
||||||
|
*/
|
||||||
|
public function on_setting_added($option, $value) {
|
||||||
|
if ($value === '1' || $value === 1 || $value === true) {
|
||||||
|
$this->schedule_sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle setting enabled/disabled change
|
||||||
|
*
|
||||||
|
* @param mixed $old_value Old option value
|
||||||
|
* @param mixed $new_value New option value
|
||||||
|
*/
|
||||||
|
public function on_setting_change($old_value, $new_value) {
|
||||||
|
if ($new_value === '1' || $new_value === 1 || $new_value === true) {
|
||||||
|
$this->schedule_sync();
|
||||||
|
} else {
|
||||||
|
$this->unschedule_sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle interval change
|
||||||
|
*
|
||||||
|
* @param mixed $old_value Old interval value
|
||||||
|
* @param mixed $new_value New interval value
|
||||||
|
*/
|
||||||
|
public function on_interval_change($old_value, $new_value) {
|
||||||
|
// Only reschedule if currently enabled
|
||||||
|
if (get_option(self::OPTION_ENABLED) === '1') {
|
||||||
|
$this->schedule_sync($new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the scheduled sync
|
||||||
|
*
|
||||||
|
* This is the main cron callback that syncs all data types.
|
||||||
|
*/
|
||||||
|
public function run_scheduled_sync() {
|
||||||
|
$start_time = time();
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Logger')) {
|
||||||
|
HVAC_Logger::info('Starting scheduled Zoho sync', 'ZohoScheduledSync');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get last sync time for incremental sync
|
||||||
|
$last_sync = $this->get_last_sync_time();
|
||||||
|
|
||||||
|
// Initialize results
|
||||||
|
$results = array(
|
||||||
|
'started_at' => date('Y-m-d H:i:s', $start_time),
|
||||||
|
'last_sync_from' => $last_sync ? date('Y-m-d H:i:s', $last_sync) : 'Never',
|
||||||
|
'events' => null,
|
||||||
|
'users' => null,
|
||||||
|
'attendees' => null,
|
||||||
|
'rsvps' => null,
|
||||||
|
'purchases' => null,
|
||||||
|
'errors' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load dependencies
|
||||||
|
require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-sync.php';
|
||||||
|
$sync = new HVAC_Zoho_Sync();
|
||||||
|
|
||||||
|
// Sync each type with since_timestamp for incremental sync
|
||||||
|
// Events
|
||||||
|
$results['events'] = $this->sync_all_batches($sync, 'sync_events', $last_sync);
|
||||||
|
|
||||||
|
// Users/Trainers
|
||||||
|
$results['users'] = $this->sync_all_batches($sync, 'sync_users', $last_sync);
|
||||||
|
|
||||||
|
// Attendees
|
||||||
|
$results['attendees'] = $this->sync_all_batches($sync, 'sync_attendees', $last_sync);
|
||||||
|
|
||||||
|
// RSVPs
|
||||||
|
$results['rsvps'] = $this->sync_all_batches($sync, 'sync_rsvps', $last_sync);
|
||||||
|
|
||||||
|
// Purchases/Orders
|
||||||
|
$results['purchases'] = $this->sync_all_batches($sync, 'sync_purchases', $last_sync);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$results['errors'][] = $e->getMessage();
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Logger')) {
|
||||||
|
HVAC_Logger::error('Scheduled sync error: ' . $e->getMessage(), 'ZohoScheduledSync');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last sync time
|
||||||
|
$this->set_last_sync_time($start_time);
|
||||||
|
|
||||||
|
// Calculate totals
|
||||||
|
$total_synced = 0;
|
||||||
|
$total_failed = 0;
|
||||||
|
foreach (array('events', 'users', 'attendees', 'rsvps', 'purchases') as $type) {
|
||||||
|
if (isset($results[$type]['synced'])) {
|
||||||
|
$total_synced += $results[$type]['synced'];
|
||||||
|
}
|
||||||
|
if (isset($results[$type]['failed'])) {
|
||||||
|
$total_failed += $results[$type]['failed'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$results['completed_at'] = date('Y-m-d H:i:s');
|
||||||
|
$results['duration_seconds'] = time() - $start_time;
|
||||||
|
$results['total_synced'] = $total_synced;
|
||||||
|
$results['total_failed'] = $total_failed;
|
||||||
|
|
||||||
|
// Save last result
|
||||||
|
update_option(self::OPTION_LAST_RESULT, $results);
|
||||||
|
|
||||||
|
if (class_exists('HVAC_Logger')) {
|
||||||
|
HVAC_Logger::info("Scheduled sync completed: {$total_synced} synced, {$total_failed} failed", 'ZohoScheduledSync');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync all batches for a given sync method
|
||||||
|
*
|
||||||
|
* @param HVAC_Zoho_Sync $sync Sync instance
|
||||||
|
* @param string $method Method name (e.g., 'sync_events')
|
||||||
|
* @param int|null $since_timestamp Optional timestamp for incremental sync
|
||||||
|
* @return array Aggregated results
|
||||||
|
*/
|
||||||
|
private function sync_all_batches($sync, $method, $since_timestamp = null) {
|
||||||
|
$offset = 0;
|
||||||
|
$limit = 50;
|
||||||
|
$aggregated = array(
|
||||||
|
'total' => 0,
|
||||||
|
'synced' => 0,
|
||||||
|
'failed' => 0,
|
||||||
|
'errors' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Call the sync method with since_timestamp support
|
||||||
|
$result = $sync->$method($offset, $limit, $since_timestamp);
|
||||||
|
|
||||||
|
// Aggregate results
|
||||||
|
$aggregated['total'] = $result['total'] ?? 0;
|
||||||
|
$aggregated['synced'] += $result['synced'] ?? 0;
|
||||||
|
$aggregated['failed'] += $result['failed'] ?? 0;
|
||||||
|
|
||||||
|
if (!empty($result['errors'])) {
|
||||||
|
$aggregated['errors'] = array_merge($aggregated['errors'], $result['errors']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for more batches
|
||||||
|
$has_more = $result['has_more'] ?? false;
|
||||||
|
$offset = $result['next_offset'] ?? ($offset + $limit);
|
||||||
|
|
||||||
|
} while ($has_more);
|
||||||
|
|
||||||
|
return $aggregated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last sync timestamp
|
||||||
|
*
|
||||||
|
* @return int|null Timestamp or null if never synced
|
||||||
|
*/
|
||||||
|
public function get_last_sync_time() {
|
||||||
|
$time = get_option(self::OPTION_LAST_SYNC);
|
||||||
|
return $time ? (int) $time : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the last sync timestamp
|
||||||
|
*
|
||||||
|
* @param int $timestamp Timestamp
|
||||||
|
*/
|
||||||
|
public function set_last_sync_time($timestamp) {
|
||||||
|
update_option(self::OPTION_LAST_SYNC, $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last sync result
|
||||||
|
*
|
||||||
|
* @return array|null Result array or null
|
||||||
|
*/
|
||||||
|
public function get_last_sync_result() {
|
||||||
|
return get_option(self::OPTION_LAST_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sync status summary
|
||||||
|
*
|
||||||
|
* @return array Status information
|
||||||
|
*/
|
||||||
|
public function get_status() {
|
||||||
|
$is_enabled = get_option(self::OPTION_ENABLED) === '1';
|
||||||
|
$interval = get_option(self::OPTION_INTERVAL, 'every_5_minutes');
|
||||||
|
$last_sync = $this->get_last_sync_time();
|
||||||
|
$next_sync = $this->get_next_scheduled();
|
||||||
|
$last_result = $this->get_last_sync_result();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'enabled' => $is_enabled,
|
||||||
|
'interval' => $interval,
|
||||||
|
'is_scheduled' => $this->is_scheduled(),
|
||||||
|
'last_sync_time' => $last_sync,
|
||||||
|
'last_sync_formatted' => $last_sync ? date('Y-m-d H:i:s', $last_sync) : 'Never',
|
||||||
|
'next_sync_time' => $next_sync,
|
||||||
|
'next_sync_formatted' => $next_sync ? date('Y-m-d H:i:s', $next_sync) : 'Not scheduled',
|
||||||
|
'last_result' => $last_result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run sync manually (for "Run Now" button)
|
||||||
|
*
|
||||||
|
* @return array Sync results
|
||||||
|
*/
|
||||||
|
public function run_now() {
|
||||||
|
return $this->run_scheduled_sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue