diff --git a/functions.php b/functions.php index 4ae8467..a0b9742 100644 --- a/functions.php +++ b/functions.php @@ -6,7 +6,7 @@ */ if ( ! defined( 'ABSPATH' ) ) { - exit; + exit; } /** @@ -15,11 +15,11 @@ if ( ! defined( 'ABSPATH' ) ) { * ------------------------------------------------------------------------ */ if ( ! defined( 'ZEITFRESSER_VERSION' ) ) { - define( 'ZEITFRESSER_VERSION', '2.3.6' ); + define( 'ZEITFRESSER_VERSION', '2.3.6' ); } if ( ! defined( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION' ) ) { - define( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION', '1.0' ); + define( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION', '1.0' ); } /** @@ -27,82 +27,38 @@ if ( ! defined( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION' ) ) { * Customizer * ------------------------------------------------------------------------ */ -require get_template_directory() . '/inc/customizer/core-settings.php'; -require get_template_directory() . '/inc/customizer/general-settings.php'; -require get_template_directory() . '/inc/customizer/layout-settings.php'; -require get_template_directory() . '/inc/customizer/toc-settings.php'; -require get_template_directory() . '/inc/customizer/social-settings.php'; -require get_template_directory() . '/inc/customizer/image-optimizer-settings.php'; +require_once get_template_directory() . '/inc/customizer/core-settings.php'; +require_once get_template_directory() . '/inc/customizer/general-settings.php'; +require_once get_template_directory() . '/inc/customizer/layout-settings.php'; +require_once get_template_directory() . '/inc/customizer/toc-settings.php'; +require_once get_template_directory() . '/inc/customizer/social-settings.php'; +require_once get_template_directory() . '/inc/customizer/image-optimizer-settings.php'; /** * ------------------------------------------------------------------------ * Utilities * ------------------------------------------------------------------------ */ -require get_template_directory() . '/inc/utilities/helpers.php'; -require get_template_directory() . '/inc/utilities/template-tags.php'; -require get_template_directory() . '/inc/utilities/template-functions.php'; -require get_template_directory() . '/inc/utilities/pagination.php'; -require get_template_directory() . '/inc/utilities/toc.php'; +require_once get_template_directory() . '/inc/utilities/helpers.php'; +require_once get_template_directory() . '/inc/utilities/template-tags.php'; +require_once get_template_directory() . '/inc/utilities/template-functions.php'; +require_once get_template_directory() . '/inc/utilities/pagination.php'; +require_once get_template_directory() . '/inc/utilities/toc.php'; /** * ------------------------------------------------------------------------ * Tools * ------------------------------------------------------------------------ */ -require get_template_directory() . '/inc/tools/image-optimizer.php'; -require get_template_directory() . '/inc/tools/code-block.php'; +require_once get_template_directory() . '/inc/tools/image-optimizer.php'; +require_once get_template_directory() . '/inc/tools/code-block.php'; /** * ------------------------------------------------------------------------ - * Upload Handling (Original File Tracking) + * Performance * ------------------------------------------------------------------------ */ -function zeitfresser_capture_original_upload( $upload, $context ) { - - if ( empty( $upload['file'] ) ) { - return $upload; - } - - // Store temporarily (request-scoped) - $GLOBALS['zeitfresser_last_uploaded_file'] = $upload['file']; - - return $upload; -} -add_filter( 'wp_handle_upload', 'zeitfresser_capture_original_upload', 10, 2 ); - -/** - * Persist original file path to attachment meta - */ -function zeitfresser_store_original_file( $attachment_id ) { - - if ( ! wp_attachment_is_image( $attachment_id ) ) { - return; - } - - if ( empty( $GLOBALS['zeitfresser_last_uploaded_file'] ) ) { - return; - } - - $file = $GLOBALS['zeitfresser_last_uploaded_file']; - - // Safety: ensure file still exists - if ( ! file_exists( $file ) ) { - return; - } - - // Prevent overwrite if already set - if ( get_post_meta( $attachment_id, '_zeitfresser_original_file', true ) ) { - return; - } - - update_post_meta( - $attachment_id, - '_zeitfresser_original_file', - $file - ); -} -add_action( 'add_attachment', 'zeitfresser_store_original_file' ); +require_once get_template_directory() . '/inc/performance/performance.php'; /** * ------------------------------------------------------------------------ @@ -111,38 +67,42 @@ add_action( 'add_attachment', 'zeitfresser_store_original_file' ); */ function zeitfresser_setup() { - load_theme_textdomain( 'zeitfresser', get_template_directory() . '/languages' ); + load_theme_textdomain( 'zeitfresser', get_template_directory() . '/languages' ); - add_theme_support( 'automatic-feed-links' ); - add_theme_support( 'title-tag' ); - add_theme_support( 'post-thumbnails' ); + add_theme_support( 'automatic-feed-links' ); + add_theme_support( 'title-tag' ); + add_theme_support( 'post-thumbnails' ); - add_theme_support( 'html5', array( - 'search-form', - 'comment-form', - 'comment-list', - 'gallery', - 'caption', - 'style', - 'script', - )); + // Editor Styles + add_theme_support( 'editor-styles' ); + add_editor_style( 'assets/css/editor.css' ); - add_theme_support( 'customize-selective-refresh-widgets' ); + add_theme_support( 'html5', array( + 'search-form', + 'comment-form', + 'comment-list', + 'gallery', + 'caption', + 'style', + 'script', + )); - add_theme_support( 'custom-logo', array( - 'height' => 250, - 'width' => 250, - 'flex-width' => true, - 'flex-height' => true, - )); + add_theme_support( 'customize-selective-refresh-widgets' ); - add_theme_support( 'align-wide' ); - add_theme_support( 'wp-block-styles' ); - add_theme_support( 'responsive-embeds' ); + add_theme_support( 'custom-logo', array( + 'height' => 250, + 'width' => 250, + 'flex-width' => true, + 'flex-height' => true, + )); - register_nav_menus( array( - 'menu-1' => esc_html__( 'Primary', 'zeitfresser' ), - )); + add_theme_support( 'align-wide' ); + add_theme_support( 'wp-block-styles' ); + add_theme_support( 'responsive-embeds' ); + + register_nav_menus( array( + 'menu-1' => esc_html__( 'Primary', 'zeitfresser' ), + )); } add_action( 'after_setup_theme', 'zeitfresser_setup' ); @@ -152,8 +112,8 @@ add_action( 'after_setup_theme', 'zeitfresser_setup' ); * ------------------------------------------------------------------------ */ function zeitfresser_custom_image_sizes() { - add_image_size( 'zeitfresser-content', 720, 0, false ); - add_image_size( 'zeitfresser-card', 480, 0, false ); + add_image_size( 'zeitfresser-content', 720, 0, false ); + add_image_size( 'zeitfresser-card', 480, 0, false ); } add_action( 'after_setup_theme', 'zeitfresser_custom_image_sizes' ); @@ -163,7 +123,7 @@ add_action( 'after_setup_theme', 'zeitfresser_custom_image_sizes' ); * ------------------------------------------------------------------------ */ function zeitfresser_content_width() { - $GLOBALS['content_width'] = apply_filters( 'zeitfresser_content_width', 640 ); + $GLOBALS['content_width'] = apply_filters( 'zeitfresser_content_width', 640 ); } add_action( 'after_setup_theme', 'zeitfresser_content_width', 0 ); @@ -173,15 +133,15 @@ add_action( 'after_setup_theme', 'zeitfresser_content_width', 0 ); * ------------------------------------------------------------------------ */ function zeitfresser_widgets_init() { - register_sidebar( array( - 'name' => esc_html__( 'Sidebar', 'zeitfresser' ), - 'id' => 'sidebar-1', - 'description' => esc_html__( 'Add widgets here.', 'zeitfresser' ), - 'before_widget' => '
', - 'after_widget' => '
', - 'before_title' => '

', - 'after_title' => '

', - )); + register_sidebar( array( + 'name' => esc_html__( 'Sidebar', 'zeitfresser' ), + 'id' => 'sidebar-1', + 'description' => esc_html__( 'Add widgets here.', 'zeitfresser' ), + 'before_widget' => '
', + 'after_widget' => '
', + 'before_title' => '

', + 'after_title' => '

', + )); } add_action( 'widgets_init', 'zeitfresser_widgets_init' ); @@ -192,510 +152,65 @@ add_action( 'widgets_init', 'zeitfresser_widgets_init' ); */ function zeitfresser_scripts() { - // Base stylesheet (theme root) - wp_enqueue_style( - 'zeitfresser', - get_template_directory_uri() . '/style.css', - [], - file_exists( get_template_directory() . '/style.css' ) - ? filemtime( get_template_directory() . '/style.css' ) - : ZEITFRESSER_VERSION - ); + /** + * Base stylesheet + */ + wp_enqueue_style( + 'zeitfresser', + get_template_directory_uri() . '/style.css', + [], + file_exists( get_template_directory() . '/style.css' ) + ? filemtime( get_template_directory() . '/style.css' ) + : ZEITFRESSER_VERSION + ); - // Styles - $fonts = zeitfresser_asset_versioned('/css/fonts.css'); - $colors = zeitfresser_asset_versioned('/css/colors.css'); + /** + * Additional styles (versioned helper) + */ + $fonts = zeitfresser_asset_versioned('/css/fonts.css'); + $colors = zeitfresser_asset_versioned('/css/colors.css'); - wp_enqueue_style( - 'zeitfresser-fonts', - $fonts['url'], - [], - $fonts['version'] - ); + wp_enqueue_style( + 'zeitfresser-fonts', + $fonts['url'], + ['zeitfresser'], + $fonts['version'] + ); - wp_enqueue_style( - 'zeitfresser-colors', - $colors['url'], - ['zeitfresser'], - $colors['version'] - ); + wp_enqueue_style( + 'zeitfresser-colors', + $colors['url'], + ['zeitfresser'], + $colors['version'] + ); - // Scripts - $nav = zeitfresser_asset_versioned('/js/navigation.js'); - $scripts = zeitfresser_asset_versioned('/js/scripts.js'); + /** + * Scripts + */ + $nav = zeitfresser_asset_versioned('/js/navigation.js'); + $scripts = zeitfresser_asset_versioned('/js/scripts.js'); - wp_enqueue_script( - 'zeitfresser-navigation', - $nav['url'], - [], - $nav['version'], - true - ); + wp_enqueue_script( + 'zeitfresser-navigation', + $nav['url'], + [], + $nav['version'], + true + ); - wp_enqueue_script( - 'zeitfresser-scripts', - $scripts['url'], - [], - $scripts['version'], - true - ); + wp_enqueue_script( + 'zeitfresser-scripts', + $scripts['url'], + ['zeitfresser-navigation'], + $scripts['version'], + true + ); - // WordPress native threaded comments - if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { - wp_enqueue_script( 'comment-reply' ); - } + /** + * WordPress native threaded comments + */ + if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) { + wp_enqueue_script( 'comment-reply' ); + } } add_action( 'wp_enqueue_scripts', 'zeitfresser_scripts', 10 ); - -// Editor Styles -function zeitfresser_editor_styles_setup() { - add_theme_support( 'editor-styles' ); - add_editor_style( 'assets/css/editor.css' ); -} -add_action( 'after_setup_theme', 'zeitfresser_editor_styles_setup' ); - -/** - * ------------------------------------------------------------------------ - * Performance Tweaks - * ------------------------------------------------------------------------ - */ - - /** - * ------------------------------------------------------------------------ - * Defer non-critical JavaScript - * ------------------------------------------------------------------------ - * - * Prevents JS from blocking page rendering. - */ -function zeitfresser_defer_scripts( $tag, $handle, $src ) { - - $defer_scripts = array( - 'zeitfresser-navigation', - 'zeitfresser-scripts', - ); - - if ( in_array( $handle, $defer_scripts, true ) ) { - return str_replace( ' src=', ' defer src=', $tag ); - } - - return $tag; -} -add_filter( 'script_loader_tag', 'zeitfresser_defer_scripts', 10, 3 ); - -/** - * ------------------------------------------------------------------------ - * Image Loading Optimization (LCP + Lazy Loading) - * ------------------------------------------------------------------------ - * - * Ensures the first visible image loads immediately (LCP), - * while all other images are lazy-loaded for performance. - */ -function zeitfresser_optimize_image_attributes( $attr, $attachment, $size ) { - - static $is_first = true; - - if ( ! is_admin() ) { - - if ( $is_first ) { - // First image (critical for LCP) - $attr['loading'] = 'eager'; - $is_first = false; - } else { - // All other images - $attr['loading'] = 'lazy'; - } - - // Improve decoding performance - $attr['decoding'] = 'async'; - } - - return $attr; -} -add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_optimize_image_attributes', 10, 3 ); - -/** - * Lower the threshold for WordPress scaled originals when auto optimization is enabled. - * - * When automatic optimization is disabled, original uploads should remain untouched. - * - * @return int|false - */ -function zeitfresser_big_image_size_threshold() { - - $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true ); - $force_enabled = ! empty( $GLOBALS['zeitfresser_force_image_optimization'] ); - - if ( ! $auto_enabled && ! $force_enabled ) { - return false; - } - - return 1800; -} -add_filter( 'big_image_size_threshold', 'zeitfresser_big_image_size_threshold' ); - -/** - * Skip generating oversized core intermediate sizes we do not use. - * - * @param array $sizes Registered intermediate sizes. - * @return array - */ -function zeitfresser_filter_intermediate_image_sizes( $sizes ) { - - $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true ); - $force_enabled = ! empty( $GLOBALS['zeitfresser_force_image_optimization'] ); - - if ( ! $auto_enabled && ! $force_enabled ) { - return $sizes; - } - - unset( - $sizes['1536x1536'], - $sizes['2048x2048'] - ); - - return $sizes; -} -add_filter( 'intermediate_image_sizes_advanced', 'zeitfresser_filter_intermediate_image_sizes' ); - -/** - * Improve attachment image attributes for layout stability and fetch priority. - * - * @param array $attr Image markup attributes. - * @param WP_Post $attachment Attachment post object. - * @param string|array $size Requested image size. - * @return array - */ -function zeitfresser_improve_attachment_dimensions( $attr, $attachment, $size ) { - - if ( empty( $attr['width'] ) || empty( $attr['height'] ) ) { - $metadata = wp_get_attachment_metadata( $attachment->ID ); - - if ( is_array( $metadata ) && ! empty( $metadata['width'] ) && ! empty( $metadata['height'] ) ) { - if ( empty( $attr['width'] ) ) { - $attr['width'] = (int) $metadata['width']; - } - - if ( empty( $attr['height'] ) ) { - $attr['height'] = (int) $metadata['height']; - } - } - } - - if ( empty( $attr['fetchpriority'] ) && ! is_admin() && ! is_feed() ) { - static $did_set_high_priority = false; - - if ( ! $did_set_high_priority && is_singular() ) { - $attr['fetchpriority'] = 'high'; - $did_set_high_priority = true; - } - } - - return $attr; -} -add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_improve_attachment_dimensions', 11, 3 ); - -/** - * ------------------------------------------------------------------------ - * Preload critical fonts - * ------------------------------------------------------------------------ - * - * Preloads only the fonts that are needed for initial rendering - * (headings + body text). This improves LCP and avoids render delays. - */ -function zeitfresser_preload_fonts() { - ?> - - - - - - get_template_directory_uri(), - 'crossorigin' => 'anonymous', - ]; - } - - return $urls; - -}, 10, 2 ); - -/** - * ------------------------------------------------------------------------ - * Critical CSS (inline for faster first render) - * ------------------------------------------------------------------------ - * - * We inline only the minimal CSS required for initial layout. - * This ensures the page structure renders immediately without - * waiting for the full stylesheet. - */ -function zeitfresser_inline_critical_css() { - ?> - - '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 ); diff --git a/inc/performance/performance.php b/inc/performance/performance.php new file mode 100644 index 0000000..ba60efd --- /dev/null +++ b/inc/performance/performance.php @@ -0,0 +1,236 @@ +ID ); + + if ( is_array( $metadata ) && ! empty( $metadata['width'] ) && ! empty( $metadata['height'] ) ) { + if ( empty( $attr['width'] ) ) { + $attr['width'] = (int) $metadata['width']; + } + + if ( empty( $attr['height'] ) ) { + $attr['height'] = (int) $metadata['height']; + } + } + } + + if ( empty( $attr['fetchpriority'] ) && ! is_admin() && ! is_feed() ) { + static $did_set_high_priority = false; + + if ( ! $did_set_high_priority && is_singular() ) { + $attr['fetchpriority'] = 'high'; + $did_set_high_priority = true; + } + } + + return $attr; +} +add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_improve_attachment_dimensions', 11, 3 ); + +/** + * ------------------------------------------------------------------------ + * Preload critical fonts + * ------------------------------------------------------------------------ + * + * Preloads only the fonts that are needed for initial rendering + * (headings + body text). This improves LCP and avoids render delays. + */ +function zeitfresser_preload_fonts() { + ?> + + + + + + get_template_directory_uri(), + 'crossorigin' => 'anonymous', + ]; + } + + return $urls; + +}, 10, 2 ); + +/** + * ------------------------------------------------------------------------ + * Critical CSS (inline for faster first render) + * ------------------------------------------------------------------------ + * + * We inline only the minimal CSS required for initial layout. + * This ensures the page structure renders immediately without + * waiting for the full stylesheet. + */ +function zeitfresser_inline_critical_css() { + ?> + + '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 */