PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.13
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.13
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 2.6.4 2.6.3 2.5.23 All 138 releases
metasync / admin / js / metasync-setup-wizard.js

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

533 lines 14.7 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 4: // SEO Settings
210 stepData.seo_settings = {
211 date_archives: $('input[name="seo_date_archives"]').is(':checked'),
212 author_archives: $('input[name="seo_author_archives"]').is(':checked'),
213 category_archives: $('input[name="seo_category_archives"]').is(':checked'),
214 tag_archives: $('input[name="seo_tag_archives"]').is(':checked')
215 };
216 break;
217
218 case 5: // Schema
219 stepData.schema = {
220 enabled: $('#schema-enabled').is(':checked'),
221 default_type: $('input[name="default_schema_type"]:checked').val()
222 };
223 break;
224
225 default:
226 // No data to save for this step
227 return;
228 }
229
230 // Save via AJAX
231 $.post(ajaxurl, {
232 action: 'metasync_save_wizard_progress',
233 nonce: metasyncWizardData.nonce,
234 step: this.currentStep,
235 data: stepData
236 });
237 },
238
239 /**
240 * Trigger SSO authentication
241 */
242 triggerSSO: function () {
243 var self = this;
244 var $button = $('#wizard-connect-btn');
245 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
246
247 $button.prop('disabled', true).text('Opening SSO...');
248
249 $.post(ajaxurl, {
250 action: 'metasync_generate_connect_url',
251 nonce: metasyncWizardData.saConnectNonce
252 }, function (response) {
253 if (response.success) {
254 // Validate URL is a safe https:// URL from an allowed SSO domain before opening.
255 // This prevents open redirect to arbitrary hosts, javascript: or data: URIs.
256 var connectUrl = response.data.connect_url;
257 var allowedSSOHosts = ['searchatlas.com', 'app.searchatlas.com', 'auth.searchatlas.com'];
258 try {
259 var parsedUrl = new URL(connectUrl);
260 var hostname = parsedUrl.hostname.toLowerCase();
261 var hostAllowed = allowedSSOHosts.some(function (allowed) {
262 return hostname === allowed || hostname.endsWith('.' + allowed);
263 });
264 if (parsedUrl.protocol === 'https:' && hostAllowed) {
265 // Open SSO popup via validated URL
266 var ssoLink = document.createElement('a');
267 ssoLink.href = parsedUrl.href;
268 ssoLink.target = pluginName.replace(/\s+/g, '') + 'SSO';
269 ssoLink.rel = 'noopener';
270 ssoLink.click();
271 self.ssoPopup = window.open('', pluginName.replace(/\s+/g, '') + 'SSO');
272
273 if (self.ssoPopup) {
274 $button.text('Waiting for authentication...');
275 // Poll for completion
276 self.pollSSOStatus(response.data.nonce_token);
277 } else {
278 $button.prop('disabled', false).text('Connect with ' + pluginName);
279 alert('Popup was blocked. Please allow popups for this site and try again.');
280 }
281 } else {
282 $button.prop('disabled', false).text('Connect with ' + pluginName);
283 alert('Invalid SSO URL. Please try again.');
284 }
285 } catch (e) {
286 $button.prop('disabled', false).text('Connect with ' + pluginName);
287 alert('Invalid SSO URL. Please try again.');
288 }
289 } else {
290 $button.prop('disabled', false).text('Connect with ' + pluginName);
291 alert('Failed to generate SSO URL. Please try again.');
292 }
293 }).fail(function () {
294 $button.prop('disabled', false).text('Connect with ' + pluginName);
295 alert('An error occurred. Please try again.');
296 });
297 },
298
299 /**
300 * Poll SSO status
301 */
302 pollSSOStatus: function (nonceToken) {
303 var self = this;
304 var attempts = 0;
305 var maxAttempts = 12; // 60 seconds (12 * 5 seconds)
306 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
307 var $button = $('#wizard-connect-btn');
308
309 self.ssoPollingInterval = setInterval(function () {
310 attempts++;
311
312 // Update button with countdown
313 var timeLeft = Math.ceil((maxAttempts - attempts) * 5);
314 $button.text('Waiting for authentication (' + timeLeft + 's)...');
315
316 $.post(ajaxurl, {
317 action: 'metasync_check_connect_status',
318 nonce: metasyncWizardData.saConnectNonce,
319 nonce_token: nonceToken
320 }, function (response) {
321 if (response.success && response.data.updated) {
322 // Stop polling and close popup
323 clearInterval(self.ssoPollingInterval);
324 self.ssoPollingInterval = null;
325
326 if (self.ssoPopup && !self.ssoPopup.closed) {
327 self.ssoPopup.close();
328 }
329 self.ssoPopup = null;
330
331 var statusCode = response.data.status_code || 200;
332
333 if (statusCode === 200) {
334 // Success: Update UI to show connected state
335 $('.wizard-step-connection').html(
336 '<div class="wizard-step-header">' +
337 '<h2>🔗 Connect to ' + pluginName + '</h2>' +
338 '<p>Link your WordPress site to your ' + pluginName + ' account for advanced features.</p>' +
339 '</div>' +
340 '<div class="wizard-step-content">' +
341 '<div class="wizard-connection-success">' +
342 '<span class="success-icon">✓</span>' +
343 '<h3>Successfully Connected!</h3>' +
344 '<p>Your site is linked to ' + pluginName + '.</p>' +
345 '</div>' +
346 '</div>'
347 );
348 } else if (statusCode === 403) {
349 // Authentication failed
350 self.resetSSOButton();
351 alert('Authentication failed. Please check your credentials and try again.');
352 } else if (statusCode === 500) {
353 // Server error
354 self.resetSSOButton();
355 alert('Server error occurred. Please try again later or contact support.');
356 } else {
357 // Unknown status
358 self.resetSSOButton();
359 alert('Unexpected response received. Please try again.');
360 }
361 }
362 }).fail(function () {
363 // Continue polling even if individual request fails
364 // Don't show error for temporary network issues
365 });
366
367 // Stop polling after max attempts (timeout)
368 if (attempts >= maxAttempts) {
369 clearInterval(self.ssoPollingInterval);
370 self.ssoPollingInterval = null;
371
372 if (self.ssoPopup && !self.ssoPopup.closed) {
373 self.ssoPopup.close();
374 }
375 self.ssoPopup = null;
376
377 self.resetSSOButton();
378 alert('Authentication timed out after 60 seconds. Please try again and complete the authentication more quickly.');
379 }
380 }, 5000); // Poll every 5 seconds
381 },
382
383 /**
384 * Reset SSO button to initial state
385 */
386 resetSSOButton: function () {
387 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
388 var $button = $('#wizard-connect-btn');
389
390 $button.prop('disabled', false).text('Connect with ' + pluginName);
391
392 // Clear any active polling
393 if (this.ssoPollingInterval) {
394 clearInterval(this.ssoPollingInterval);
395 this.ssoPollingInterval = null;
396 }
397
398 // Close popup if still open
399 if (this.ssoPopup && !this.ssoPopup.closed) {
400 this.ssoPopup.close();
401 }
402 this.ssoPopup = null;
403 },
404
405 /**
406 * Update import button state based on checkbox selection
407 */
408 updateImportButtonState: function ($card) {
409 var $button = $card.find('.wizard-import-btn');
410 var hasChecked = $card.find('.import-option:checked').length > 0;
411 $button.prop('disabled', !hasChecked);
412 },
413
414 /**
415 * Run import from selected plugin
416 */
417 runImport: function (plugin, $button) {
418 var $card = $button.closest('.wizard-import-card');
419 var $progress = $card.find('.import-progress');
420 var $progressBar = $card.find('.import-progress-bar');
421
422 // Get selected import types
423 var selectedTypes = [];
424 $card.find('.import-option:checked').each(function () {
425 selectedTypes.push($(this).data('type'));
426 });
427
428 if (selectedTypes.length === 0) {
429 alert('Please select at least one import type');
430 return;
431 }
432
433 $button.prop('disabled', true).text('Importing...');
434 $progress.show();
435
436 var totalImports = selectedTypes.length;
437 var completedImports = 0;
438
439 // Import each type sequentially
440 function importNext(index) {
441 if (index >= selectedTypes.length) {
442 // All imports complete
443 $button.text('✓ Import Complete').addClass('success');
444 setTimeout(function () {
445 $progress.fadeOut();
446 }, 1000);
447 return;
448 }
449
450 var type = selectedTypes[index];
451
452 // Indexation imports are batched server-side; loop until complete
453 var offset = 0;
454
455 function postImport() {
456 $.post(ajaxurl, {
457 action: 'metasync_import_external_data',
458 nonce: metasyncWizardData.importNonce,
459 type: type,
460 plugin: plugin,
461 offset: offset
462 }, function (response) {
463 var data = response && response.data ? response.data : {};
464
465 // Continue batching until the server reports completion
466 if (type === 'indexation' && response.success && data.is_complete === false) {
467 offset = data.processed || (offset + 1);
468 postImport();
469 return;
470 }
471
472 completedImports++;
473 var progress = (completedImports / totalImports) * 100;
474 $progressBar.css('width', progress + '%');
475
476 // Import next type
477 importNext(index + 1);
478 }).fail(function () {
479 $button.prop('disabled', false).text('Import Failed');
480 $progress.hide();
481 alert('Import failed for ' + type + '. Please try again.');
482 });
483 }
484
485 postImport();
486 }
487
488 importNext(0);
489 },
490
491 /**
492 * Complete wizard
493 */
494 completeWizard: function () {
495 var $button = $('.wizard-complete-btn');
496
497 $button.prop('disabled', true).text('Completing setup...');
498
499 $.post(ajaxurl, {
500 action: 'metasync_complete_wizard',
501 nonce: metasyncWizardData.nonce
502 }, function (response) {
503 if (response.success) {
504 // Redirect to dashboard
505 window.location.href = metasyncWizardData.dashboardUrl;
506 } else {
507 $button.prop('disabled', false).text('Get Started with MetaSync');
508 alert('Failed to complete wizard. Please try again.');
509 }
510 }).fail(function () {
511 $button.prop('disabled', false).text('Get Started with MetaSync');
512 alert('An error occurred. Please try again.');
513 });
514 },
515
516 /**
517 * Check if step is accessible
518 */
519 isStepAccessible: function (step) {
520 // Allow jumping to any previous step or current step
521 return step <= this.currentStep;
522 }
523 };
524
525 // Initialize wizard on page load
526 $(document).ready(function () {
527 if ($('.metasync-wizard-wrap').length) {
528 MetasyncWizard.init();
529 }
530 });
531
532 })(jQuery);
533