| 1 |
/** |
| 2 |
* Tools page: "Migrate from Another Plugin" two-step preview/confirm flow. |
| 3 |
* |
| 4 |
* Reads ajaxUrl, nonce, and translatable messages from a JSON blob on the |
| 5 |
* configuration carrier element's `data-abj404-migrate` attribute |
| 6 |
* (#abj404-migrate-config). Click "Preview Import" -> AJAX preview -> |
| 7 |
* show count + Confirm Import (or "Nothing to import" + Back). |
| 8 |
*/ |
| 9 |
(function () { |
| 10 |
'use strict'; |
| 11 |
|
| 12 |
// Migration preview is a one-shot admin action (reads the source |
| 13 |
// plugin's tables and counts rows to migrate). A generous bound avoids |
| 14 |
// false-positive aborts on a slow-but-working host while still |
| 15 |
// guaranteeing the spinner/disabled state cannot hang forever if |
| 16 |
// admin-ajax never responds (M501). |
| 17 |
var PREVIEW_TIMEOUT_MS = 30000; |
| 18 |
|
| 19 |
// Returns the translated config-unusable message when wp.i18n's locale |
| 20 |
// data is available, else the English fallback. The literal must be |
| 21 |
// passed directly to wp.i18n.__() (not through a variable) so make-pot |
| 22 |
// can statically extract it; mirrors the switch-dispatched literal |
| 23 |
// pattern in support-request-modal-view.js's t(key) for the same |
| 24 |
// situation: a user-facing string needed on a failure path where the |
| 25 |
// normal PHP-supplied, pre-translated config (cfg) is unavailable. |
| 26 |
function configUnusableMessage() { |
| 27 |
if (window.wp && window.wp.i18n && typeof window.wp.i18n.__ === 'function') { |
| 28 |
return window.wp.i18n.__('Could not load the migration tool configuration. Reload this page and try again.', '404-solution'); |
| 29 |
} |
| 30 |
return 'Could not load the migration tool configuration. Reload this page and try again.'; |
| 31 |
} |
| 32 |
|
| 33 |
function readConfig() { |
| 34 |
var el = document.getElementById('abj404-migrate-config'); |
| 35 |
if (!el) { return null; } |
| 36 |
var raw = el.getAttribute('data-abj404-migrate'); |
| 37 |
if (!raw) { return null; } |
| 38 |
try { |
| 39 |
return JSON.parse(raw); |
| 40 |
} catch (e) { |
| 41 |
// Malformed config JSON leaves ajaxUrl/nonce/messages entirely |
| 42 |
// unavailable, so Preview Import cannot function at all. Log for |
| 43 |
// diagnosis; the caller disables the control with a safe, |
| 44 |
// non-cfg-dependent message instead of wiring a click handler |
| 45 |
// that would silently do nothing. |
| 46 |
if (window.console && window.console.error) { |
| 47 |
window.console.error('404 Solution: abj404-migrate-config data-abj404-migrate attribute is not valid JSON', raw, e); |
| 48 |
} |
| 49 |
return null; |
| 50 |
} |
| 51 |
} |
| 52 |
|
| 53 |
var cfg = null; |
| 54 |
|
| 55 |
function showStep1() { |
| 56 |
document.getElementById('abj404-migrate-step1').style.display = ''; |
| 57 |
document.getElementById('abj404-migrate-step2').style.display = 'none'; |
| 58 |
} |
| 59 |
|
| 60 |
function showStep2(count, source, label) { |
| 61 |
document.getElementById('abj404-migrate-step1').style.display = 'none'; |
| 62 |
document.getElementById('abj404-migrate-step2').style.display = ''; |
| 63 |
var msgEl = document.getElementById('abj404-migrate-preview-msg'); |
| 64 |
var form = document.getElementById('abj404-migrate-confirm-form'); |
| 65 |
var noForm = document.getElementById('abj404-migrate-back-noform'); |
| 66 |
if (count > 0) { |
| 67 |
msgEl.textContent = cfg.msgFound.replace('%d', count).replace('%s', label); |
| 68 |
document.getElementById('abj404-migrate-confirm-source').value = source; |
| 69 |
form.style.display = ''; |
| 70 |
noForm.style.display = 'none'; |
| 71 |
} else { |
| 72 |
msgEl.textContent = cfg.msgNone.replace('%s', label); |
| 73 |
form.style.display = 'none'; |
| 74 |
noForm.style.display = ''; |
| 75 |
} |
| 76 |
} |
| 77 |
|
| 78 |
function showError() { |
| 79 |
document.getElementById('abj404-migrate-step1').style.display = 'none'; |
| 80 |
document.getElementById('abj404-migrate-step2').style.display = ''; |
| 81 |
document.getElementById('abj404-migrate-preview-msg').textContent = cfg.msgError; |
| 82 |
document.getElementById('abj404-migrate-confirm-form').style.display = 'none'; |
| 83 |
document.getElementById('abj404-migrate-back-noform').style.display = ''; |
| 84 |
} |
| 85 |
|
| 86 |
document.addEventListener('DOMContentLoaded', function () { |
| 87 |
var previewBtn = document.getElementById('abj404-migrate-preview-btn'); |
| 88 |
cfg = readConfig(); |
| 89 |
if (!cfg) { |
| 90 |
// Config is unusable (missing feature, or the catch branch in |
| 91 |
// readConfig() already logged a parse failure): the preview |
| 92 |
// button has nowhere to send its AJAX request, so disable it |
| 93 |
// with an explanation instead of leaving a click handler |
| 94 |
// silently never wired. |
| 95 |
if (previewBtn) { |
| 96 |
previewBtn.disabled = true; |
| 97 |
previewBtn.title = configUnusableMessage(); |
| 98 |
} |
| 99 |
return; |
| 100 |
} |
| 101 |
|
| 102 |
var backBtn = document.getElementById('abj404-migrate-back-btn'); |
| 103 |
var backBtn2 = document.getElementById('abj404-migrate-back-btn2'); |
| 104 |
if (previewBtn) { |
| 105 |
previewBtn.addEventListener('click', function () { |
| 106 |
var select = document.getElementById('abj404-import-source'); |
| 107 |
var source = select ? select.value : ''; |
| 108 |
if (!source) { return; } |
| 109 |
var spinner = document.getElementById('abj404-migrate-preview-spinner'); |
| 110 |
if (spinner) { spinner.style.display = ''; } |
| 111 |
previewBtn.disabled = true; |
| 112 |
var fd = new FormData(); |
| 113 |
fd.append('action', 'abj404_crossPluginPreview'); |
| 114 |
fd.append('nonce', cfg.nonce); |
| 115 |
fd.append('import_source', source); |
| 116 |
// A stalled admin-ajax response must not leave the button |
| 117 |
// disabled and the spinner visible forever (M501); abort and |
| 118 |
// let the existing generic .catch() below show msgError, |
| 119 |
// exactly as it already does for any other fetch failure. |
| 120 |
var controller = new AbortController(); |
| 121 |
var timeoutId = setTimeout(function () { controller.abort(); }, PREVIEW_TIMEOUT_MS); |
| 122 |
// ajax-direct-approved: cross-plugin migration preview posts FormData and manages the two-step UI state locally. |
| 123 |
fetch(cfg.ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin', signal: controller.signal }) // allow-direct-network: cross-plugin migration preview; no project-wide adapter exists for this AJAX surface |
| 124 |
.then(function (r) { clearTimeout(timeoutId); return r.json(); }) |
| 125 |
.then(function (resp) { |
| 126 |
if (spinner) { spinner.style.display = 'none'; } |
| 127 |
previewBtn.disabled = false; |
| 128 |
if (resp && resp.success && resp.data) { |
| 129 |
showStep2(parseInt(resp.data.count, 10) || 0, resp.data.source, resp.data.label); |
| 130 |
} else { |
| 131 |
showError(); |
| 132 |
} |
| 133 |
}) |
| 134 |
.catch(function () { |
| 135 |
clearTimeout(timeoutId); |
| 136 |
if (spinner) { spinner.style.display = 'none'; } |
| 137 |
previewBtn.disabled = false; |
| 138 |
showError(); |
| 139 |
}); |
| 140 |
}); |
| 141 |
} |
| 142 |
if (backBtn) { backBtn.addEventListener('click', function () { showStep1(); }); } |
| 143 |
if (backBtn2) { backBtn2.addEventListener('click', function () { showStep1(); }); } |
| 144 |
}); |
| 145 |
})(); |
| 146 |
|