PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.10
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.10
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.10, at admin/js/metasync-setup-wizard.js

517 lines 14.3 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 console.log('SSO polling request failed, continuing...');
366 });
367
368 // Stop polling after max attempts (timeout)
369 if (attempts >= maxAttempts) {
370 clearInterval(self.ssoPollingInterval);
371 self.ssoPollingInterval = null;
372
373 if (self.ssoPopup && !self.ssoPopup.closed) {
374 self.ssoPopup.close();
375 }
376 self.ssoPopup = null;
377
378 self.resetSSOButton();
379 alert('Authentication timed out after 60 seconds. Please try again and complete the authentication more quickly.');
380 }
381 }, 5000); // Poll every 5 seconds
382 },
383
384 /**
385 * Reset SSO button to initial state
386 */
387 resetSSOButton: function () {
388 var pluginName = metasyncWizardData.pluginName || 'Search Atlas';
389 var $button = $('#wizard-connect-btn');
390
391 $button.prop('disabled', false).text('Connect with ' + pluginName);
392
393 // Clear any active polling
394 if (this.ssoPollingInterval) {
395 clearInterval(this.ssoPollingInterval);
396 this.ssoPollingInterval = null;
397 }
398
399 // Close popup if still open
400 if (this.ssoPopup && !this.ssoPopup.closed) {
401 this.ssoPopup.close();
402 }
403 this.ssoPopup = null;
404 },
405
406 /**
407 * Update import button state based on checkbox selection
408 */
409 updateImportButtonState: function ($card) {
410 var $button = $card.find('.wizard-import-btn');
411 var hasChecked = $card.find('.import-option:checked').length > 0;
412 $button.prop('disabled', !hasChecked);
413 },
414
415 /**
416 * Run import from selected plugin
417 */
418 runImport: function (plugin, $button) {
419 var $card = $button.closest('.wizard-import-card');
420 var $progress = $card.find('.import-progress');
421 var $progressBar = $card.find('.import-progress-bar');
422
423 // Get selected import types
424 var selectedTypes = [];
425 $card.find('.import-option:checked').each(function () {
426 selectedTypes.push($(this).data('type'));
427 });
428
429 if (selectedTypes.length === 0) {
430 alert('Please select at least one import type');
431 return;
432 }
433
434 $button.prop('disabled', true).text('Importing...');
435 $progress.show();
436
437 var totalImports = selectedTypes.length;
438 var completedImports = 0;
439
440 // Import each type sequentially
441 function importNext(index) {
442 if (index >= selectedTypes.length) {
443 // All imports complete
444 $button.text('✓ Import Complete').addClass('success');
445 setTimeout(function () {
446 $progress.fadeOut();
447 }, 1000);
448 return;
449 }
450
451 var type = selectedTypes[index];
452
453 $.post(ajaxurl, {
454 action: 'metasync_import_external_data',
455 nonce: metasyncWizardData.importNonce,
456 type: type,
457 plugin: plugin
458 }, function (response) {
459 completedImports++;
460 var progress = (completedImports / totalImports) * 100;
461 $progressBar.css('width', progress + '%');
462
463 // Import next type
464 importNext(index + 1);
465 }).fail(function () {
466 $button.prop('disabled', false).text('Import Failed');
467 $progress.hide();
468 alert('Import failed for ' + type + '. Please try again.');
469 });
470 }
471
472 importNext(0);
473 },
474
475 /**
476 * Complete wizard
477 */
478 completeWizard: function () {
479 var $button = $('.wizard-complete-btn');
480
481 $button.prop('disabled', true).text('Completing setup...');
482
483 $.post(ajaxurl, {
484 action: 'metasync_complete_wizard',
485 nonce: metasyncWizardData.nonce
486 }, function (response) {
487 if (response.success) {
488 // Redirect to dashboard
489 window.location.href = metasyncWizardData.dashboardUrl;
490 } else {
491 $button.prop('disabled', false).text('Get Started with MetaSync');
492 alert('Failed to complete wizard. Please try again.');
493 }
494 }).fail(function () {
495 $button.prop('disabled', false).text('Get Started with MetaSync');
496 alert('An error occurred. Please try again.');
497 });
498 },
499
500 /**
501 * Check if step is accessible
502 */
503 isStepAccessible: function (step) {
504 // Allow jumping to any previous step or current step
505 return step <= this.currentStep;
506 }
507 };
508
509 // Initialize wizard on page load
510 $(document).ready(function () {
511 if ($('.metasync-wizard-wrap').length) {
512 MetasyncWizard.init();
513 }
514 });
515
516 })(jQuery);
517