upskill-event-manager/assets/js/zoho-admin.js

706 lines
No EOL
31 KiB
JavaScript

/**
* Zoho CRM Admin JavaScript
*
* @package HVACCommunityEvents
*/
jQuery(document).ready(function ($) {
// =====================================================
// Password visibility toggle
// =====================================================
$('#toggle-secret').on('click', function () {
var passwordField = $('#zoho_client_secret');
var toggleBtn = $(this);
if (passwordField.attr('type') === 'password') {
passwordField.attr('type', 'text');
toggleBtn.text('Hide');
} else {
passwordField.attr('type', 'password');
toggleBtn.text('Show');
}
});
// =====================================================
// Copy redirect URI to clipboard
// =====================================================
$('#copy-redirect-uri').on('click', function () {
var redirectUri = hvacZoho.redirectUri || '';
if (!redirectUri) {
alert('Redirect URI not available');
return;
}
navigator.clipboard.writeText(redirectUri).then(function () {
$('#copy-redirect-uri').text('Copied!').prop('disabled', true);
setTimeout(function () {
$('#copy-redirect-uri').text('Copy').prop('disabled', false);
}, 2000);
}).catch(function () {
// Fallback for older browsers
var tempInput = $('<input>');
$('body').append(tempInput);
tempInput.val(redirectUri).select();
document.execCommand('copy');
tempInput.remove();
$('#copy-redirect-uri').text('Copied!').prop('disabled', true);
setTimeout(function () {
$('#copy-redirect-uri').text('Copy').prop('disabled', false);
}, 2000);
});
});
// =====================================================
// Flush rewrite rules
// =====================================================
$('#flush-rewrite-rules').on('click', function () {
var button = $(this);
button.prop('disabled', true).text('Flushing...');
$.post(hvacZoho.ajaxUrl, {
action: 'hvac_zoho_flush_rewrite_rules'
}, function (response) {
if (response.success) {
button.text('Flushed!').css('color', '#46b450');
setTimeout(function () {
location.reload();
}, 1000);
} else {
button.text('Error').css('color', '#dc3232');
setTimeout(function () {
button.prop('disabled', false).text('Flush Rules').css('color', '');
}, 2000);
}
});
});
// =====================================================
// Credentials form submission
// =====================================================
// =====================================================
// Credentials form submission
// =====================================================
$('#zoho-credentials-form').on('submit', function (e) {
e.preventDefault();
var formData = {
action: 'hvac_zoho_save_credentials',
zoho_client_id: $('#zoho_client_id').val(),
zoho_client_secret: $('#zoho_client_secret').val(),
nonce: $('input[name="hvac_zoho_nonce"]').val()
};
var $saveBtn = $('#save-credentials');
$saveBtn.prop('disabled', true).text('Saving...');
// Clear any previous messages
$('.notice').remove();
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: formData,
success: function (response) {
if (response.success) {
window.location.href = window.location.href.split('?')[0] + '?page=hvac-zoho-sync&credentials_saved=1';
} else {
// Create error notice
var errorHtml = '<div class="notice notice-error is-dismissible"><p>' +
'<strong>Error saving credentials:</strong> ' + response.data.message +
'</p></div>';
$('h1').after(errorHtml);
$saveBtn.prop('disabled', false).text('Save Credentials');
}
},
error: function (xhr, status, error) {
console.error('Zoho Save Error:', xhr);
var errorMessage = 'Unknown error occurred';
var errorDetails = '';
if (xhr.status === 400 || xhr.status === 403) {
errorMessage = 'Request blocked (' + xhr.status + ')';
errorDetails = 'This is likely due to a security plugin or Web Application Firewall (WAF) blocking the request. ' +
'The content (e.g. Client Secret) might be triggering a false positive security rule.';
} else if (xhr.status === 500) {
errorMessage = 'Server Error (500)';
errorDetails = 'Check the server error logs for more information.';
} else if (status === 'timeout') {
errorMessage = 'Request timed out';
} else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
errorMessage = xhr.responseJSON.data.message;
} else {
errorMessage = error || status;
}
var errorHtml = '<div class="notice notice-error is-dismissible" style="border-left-color: #dc3232;">' +
'<p><strong>❌ Save Failed:</strong> ' + errorMessage + '</p>';
if (errorDetails) {
errorHtml += '<p><em>' + errorDetails + '</em></p>';
}
// Add debug details
errorHtml += '<details style="margin-top: 10px;">' +
'<summary>Technical Details</summary>' +
'<ul style="margin-top: 5px; font-size: 12px; background: #f0f0f0; padding: 10px;">' +
'<li>Status: ' + xhr.status + ' ' + xhr.statusText + '</li>' +
'<li>Response: ' + (xhr.responseText ? xhr.responseText.substring(0, 100) + '...' : '(empty)') + '</li>' +
'</ul>' +
'</details></div>';
$('h1').after(errorHtml);
// Re-enable button
$saveBtn.prop('disabled', false).text('Save Credentials');
// Scroll to error
$('html, body').animate({ scrollTop: 0 }, 'slow');
}
});
});
// =====================================================
// OAuth authorization handler
// =====================================================
$('#start-oauth').on('click', function () {
var clientId = $('#zoho_client_id').val();
var clientSecret = $('#zoho_client_secret').val();
if (!clientId || !clientSecret) {
alert('Please save your credentials first before starting OAuth authorization.');
return;
}
// Use server-generated OAuth URL with CSRF state parameter
var oauthUrl = hvacZoho.oauthUrl || '';
if (!oauthUrl) {
alert('OAuth URL not available. Please save your credentials first and refresh the page.');
return;
}
// Open OAuth URL in the same window to handle callback properly
window.location.href = oauthUrl;
});
// =====================================================
// Test connection
// =====================================================
$('#test-connection').on('click', function () {
var $button = $(this);
var $status = $('#connection-status');
$button.prop('disabled', true).text('Testing...');
$status.html('');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_test_connection',
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var successHtml = '<div class="notice notice-success">';
successHtml += '<p><strong>' + response.data.message + '</strong></p>';
// Show credential details
if (response.data.client_id) {
successHtml += '<p>Client ID: ' + response.data.client_id + '</p>';
}
if (response.data.client_secret_exists) {
successHtml += '<p>Client Secret: ✓ Loaded</p>';
}
if (response.data.refresh_token_exists) {
successHtml += '<p>Refresh Token: ✓ Found</p>';
} else {
successHtml += '<p>Refresh Token: ❌ Missing (OAuth required)</p>';
}
// Show debug info if available
if (response.data.debug) {
successHtml += '<details style="margin-top: 10px;">';
successHtml += '<summary>Debug Information</summary>';
successHtml += '<pre style="background: #f0f0f0; padding: 10px; font-size: 12px;">';
successHtml += JSON.stringify(response.data.debug, null, 2);
successHtml += '</pre></details>';
}
successHtml += '</div>';
$status.html(successHtml);
} else {
// Debug: Log the response to see what we're getting
console.log('Error response:', response);
console.log('Message:', response.data.message);
console.log('Has auth_url:', !!response.data.auth_url);
// Handle OAuth authorization case specially
if (response.data.message === 'OAuth Authorization Required' && response.data.auth_url) {
var authHtml = '<div class="notice notice-warning">';
authHtml += '<h3>🔐 OAuth Authorization Required</h3>';
authHtml += '<p><strong>' + response.data.details + '</strong></p>';
if (response.data.next_steps) {
authHtml += '<ol>';
response.data.next_steps.forEach(function (step) {
authHtml += '<li>' + step + '</li>';
});
authHtml += '</ol>';
}
authHtml += '<p><a href="' + response.data.auth_url + '" target="_blank" class="button button-primary" style="margin: 10px 0;">🚀 Authorize with Zoho CRM</a></p>';
// Show credential status
if (response.data.credentials_status) {
authHtml += '<p><strong>Current Status:</strong></p>';
authHtml += '<ul>';
authHtml += '<li>Client ID: ' + response.data.credentials_status.client_id + '</li>';
authHtml += '<li>Client Secret: ' + (response.data.credentials_status.client_secret_exists ? '✓ Loaded' : '❌ Missing') + '</li>';
authHtml += '<li>Refresh Token: ' + (response.data.credentials_status.refresh_token_exists ? '✓ Found' : '❌ Missing') + '</li>';
authHtml += '</ul>';
}
authHtml += '<p><em>After authorization, come back and test the connection again.</em></p>';
authHtml += '</div>';
$status.html(authHtml);
return;
} else {
console.log('OAuth conditions not met:');
console.log('Message matches:', response.data.message === 'OAuth Authorization Required');
console.log('Auth URL exists:', !!response.data.auth_url);
}
// Create detailed error display for other errors
var errorHtml = '<div class="notice notice-error">';
errorHtml += '<p><strong>' + response.data.message + ':</strong> ' + response.data.error + '</p>';
// Add error code if available
if (response.data.code) {
errorHtml += '<p><strong>Error Code:</strong> ' + response.data.code + '</p>';
}
// Add details if available
if (response.data.details) {
errorHtml += '<p><strong>Details:</strong> ' + response.data.details + '</p>';
}
// Add debugging info
errorHtml += '<div class="hvac-zoho-debug-info">';
errorHtml += '<p><strong>Debug Information:</strong></p>';
errorHtml += '<p>Check the PHP error log for more details.</p>';
errorHtml += '<p>Log location: wp-content/plugins/hvac-community-events/logs/zoho-debug.log</p>';
// Add raw response data if available
if (response.data.raw) {
errorHtml += '<details>';
errorHtml += '<summary>Raw Response Data (click to expand)</summary>';
errorHtml += '<pre style="background: #f0f0f0; padding: 10px; max-height: 300px; overflow: auto;">' +
JSON.stringify(JSON.parse(response.data.raw), null, 2) +
'</pre>';
errorHtml += '</details>';
}
// Add file/line info if available (for exceptions)
if (response.data.file) {
errorHtml += '<p><strong>File:</strong> ' + response.data.file + '</p>';
}
// Add trace if available
if (response.data.trace) {
errorHtml += '<details>';
errorHtml += '<summary>Stack Trace (click to expand)</summary>';
errorHtml += '<pre style="background: #f0f0f0; padding: 10px; max-height: 300px; overflow: auto;">' +
response.data.trace +
'</pre>';
errorHtml += '</details>';
}
errorHtml += '</div>'; // Close debug info
errorHtml += '</div>'; // Close notice
$status.html(errorHtml);
}
},
error: function (xhr, status, error) {
var errorHtml = '<div class="notice notice-error">';
errorHtml += '<p><strong>AJAX Error:</strong> Connection test failed</p>';
errorHtml += '<p><strong>Status:</strong> ' + status + '</p>';
errorHtml += '<p><strong>Error:</strong> ' + error + '</p>';
errorHtml += '</div>';
$status.html(errorHtml);
},
complete: function () {
$button.prop('disabled', false).text('Test Connection');
}
});
});
// =====================================================
// Sync data with batch progress
// =====================================================
/**
* Sync with progress - auto-continues through all batches
* @param {jQuery} $button - The sync button
* @param {string} type - Sync type (events, users, attendees, rsvps, purchases)
* @param {jQuery} $status - Status container element
* @param {number} offset - Current offset
* @param {object} accumulated - Accumulated results across batches
*/
function syncWithProgress($button, type, $status, offset, accumulated) {
accumulated = accumulated || {
synced: 0,
failed: 0,
errors: [],
total: 0,
staging_mode: false,
responses: [],
test_data: []
};
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_sync_data',
type: type,
offset: offset,
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var result = response.data;
// Update accumulated totals
accumulated.total = result.total; // Total is consistent across batches
accumulated.synced += result.synced;
accumulated.failed += result.failed;
accumulated.staging_mode = result.staging_mode;
// Merge arrays
if (result.errors && result.errors.length > 0) {
accumulated.errors = accumulated.errors.concat(result.errors);
}
if (result.responses && result.responses.length > 0) {
accumulated.responses = accumulated.responses.concat(result.responses);
}
if (result.test_data && result.test_data.length > 0) {
accumulated.test_data = accumulated.test_data.concat(result.test_data);
}
// Calculate progress
var processed = accumulated.synced + accumulated.failed;
var percent = accumulated.total > 0 ? Math.round((processed / accumulated.total) * 100) : 0;
// Update progress bar
var progressHtml = '<div class="sync-progress-bar" style="margin: 10px 0;">' +
'<div style="background: #e0e0e0; border-radius: 4px; overflow: hidden; height: 20px;">' +
'<div style="background: linear-gradient(90deg, #0073aa, #00a0d2); height: 100%; width: ' + percent + '%; transition: width 0.3s;"></div>' +
'</div>' +
'<p style="margin: 5px 0; font-size: 13px;">' +
'<strong>' + processed + ' of ' + accumulated.total + '</strong> processed (' + percent + '%)' +
'</p></div>';
$status.html(progressHtml);
// Check if there are more batches
if (result.has_more && result.next_offset > offset) {
// Continue with next batch
syncWithProgress($button, type, $status, result.next_offset, accumulated);
} else {
// All done! Show final results
displaySyncResults($button, type, $status, accumulated, result);
}
} else {
$status.html('<div class="notice notice-error"><p>' + response.data.message + ': ' + response.data.error + '</p></div>');
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
},
error: function () {
$status.html('<div class="notice notice-error"><p>Sync failed - network or server error</p></div>');
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
});
}
/**
* Display final sync results
*/
function displaySyncResults($button, type, $status, accumulated, lastResult) {
var html = '<div class="notice notice-success">';
if (accumulated.staging_mode) {
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
html += '<p>No data was sent to Zoho CRM. This is a dry-run showing what would sync.</p>';
} else {
html += '<p><strong>✅ Sync completed successfully!</strong></p>';
}
if (lastResult.version) {
html += '<p><strong>Server Code Version:</strong> ' + lastResult.version + '</p>';
}
html += '<ul>' +
'<li>Total records: ' + accumulated.total + '</li>' +
'<li>Synced: ' + accumulated.synced + '</li>' +
'<li>Failed: ' + accumulated.failed + '</li>' +
'</ul>';
// Show test data for staging
if (accumulated.test_data && accumulated.test_data.length > 0) {
html += '<details>' +
'<summary>View test data (first 5 records)</summary>' +
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
JSON.stringify(accumulated.test_data.slice(0, 5), null, 2) +
'</pre>' +
'</details>';
}
// Debug info
if (lastResult.debug_info) {
html += '<details style="margin-top: 10px;">' +
'<summary>🔍 Debug: Mode Detection Info</summary>' +
'<div style="background: #f0f0f0; padding: 10px; font-size: 12px; margin-top: 5px;">';
if (typeof lastResult.debug_info.is_staging !== 'undefined') {
html += '<p><strong>Is Staging:</strong> ' + (lastResult.debug_info.is_staging ? '✅ YES' : '❌ NO') + '</p>';
}
if (lastResult.debug_info.site_url) {
html += '<p><strong>Site URL:</strong> ' + lastResult.debug_info.site_url + '</p>';
}
html += '<pre style="background: #e0e0e0; padding: 5px; margin-top: 5px; max-height: 150px; overflow: auto;">' +
JSON.stringify(lastResult.debug_info, null, 2) +
'</pre>' +
'</div>' +
'</details>';
}
// Show errors if any
if (accumulated.errors && accumulated.errors.length > 0) {
html += '<details style="margin-top: 10px; border: 2px solid #dc3232;">' +
'<summary style="font-weight: bold; color: #dc3232;">❌ Errors (' + accumulated.errors.length + ')</summary>' +
'<pre style="background: #fff0f0; padding: 10px; overflow: auto; max-height: 300px;">' +
JSON.stringify(accumulated.errors.slice(0, 20), null, 2) +
'</pre>' +
'</details>';
}
// Show API responses preview
if (accumulated.responses && accumulated.responses.length > 0) {
html += '<details style="margin-top: 10px;">' +
'<summary>📡 Raw API Responses (first 10)</summary>' +
'<pre style="background: #f0f0f0; padding: 10px; overflow: auto; max-height: 200px;">' +
JSON.stringify(accumulated.responses.slice(0, 10), null, 2) +
'</pre>' +
'</details>';
}
html += '</div>';
$status.html(html);
$button.prop('disabled', false).text('Sync ' + type.charAt(0).toUpperCase() + type.slice(1));
}
// Sync button click handler
$('.sync-button').on('click', function () {
var $button = $(this);
var type = $button.data('type');
var $status = $('#' + type + '-status');
$button.prop('disabled', true).text('Syncing...');
$status.html('<p>Starting sync for ' + type + '...</p>');
// Start sync with batch progress
syncWithProgress($button, type, $status, 0, null);
});
// Save settings
$('#zoho-settings-form').on('submit', function (e) {
e.preventDefault();
var $form = $(this);
var $button = $form.find('button[type="submit"]');
$button.prop('disabled', true).text('Saving...');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_save_settings',
nonce: hvacZoho.nonce,
auto_sync: $form.find('input[name="auto_sync"]').is(':checked') ? '1' : '0',
sync_frequency: $form.find('select[name="sync_frequency"]').val()
},
success: function (response) {
if (response.success) {
// Reload page to show updated status
window.location.reload();
} else {
// Use toast notification instead of alert
if (window.HVACToast) {
HVACToast.error('Error saving settings: ' + response.data.message);
} else {
alert('Error saving settings: ' + response.data.message);
}
$button.prop('disabled', false).text('Save Settings');
}
},
error: function () {
// Use toast notification instead of alert
if (window.HVACToast) {
HVACToast.error('Error saving settings');
} else {
alert('Error saving settings');
}
$button.prop('disabled', false).text('Save Settings');
}
});
});
// =====================================================
// Run Scheduled Sync Now Handler
// =====================================================
$('#run-scheduled-sync-now').on('click', function () {
var $button = $(this);
var $status = $('#scheduled-sync-status');
$button.prop('disabled', true).text('Running...');
$status.html('<p>Starting scheduled sync...</p>');
$.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_run_scheduled_sync',
nonce: hvacZoho.nonce
},
success: function (response) {
if (response.success) {
var result = response.data.result;
var html = '<div class="notice notice-success">';
if (result.events && result.events.staging_mode) {
html += '<h4>🔧 STAGING MODE - Simulation Complete</h4>';
html += '<p>No data was sent to Zoho CRM.</p>';
} else {
html += '<p><strong>✅ Scheduled sync completed!</strong></p>';
}
html += '<ul>';
html += '<li>Total synced: ' + (result.total_synced || 0) + '</li>';
html += '<li>Total failed: ' + (result.total_failed || 0) + '</li>';
html += '<li>Duration: ' + (result.duration_seconds || 0) + ' seconds</li>';
html += '</ul>';
// Show details per type
html += '<details><summary>Details by type</summary><ul>';
['events', 'users', 'attendees', 'rsvps', 'purchases'].forEach(function (type) {
if (result[type]) {
html += '<li><strong>' + type + ':</strong> ' +
(result[type].synced || 0) + ' synced, ' +
(result[type].failed || 0) + ' failed</li>';
}
});
html += '</ul></details>';
html += '</div>';
$status.html(html);
} else {
$status.html('<div class="notice notice-error"><p>Sync failed: ' + response.data.message + '</p></div>');
}
},
error: function () {
$status.html('<div class="notice notice-error"><p>Error running scheduled sync</p></div>');
},
complete: function () {
$button.prop('disabled', false).text('🔄 Run Sync Now');
}
});
});
// =====================================================
// Diagnostic Test Handler
// =====================================================
$('#diagnostic-test').on('click', function () {
var $button = $(this);
$button.prop('disabled', true).text('Testing...');
// Remove existing notices
$('.notice').remove();
// Test 1: Simple GET
var runSimpleTest = function () {
return $.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_simple_test'
}
});
};
// Test 2: Payload Test (simulates credentials)
var runPayloadTest = function () {
var fakeId = '1000.' + new Array(20).join('a');
var fakeSecret = new Array(30).join('b');
return $.ajax({
url: hvacZoho.ajaxUrl,
method: 'POST',
data: {
action: 'hvac_zoho_simple_test',
test_payload: 'SIMULATED_CREDENTIALS',
zoho_client_id: fakeId,
zoho_client_secret: fakeSecret
}
});
};
// Execute tests sequence
runSimpleTest()
.then(function (response) {
if (response.success) {
console.log('Simple test passed');
return runPayloadTest();
} else {
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
}
})
.then(function (response) {
if (response.success) {
// Success!
var successHtml = '<div class="notice notice-success is-dismissible">' +
'<p><strong>✅ Diagnostic Test Passed</strong></p>' +
'<p>AJAX requests are working correctly. No WAF blocking detected for credential-like data.</p>' +
'</div>';
$('h1').after(successHtml);
} else {
return $.Deferred().reject({ status: 200, statusText: 'OK', responseJSON: response });
}
})
.fail(function (xhr) {
var errorHtml = '<div class="notice notice-error is-dismissible">' +
'<p><strong>❌ Diagnostic Test Failed</strong></p>';
if (xhr.status === 400 || xhr.status === 403) {
errorHtml += '<p><strong>WAF Blocking Detected!</strong></p>';
errorHtml += '<p>The server returned ' + xhr.status + ' when sending data.</p>';
} else {
errorHtml += '<p>Status: ' + (xhr.status || 'Unknown') + '</p>';
if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
errorHtml += '<p>Message: ' + xhr.responseJSON.data.message + '</p>';
}
}
errorHtml += '</div>';
$('h1').after(errorHtml);
})
.always(function () {
$button.prop('disabled', false).text('🏥 Run Diagnostic Test');
});
});
});