✅ Fixed bulk conversion getting stuck on missing files ✅ Added robust error handling and timeout protection ✅ Improved JavaScript response parsing ✅ Added file existence validation ✅ Fixed missing PHP class imports ✅ Added comprehensive try-catch error recovery 🔧 Key fixes: - File existence checks before conversion attempts - 30-second timeout protection per file - Graceful handling of 500 errors and JSON parsing issues - Automatic continuation to next file on failures - Cache busting for JavaScript updates 🎯 Result: Bulk conversion now completes successfully even with missing files 🚀 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
486 lines
19 KiB
JavaScript
486 lines
19 KiB
JavaScript
|
|
function openBulkConvertPopup() {
|
|
document.getElementById('bulkconvertlog').innerHTML = '';
|
|
document.getElementById('bulkconvertcontent').innerHTML = '<div>Receiving list of files to convert...</div>';
|
|
tb_show('Bulk Convert', '#TB_inline?inlineId=bulkconvertpopup');
|
|
|
|
var data = {
|
|
'action': 'list_unconverted_files',
|
|
'nonce' : window.webpExpress['ajax-nonces']['list-unconverted-files'],
|
|
};
|
|
jQuery.ajax({
|
|
type: "POST",
|
|
url: ajaxurl,
|
|
data: data,
|
|
dataType: 'text',
|
|
timeout: 30000,
|
|
error: function (jqXHR, status, errorThrown) {
|
|
html = '<h1>Error: ' + status + '</h1>';
|
|
html += errorThrown;
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
},
|
|
success: function(response) {
|
|
if ((typeof response == 'object') && (response['success'] == false)) {
|
|
html = '<h1>Error</h1>';
|
|
if (response['data'] && ((typeof response['data']) == 'string')) {
|
|
html += webpexpress_escapeHTML(response['data']);
|
|
}
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
return;
|
|
}
|
|
if (response == '') {
|
|
html = '<h1>Error</h1>';
|
|
html += '<p>Could not fetch list of files to convert. The server returned nothing (which is unexpected - ' +
|
|
'it is not simply because there are no files to convert.)</p>';
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
return;
|
|
}
|
|
var responseObj;
|
|
try {
|
|
responseObj = JSON.parse(response);
|
|
} catch (e) {
|
|
html = '<h1>Error</h1>';
|
|
html += '<p>The ajax call did not return valid JSON, as expected.</p>';
|
|
html += '<p>Check the javascript console to see what was returned.</p>';
|
|
console.log('The ajax call did not return valid JSON, as expected');
|
|
console.log('Here is what was received:');
|
|
console.log(response);
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
return;
|
|
}
|
|
var bulkInfo = {
|
|
'groups': responseObj,
|
|
'groupPointer': 0,
|
|
'filePointer': 0,
|
|
'paused': false,
|
|
'webpTotalFilesize': 0,
|
|
'orgTotalFilesize': 0,
|
|
};
|
|
window.webpexpress_bulkconvert = bulkInfo;
|
|
|
|
// count files
|
|
var numFiles = 0;
|
|
for (var i=0; i<bulkInfo.groups.length; i++) {
|
|
numFiles += bulkInfo.groups[i].files.length;
|
|
}
|
|
|
|
//console.log(JSON.parse(response));
|
|
var html = '';
|
|
if (numFiles == 0) {
|
|
html += '<p>There are no unconverted files</p>';
|
|
} else {
|
|
html += '<div>'
|
|
html += '<p>There are ' + numFiles + ' unconverted files.</p>';
|
|
html += '<p><i>Note that in a typical setup, you will have redirect rules which trigger conversion when needed, ' +
|
|
'and thus you have no need for bulk conversion. In fact, in that case, you should probably not bulk convert ' +
|
|
'because bulk conversion will also convert images and thumbnails which are not in use, and thus take up ' +
|
|
'more disk space than necessary. The bulk conversion feature was only added in order to make the plugin usable even when ' +
|
|
'there are problems with redirects (ie on Nginx in case you do not have access to the config or on Microsoft IIS). ' +
|
|
'</i></p><br>';
|
|
html += '<button onclick="startBulkConversion()" class="button button-primary" type="button">Start conversion</button>';
|
|
html += '</div>';
|
|
}
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
}
|
|
});
|
|
}
|
|
|
|
function pauseBulkConversion() {
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
bulkInfo.paused = true;
|
|
}
|
|
|
|
function pauseOrResumeBulkConversion() {
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
bulkInfo.paused = !bulkInfo.paused;
|
|
|
|
document.getElementById('bulkPauseResumeBtn').innerText = (bulkInfo.paused ? 'Resume' : 'Pause');
|
|
|
|
if (!bulkInfo.paused) {
|
|
convertNextInBulkQueue();
|
|
}
|
|
}
|
|
|
|
function startBulkConversion() {
|
|
var html = '<br>';
|
|
html += '<style>' +
|
|
'.has-tip {cursor:pointer; position:static;}\n' +
|
|
'.has-tip .tip {display: none}\n' +
|
|
'.has-tip:hover .tip {display: block}\n' +
|
|
'.tip{padding: 5px 10px; background-color:#ff9;min-width:310px; font-size:10px; color: black; border:1px solid black; max-width:90%;z-index:10}\n' +
|
|
'.reduction {float:right;}\n' +
|
|
'</style>';
|
|
html += '<button id="bulkPauseResumeBtn" onclick="pauseOrResumeBulkConversion()" class="button button-primary" type="button">Pause</button>';
|
|
//html += '<div id="conversionlog" class="das-popup">test</div>';
|
|
//html += '<div id="bulkconvertlog"></div>';
|
|
document.getElementById('bulkconvertcontent').innerHTML = html;
|
|
document.getElementById('bulkconvertlog').innerHTML = '';
|
|
|
|
convertNextInBulkQueue();
|
|
}
|
|
|
|
function convertDone() {
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
document.getElementById('bulkconvertlog').innerHTML += '<p><b>Done!</b></p>' +
|
|
'<p>Total reduction: ' + getReductionHtml(bulkInfo['orgTotalFilesize'], bulkInfo['webpTotalFilesize'], 'Total size of converted originals', 'Total size of converted webp files') + '</p>'
|
|
|
|
document.getElementById('bulkPauseResumeBtn').style.display = 'none';
|
|
}
|
|
|
|
function getPrintableSizeInfo(orgSize, webpSize) {
|
|
if (orgSize < 10000) {
|
|
return {
|
|
'org': orgSize + ' bytes',
|
|
'webp': webpSize + ' bytes'
|
|
};
|
|
} else {
|
|
return {
|
|
'org': Math.round(orgSize / 1024) + ' kb',
|
|
'webp': Math.round(webpSize / 1024) + ' kb'
|
|
};
|
|
}
|
|
}
|
|
|
|
function getReductionHtml(orgSize, webpSize, sizeOfOriginalText, sizeOfWebpText) {
|
|
var reduction = Math.round((orgSize - webpSize)/orgSize * 100);
|
|
var sizeInfo = getPrintableSizeInfo(orgSize, webpSize);
|
|
var hoverText = sizeOfOriginalText + ': ' + sizeInfo['org'] + '.<br>' + sizeOfWebpText + ': ' + sizeInfo['webp'];
|
|
|
|
// ps: this is all safe to print
|
|
return '<span class="has-tip reduction">' + reduction + '%' +
|
|
'<span class="tip">' + hoverText + '</span>' +
|
|
'</span><br>';
|
|
}
|
|
|
|
function logLn() {
|
|
var html = '';
|
|
for (i = 0; i < arguments.length; i++) {
|
|
html += arguments[i];
|
|
}
|
|
var spanEl = document.createElement('span');
|
|
spanEl.innerHTML = html;
|
|
|
|
document.getElementById('bulkconvertlog').appendChild(spanEl);
|
|
|
|
//document.getElementById('bulkconvertlog').innerHTML += html;
|
|
}
|
|
|
|
function webpexpress_viewLog(groupPointer, filePointer) {
|
|
|
|
/*
|
|
disabled until I am certain that security is in place.
|
|
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
var group = bulkInfo.groups[groupPointer];
|
|
var filename = group.files[filePointer];
|
|
var source = group.root + '/' + filename;
|
|
|
|
var w = Math.min(1200, Math.max(200, document.documentElement.clientWidth - 100));
|
|
var h = Math.max(250, document.documentElement.clientHeight - 80);
|
|
|
|
document.getElementById('conversionlog_content').innerHTML = 'loading log...'; // + source;
|
|
|
|
jQuery.ajax({
|
|
method: 'POST',
|
|
url: ajaxurl,
|
|
data: {
|
|
'action': 'webpexpress_view_log',
|
|
'nonce' : window.webpExpress['ajax-nonces']['view-log'],
|
|
'source': source
|
|
},
|
|
success: (response) => {
|
|
//alert(response);
|
|
if ((typeof response == 'object') && (response['success'] == false)) {
|
|
html = '<h1>Error</h1>';
|
|
if (response['data'] && ((typeof response['data']) == 'string')) {
|
|
html += webpexpress_escapeHTML(response['data']);
|
|
}
|
|
document.getElementById('conversionlog_content').innerHTML = html;
|
|
return;
|
|
}
|
|
|
|
var result = JSON.parse(response);
|
|
|
|
// the "log" result is a simply form of markdown, using just italic, bold and newlines.
|
|
// It ought not to return anything evil, but for good practice, let us encode.
|
|
result = webpexpress_escapeHTML(result);
|
|
|
|
var html = '<h1>Conversion log</h1><br>' + '<pre style="white-space:pre-wrap">' + result + '</pre>';
|
|
|
|
document.getElementById('conversionlog_content').innerHTML = html;
|
|
},
|
|
error: () => {
|
|
//responseCallback({requestError: true});
|
|
},
|
|
});
|
|
|
|
//<h1>Conversion log</h1>
|
|
//tb_show('Conversion log', '#TB_inline?inlineId=conversionlog');
|
|
openDasPopup('conversionlog', w, h);
|
|
*/
|
|
}
|
|
|
|
function convertNextInBulkQueue() {
|
|
var html;
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
//console.log('convertNextInBulkQueue', bulkInfo);
|
|
|
|
// Current group might contain 0, - skip if that is the case
|
|
while ((bulkInfo.groupPointer < bulkInfo.groups.length) && (bulkInfo.filePointer >= bulkInfo.groups[bulkInfo.groupPointer].files.length)) {
|
|
logLn(
|
|
'<h3>' + bulkInfo.groups[bulkInfo.groupPointer].groupName + '</h3>',
|
|
'<p>Nothing to convert</p>'
|
|
);
|
|
|
|
bulkInfo.groupPointer++;
|
|
bulkInfo.filePointer = 0;
|
|
}
|
|
|
|
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
|
|
convertDone();
|
|
return;
|
|
}
|
|
|
|
var group = bulkInfo.groups[bulkInfo.groupPointer];
|
|
var filename = group.files[bulkInfo.filePointer];
|
|
|
|
if (bulkInfo.filePointer == 0) {
|
|
logLn('<h3>' + group.groupName + '</h3>');
|
|
}
|
|
|
|
logLn('Converting <i>' + filename + '</i>');
|
|
|
|
var data = {
|
|
'action': 'convert_file',
|
|
'nonce' : window.webpExpress['ajax-nonces']['convert'],
|
|
'filename': group.root + '/' + filename
|
|
|
|
//'whatever': ajax_object.we_value // We pass php values differently!
|
|
};
|
|
|
|
function responseCallback(response){
|
|
try {
|
|
if ((typeof response == 'object') && (response['success'] == false)) {
|
|
html = '<h1>Error</h1>';
|
|
if (response['data'] && ((typeof response['data']) == 'string')) {
|
|
// disabled. Need to check if it is secure
|
|
//html += webpexpress_escapeHTML(response['data']);
|
|
}
|
|
logLn(html);
|
|
return
|
|
}
|
|
|
|
var result;
|
|
|
|
// Handle different types of responses safely
|
|
if (typeof response.requestError === 'boolean' && response.requestError) {
|
|
result = {
|
|
success: false,
|
|
msg: 'Request failed',
|
|
log: '',
|
|
};
|
|
} else if (typeof response === 'string') {
|
|
try {
|
|
result = JSON.parse(response);
|
|
} catch (e) {
|
|
result = {
|
|
success: false,
|
|
msg: 'Invalid response received from server',
|
|
log: '',
|
|
};
|
|
}
|
|
} else if (typeof response === 'object' && response !== null) {
|
|
// If it's already an object, check if it has the expected structure
|
|
if (typeof response.success !== 'undefined') {
|
|
result = response;
|
|
} else {
|
|
result = {
|
|
success: false,
|
|
msg: 'Invalid object response received from server',
|
|
log: '',
|
|
};
|
|
}
|
|
} else {
|
|
result = {
|
|
success: false,
|
|
msg: 'Unexpected response type: ' + typeof response,
|
|
log: '',
|
|
};
|
|
}
|
|
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
if (!bulkInfo || !bulkInfo.groups || bulkInfo.groupPointer >= bulkInfo.groups.length) {
|
|
logLn('<span style="color:red">Bulk conversion state is invalid</span><br>');
|
|
convertDone();
|
|
return;
|
|
}
|
|
var group = bulkInfo.groups[bulkInfo.groupPointer];
|
|
if (!group || !group.files || bulkInfo.filePointer >= group.files.length) {
|
|
logLn('<span style="color:red">Group or file index is invalid</span><br>');
|
|
convertDone();
|
|
return;
|
|
}
|
|
var filename = group.files[bulkInfo.filePointer];
|
|
|
|
//console.log(result);
|
|
|
|
var html = '';
|
|
|
|
var htmlViewLog = '';
|
|
|
|
// uncommented until I'm certain that security is in place
|
|
//var htmlViewLog = ' <a style="cursor:pointer" onclick="webpexpress_viewLog(' + bulkInfo.groupPointer + ',' + bulkInfo.filePointer + ')">view log</a>';
|
|
if (result['success']) {
|
|
|
|
//console.log('nonce tick:' + result['nonce-tick']);
|
|
|
|
if (result['new-convert-nonce']) {
|
|
//console.log('new convert nonce:' + result['new-convert-nonce']);
|
|
window.webpExpress['ajax-nonces']['convert'] = result['new-convert-nonce'];
|
|
}
|
|
|
|
var orgSize = result['filesize-original'];
|
|
var webpSize = result['filesize-webp'];
|
|
var orgSizePrint, webpSizePrint;
|
|
|
|
bulkInfo['orgTotalFilesize'] += orgSize;
|
|
bulkInfo['webpTotalFilesize'] += webpSize;
|
|
//'- Saved at: ' + result['destination-path'] +
|
|
/*
|
|
html += ' <span style="color:green">ok</span></span>' +
|
|
htmlViewLog +
|
|
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')*/
|
|
|
|
html += ' <span style="color:green" class="has-tip">ok' +
|
|
'<span class="tip">' +
|
|
'<b>Destination:</b><br>' + result['destination-path'] + '<br><br>' +
|
|
'<b>Url:</b><br><a href="' + result['destination-url'] + '">' + result['destination-url'] + '<br>' +
|
|
'</span>' +
|
|
'</span>' +
|
|
getReductionHtml(orgSize, webpSize, 'Size of original', 'Size of webp')
|
|
} else {
|
|
html += ' <span style="color:red">failed</span>' + htmlViewLog;
|
|
|
|
if (result['msg']) {
|
|
// Show the error message but continue processing
|
|
html += '<br><span style="color:red; font-size:13px">' + webpexpress_escapeHTML(result['msg']) + '</span>';
|
|
}
|
|
|
|
// Only stop for critical errors (security nonce issues), not file-specific errors
|
|
if (result['stop'] && result['msg'] && result['msg'].indexOf('security nonce') !== -1) {
|
|
logLn(html);
|
|
logLn('<br><span style="color:red; font-weight:bold;">Bulk conversion stopped due to security error. Please reload the page (F5) and try again.</span>');
|
|
return;
|
|
}
|
|
|
|
html += '<br>';
|
|
}
|
|
logLn(html);
|
|
|
|
|
|
// Get next
|
|
bulkInfo.filePointer++;
|
|
if (bulkInfo.filePointer == group.files.length) {
|
|
bulkInfo.filePointer = 0;
|
|
bulkInfo.groupPointer++;
|
|
}
|
|
if (bulkInfo.groupPointer == bulkInfo.groups.length) {
|
|
convertDone();
|
|
} else {
|
|
if (bulkInfo.paused) {
|
|
document.getElementById('bulkconvertlog').innerHTML += '<p><i>on pause</i><br>' +
|
|
'Reduction this far: ' + getReductionHtml(bulkInfo['orgTotalFilesize'], bulkInfo['webpTotalFilesize'], 'Total size of originals this far', 'Total size of webp files this far') + '</p>'
|
|
|
|
bulkInfo['orgTotalFilesize'] += orgSize;
|
|
bulkInfo['webpTotalFilesize'] += webpSize;
|
|
|
|
} else {
|
|
convertNextInBulkQueue();
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
// Catch any unexpected errors in responseCallback
|
|
logLn(' <span style="color:red">JavaScript error in response processing: ' + error.message + '</span><br>');
|
|
|
|
// Try to continue with next file
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
|
|
bulkInfo.filePointer++;
|
|
if (bulkInfo.filePointer >= bulkInfo.groups[bulkInfo.groupPointer].files.length) {
|
|
bulkInfo.filePointer = 0;
|
|
bulkInfo.groupPointer++;
|
|
}
|
|
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
|
|
convertDone();
|
|
} else if (!bulkInfo.paused) {
|
|
convertNextInBulkQueue();
|
|
}
|
|
} else {
|
|
convertDone();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// jQuery.post(ajaxurl, data, responseCallback);
|
|
jQuery.ajax({
|
|
method: 'POST',
|
|
url: ajaxurl,
|
|
data: data,
|
|
timeout: 30000, // 30 second timeout per file
|
|
success: (response) => {
|
|
responseCallback(response);
|
|
},
|
|
error: (jqXHR, textStatus, errorThrown) => {
|
|
var errorMsg = 'unknown error';
|
|
if (textStatus === 'timeout') {
|
|
errorMsg = 'timeout (30s)';
|
|
} else if (textStatus === 'error') {
|
|
if (jqXHR.status === 500) {
|
|
errorMsg = 'server error (500)';
|
|
} else if (jqXHR.status === 0) {
|
|
errorMsg = 'connection failed';
|
|
} else {
|
|
errorMsg = 'error (' + jqXHR.status + ')';
|
|
}
|
|
} else {
|
|
errorMsg = 'error (' + textStatus + ')';
|
|
}
|
|
|
|
// Get current file info for logging
|
|
var bulkInfo = window.webpexpress_bulkconvert;
|
|
var filename = 'unknown file';
|
|
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
|
|
var group = bulkInfo.groups[bulkInfo.groupPointer];
|
|
if (group && group.files && bulkInfo.filePointer < group.files.length) {
|
|
filename = group.files[bulkInfo.filePointer];
|
|
}
|
|
}
|
|
|
|
logLn('Converting <i>' + filename + '</i> <span style="color:red">' + errorMsg + '</span><br>');
|
|
|
|
// Continue with next file instead of stopping
|
|
if (bulkInfo && bulkInfo.groups && bulkInfo.groupPointer < bulkInfo.groups.length) {
|
|
var group = bulkInfo.groups[bulkInfo.groupPointer];
|
|
|
|
// Get next
|
|
bulkInfo.filePointer++;
|
|
if (bulkInfo.filePointer >= group.files.length) {
|
|
bulkInfo.filePointer = 0;
|
|
bulkInfo.groupPointer++;
|
|
}
|
|
if (bulkInfo.groupPointer >= bulkInfo.groups.length) {
|
|
convertDone();
|
|
} else {
|
|
if (!bulkInfo.paused) {
|
|
convertNextInBulkQueue();
|
|
}
|
|
}
|
|
} else {
|
|
convertDone();
|
|
}
|
|
},
|
|
});
|
|
}
|