'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'fields' => 'ids', 'posts_per_page' => 1, 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_zeitfresser_media_optimized_version', 'compare' => 'NOT EXISTS', ], [ 'key' => '_zeitfresser_media_optimized_version', 'value' => ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION, 'compare' => '!=', ], ], 'no_found_rows' => false, ]); return (int) $query->found_posts; } /** * Count total images */ function zeitfresser_get_total_images_count() { $query = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'fields' => 'ids', 'posts_per_page' => 1, 'no_found_rows' => false, ]); return (int) $query->found_posts; } /** * NEW: Cleanup counters (ONLY ADDITIVE) */ function zeitfresser_get_total_originals_count() { $query = new WP_Query([ 'post_type'=>'attachment', 'post_status'=>'inherit', 'post_mime_type'=>'image', 'posts_per_page'=>1, 'fields'=>'ids', 'meta_query'=>[ ['key'=>'_zeitfresser_original_file','compare'=>'EXISTS'] ], 'no_found_rows'=>false ]); return (int) $query->found_posts; } function zeitfresser_get_remaining_originals_count() { $query = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_zeitfresser_original_file', 'compare' => 'EXISTS', ], [ 'key' => '_zeitfresser_original_deleted', 'compare' => 'NOT EXISTS', ], [ 'key' => '_zeitfresser_media_optimized_version', 'value' => ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION, 'compare' => '=', ], ], 'no_found_rows' => false, ]); return (int) $query->found_posts; } function zeitfresser_get_unoptimized_originals_count() { $query = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_zeitfresser_original_file', 'compare' => 'EXISTS', ], [ 'key' => '_zeitfresser_original_deleted', 'compare' => 'NOT EXISTS', ], [ 'relation' => 'OR', [ 'key' => '_zeitfresser_media_optimized_version', 'compare' => 'NOT EXISTS', ], [ 'key' => '_zeitfresser_media_optimized_version', 'value' => ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION, 'compare' => '!=', ], ], ], 'no_found_rows' => false, ]); return (int) $query->found_posts; } /** * Build a list of original-format files belonging to one attachment. * * This includes: * - the original uploaded file * - the original-format main generated file (e.g. scaled JPG) * - original-format sub-size files derived from attachment metadata * * @param int $attachment_id Attachment ID. * @param string $original Absolute path to the original uploaded file. * @return array */ function zeitfresser_get_original_family_files( $attachment_id, $original ) { $files = []; if ( empty( $original ) ) { return $files; } $original_ext = strtolower( pathinfo( $original, PATHINFO_EXTENSION ) ); $base_name = pathinfo( $original, PATHINFO_FILENAME ); $dir = dirname( $original ); if ( empty( $original_ext ) ) { return $files; } // Original $files[] = $original; // --- ORIGINAL LOGIC (metadata-based) --- $metadata = wp_get_attachment_metadata( $attachment_id ); $upload_dir = wp_get_upload_dir(); if ( ! empty( $metadata['file'] ) ) { $current_main_absolute = trailingslashit( $upload_dir['basedir'] ) . $metadata['file']; // scaled/main $files[] = preg_replace( '/\.[^.]+$/', '.' . $original_ext, $current_main_absolute ); $current_dir = dirname( $current_main_absolute ); if ( ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { foreach ( $metadata['sizes'] as $size ) { if ( empty( $size['file'] ) ) { continue; } $files[] = $current_dir . '/' . preg_replace( '/\.[^.]+$/', '.' . $original_ext, $size['file'] ); } } } // --- NEW: FILESYSTEM FALLBACK (CRITICAL FIX) --- foreach ( glob( $dir . '/' . $base_name . '*.' . $original_ext ) as $file ) { $files[] = $file; } return array_values( array_unique( array_filter( $files ) ) ); } /** * Check whether any original-format family files still exist. * * @param int $attachment_id Attachment ID. * @param string $original Absolute path to the original uploaded file. * @return bool */ function zeitfresser_original_family_exists( $attachment_id, $original ) { $files = zeitfresser_get_original_family_files( $attachment_id, $original ); foreach ( $files as $file ) { if ( file_exists( $file ) ) { return true; } } return false; } /** * Delete all original-format files belonging to one attachment. * * @param int $attachment_id Attachment ID. * @param string $original Absolute path to the original uploaded file. * @return int Number of deleted files. */ function zeitfresser_delete_original_family_files( $attachment_id, $original ) { $deleted_files = 0; $files = zeitfresser_get_original_family_files( $attachment_id, $original ); foreach ( $files as $file ) { if ( ! file_exists( $file ) ) { continue; } if ( ! is_writable( $file ) ) { continue; } if ( unlink( $file ) ) { $deleted_files++; } } return $deleted_files; } /** * DELETE ORIGINALS (manual batch) */ function zeitfresser_delete_originals_batch( $batch_size = 10 ) { $deleted = 0; $query = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'fields' => 'ids', 'posts_per_page' => $batch_size, 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_zeitfresser_original_file', 'compare' => 'EXISTS', ], [ 'key' => '_zeitfresser_original_deleted', 'compare' => 'NOT EXISTS', ], ], ]); foreach ( $query->posts as $attachment_id ) { $original = get_post_meta( $attachment_id, '_zeitfresser_original_file', true ); if ( ! $original ) { continue; } $optimized_version = get_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', true ); if ( ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION !== $optimized_version ) { continue; } $ext = strtolower( pathinfo( $original, PATHINFO_EXTENSION ) ); if ( in_array( $ext, [ 'webp', 'avif' ], true ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); continue; } // Nothing left to delete -> mark as done. if ( ! zeitfresser_original_family_exists( $attachment_id, $original ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); continue; } zeitfresser_delete_original_family_files( $attachment_id, $original ); // Only mark as deleted when the full original family is gone. if ( ! zeitfresser_original_family_exists( $attachment_id, $original ) ) { $deleted++; update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); } } return $deleted; } /** * Optimizer batch for manual processing. * * Manual optimization must work independently of the auto-optimize upload toggle. * * @param int $batch_size Number of images per batch. * @return array */ function zeitfresser_process_legacy_images_batch( $batch_size = 25 ) { $results = [ 'processed' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => 0, ]; $query = new WP_Query([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => 'image', 'fields' => 'ids', 'posts_per_page' => $batch_size, 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_zeitfresser_media_optimized_version', 'compare' => 'NOT EXISTS', ], [ 'key' => '_zeitfresser_media_optimized_version', 'value' => ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION, 'compare' => '!=', ], ], ]); // Force optimization for manual tool runs, regardless of upload automation setting. $GLOBALS['zeitfresser_force_image_optimization'] = true; foreach ( $query->posts as $attachment_id ) { $results['processed']++; $file = get_attached_file( $attachment_id ); if ( empty( $file ) || ! file_exists( $file ) ) { update_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', 'missing' ); $results['skipped']++; continue; } if ( ! get_post_meta( $attachment_id, '_zeitfresser_original_file', true ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_file', $file ); } $metadata = wp_generate_attachment_metadata( $attachment_id, $file ); if ( is_wp_error( $metadata ) || empty( $metadata ) ) { $results['errors']++; continue; } wp_update_attachment_metadata( $attachment_id, $metadata ); update_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION ); $results['updated']++; } unset( $GLOBALS['zeitfresser_force_image_optimization'] ); return $results; } /** * AJAX: Optimizer (extended output only) */ function zeitfresser_ajax_optimize_images() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error(); } check_ajax_referer( 'zeitfresser_image_optimizer', 'nonce' ); $results = zeitfresser_process_legacy_images_batch( 25 ); $pending = zeitfresser_get_pending_legacy_images_count(); $total = zeitfresser_get_total_images_count(); $cleanup_total = zeitfresser_get_total_originals_count(); $cleanup_remaining = zeitfresser_get_remaining_originals_count(); $cleanup_unoptimized = zeitfresser_get_unoptimized_originals_count(); $cleanup_deleted = $cleanup_total - ( $cleanup_remaining + $cleanup_unoptimized ); $cleanup_progress = $cleanup_total > 0 ? round( ( $cleanup_deleted / $cleanup_total ) * 100 ) : 0; wp_send_json_success([ 'processed' => $results['processed'], 'updated' => $results['updated'], 'pending' => $pending, 'total' => $total, 'cleanup_total' => $cleanup_total, 'cleanup_remaining' => $cleanup_remaining, 'cleanup_unoptimized' => $cleanup_unoptimized, 'cleanup_deleted' => $cleanup_deleted, 'cleanup_progress' => $cleanup_progress, ]); } add_action( 'wp_ajax_zeitfresser_optimize_images', 'zeitfresser_ajax_optimize_images' ); /** * AJAX: Delete (extended ONLY) */ function zeitfresser_ajax_delete_originals() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error(); } check_ajax_referer( 'zeitfresser_image_optimizer', 'nonce' ); $deleted = zeitfresser_delete_originals_batch( 10 ); $total = zeitfresser_get_total_originals_count(); $remaining = zeitfresser_get_remaining_originals_count(); $unoptimized = zeitfresser_get_unoptimized_originals_count(); $deleted_total = $total - ( $remaining + $unoptimized ); wp_send_json_success([ 'deleted' => $deleted, 'total' => $total, 'remaining' => $remaining, 'unoptimized' => $unoptimized, 'deleted_total' => $deleted_total, ]); } add_action( 'wp_ajax_zeitfresser_delete_originals', 'zeitfresser_ajax_delete_originals' ); /** * Render UI */ function zeitfresser_render_image_optimizer_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $pending = zeitfresser_get_pending_legacy_images_count(); $total = zeitfresser_get_total_images_count(); $optimized = $total - $pending; $progress = $total > 0 ? round(($optimized / $total) * 100) : 0; // 🔥 NEW: Cleanup counters $cleanup_total = zeitfresser_get_total_originals_count(); $cleanup_remaining = zeitfresser_get_remaining_originals_count(); $cleanup_unoptimized = zeitfresser_get_unoptimized_originals_count(); $cleanup_deleted = $cleanup_total - ( $cleanup_remaining + $cleanup_unoptimized ); $cleanup_progress = $cleanup_total > 0 ? round(($cleanup_deleted / $cleanup_total) * 100) : 0; $cleanup_nothing_to_do = ( 0 === $cleanup_remaining && ( $cleanup_unoptimized > 0 || $cleanup_deleted >= $cleanup_total ) ); $cleanup_button_disabled = $cleanup_nothing_to_do; $cleanup_button_label = $cleanup_nothing_to_do ? '🧹 Nothing to clean yet' : '🧹 Delete Originals'; ?>
How this tool works
This tool helps you optimize your existing media library for better performance.
• Images are converted to modern formats (AVIF/WebP) for smaller file sizes.
• The original file path is safely stored before optimization.
• Once optimized, original images can be deleted to save disk space.
Automation:
• You can enable automatic optimization on upload in the Customizer under Image Optimizer Settings.
• Optionally, original images can also be deleted automatically after successful optimization.
Safety:
• Images are only processed once per version.
• Original files are only deleted when safe.
• The tool can be run multiple times without side effects.
Tip: You can either automate the process via the Customizer or use this tool manually for full control.
Total Images:
Optimized:
Pending:
Progress: %
Total Originals:
Deleted:
Ready for Cleanup:
Not Optimized Yet:
Cleanup Progress: %
🚀 Optimizer: Idle
🧹 Cleanup: Idle