WebP Express CloudHost.es Fix v0.25.9-cloudhost
✅ 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>
This commit is contained in:
485
lib/options/js/bulk-convert.js
Normal file
485
lib/options/js/bulk-convert.js
Normal file
@@ -0,0 +1,485 @@
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user