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:
337
lib/classes/BulkConvert.php
Normal file
337
lib/classes/BulkConvert.php
Normal file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace WebPExpress;
|
||||
|
||||
//use \Onnov\DetectEncoding\EncodingDetector;
|
||||
|
||||
use \WebPExpress\Config;
|
||||
use \WebPExpress\ConvertHelperIndependent;
|
||||
use \WebPExpress\ImageRoots;
|
||||
use \WebPExpress\PathHelper;
|
||||
use \WebPExpress\Paths;
|
||||
|
||||
class BulkConvert
|
||||
{
|
||||
|
||||
public static function defaultListOptions($config)
|
||||
{
|
||||
return [
|
||||
//'root' => Paths::getUploadDirAbs(),
|
||||
'ext' => $config['destination-extension'],
|
||||
'destination-folder' => $config['destination-folder'], /* hm, "destination-folder" is a bad name... */
|
||||
'webExpressContentDirAbs' => Paths::getWebPExpressContentDirAbs(),
|
||||
'uploadDirAbs' => Paths::getUploadDirAbs(),
|
||||
'useDocRootForStructuringCacheDir' => (($config['destination-structure'] == 'doc-root') && (Paths::canUseDocRootForStructuringCacheDir())),
|
||||
'imageRoots' => new ImageRoots(Paths::getImageRootsDefForSelectedIds($config['scope'])), // (Paths::getImageRootsDef()
|
||||
'filter' => [
|
||||
'only-converted' => false,
|
||||
'only-unconverted' => true,
|
||||
'image-types' => $config['image-types'],
|
||||
'max-depth' => 100,
|
||||
],
|
||||
'flattenList' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get grouped list of files. They are grouped by image roots.
|
||||
*
|
||||
*/
|
||||
public static function getList($config, $listOptions = null)
|
||||
{
|
||||
|
||||
/*
|
||||
isUploadDirMovedOutOfWPContentDir
|
||||
isUploadDirMovedOutOfAbsPath
|
||||
isPluginDirMovedOutOfAbsPath
|
||||
isPluginDirMovedOutOfWpContent
|
||||
isWPContentDirMovedOutOfAbsPath */
|
||||
|
||||
if (is_null($listOptions)) {
|
||||
$listOptions = self::defaultListOptions($config);
|
||||
}
|
||||
|
||||
$rootIds = Paths::filterOutSubRoots($config['scope']);
|
||||
|
||||
$groups = [];
|
||||
foreach ($rootIds as $rootId) {
|
||||
$groups[] = [
|
||||
'groupName' => $rootId,
|
||||
'root' => Paths::getAbsDirById($rootId)
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($groups as $i => &$group) {
|
||||
$listOptions['root'] = $group['root'];
|
||||
/*
|
||||
No use, because if uploads is in wp-content, the cache root will be different for the files in uploads (if mingled)
|
||||
$group['image-root'] = ConvertHelperIndependent::getDestinationFolder(
|
||||
$group['root'],
|
||||
$listOptions['destination-folder'],
|
||||
$listOptions['ext'],
|
||||
$listOptions['webExpressContentDirAbs'],
|
||||
$listOptions['uploadDirAbs']
|
||||
);*/
|
||||
$group['files'] = self::getListRecursively('.', $listOptions);
|
||||
//'image-root' => ConvertHelperIndependent::getDestinationFolder()
|
||||
}
|
||||
|
||||
return $groups;
|
||||
//self::moveRecursively($toDir, $fromDir, $srcDir, $fromExt, $toExt);
|
||||
}
|
||||
|
||||
/**
|
||||
* $filter: all | converted | not-converted. "not-converted" for example returns paths to images that has not been converted
|
||||
*/
|
||||
public static function getListRecursively($relDir, &$listOptions, $depth = 0)
|
||||
{
|
||||
$dir = $listOptions['root'] . '/' . $relDir;
|
||||
|
||||
// Canonicalize because dir might contain "/./", which causes file_exists to fail (#222)
|
||||
$dir = PathHelper::canonicalize($dir);
|
||||
|
||||
if (!@file_exists($dir) || !@is_dir($dir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fileIterator = new \FilesystemIterator($dir);
|
||||
|
||||
$results = [];
|
||||
$filter = &$listOptions['filter'];
|
||||
|
||||
while ($fileIterator->valid()) {
|
||||
$filename = $fileIterator->getFilename();
|
||||
|
||||
if (($filename != ".") && ($filename != "..")) {
|
||||
|
||||
if (@is_dir($dir . "/" . $filename)) {
|
||||
if ($listOptions['flattenList']) {
|
||||
$results = array_merge($results, self::getListRecursively($relDir . "/" . $filename, $listOptions, $depth+1));
|
||||
} else {
|
||||
$r = [
|
||||
'name' => $filename,
|
||||
'isDir' => true,
|
||||
];
|
||||
if ($depth > $listOptions['max-depth']) {
|
||||
return $r; // one item is enough to determine that it is not empty
|
||||
}
|
||||
if ($depth < $listOptions['max-depth']) {
|
||||
$r['children'] = self::getListRecursively($relDir . "/" . $filename, $listOptions, $depth+1);
|
||||
$r['isEmpty'] = (count($r['children']) == 0);
|
||||
} else if ($depth == $listOptions['max-depth']) {
|
||||
$c = self::getListRecursively($relDir . "/" . $filename, $listOptions, $depth+1);
|
||||
$r['isEmpty'] = (count($c) == 0);
|
||||
//$r['isEmpty'] = !(new \FilesystemIterator($dir))->valid();
|
||||
}
|
||||
$results[] = $r;
|
||||
}
|
||||
} else {
|
||||
// its a file - check if its a jpeg or png
|
||||
|
||||
if (!isset($filter['_regexPattern'])) {
|
||||
$imageTypes = $filter['image-types'];
|
||||
$fileExtensions = [];
|
||||
if ($imageTypes & 1) {
|
||||
$fileExtensions[] = 'jpe?g';
|
||||
}
|
||||
if ($imageTypes & 2) {
|
||||
$fileExtensions[] = 'png';
|
||||
}
|
||||
$filter['_regexPattern'] = '#\.(' . implode('|', $fileExtensions) . ')$#';
|
||||
}
|
||||
|
||||
if (preg_match($filter['_regexPattern'], $filename)) {
|
||||
$addThis = true;
|
||||
|
||||
$destination = ConvertHelperIndependent::getDestination(
|
||||
$dir . "/" . $filename,
|
||||
$listOptions['destination-folder'],
|
||||
$listOptions['ext'],
|
||||
$listOptions['webExpressContentDirAbs'],
|
||||
$listOptions['uploadDirAbs'],
|
||||
$listOptions['useDocRootForStructuringCacheDir'],
|
||||
$listOptions['imageRoots']
|
||||
);
|
||||
$webpExists = @file_exists($destination);
|
||||
|
||||
if (($filter['only-converted']) || ($filter['only-unconverted'])) {
|
||||
//$cacheDir = $listOptions['image-root'] . '/' . $relDir;
|
||||
|
||||
// Check if corresponding webp exists
|
||||
/*
|
||||
if ($listOptions['ext'] == 'append') {
|
||||
$webpExists = @file_exists($cacheDir . "/" . $filename . '.webp');
|
||||
} else {
|
||||
$webpExists = @file_exists(preg_replace("/\.(jpe?g|png)\.webp$/", '.webp', $filename));
|
||||
}*/
|
||||
|
||||
if (!$webpExists && ($filter['only-converted'])) {
|
||||
$addThis = false;
|
||||
}
|
||||
if ($webpExists && ($filter['only-unconverted'])) {
|
||||
$addThis = false;
|
||||
}
|
||||
} else {
|
||||
$addThis = true;
|
||||
}
|
||||
|
||||
if ($addThis) {
|
||||
|
||||
$path = substr($relDir . "/", 2) . $filename; // (we cut the leading "./" off with substr)
|
||||
|
||||
// Additional safety check: verify the file actually exists before adding to list
|
||||
$fullPath = $dir . "/" . $filename;
|
||||
if (!file_exists($fullPath)) {
|
||||
continue; // Skip this file if it doesn't exist
|
||||
}
|
||||
|
||||
// Check if the string can be encoded to json (if not: change it to a string that can)
|
||||
if (json_encode($path, JSON_UNESCAPED_UNICODE) === false) {
|
||||
/*
|
||||
json_encode failed. This means that the string was not UTF-8.
|
||||
Lets see if we can convert it to UTF-8.
|
||||
This is however tricky business (see #471)
|
||||
*/
|
||||
|
||||
$encodedToUTF8 = false;
|
||||
|
||||
// First try library that claims to do better than mb_detect_encoding
|
||||
/*
|
||||
DISABLED, because Onnov EncodingDetector requires PHP 7.2
|
||||
https://wordpress.org/support/topic/get-http-error-500-after-new-update-2/
|
||||
|
||||
if (!$encodedToUTF8) {
|
||||
$detector = new EncodingDetector();
|
||||
|
||||
$dectedEncoding = $detector->getEncoding($path);
|
||||
|
||||
if ($dectedEncoding !== 'utf-8') {
|
||||
if (function_exists('iconv')) {
|
||||
$res = iconv($dectedEncoding, 'utf-8//TRANSLIT', $path);
|
||||
if ($res !== false) {
|
||||
$path = $res;
|
||||
$encodedToUTF8 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// iconvXtoEncoding should work now hm, issue #5 has been fixed
|
||||
$path = $detector->iconvXtoEncoding($path);
|
||||
$encodedToUTF8 = true;
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
// Try mb_detect_encoding
|
||||
if (!$encodedToUTF8) {
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
$encoding = mb_detect_encoding($path, mb_detect_order(), true);
|
||||
if ($encoding) {
|
||||
$path = mb_convert_encoding($path, 'UTF-8', $encoding);
|
||||
$encodedToUTF8 = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$encodedToUTF8) {
|
||||
/*
|
||||
We haven't yet succeeded in encoding to UTF-8.
|
||||
What should we do?
|
||||
1. Skip the file? (no, the user will not know about the problem then)
|
||||
2. Add it anyway? (no, if this string causes problems to json_encode, then we will have
|
||||
the same problem when encoding the entire list - result: an empty list)
|
||||
3. Try wp_json_encode? (no, it will fall back on "wp_check_invalid_utf8", which has a number of
|
||||
things we do not want)
|
||||
4. Encode it to UTF-8 assuming that the string is encoded in the most common encoding (Windows-1252) ?
|
||||
(yes, if we are lucky with the guess, it will work. If it is in another encoding, the conversion
|
||||
will not be correct, and the user will then know about the problem. And either way, we will
|
||||
have UTF-8 string, which will not break encoding of the list)
|
||||
*/
|
||||
|
||||
// https://stackoverflow.com/questions/6606713/json-encode-non-utf-8-strings
|
||||
if (function_exists('mb_convert_encoding')) {
|
||||
$path = mb_convert_encoding($path, "UTF-8", "Windows-1252");
|
||||
} elseif (function_exists('iconv')) {
|
||||
$path = iconv("CP1252", "UTF-8", $path);
|
||||
} elseif (function_exists('utf8_encode')) {
|
||||
// utf8_encode converts from ISO-8859-1 to UTF-8
|
||||
$path = utf8_encode($path);
|
||||
} else {
|
||||
$path = '[cannot encode this filename to UTF-8]';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if ($listOptions['flattenList']) {
|
||||
$results[] = $path;
|
||||
} else {
|
||||
$results[] = [
|
||||
'name' => basename($path),
|
||||
'isConverted' => $webpExists
|
||||
];
|
||||
if ($depth > $listOptions['max-depth']) {
|
||||
return $results; // one item is enough to determine that it is not empty
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$fileIterator->next();
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/*
|
||||
public static function convertFile($source)
|
||||
{
|
||||
$config = Config::loadConfigAndFix();
|
||||
$options = Config::generateWodOptionsFromConfigObj($config);
|
||||
|
||||
$destination = ConvertHelperIndependent::getDestination(
|
||||
$source,
|
||||
$options['destination-folder'],
|
||||
$options['destination-extension'],
|
||||
Paths::getWebPExpressContentDirAbs(),
|
||||
Paths::getUploadDirAbs()
|
||||
);
|
||||
$result = ConvertHelperIndependent::convert($source, $destination, $options);
|
||||
|
||||
//$result['destination'] = $destination;
|
||||
if ($result['success']) {
|
||||
$result['filesize-original'] = @filesize($source);
|
||||
$result['filesize-webp'] = @filesize($destination);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
*/
|
||||
|
||||
public static function processAjaxListUnconvertedFiles()
|
||||
{
|
||||
if (!check_ajax_referer('webpexpress-ajax-list-unconverted-files-nonce', 'nonce', false)) {
|
||||
wp_send_json_error('The security nonce has expired. You need to reload the settings page (press F5) and try again)');
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$config = Config::loadConfigAndFix();
|
||||
$arr = self::getList($config);
|
||||
|
||||
// We use "wp_json_encode" rather than "json_encode" because it handles problems if there is non UTF-8 characters
|
||||
// There should be none, as we have taken our measures, but no harm in taking extra precautions
|
||||
$json = wp_json_encode($arr, JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
if ($json === false) {
|
||||
// TODO: We can do better error handling than this!
|
||||
echo '';
|
||||
} else {
|
||||
echo $json;
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user