PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / admin / js / metasync-setup-wizard.js

metasync-setup-wizard.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at admin/js/metasync-setup-wizard.js

539 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Setup Wizard JavaScript
3 *
4 * Handles wizard navigation, validation, SSO integration, and imports.
5 *
6 * @package Metasync
7 * @subpackage Metasync/admin/js
8 */
9
10 /* global metasyncWizardData */
11
12 (function ($) {
13 'use strict';
14
15 var MetasyncWizard = {
16 currentStep: 1,
17 totalSteps: 6,
18 state: {},
19 ssoPopup: null,
20 ssoPollingInterval: null,
21
22 /**
23 * Initialize wizard
24 */
25 init: function () {
26 this.bindEvents();
27 this.renderStep(this.currentStep);
28 },
29
30 /**
31 * Bind all event handlers
32 */
33 bindEvents: function () {
34 var self = this;
35
36 // Navigation buttons
37 $('.wizard-btn-next').on('click', function () {
38 self.nextStep();
39 });
40
41 $('.wizard-btn-prev').on('click', function () {
42 self.prevStep();
43 });
44
45 $('.wizard-btn-skip').on('click', function () {
46 self.skipStep();
47 });
48
49 // Progress step indicators (click to jump to completed steps)
50 $('.wizard-progress-step').on('click', function () {
51 var step = parseInt($(this).data('step'));
52 if (self.isStepAccessible(step)) {
53 self.goToStep(step);
54 }
55 });
56
57 // Connection button (reuse existing SSO)
58 $('#wizard-connect-btn').on('click', function () {
59 self.triggerSSO();
60 });
61
62 // Skip connection link
63 $('.wizard-skip-connection').on('click', function (e) {
64 e.preventDefault();
65 self.nextStep();
66 });
67
68 // Import buttons
69 $(document).on('click', '.wizard-import-btn', function () {
70 var plugin = $(this).data('plugin');
71 self.runImport(plugin, $(this));
72 });
73
74 // Import option checkboxes - enable/disable import button
75 $(document).on('change', '.import-option', function () {
76 var $card = $(this).closest('.wizard-import-card');
77 self.updateImportButtonState($card);
78 });
79
80 // Apply recommended SEO settings
81 $('.wizard-apply-recommended').on('click', function () {
82 $('input[name="seo_category_archives"]').prop('checked', true);
83 $('input[name="seo_tag_archives"]').prop('checked', true);
84 $('input[name="seo_date_archives"]').prop('checked', false);
85 $('input[name="seo_author_archives"]').prop('checked', false);
86 });
87
88 // Schema enable/disable
89 $('#schema-enabled').on('change', function () {
90 if ($(this).is(':checked')) {
91 $('.wizard-schema-settings').slideDown();
92 } else {
93 $('.wizard-schema-settings').slideUp();
94 }
95 });
96
97 // Complete button
98 $('.wizard-complete-btn').on('click', function () {
99 self.completeWizard();
100 });
101 },
102
103 /**
104 * Go to next step
105 */
106 nextStep: function () {
107 if (this.validateCurrentStep()) {
108 this.saveStepData();
109 if (this.currentStep < this.totalSteps) {
110 this.currentStep++;
111 this.renderStep(this.currentStep);
112 }
113 }
114 },
115
116 /**
117 * Go to previous step
118 */
119 prevStep: function () {
120 if (this.currentStep > 1) {
121 this.currentStep--;
122 this.renderStep(this.currentStep);
123 }
124 },
125
126 /**
127 * Skip current step
128 */
129 skipStep: function () {
130 // Just go to next step without validation
131 if (this.currentStep < this.totalSteps) {
132 this.currentStep++;
133 this.renderStep(this.currentStep);
134 }
135 },
136
137 /**
138 * Go to specific step
139 */
140 goToStep: function (step) {
141 if (step >= 1 && step <= this.totalSteps) {
142 this.currentStep = step;
143 this.renderStep(step);
144 }
145 },
146
147 /**
148 * Render specific step
149 */
150 renderStep: function (step) {
151 // Hide all steps
152 $('.wizard-step').removeClass('active');
153
154 // Show current step
155 $('.wizard-step[data-step="' + step + '"]').addClass('active');
156
157 // Update progress bar
158 var progress = (step / this.totalSteps) * 100;
159 $('.wizard-progress-bar').css('width', progress + '%');
160
161 // Update step indicators
162 $('.wizard-progress-step').each(function () {
163 var stepNum = parseInt($(this).data('step'));
164 $(this).toggleClass('active', stepNum === step);
165 $(this).toggleClass('completed', stepNum < step);
166 });
167
168 // Update navigation buttons
169 $('.wizard-btn-prev').prop('disabled', step === 1);
170
171 if (step === this.totalSteps) {
172 $('.wizard-btn-next').hide();
173 $('.wizard-btn-skip').hide();
174 } else {
175 $('.wizard-btn-next').show().text(step === this.totalSteps - 1 ? 'Next →' : 'Next →');
176 $('.wizard-btn-skip').toggle(step > 1);
177 }
178
179 // Scroll to top
180 $('html, body').scrollTop(0);
181
182 // Initialize import button states for step 3
183 if (step === 3) {
184 var self = this;
185 $('.wizard-import-card').each(function () {
186 self.updateImportButtonState($(this));
187 });
188 }
189 },
190
191 /**
192 * Validate current step
193 */
194 validateCurrentStep: function () {
195 // All steps are optional, so always return true
196 // This allows users to skip any step
197 return true;
198 },
199
200 /**
201 * Save step data to server
202 */
203 saveStepData: function () {
204 var stepData = {};
205
206 // Collect data based on current step
207 switch(this.currentStep) {
208
209 case 2: // Connection and analytics consent
210 stepData.analytics = {
211 enabled: $('#wizard-analytics-consent').is(':checked')
212 };
213 break;
214
215 case 4: // SEO Settings
216 stepData.seo_settings = {
217 date_archives: $('input[name="seo_date_archives"]').is(':checked'),
218 author_archives: $('input[name="seo_author_archives"]').is(':checked'),
219 category_archives: $('input[name="seo_category_archives"]').is(':checked'),
220 tag_archives: $('input[name="seo_tag_archives"]').is(':checked')
221 };
222 break;
223
224 case 5: // Schema
225 stepData.schema = {
226 enabled: $('#schema-enabled').is(':checked'),
227 default_type: $('input[name="default_schema_type"]:checked').val()
228 };
229 break;
230
231 default:
232 // No data to save for this step
233 return;
234 }
235
236 // Save via AJAX
237 $.post(ajaxurl, {
238 action: 'metasync_save_wizard_progress',
239 nonce: metasyncWizardData.nonce,
240 step: this.currentStep,
241 data: stepData
242 });
243 },
244
245 /**
246 * Trigger SSO authentication
247 */
248 triggerSSO: function () {
249 var self = this;
250 var $button = $('#wizard-connect-btn');
251 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
252
253 $button.prop('disabled', true).text('Opening SSO...');
254
255 $.post(ajaxurl, {
256 action: 'metasync_generate_connect_url',
257 nonce: metasyncWizardData.saConnectNonce
258 }, function (response) {
259 if (response.success) {
260 // Validate URL is a safe https:// URL from an allowed SSO domain before opening.
261 // This prevents open redirect to arbitrary hosts, javascript: or data: URIs.
262 var connectUrl = response.data.connect_url;
263 var allowedSSOHosts = ['searchatlas.com', 'app.searchatlas.com', 'auth.searchatlas.com'];
264 try {
265 var parsedUrl = new URL(connectUrl);
266 var hostname = parsedUrl.hostname.toLowerCase();
267 var hostAllowed = allowedSSOHosts.some(function (allowed) {
268 return hostname === allowed || hostname.endsWith('.' + allowed);
269 });
270 if (parsedUrl.protocol === 'https:' && hostAllowed) {
271 // Open SSO popup via validated URL
272 var ssoLink = document.createElement('a');
273 ssoLink.href = parsedUrl.href;
274 ssoLink.target = pluginName.replace(/\s+/g, '') + 'SSO';
275 ssoLink.rel = 'noopener';
276 ssoLink.click();
277 self.ssoPopup = window.open('', pluginName.replace(/\s+/g, '') + 'SSO');
278
279 if (self.ssoPopup) {
280 $button.text('Waiting for authentication...');
281 // Poll for completion
282 self.pollSSOStatus(response.data.nonce_token);
283 } else {
284 $button.prop('disabled', false).text('Connect with ' + pluginName);
285 alert('Popup was blocked. Please allow popups for this site and try again.');
286 }
287 } else {
288 $button.prop('disabled', false).text('Connect with ' + pluginName);
289 alert('Invalid SSO URL. Please try again.');
290 }
291 } catch (e) {
292 $button.prop('disabled', false).text('Connect with ' + pluginName);
293 alert('Invalid SSO URL. Please try again.');
294 }
295 } else {
296 $button.prop('disabled', false).text('Connect with ' + pluginName);
297 alert('Failed to generate SSO URL. Please try again.');
298 }
299 }).fail(function () {
300 $button.prop('disabled', false).text('Connect with ' + pluginName);
301 alert('An error occurred. Please try again.');
302 });
303 },
304
305 /**
306 * Poll SSO status
307 */
308 pollSSOStatus: function (nonceToken) {
309 var self = this;
310 var attempts = 0;
311 var maxAttempts = 12; // 60 seconds (12 * 5 seconds)
312 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
313 var $button = $('#wizard-connect-btn');
314
315 self.ssoPollingInterval = setInterval(function () {
316 attempts++;
317
318 // Update button with countdown
319 var timeLeft = Math.ceil((maxAttempts - attempts) * 5);
320 $button.text('Waiting for authentication (' + timeLeft + 's)...');
321
322 $.post(ajaxurl, {
323 action: 'metasync_check_connect_status',
324 nonce: metasyncWizardData.saConnectNonce,
325 nonce_token: nonceToken
326 }, function (response) {
327 if (response.success && response.data.updated) {
328 // Stop polling and close popup
329 clearInterval(self.ssoPollingInterval);
330 self.ssoPollingInterval = null;
331
332 if (self.ssoPopup && !self.ssoPopup.closed) {
333 self.ssoPopup.close();
334 }
335 self.ssoPopup = null;
336
337 var statusCode = response.data.status_code || 200;
338
339 if (statusCode === 200) {
340 // Success: Update UI to show connected state
341 $('.wizard-step-connection').html(
342 '<div class="wizard-step-header">' +
343 '<h2>🔗 Connect to ' + pluginName + '</h2>' +
344 '<p>Link your WordPress site to your ' + pluginName + ' account for advanced features.</p>' +
345 '</div>' +
346 '<div class="wizard-step-content">' +
347 '<div class="wizard-connection-success">' +
348 '<span class="success-icon">✓</span>' +
349 '<h3>Successfully Connected!</h3>' +
350 '<p>Your site is linked to ' + pluginName + '.</p>' +
351 '</div>' +
352 '</div>'
353 );
354 } else if (statusCode === 403) {
355 // Authentication failed
356 self.resetSSOButton();
357 alert('Authentication failed. Please check your credentials and try again.');
358 } else if (statusCode === 500) {
359 // Server error
360 self.resetSSOButton();
361 alert('Server error occurred. Please try again later or contact support.');
362 } else {
363 // Unknown status
364 self.resetSSOButton();
365 alert('Unexpected response received. Please try again.');
366 }
367 }
368 }).fail(function () {
369 // Continue polling even if individual request fails
370 // Don't show error for temporary network issues
371 });
372
373 // Stop polling after max attempts (timeout)
374 if (attempts >= maxAttempts) {
375 clearInterval(self.ssoPollingInterval);
376 self.ssoPollingInterval = null;
377
378 if (self.ssoPopup && !self.ssoPopup.closed) {
379 self.ssoPopup.close();
380 }
381 self.ssoPopup = null;
382
383 self.resetSSOButton();
384 alert('Authentication timed out after 60 seconds. Please try again and complete the authentication more quickly.');
385 }
386 }, 5000); // Poll every 5 seconds
387 },
388
389 /**
390 * Reset SSO button to initial state
391 */
392 resetSSOButton: function () {
393 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
394 var $button = $('#wizard-connect-btn');
395
396 $button.prop('disabled', false).text('Connect with ' + pluginName);
397
398 // Clear any active polling
399 if (this.ssoPollingInterval) {
400 clearInterval(this.ssoPollingInterval);
401 this.ssoPollingInterval = null;
402 }
403
404 // Close popup if still open
405 if (this.ssoPopup && !this.ssoPopup.closed) {
406 this.ssoPopup.close();
407 }
408 this.ssoPopup = null;
409 },
410
411 /**
412 * Update import button state based on checkbox selection
413 */
414 updateImportButtonState: function ($card) {
415 var $button = $card.find('.wizard-import-btn');
416 var hasChecked = $card.find('.import-option:checked').length > 0;
417 $button.prop('disabled', !hasChecked);
418 },
419
420 /**
421 * Run import from selected plugin
422 */
423 runImport: function (plugin, $button) {
424 var $card = $button.closest('.wizard-import-card');
425 var $progress = $card.find('.import-progress');
426 var $progressBar = $card.find('.import-progress-bar');
427
428 // Get selected import types
429 var selectedTypes = [];
430 $card.find('.import-option:checked').each(function () {
431 selectedTypes.push($(this).data('type'));
432 });
433
434 if (selectedTypes.length === 0) {
435 alert('Please select at least one import type');
436 return;
437 }
438
439 $button.prop('disabled', true).text('Importing...');
440 $progress.show();
441
442 var totalImports = selectedTypes.length;
443 var completedImports = 0;
444
445 // Import each type sequentially
446 function importNext(index) {
447 if (index >= selectedTypes.length) {
448 // All imports complete
449 $button.text('✓ Import Complete').addClass('success');
450 setTimeout(function () {
451 $progress.fadeOut();
452 }, 1000);
453 return;
454 }
455
456 var type = selectedTypes[index];
457
458 // Indexation imports are batched server-side; loop until complete
459 var offset = 0;
460
461 function postImport() {
462 $.post(ajaxurl, {
463 action: 'metasync_import_external_data',
464 nonce: metasyncWizardData.importNonce,
465 type: type,
466 plugin: plugin,
467 offset: offset
468 }, function (response) {
469 var data = response && response.data ? response.data : {};
470
471 // Continue batching until the server reports completion
472 if (type === 'indexation' && response.success && data.is_complete === false) {
473 offset = data.processed || (offset + 1);
474 postImport();
475 return;
476 }
477
478 completedImports++;
479 var progress = (completedImports / totalImports) * 100;
480 $progressBar.css('width', progress + '%');
481
482 // Import next type
483 importNext(index + 1);
484 }).fail(function () {
485 $button.prop('disabled', false).text('Import Failed');
486 $progress.hide();
487 alert('Import failed for ' + type + '. Please try again.');
488 });
489 }
490
491 postImport();
492 }
493
494 importNext(0);
495 },
496
497 /**
498 * Complete wizard
499 */
500 completeWizard: function () {
501 var $button = $('.wizard-complete-btn');
502
503 $button.prop('disabled', true).text('Completing setup...');
504
505 $.post(ajaxurl, {
506 action: 'metasync_complete_wizard',
507 nonce: metasyncWizardData.nonce
508 }, function (response) {
509 if (response.success) {
510 // Redirect to dashboard
511 window.location.href = metasyncWizardData.dashboardUrl;
512 } else {
513 $button.prop('disabled', false).text('Get Started with MetaSync');
514 alert('Failed to complete wizard. Please try again.');
515 }
516 }).fail(function () {
517 $button.prop('disabled', false).text('Get Started with MetaSync');
518 alert('An error occurred. Please try again.');
519 });
520 },
521
522 /**
523 * Check if step is accessible
524 */
525 isStepAccessible: function (step) {
526 // Allow jumping to any previous step or current step
527 return step <= this.currentStep;
528 }
529 };
530
531 // Initialize wizard on page load
532 $(document).ready(function () {
533 if ($('.metasync-wizard-wrap').length) {
534 MetasyncWizard.init();
535 }
536 });
537
538 })(jQuery);
539