'image/avif' ) ) ) { $formats['image/jpeg'] = 'image/avif'; $formats['image/png'] = 'image/avif'; // Fallback to WebP. } elseif ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) { $formats['image/jpeg'] = 'image/webp'; $formats['image/png'] = 'image/webp'; } } return $formats; } add_filter( 'image_editor_output_format', 'zeitfresser_image_output_format' ); /** * Mark images as optimized only when optimization is actually active. * * @param array $metadata Attachment metadata. * @param int $attachment_id Attachment ID. * @return array */ function zeitfresser_mark_new_images_optimized( $metadata, $attachment_id ) { if ( ! wp_attachment_is_image( $attachment_id ) ) { return $metadata; } $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true ); $force_enabled = ! empty( $GLOBALS['zeitfresser_force_image_optimization'] ); if ( ! $auto_enabled && ! $force_enabled ) { return $metadata; } update_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION ); return $metadata; } add_filter( 'wp_generate_attachment_metadata', 'zeitfresser_mark_new_images_optimized', 20, 2 ); /** * Auto Optimize Hook */ add_filter( 'wp_generate_attachment_metadata', 'zeitfresser_auto_optimize_on_upload', 15, 2 ); function zeitfresser_auto_optimize_on_upload( $metadata, $attachment_id ) { // Only images if ( ! wp_attachment_is_image( $attachment_id ) ) { return $metadata; } // Feature toggle if ( ! get_theme_mod( 'ztfr_auto_optimize', true ) ) { return $metadata; } $file = get_attached_file( $attachment_id ); if ( ! $file || ! file_exists( $file ) ) { return $metadata; } // DO NOT overwrite captured original if ( ! get_post_meta( $attachment_id, '_zeitfresser_original_file', true ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_file', $file ); } // Mark optimized (IMPORTANT: no re-trigger here) update_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION ); return $metadata; } /** * Auto Delete Hook */ add_filter( 'wp_generate_attachment_metadata', 'zeitfresser_auto_delete_original_after_upload', 30, 2 ); function zeitfresser_auto_delete_original_after_upload( $metadata, $attachment_id ) { if ( ! wp_attachment_is_image( $attachment_id ) ) { return $metadata; } $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true ); $delete_enabled = get_theme_mod( 'ztfr_auto_delete', false ); if ( ! $auto_enabled || ! $delete_enabled ) { return $metadata; } $original = get_post_meta( $attachment_id, '_zeitfresser_original_file', true ); if ( ! $original ) { return $metadata; } $ext = strtolower( pathinfo( $original, PATHINFO_EXTENSION ) ); // Skip modern formats if ( in_array( $ext, [ 'webp', 'avif' ], true ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); return $metadata; } // ๐ฅ Wenn nichts mehr existiert โ fertig if ( ! zeitfresser_original_family_exists( $attachment_id, $original ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); return $metadata; } // ๐ฅ HIER passiert der Fix zeitfresser_delete_original_family_files( $attachment_id, $original ); // Final check if ( ! zeitfresser_original_family_exists( $attachment_id, $original ) ) { update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 ); } return $metadata; } /** * Keep generated image quality balanced for file size and visual fidelity. * * @param int $quality Proposed image quality. * @param string $mime_type Image mime type. * @return int */ function zeitfresser_image_quality( $quality, $mime_type = 'image/jpeg' ) { $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true ); $force_enabled = ! empty( $GLOBALS['zeitfresser_force_image_optimization'] ); if ( ! $auto_enabled && ! $force_enabled ) { return $quality; } return match ($mime_type) { 'image/avif' => 50, 'image/webp' => 75, default => 82, }; } add_filter( 'wp_editor_set_quality', 'zeitfresser_image_quality', 10, 2 ); /** * Improve responsive image sizes attribute. * * @param string $sizes Existing sizes attribute. * @param array $size Requested image size. * @return string */ function zeitfresser_responsive_image_sizes( $sizes, $size ) { // Single post content if ( is_singular() ) { return '(max-width: 768px) 100vw, (max-width: 1200px) 720px, 720px'; } // Archive / blog overview if ( is_home() || is_front_page() || is_archive() || is_search() ) { return '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 400px'; } return $sizes; } add_filter( 'wp_calculate_image_sizes', 'zeitfresser_responsive_image_sizes', 10, 2 ); /** * Register admin page */ function zeitfresser_register_image_optimizer_page() { add_theme_page( 'Image Optimizer', 'Image Optimizer', 'manage_options', 'zeitfresser-image-optimizer', 'zeitfresser_render_image_optimizer_page' ); } add_action( 'admin_menu', 'zeitfresser_register_image_optimizer_page' ); /** * Count pending images */ function zeitfresser_get_pending_legacy_images_count() { $query = new WP_Query([ 'post_type' => '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