Add WebP conversion and garbage collection to MLS plugin
Image handling improvements: - Convert PNG and images >500KB to WebP format - Resize images wider than 1600px maintaining aspect ratio - Check for .webp version before falling back to original - WebP quality set to 80 (equivalent to JPEG 90%) Garbage collection for disk space management: - New MLS_Garbage_Collector class runs after each sync - Only active when MLS_GC_DISK_THRESHOLD defined in wp-config - Deletes image directories older than 24 hours, oldest first - Stops when free space reaches 5GB or 2GB deleted per run - Protects recently accessed images from deletion Documentation: - Added Garbage Collection section to README - Updated Features list and File Structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,127 @@ class MLS_Media_Handler {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebP quality setting (80 is roughly equivalent to JPEG 90)
|
||||
*/
|
||||
const WEBP_QUALITY = 80;
|
||||
|
||||
/**
|
||||
* Max image width in pixels
|
||||
*/
|
||||
const MAX_IMAGE_WIDTH = 1600;
|
||||
|
||||
/**
|
||||
* File size threshold for WebP conversion (500KB)
|
||||
*/
|
||||
const WEBP_SIZE_THRESHOLD = 512000;
|
||||
|
||||
/**
|
||||
* Convert image to WebP format if needed
|
||||
*
|
||||
* Converts to WebP if:
|
||||
* - Image is PNG, OR
|
||||
* - Image is larger than WEBP_SIZE_THRESHOLD
|
||||
*
|
||||
* Also resizes if width > MAX_IMAGE_WIDTH
|
||||
*
|
||||
* @param string $file_path Absolute path to image file
|
||||
* @param string $extension Original file extension
|
||||
* @return array ['path' => new path, 'extension' => new extension, 'converted' => bool]
|
||||
*/
|
||||
private function maybe_convert_to_webp($file_path, $extension) {
|
||||
$result = array(
|
||||
'path' => $file_path,
|
||||
'extension' => $extension,
|
||||
'converted' => false,
|
||||
);
|
||||
|
||||
// Skip if already WebP or GIF (preserve animations)
|
||||
if ($extension === 'webp' || $extension === 'gif') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$file_size = filesize($file_path);
|
||||
$is_png = ($extension === 'png');
|
||||
$is_large = ($file_size > self::WEBP_SIZE_THRESHOLD);
|
||||
|
||||
// Only convert PNGs or large files
|
||||
if (!$is_png && !$is_large) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Check if we have image editing capability
|
||||
if (!function_exists('wp_get_image_editor')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$editor = wp_get_image_editor($file_path);
|
||||
if (is_wp_error($editor)) {
|
||||
$this->logger->warning('Could not load image for WebP conversion', array(
|
||||
'path' => $file_path,
|
||||
'error' => $editor->get_error_message(),
|
||||
));
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get current dimensions
|
||||
$size = $editor->get_size();
|
||||
$needs_resize = ($size['width'] > self::MAX_IMAGE_WIDTH);
|
||||
|
||||
// Resize if needed
|
||||
if ($needs_resize) {
|
||||
$editor->resize(self::MAX_IMAGE_WIDTH, null, false);
|
||||
}
|
||||
|
||||
// Set quality (80 is roughly equivalent to JPEG 90)
|
||||
$editor->set_quality(self::WEBP_QUALITY);
|
||||
|
||||
// Generate WebP path
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $file_path);
|
||||
|
||||
// Save as WebP
|
||||
$saved = $editor->save($webp_path, 'image/webp');
|
||||
|
||||
if (is_wp_error($saved)) {
|
||||
$this->logger->warning('WebP conversion failed', array(
|
||||
'path' => $file_path,
|
||||
'error' => $saved->get_error_message(),
|
||||
));
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Delete original file
|
||||
@unlink($file_path);
|
||||
|
||||
$this->logger->debug('Converted image to WebP', array(
|
||||
'original_path' => $file_path,
|
||||
'original_size' => $file_size,
|
||||
'webp_path' => $webp_path,
|
||||
'webp_size' => filesize($webp_path),
|
||||
'resized' => $needs_resize,
|
||||
));
|
||||
|
||||
return array(
|
||||
'path' => $webp_path,
|
||||
'extension' => 'webp',
|
||||
'converted' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WebP version of file exists, return that path if so
|
||||
*
|
||||
* @param string $file_path Original file path
|
||||
* @return string Path to use (WebP if exists, otherwise original)
|
||||
*/
|
||||
private function prefer_webp_path($file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $file_path);
|
||||
if (file_exists($webp_path)) {
|
||||
return $webp_path;
|
||||
}
|
||||
return $file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base upload directory for MLS media
|
||||
*
|
||||
@@ -195,10 +316,16 @@ class MLS_Media_Handler {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Already cached
|
||||
// Already cached - check for WebP version first
|
||||
if ($media->local_url && $media->local_path) {
|
||||
$file_path = $this->get_upload_dir() . '/' . $media->local_path;
|
||||
if (file_exists($file_path)) {
|
||||
$actual_path = $this->prefer_webp_path($file_path);
|
||||
if (file_exists($actual_path)) {
|
||||
// If WebP version exists, return WebP URL
|
||||
if ($actual_path !== $file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $media->local_path);
|
||||
return $this->get_upload_url() . '/' . $webp_path;
|
||||
}
|
||||
return $media->local_url;
|
||||
}
|
||||
}
|
||||
@@ -235,7 +362,13 @@ class MLS_Media_Handler {
|
||||
|
||||
if ($cached) {
|
||||
$file_path = $this->get_upload_dir() . '/' . $cached->local_path;
|
||||
if (file_exists($file_path)) {
|
||||
$actual_path = $this->prefer_webp_path($file_path);
|
||||
if (file_exists($actual_path)) {
|
||||
// If WebP version exists, return WebP URL
|
||||
if ($actual_path !== $file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $cached->local_path);
|
||||
return $this->get_upload_url() . '/' . $webp_path;
|
||||
}
|
||||
return $cached->local_url;
|
||||
}
|
||||
}
|
||||
@@ -253,10 +386,16 @@ class MLS_Media_Handler {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If already cached and file exists, return it
|
||||
// If already cached and file exists, return it - check for WebP first
|
||||
if ($media->local_url && $media->local_path) {
|
||||
$file_path = $this->get_upload_dir() . '/' . $media->local_path;
|
||||
if (file_exists($file_path)) {
|
||||
$actual_path = $this->prefer_webp_path($file_path);
|
||||
if (file_exists($actual_path)) {
|
||||
// If WebP version exists, return WebP URL
|
||||
if ($actual_path !== $file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $media->local_path);
|
||||
return $this->get_upload_url() . '/' . $webp_path;
|
||||
}
|
||||
return $media->local_url;
|
||||
}
|
||||
}
|
||||
@@ -292,10 +431,16 @@ class MLS_Media_Handler {
|
||||
|
||||
$fetched = 0;
|
||||
foreach ($media as &$item) {
|
||||
// Check if cached and file exists
|
||||
// Check if cached and file exists - prefer WebP version
|
||||
if ($item->local_url && $item->local_path) {
|
||||
$file_path = $this->get_upload_dir() . '/' . $item->local_path;
|
||||
if (file_exists($file_path)) {
|
||||
$actual_path = $this->prefer_webp_path($file_path);
|
||||
if (file_exists($actual_path)) {
|
||||
// If WebP version exists, update the URL
|
||||
if ($actual_path !== $file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $item->local_path);
|
||||
$item->local_url = $this->get_upload_url() . '/' . $webp_path;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -358,8 +503,14 @@ class MLS_Media_Handler {
|
||||
|
||||
if ($updated_media && $updated_media->local_path) {
|
||||
$file_path = $this->get_upload_dir() . '/' . $updated_media->local_path;
|
||||
if (file_exists($file_path)) {
|
||||
$actual_path = $this->prefer_webp_path($file_path);
|
||||
if (file_exists($actual_path)) {
|
||||
// Another request cached it while we waited
|
||||
// If WebP version exists, return WebP URL
|
||||
if ($actual_path !== $file_path) {
|
||||
$webp_path = preg_replace('/\.[^.]+$/', '.webp', $updated_media->local_path);
|
||||
return $this->get_upload_url() . '/' . $webp_path;
|
||||
}
|
||||
return $updated_media->local_url;
|
||||
}
|
||||
}
|
||||
@@ -415,17 +566,29 @@ class MLS_Media_Handler {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert to WebP if PNG or file is large (>500KB)
|
||||
$conversion = $this->maybe_convert_to_webp($file_path, $extension);
|
||||
if ($conversion['converted']) {
|
||||
$file_path = $conversion['path'];
|
||||
$extension = $conversion['extension'];
|
||||
$filename = $media->media_order . '.' . $extension;
|
||||
$content_type = 'image/webp';
|
||||
}
|
||||
|
||||
// Update database
|
||||
$prefix = substr($media->listing_key, 0, 2);
|
||||
$relative_path = $prefix . '/' . $media->listing_key . '/' . $filename;
|
||||
$local_url = $this->get_upload_url() . '/' . $relative_path;
|
||||
|
||||
// Get actual file size after any conversion
|
||||
$final_size = filesize($file_path);
|
||||
|
||||
$wpdb->update(
|
||||
$this->db->media_table(),
|
||||
array(
|
||||
'local_path' => $relative_path,
|
||||
'local_url' => $local_url,
|
||||
'file_size' => strlen($body),
|
||||
'file_size' => $final_size,
|
||||
'mime_type' => $content_type,
|
||||
'downloaded_at' => current_time('mysql'),
|
||||
),
|
||||
@@ -435,7 +598,9 @@ class MLS_Media_Handler {
|
||||
$this->logger->debug('Media fetched and cached', array(
|
||||
'listing_key' => $media->listing_key,
|
||||
'media_key' => $media->media_key,
|
||||
'size' => strlen($body),
|
||||
'original_size' => strlen($body),
|
||||
'final_size' => $final_size,
|
||||
'converted' => $conversion['converted'],
|
||||
));
|
||||
|
||||
return $local_url;
|
||||
|
||||
Reference in New Issue
Block a user