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 / views / js / metasync-import-external-data.js

metasync-import-external-data.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at views/js/metasync-import-external-data.js

352 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global metasyncImportData, jQuery, ajaxurl */
2 /**
3 * MetaSync Import External Data
4 *
5 * Extracted for Phase 5, #887.
6 * Handles importing external plugin data (redirections, SEO metadata)
7 * with batch progress tracking for SEO metadata imports.
8 *
9 * Localized object: metasyncImportData
10 * - importNonce (string) Nonce for metasync_import_external_data
11 * - seoImportNonce (string) Nonce for metasync_import_seo_metadata
12 *
13 * @since Phase 5
14 */
15 jQuery(document).ready(function ($) {
16 var importNonce = metasyncImportData.importNonce;
17 var seoImportNonce = metasyncImportData.seoImportNonce;
18 var currentImportData = null;
19
20 // Handle import button clicks
21 $('.metasync-import-btn').on('click', function () {
22 var btn = $(this);
23 var type = btn.data('type');
24 var plugin = btn.data('plugin');
25 var card = btn.closest('.metasync-plugin-card');
26
27 // Special handling for SEO metadata - show options modal
28 if (type === 'seo_metadata') {
29 currentImportData = {
30 btn: btn,
31 type: type,
32 plugin: plugin,
33 card: card
34 };
35 $('#metasync-seo-options-modal').addClass('active');
36 return;
37 }
38
39 // Indexation imports are batched to avoid PHP memory exhaustion on large sites
40 if (type === 'indexation') {
41 performIndexationImport(btn, type, plugin, card);
42 return;
43 }
44
45 // For other import types, proceed directly
46 performImport(btn, type, plugin, card);
47 });
48
49 // Modal close handlers
50 $('.metasync-modal-close, #modal-cancel').on('click', function () {
51 $('#metasync-seo-options-modal').removeClass('active');
52 $('.metasync-progress-container').removeClass('active');
53 $('.metasync-progress-fill').css('width', '0%');
54 $('.metasync-progress-text').text('0%');
55 $('.metasync-progress-status').text('');
56 currentImportData = null;
57 });
58
59 // Click outside modal to close
60 $('.metasync-modal-overlay').on('click', function (e) {
61 if (e.target === this) {
62 $(this).find('.metasync-modal-close').click();
63 }
64 });
65
66 // Start Import button in modal
67 $('#modal-start-import').on('click', function () {
68 if (!currentImportData) {
69 return;
70 }
71
72 var options = {
73 import_titles: $('#import-titles').is(':checked'),
74 import_descriptions: $('#import-descriptions').is(':checked'),
75 import_social_text: $('#import-social-text').is(':checked'),
76 import_social_images: $('#import-social-images').is(':checked'),
77 overwrite_existing: $('#overwrite-existing').is(':checked')
78 };
79
80 // Disable form elements
81 $('#modal-start-import, #modal-cancel, #import-titles, #import-descriptions, #import-social-text, #import-social-images, #overwrite-existing')
82 .prop('disabled', true);
83
84 // Show progress
85 $('.metasync-progress-container').addClass('active');
86
87 // Start batch import
88 performBatchImport(currentImportData, options);
89 });
90
91 // Perform direct import (for non-SEO metadata types)
92 function performImport(btn, type, plugin, card) {
93 var resultDiv = card.find('.metasync-import-result');
94
95 btn.prop('disabled', true).text('Importing...');
96 resultDiv.removeClass('success error').hide();
97
98 $.ajax({
99 url: ajaxurl,
100 type: 'POST',
101 data: {
102 action: 'metasync_import_external_data',
103 nonce: importNonce,
104 type: type,
105 plugin: plugin
106 },
107 success: function (response) {
108 var data = (response && response.data) ? response.data : {};
109 var message = data.message || 'Import failed.';
110
111 if (response.success) {
112 btn.addClass('success').text('\u2713 Imported');
113 resultDiv.addClass('success');
114 renderImportResult(resultDiv, data, message);
115 resultDiv.slideDown();
116 } else if (type === 'schema' && data.breakdown && hasCandidates(data.breakdown)) {
117 // Nothing new was written, but the run did look at real
118 // source records: already-migrated and unsupported counts
119 // are information, not an error to retry.
120 btn.addClass('success').text('\u2713 Checked');
121 resultDiv.addClass('success');
122 renderImportResult(resultDiv, data, message);
123 resultDiv.slideDown();
124 } else {
125 btn.prop('disabled', false).text('Import ' + type.charAt(0).toUpperCase() + type.slice(1));
126 resultDiv.addClass('error').text(message).slideDown();
127 }
128 },
129 error: function () {
130 btn.prop('disabled', false).text('Retry Import');
131 resultDiv.addClass('error').text('Network error. Please try again.').slideDown();
132 }
133 });
134 }
135
136 /**
137 * Whether a schema-import breakdown refers to real source records.
138 *
139 * A zero-candidate run means no source schema was ever detected \u2014 that
140 * stays a plain error. Anything else is an accurate report about records
141 * that do exist.
142 */
143 function hasCandidates(breakdown) {
144 return (parseInt(breakdown.total_candidates, 10) || 0) > 0;
145 }
146
147 /**
148 * Render an import result with its per-category breakdown.
149 *
150 * The message line already summarises the outcome; the breakdown lines
151 * spell out imported / skipped (already had MetaSync schema) / unsupported,
152 * each only when that category is non-zero so a plain import stays a plain
153 * one-line result.
154 */
155 function renderImportResult(resultDiv, data, message) {
156 resultDiv.empty();
157
158 resultDiv.append($('<div>').text(message));
159
160 var breakdown = data.breakdown;
161 if (!breakdown) {
162 return;
163 }
164
165 var imported = parseInt(breakdown.imported, 10) || 0;
166 var alreadyImported = parseInt(breakdown.already_imported, 10) || 0;
167 var unsupported = parseInt(breakdown.unsupported, 10) || 0;
168
169 if (alreadyImported > 0) {
170 resultDiv.append(
171 $('<div>').css('margin-top', '6px')
172 .text('\u2022 ' + alreadyImported + ' post(s) already had MetaSync schema and were skipped.')
173 );
174 }
175 if (unsupported > 0) {
176 resultDiv.append(
177 $('<div>').css('margin-top', '6px')
178 .text('\u2022 ' + unsupported + ' post(s) had unsupported or empty schema settings.')
179 );
180 }
181 if (imported > 0) {
182 resultDiv.append(
183 $('<div>').css('margin-top', '6px')
184 .text('\u2022 ' + imported + ' post(s) imported.')
185 );
186 }
187 }
188
189 // Perform batched indexation import with progress tracking
190 function performIndexationImport(btn, type, plugin, card) {
191 var resultDiv = card.find('.metasync-import-result');
192 var progressContainer = card.find('.metasync-progress-container');
193 var progressFill = card.find('.metasync-progress-fill');
194 var progressText = card.find('.metasync-progress-text');
195 var offset = 0;
196 var totalImported = 0;
197 var totalSkipped = 0;
198
199 btn.prop('disabled', true).text('Importing...');
200 resultDiv.removeClass('success error').hide();
201 progressFill.css('width', '0%');
202 progressText.text('0%');
203 progressContainer.addClass('active');
204
205 function processBatch() {
206 $.ajax({
207 url: ajaxurl,
208 type: 'POST',
209 data: {
210 action: 'metasync_import_external_data',
211 nonce: importNonce,
212 type: type,
213 plugin: plugin,
214 offset: offset
215 },
216 success: function (response) {
217 if (response.success && response.data) {
218 var data = response.data;
219 totalImported += data.imported || 0;
220 totalSkipped += data.skipped || 0;
221
222 var percent = data.progress_percent || 0;
223 progressFill.css('width', percent + '%');
224 progressText.text(percent + '%');
225
226 resultDiv.removeClass('error').addClass('success')
227 .text('Processing... ' + (data.processed || 0).toLocaleString() + ' of ' + (data.total || 0).toLocaleString() + ' posts')
228 .show();
229
230 if (data.is_complete) {
231 progressFill.css('width', '100%');
232 progressText.text('100%');
233 btn.addClass('success').text('✓ Imported');
234 resultDiv.text('✓ Import Complete! Imported: ' + totalImported.toLocaleString() + ' | Skipped: ' + totalSkipped.toLocaleString());
235 } else {
236 offset = data.processed || 0;
237 processBatch();
238 }
239 } else {
240 btn.prop('disabled', false).text('Retry Import');
241 progressContainer.removeClass('active');
242 var errMsg = (response.data && response.data.message) ? response.data.message : 'Import failed.';
243 resultDiv.removeClass('success').addClass('error').text(errMsg).show();
244 }
245 },
246 error: function () {
247 btn.prop('disabled', false).text('Retry Import');
248 progressContainer.removeClass('active');
249 resultDiv.removeClass('success').addClass('error').text('Network error. Please try again.').show();
250 }
251 });
252 }
253
254 processBatch();
255 }
256
257 // Perform batch import with progress tracking (for SEO metadata)
258 function performBatchImport(importData, options) {
259 var offset = 0;
260 var totalImported = 0;
261 var totalSkipped = 0;
262 var totalUnresolved = 0;
263
264 function processBatch() {
265 $.ajax({
266 url: ajaxurl,
267 type: 'POST',
268 data: {
269 action: 'metasync_import_seo_metadata',
270 nonce: seoImportNonce,
271 plugin: importData.plugin,
272 import_titles: options.import_titles ? 1 : 0,
273 import_descriptions: options.import_descriptions ? 1 : 0,
274 import_social_text: options.import_social_text ? 1 : 0,
275 import_social_images: options.import_social_images ? 1 : 0,
276 overwrite_existing: options.overwrite_existing ? 1 : 0,
277 offset: offset
278 },
279 success: function (response) {
280 if (response.success && response.data) {
281 var data = response.data;
282 totalImported += data.imported || 0;
283 totalSkipped += data.skipped || 0;
284 // Accumulate across batches — a single batch's count is
285 // not the whole import.
286 totalUnresolved += data.unresolved || 0;
287
288 // Update progress bar
289 var percent = data.progress_percent || 0;
290 $('.metasync-progress-fill').css('width', percent + '%');
291 $('.metasync-progress-text').text(percent + '%');
292 $('.metasync-progress-status').text(
293 'Processing... ' + (data.processed || 0) + ' of ' + (data.total || 0) + ' posts'
294 );
295
296 if (data.is_complete) {
297 // Import complete
298 var $seoStatus = $('.metasync-progress-status').empty()
299 .append($('<strong>').css('color', 'var(--dashboard-success)').text('\u2713 Import Complete!'))
300 .append(document.createTextNode(' Imported: ' + totalImported + ' posts | Skipped: ' + totalSkipped + ' posts'));
301
302 importData.btn.addClass('success').text('\u2713 Imported');
303
304 if (totalUnresolved > 0) {
305 $seoStatus.append(
306 $('<div>')
307 .css({ 'color': 'var(--dashboard-warning)', 'margin-top': '8px' })
308 .text('\u26a0 ' + totalUnresolved + ' value(s) skipped because placeholders could not be resolved.')
309 );
310 // Leave the modal open so the warning is actually
311 // read rather than auto-dismissed after 2s.
312 $('#modal-start-import, #modal-cancel, #import-titles, #import-descriptions, #import-social-text, #import-social-images, #overwrite-existing')
313 .prop('disabled', false);
314 } else {
315 // Close modal after 2 seconds
316 setTimeout(function () {
317 $('#metasync-seo-options-modal .metasync-modal-close').click();
318 // Re-enable button for future imports
319 $('#modal-start-import, #modal-cancel, #import-titles, #import-descriptions, #import-social-text, #import-social-images, #overwrite-existing')
320 .prop('disabled', false);
321 }, 2000);
322 }
323 } else {
324 // Continue with next batch
325 offset = data.processed || 0;
326 processBatch();
327 }
328 } else {
329 // Error occurred
330 var importErrMsg = (response.data && response.data.message ? response.data.message : 'Unknown error occurred');
331 var $importStatus = $('.metasync-progress-status').empty();
332 $importStatus.append($('<strong>').css('color', 'var(--dashboard-error)').text('\u2717 Import Failed'));
333 $importStatus[0].appendChild(document.createTextNode(' ' + importErrMsg));
334 $('#modal-start-import, #modal-cancel, #import-titles, #import-descriptions, #import-social-text, #import-social-images, #overwrite-existing')
335 .prop('disabled', false);
336 }
337 },
338 error: function () {
339 $('.metasync-progress-status').empty()
340 .append($('<strong>').css('color', 'var(--dashboard-error)').text('\u2717 Network Error'))
341 .append(document.createTextNode(' Please try again'));
342 $('#modal-start-import, #modal-cancel, #import-titles, #import-descriptions, #import-social-text, #import-social-images, #overwrite-existing')
343 .prop('disabled', false);
344 }
345 });
346 }
347
348 // Start first batch
349 processBatch();
350 }
351 });
352