var Restore = function(options) { this.init(options); };
const QUEUE_STATUS_PENDING = 1;
const QUEUE_STATUS_STARTED = 2;
const QUEUE_STATUS_PREPARING = 3;
const QUEUE_STATUS_DONE = 100;
const QUEUE_STATUS_PARTIALLY = 101;
const QUEUE_STATUS_FAILED = 102;
const QUEUE_STATUS_ABORTED = 103;
const QUEUE_STATUS_NEVER_FINISHED = 104;
const logBuffer = [];
let updateScheduled = false;
const maxLogLines = 500;
function appendLogEntry(entry) {
const container = document.getElementById("log-container");
const newEntry = document.createElement("div");
newEntry.className = "log-entry";
newEntry.textContent = entry;
container.appendChild(newEntry);
while (container.children.length > maxLogLines) {
container.removeChild(container.firstChild);
}
container.scrollTop = container.scrollHeight;
}
// Buffered version to avoid frequent DOM updates
function bufferedLog(entry) {
logBuffer.push(entry);
if (!updateScheduled) {
updateScheduled = true;
setTimeout(() => {
flushLog();
updateScheduled = false;
}, 300); // throttle interval
}
}
function flushLog() {
for (const entry of logBuffer) {
appendLogEntry(entry);
}
logBuffer.length = 0;
}
Restore.prototype = {
_queueId: '',
_interval: null,
_intervalTime: 3000,
_completed: false,
_restoreErrorCount: {},
_updateProgressBar: function (id, progress) {
if (isNaN(progress) || progress < 0) progress = 0; // Default to 0 if the percentage is invalid
else if (progress > 100) progress = 100; // Cap at 100 if percentage exceeds 100
const elm = document.getElementById(id);
elm.style.width = progress + '%';
elm.innerText = progress + '%';
},
_ajax: function (options, name) {
if(this._restoreErrorCount[name] === undefined) this._restoreErrorCount[name] = 0;
if (options.data === undefined || typeof options.data != 'object') options.data = {};
if (options.success === undefined || typeof options.success != 'function') options.success = function(){};
if (options.fail === undefined || typeof options.fail != 'function') options.fail = function(){};
options.data.id = this._queueId;
const xhr = new XMLHttpRequest();
xhr.open("POST", location.protocol + '//' + location.host + location.pathname, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.response);
if (response.success) {
this._restoreErrorCount[name] = 0; // Reset error count on success
options.success(response.message, response.data);
} else {
this._restoreErrorCount[name]++;
this._logError(`Error ${xhr.status}: ${response.message} (Attempt ${this._restoreErrorCount[name]}/10)`);
if (this._restoreErrorCount[name] >= 10) {
options.fail(xhr.status, response.message);
} else {
setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
}
}
} catch (e) {
this._restoreErrorCount[name]++;
this._logError(`Error ${xhr.status}: Invalid JSON response (Attempt ${this._restoreErrorCount[name]}/10)`);
if (this._restoreErrorCount[name] >= 10) {
options.fail(xhr.status, "Invalid response received from the server");
} else {
setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
}
}
} else {
this._restoreErrorCount[name]++;
this._logError(`HTTP Error ${xhr.status}: Attempt ${this._restoreErrorCount[name]}/10`);
if (this._restoreErrorCount[name] >= 10) {
options.fail(xhr.status, xhr.response);
} else {
setTimeout(() => this._ajax(options, name), 3000); // Retry after 3 seconds
}
}
}
};
xhr.send(new URLSearchParams(options.data).toString());
},
_logError: function (message) {
const currentLogEntries = data.log_entries.length;
if (!self._lastLogCount) self._lastLogCount = 0;
// Append only new entries
if (currentLogEntries > self._lastLogCount) {
const newEntries = data.log_entries.slice(self._lastLogCount);
newEntries.forEach(entry => bufferedLog(entry));
self._lastLogCount = currentLogEntries;
}
},
_redirect: function () {
setTimeout(function () {
let pathArray = window.location.pathname.split('/');
let wpIndex = pathArray.indexOf('wp-content');
if (wpIndex !== -1) {
pathArray = pathArray.slice(0, wpIndex);
} else {
pathArray.pop();
}
let basePath = pathArray.join('/');
window.location.href = window.location.origin + basePath + '/wp-admin';
}, 3000);
},
cancel: function () {
this.stop();
this._ajax({
data: { action: 'cancel' },
success: () => { // Arrow function ensures 'this' remains in scope
this._success('Restore has been canceled. Redirecting...');
this._redirect();
},
fail: (status, message) => {
this._error(message);
}
}, 'cancel');
},
refresh: function () {
window.location.reload();
},
completed: function () {
this._ajax({
data: { action: 'completed' },
success: () => {
this._success('Restore is completed! Redirecting to your website...');
this._redirect();
},
fail: (status, message) => {
this._error(message);
}
}, 'completed');
},
_success: function (message) {
document.getElementById('success-message').innerText = message;
document.getElementById('success-alert').style.display = 'block';
},
_error: function (message) {
document.getElementById('error-message').innerText = message;
document.getElementById('error-alert').style.display = 'block';
},
closeSuccess: function () {
document.getElementById('success-alert').style.display = 'none';
},
closeError: function () {
document.getElementById('error-alert').style.display = 'none';
},
stop: function () {
if(this._interval !== null) {
this._interval.clearInterval();
this._interval = null;
}
},
_checkStatus: function () {
const self = this;
this._ajax({
data: { action: 'status' },
success: function (message, data) {
// Update progress bars
self._updateProgressBar('progress', data.progress.percentage);
self._updateProgressBar('subprogress', data.progress.sub_percentage);
document.getElementById('subprogress-title').innerText = data.progress.message + ':';
document.getElementById('subprogress-container').style.display = 'block';
// Handle log updates efficiently
const log = document.getElementById('log-container');
const currentLogEntries = data.log_entries.length;
// Store last log entry count to prevent unnecessary updates
if (!self._lastLogCount || self._lastLogCount !== currentLogEntries) {
self._lastLogCount = currentLogEntries; // Update log count
const logFragment = document.createDocumentFragment();
data.log_entries.forEach(entry => {
const logEntry = document.createElement("div");
logEntry.classList.add("log-entry");
logEntry.innerText = entry;
logFragment.appendChild(logEntry);
});
// Append new logs only if there’s a change
log.innerHTML = ''; // Clear previous log only when updating
log.appendChild(logFragment);
setTimeout(() => {
log.scrollTop = log.scrollHeight;
}, 50);
}
// Recheck status if the process is ongoing
if (data.status < QUEUE_STATUS_DONE) {
setTimeout(function () {
self._checkStatus();
}, self._intervalTime);
} else {
if (data.status > QUEUE_STATUS_DONE) {
if (data.status === QUEUE_STATUS_PARTIALLY) self._error("Restore partially completed");
else if (data.status === QUEUE_STATUS_FAILED) self._error("Error occurred during restore, please review the logs");
else if (data.status === QUEUE_STATUS_ABORTED) self._error("Restore aborted");
else if (data.status === QUEUE_STATUS_NEVER_FINISHED) self._error("Restore never finished");
} else {
self._success("Restore is completed!");
document.getElementById('finalize-btn').style.display = 'inline-block';
}
}
},
fail: function (status, message) {
self._error(message);
}
}, '_checkStatus');
},
start: function () {
const self = this;
setTimeout(function () { self._checkStatus(); }, 1000);
this._restore();
},
_restore: function () {
const self = this;
this._ajax({
data: { action: 'restore' },
success: function(message, data) {
if(!self._completed && data.status < QUEUE_STATUS_DONE) self._restore();
},
fail: function (status, message) {
self._error(message);
}
}, '_restore');
},
init: function (options) {
this._queueId = options.queue_id
if(options.interval !== undefined) this._intervalTime = options.interval;
}
};