2 Commits

Author SHA1 Message Date
Dome af8a9447b0 Update customizer.php 2026-04-25 02:26:19 +02:00
Dome b0eb1d9526 feat(performance): introduce image optimization pipeline with optional automation and cleanup
- Add AVIF/WebP conversion for uploads and legacy media
- Implement manual batch optimizer via Performance Tools dashboard
- Introduce automatic optimization toggle via Customizer
- Add optional automatic deletion of original images after optimization
- Ensure safe processing with versioned metadata and idempotent operations
- Decouple manual optimization from automation logic using force flag
- Add live progress UI for optimization and cleanup processes
- Improve UX with status indicators, dependency handling and warnings
2026-04-25 02:18:25 +02:00
5 changed files with 1024 additions and 264 deletions
+343 -39
View File
@@ -4,7 +4,7 @@
* *
* @package zeitfresser * @package zeitfresser
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
@@ -17,10 +17,68 @@ if ( ! defined( 'DAISY_BLOG_VERSION' ) ) {
define( 'DAISY_BLOG_VERSION', ZEITFRESSER_VERSION ); define( 'DAISY_BLOG_VERSION', ZEITFRESSER_VERSION );
} }
if ( ! defined( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION' ) ) {
define( 'ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION', '1.0' );
}
require get_template_directory() . '/inc/zeitfresser-helpers.php'; require get_template_directory() . '/inc/zeitfresser-helpers.php';
require get_template_directory() . '/inc/performance-tools.php'; require get_template_directory() . '/inc/performance-tools.php';
require get_template_directory() . '/inc/zeitfresser-toc.php'; require get_template_directory() . '/inc/zeitfresser-toc.php';
/**
* Upload Handler
*/
add_filter('wp_handle_upload', 'zeitfresser_capture_original_upload', 10, 2);
/**
* Capture original upload path safely
*/
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' );
/** /**
* Theme setup. * Theme setup.
* *
@@ -68,6 +126,19 @@ function zeitfresser_setup() {
} }
add_action( 'after_setup_theme', 'zeitfresser_setup' ); add_action( 'after_setup_theme', 'zeitfresser_setup' );
/**
* Register optimized image sizes
*/
function zeitfresser_custom_image_sizes() {
// Content images (main article)
add_image_size( 'zeitfresser-content', 720, 0, false );
// Archive / card layout
add_image_size( 'zeitfresser-card', 480, 0, false );
}
add_action( 'after_setup_theme', 'zeitfresser_custom_image_sizes' );
/** /**
* Set the content width in pixels. * Set the content width in pixels.
* *
@@ -268,21 +339,34 @@ function zeitfresser_cleanup_wp_head() {
add_action( 'init', 'zeitfresser_cleanup_wp_head' ); add_action( 'init', 'zeitfresser_cleanup_wp_head' );
/** /**
* Preload local fonts for better performance * Preload critical local fonts only
*/ */
function zeitfresser_preload_fonts() { function zeitfresser_preload_fonts() {
?> ?>
<!-- Critical Fonts Only -->
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/oswald-400.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/oswald-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/oswald-500.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/oswald-700.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/oswald-700.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/roboto-400.woff2" as="font" type="font/woff2" crossorigin> <link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/roboto-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/roboto-500.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo get_template_directory_uri(); ?>/fonts/roboto-700.woff2" as="font" type="font/woff2" crossorigin>
<?php <?php
} }
add_action('wp_head', 'zeitfresser_preload_fonts', 1); add_action('wp_head', 'zeitfresser_preload_fonts', 1);
/**
* Optimize font loading with preconnect
*/
add_filter( 'wp_resource_hints', function( $urls, $relation_type ) {
if ( 'preconnect' === $relation_type ) {
$urls[] = [
'href' => get_template_directory_uri(),
'crossorigin' => 'anonymous',
];
}
return $urls;
}, 10, 2 );
/** /**
* Remove front-end dashicons for visitors. * Remove front-end dashicons for visitors.
* *
@@ -317,13 +401,18 @@ function zeitfresser_optimize_image_attributes( $attr, $attachment, $size ) {
add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_optimize_image_attributes', 10, 3 ); add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_optimize_image_attributes', 10, 3 );
/** /**
* Lower the threshold for WordPress scaled originals. * Lower the threshold for WordPress scaled originals when auto optimization is enabled.
* *
* This prevents very large uploads from shipping oversized source images. * When automatic optimization is disabled, original uploads should remain untouched.
* *
* @return int * @return int|false
*/ */
function zeitfresser_big_image_size_threshold() { function zeitfresser_big_image_size_threshold() {
if ( ! get_theme_mod( 'ztfr_auto_optimize', true ) ) {
return false;
}
return 1800; return 1800;
} }
add_filter( 'big_image_size_threshold', 'zeitfresser_big_image_size_threshold' ); add_filter( 'big_image_size_threshold', 'zeitfresser_big_image_size_threshold' );
@@ -335,28 +424,184 @@ add_filter( 'big_image_size_threshold', 'zeitfresser_big_image_size_threshold' )
* @return array * @return array
*/ */
function zeitfresser_filter_intermediate_image_sizes( $sizes ) { function zeitfresser_filter_intermediate_image_sizes( $sizes ) {
unset( $sizes['1536x1536'], $sizes['2048x2048'] );
// Remove oversized defaults
unset(
$sizes['1536x1536'],
$sizes['2048x2048']
);
return $sizes; return $sizes;
} }
add_filter( 'intermediate_image_sizes_advanced', 'zeitfresser_filter_intermediate_image_sizes' ); add_filter( 'intermediate_image_sizes_advanced', 'zeitfresser_filter_intermediate_image_sizes' );
/** /**
* Convert generated JPEG and PNG sub-sizes to WebP when supported by the server. * Convert generated JPEG and PNG files to AVIF/WebP when enabled.
*
* Auto optimization can be disabled for uploads via Customizer.
* Manual optimization may still force conversion through a request-scoped flag.
* *
* @param array $formats Output format map. * @param array $formats Output format map.
* @return array * @return array
*/ */
function zeitfresser_image_output_format( $formats ) { function zeitfresser_image_output_format( $formats ) {
if ( function_exists( 'wp_image_editor_supports' ) && wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
$formats['image/jpeg'] = 'image/webp'; $auto_enabled = get_theme_mod( 'ztfr_auto_optimize', true );
$formats['image/png'] = 'image/webp'; $force_enabled = ! empty( $GLOBALS['zeitfresser_force_image_optimization'] );
if ( ! $auto_enabled && ! $force_enabled ) {
return $formats;
}
if ( function_exists( 'wp_image_editor_supports' ) ) {
// Prefer AVIF if supported.
if ( wp_image_editor_supports( array( 'mime_type' => '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; return $formats;
} }
add_filter( 'image_editor_output_format', 'zeitfresser_image_output_format' ); 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 upload-captured original
if ( ! get_post_meta( $attachment_id, '_zeitfresser_original_file', true ) ) {
// fallback only
update_post_meta( $attachment_id, '_zeitfresser_original_file', $file );
}
// mark as optimized
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 ) {
// 🔒 only images
if ( ! wp_attachment_is_image( $attachment_id ) ) {
return $metadata;
}
// 🔒 feature toggles
if ( ! get_theme_mod( 'ztfr_auto_optimize', true ) ) {
return $metadata;
}
if ( ! get_theme_mod( 'ztfr_auto_delete', false ) ) {
return $metadata;
}
$original = get_post_meta(
$attachment_id,
'_zeitfresser_original_file',
true
);
if ( ! $original || ! file_exists( $original ) ) {
return $metadata;
}
// skip modern formats
$ext = strtolower( pathinfo( $original, PATHINFO_EXTENSION ) );
if ( in_array( $ext, [ 'webp', 'avif' ], true ) ) {
update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 );
return $metadata;
}
if ( ! is_writable( $original ) ) {
return $metadata;
}
// 🔥 delete original
if ( unlink( $original ) ) {
update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 );
}
return $metadata;
}
/** /**
* Keep generated image quality balanced for file size and visual fidelity. * Keep generated image quality balanced for file size and visual fidelity.
* *
@@ -365,11 +610,18 @@ add_filter( 'image_editor_output_format', 'zeitfresser_image_output_format' );
* @return int * @return int
*/ */
function zeitfresser_image_quality( $quality, $mime_type = 'image/jpeg' ) { function zeitfresser_image_quality( $quality, $mime_type = 'image/jpeg' ) {
if ( 'image/png' === $mime_type ) {
return $quality;
}
return 82; switch ( $mime_type ) {
case 'image/avif':
return 50;
case 'image/webp':
return 75;
case 'image/jpeg':
default:
return 82;
}
} }
add_filter( 'wp_editor_set_quality', 'zeitfresser_image_quality', 10, 2 ); add_filter( 'wp_editor_set_quality', 'zeitfresser_image_quality', 10, 2 );
@@ -399,7 +651,7 @@ function zeitfresser_improve_attachment_dimensions( $attr, $attachment, $size )
if ( empty( $attr['fetchpriority'] ) && ! is_admin() && ! is_feed() ) { if ( empty( $attr['fetchpriority'] ) && ! is_admin() && ! is_feed() ) {
static $did_set_high_priority = false; static $did_set_high_priority = false;
if ( ! $did_set_high_priority && ( is_home() || is_front_page() || is_archive() || is_search() || is_singular() ) ) { if ( ! $did_set_high_priority && is_singular() ) {
$attr['fetchpriority'] = 'high'; $attr['fetchpriority'] = 'high';
$did_set_high_priority = true; $did_set_high_priority = true;
} }
@@ -409,6 +661,62 @@ function zeitfresser_improve_attachment_dimensions( $attr, $attachment, $size )
} }
add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_improve_attachment_dimensions', 11, 3 ); add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_improve_attachment_dimensions', 11, 3 );
/**
* 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 );
/**
* Determine the most likely LCP image URL.
*
* @return string
*/
function zeitfresser_get_lcp_image_url() {
// Single posts/pages → featured image
if ( is_singular() ) {
$post_id = get_queried_object_id();
if ( $post_id && has_post_thumbnail( $post_id ) ) {
return get_the_post_thumbnail_url( $post_id, 'large' );
}
}
// Archives / homepage → first post with thumbnail
if ( is_home() || is_front_page() || is_archive() || is_search() ) {
global $wp_query;
if ( ! empty( $wp_query->posts ) ) {
foreach ( $wp_query->posts as $post ) {
if ( has_post_thumbnail( $post->ID ) ) {
return get_the_post_thumbnail_url( $post->ID, 'medium_large' );
}
}
}
}
return '';
}
/** /**
* Preload the most likely LCP image for archive and singular views. * Preload the most likely LCP image for archive and singular views.
@@ -417,39 +725,35 @@ add_filter( 'wp_get_attachment_image_attributes', 'zeitfresser_improve_attachmen
* @return array * @return array
*/ */
function zeitfresser_preload_resources( $resources ) { function zeitfresser_preload_resources( $resources ) {
if ( is_admin() || is_feed() || is_embed() ) { if ( is_admin() || is_feed() || is_embed() ) {
return $resources; return $resources;
} }
$image_url = ''; // 🔥 NEW: use smart detection
$image_type = ''; $image_url = zeitfresser_get_lcp_image_url();
if ( is_singular() ) {
$object_id = get_queried_object_id();
if ( $object_id && has_post_thumbnail( $object_id ) ) {
$image_url = get_the_post_thumbnail_url( $object_id, 'large' );
}
} elseif ( is_home() || is_front_page() || is_archive() || is_search() ) {
global $wp_query;
if ( isset( $wp_query->posts[0]->ID ) && has_post_thumbnail( $wp_query->posts[0]->ID ) ) {
$image_url = get_the_post_thumbnail_url( $wp_query->posts[0]->ID, 'thumbnail' );
}
}
if ( empty( $image_url ) ) { if ( empty( $image_url ) ) {
return $resources; return $resources;
} }
$extension = strtolower( pathinfo( wp_parse_url( $image_url, PHP_URL_PATH ), PATHINFO_EXTENSION ) ); // Robust extension detection
$extension = strtolower(
pathinfo(
wp_parse_url( $image_url, PHP_URL_PATH ),
PATHINFO_EXTENSION
)
);
if ( 'jpg' === $extension || 'jpeg' === $extension ) { // MIME fallback
$image_type = 'image/jpeg'; $image_type = 'image/jpeg';
} elseif ( 'png' === $extension ) {
if ( 'png' === $extension ) {
$image_type = 'image/png'; $image_type = 'image/png';
} elseif ( 'webp' === $extension ) { } elseif ( 'webp' === $extension ) {
$image_type = 'image/webp'; $image_type = 'image/webp';
} elseif ( 'avif' === $extension ) {
$image_type = 'image/avif';
} }
$resources[] = array_filter( $resources[] = array_filter(
+179 -2
View File
@@ -9,8 +9,10 @@
* Add postMessage support for site title and description for the Theme Customizer. * Add postMessage support for site title and description for the Theme Customizer.
* *
* @param WP_Customize_Manager $wp_customize Theme Customizer object. * @param WP_Customize_Manager $wp_customize Theme Customizer object.
* @return void
*/ */
function zeitfresser_customize_register( $wp_customize ) { function zeitfresser_customize_register( $wp_customize ) {
$wp_customize->get_setting( 'blogname' )->transport = 'postMessage'; $wp_customize->get_setting( 'blogname' )->transport = 'postMessage';
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage'; $wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';
$wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage'; $wp_customize->get_setting( 'header_textcolor' )->transport = 'postMessage';
@@ -23,6 +25,7 @@ function zeitfresser_customize_register( $wp_customize ) {
'render_callback' => 'zeitfresser_customize_partial_blogname', 'render_callback' => 'zeitfresser_customize_partial_blogname',
) )
); );
$wp_customize->selective_refresh->add_partial( $wp_customize->selective_refresh->add_partial(
'blogdescription', 'blogdescription',
array( array(
@@ -31,6 +34,59 @@ function zeitfresser_customize_register( $wp_customize ) {
) )
); );
} }
/**
* Performance Tools section
*/
$wp_customize->add_section(
'ztfr_performance_tools',
array(
'title' => 'Performance Tools Settings',
'priority' => 160,
)
);
/**
* Auto optimize uploaded images.
*/
$wp_customize->add_setting(
'ztfr_auto_optimize',
array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'ztfr_auto_optimize',
array(
'type' => 'checkbox',
'section' => 'ztfr_performance_tools',
'label' => 'Auto Optimize Pictures on Upload',
'description' => 'Automatically converts uploaded images to modern formats (AVIF/WebP) for improved performance.',
)
);
/**
* Auto delete originals after successful optimization.
*/
$wp_customize->add_setting(
'ztfr_auto_delete',
array(
'default' => false,
'sanitize_callback' => 'wp_validate_boolean',
)
);
$wp_customize->add_control(
'ztfr_auto_delete',
array(
'type' => 'checkbox',
'section' => 'ztfr_performance_tools',
'label' => 'Auto Delete Original Pictures on Upload',
'description' => 'Automatically deletes original images after optimization. ⚠️ This action cannot be undone.',
)
);
} }
add_action( 'customize_register', 'zeitfresser_customize_register' ); add_action( 'customize_register', 'zeitfresser_customize_register' );
@@ -53,9 +109,130 @@ function zeitfresser_customize_partial_blogdescription() {
} }
/** /**
* Binds JS handlers to make Theme Customizer preview reload changes asynchronously. * Bind JS handlers to make Theme Customizer preview reload changes asynchronously.
*
* @return void
*/ */
function zeitfresser_customize_preview_js() { function zeitfresser_customize_preview_js() {
wp_enqueue_script( 'zeitfresser-customizer', get_template_directory_uri() . '/js/customizer.js', array( 'customize-preview' ), ZEITFRESSER_VERSION, true ); wp_enqueue_script(
'zeitfresser-customizer',
get_template_directory_uri() . '/js/customizer.js',
array( 'customize-preview' ),
ZEITFRESSER_VERSION,
true
);
} }
add_action( 'customize_preview_init', 'zeitfresser_customize_preview_js' ); add_action( 'customize_preview_init', 'zeitfresser_customize_preview_js' );
/**
* Add dependency logic and status box for Performance Tools settings.
*
* @return void
*/
function zeitfresser_customize_controls_dependency_js() {
?>
<script>
(function() {
function getOptimizeInput() {
return document.querySelector('#customize-control-ztfr_auto_optimize input[type="checkbox"]');
}
function getDeleteInput() {
return document.querySelector('#customize-control-ztfr_auto_delete input[type="checkbox"]');
}
function getDeleteControl() {
return document.getElementById('customize-control-ztfr_auto_delete');
}
function ensureStatusBox() {
let box = document.getElementById('ztfr-auto-status-box');
if (box) {
return box;
}
const optimizeControl = document.getElementById('customize-control-ztfr_auto_optimize');
if (!optimizeControl || !optimizeControl.parentNode) {
return null;
}
box = document.createElement('li');
box.id = 'ztfr-auto-status-box';
box.className = 'customize-control';
box.innerHTML =
'<span style="display:block;font-weight:600;margin-bottom:6px;">Current Mode</span>' +
'<span id="ztfr-auto-status-text">Checking...</span>';
optimizeControl.parentNode.insertBefore(box, optimizeControl);
return box;
}
function updateState() {
const optimizeInput = getOptimizeInput();
const deleteInput = getDeleteInput();
const deleteControl = getDeleteControl();
const statusBox = ensureStatusBox();
const statusText = document.getElementById('ztfr-auto-status-text');
if (!optimizeInput || !deleteInput || !deleteControl || !statusBox || !statusText) {
return;
}
if (!optimizeInput.checked) {
deleteInput.checked = false;
deleteInput.disabled = true;
deleteControl.style.opacity = '0.5';
statusText.textContent = '⚪ Manual Mode (no automation)';
} else {
deleteInput.disabled = false;
deleteControl.style.opacity = '1';
if (deleteInput.checked) {
statusText.textContent = '🟢 Full Auto Mode (optimize + delete)';
} else {
statusText.textContent = '🟡 Auto Optimize enabled (originals kept)';
}
}
}
function init() {
updateState();
document.addEventListener('change', function(event) {
const target = event.target;
if (
target &&
(
target.matches('#customize-control-ztfr_auto_optimize input[type="checkbox"]') ||
target.matches('#customize-control-ztfr_auto_delete input[type="checkbox"]')
)
) {
updateState();
}
});
let tries = 0;
const interval = setInterval(function() {
updateState();
tries++;
if (tries > 20) {
clearInterval(interval);
}
}, 300);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<?php
}
add_action( 'customize_controls_enqueue_scripts', 'zeitfresser_customize_controls_dependency_js' );
+496 -220
View File
@@ -1,272 +1,548 @@
<?php <?php
/** /**
* Performance tools for existing media and webfonts. * Performance tools for existing media.
* *
* @package zeitfresser * @package zeitfresser
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
/** /**
* Register the performance tools admin page. * Register admin page
*
* @return void
*/ */
function zeitfresser_register_performance_tools_page() { function zeitfresser_register_performance_tools_page() {
add_theme_page( add_theme_page(
esc_html__( 'Performance Tools', 'zeitfresser' ), 'Performance Tools',
esc_html__( 'Performance Tools', 'zeitfresser' ), 'Performance Tools',
'manage_options', 'manage_options',
'zeitfresser-performance-tools', 'zeitfresser-performance-tools',
'zeitfresser_render_performance_tools_page' 'zeitfresser_render_performance_tools_page'
); );
} }
add_action( 'admin_menu', 'zeitfresser_register_performance_tools_page' ); add_action( 'admin_menu', 'zeitfresser_register_performance_tools_page' );
/** /**
* Count attachments still waiting for one-time optimization. * Count pending images
*
* @return int
*/ */
function zeitfresser_get_pending_legacy_images_count() { function zeitfresser_get_pending_legacy_images_count() {
$query = new WP_Query( $query = new WP_Query([
array( 'post_type' => 'attachment',
'post_type' => 'attachment', 'post_status' => 'inherit',
'post_status' => 'inherit', 'post_mime_type' => 'image',
'post_mime_type' => 'image', 'fields' => 'ids',
'fields' => 'ids', 'posts_per_page' => 1,
'posts_per_page' => 1, 'meta_query' => [
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'relation' => 'OR',
array( [
'key' => '_zeitfresser_media_optimized', 'key' => '_zeitfresser_media_optimized_version',
'compare' => 'NOT EXISTS', 'compare' => 'NOT EXISTS',
), ],
), [
'no_found_rows' => false, 'key' => '_zeitfresser_media_optimized_version',
'cache_results' => false, 'value' => ZEITFRESSER_IMAGE_OPTIMIZATION_VERSION,
'update_post_meta_cache' => false, 'compare' => '!=',
'update_post_term_cache' => false, ],
) ],
); 'no_found_rows' => false,
]);
return (int) $query->found_posts; return (int) $query->found_posts;
} }
/** /**
* Process a batch of legacy images with current thumbnail/webp rules. * 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']
],
'no_found_rows'=>false
]);
return (int) $query->found_posts;
}
/**
* DELETE ORIGINALS (UNCHANGED)
*/
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;
}
if ( ! file_exists( $original ) ) {
update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 );
continue;
}
if ( ! is_writable( $original ) ) {
continue;
}
if ( unlink( $original ) ) {
$deleted++;
update_post_meta( $attachment_id, '_zeitfresser_original_deleted', 1 );
}
}
return $deleted;
}
/**
* Optimizer batch for manual processing.
* *
* @param int $batch_size Number of images to process. * Manual optimization must work independently of the auto-optimize upload toggle.
*
* @param int $batch_size Number of images per batch.
* @return array * @return array
*/ */
function zeitfresser_process_legacy_images_batch( $batch_size = 20 ) { function zeitfresser_process_legacy_images_batch( $batch_size = 25 ) {
$results = array(
'processed' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
);
$query = new WP_Query( $results = [
array( 'processed' => 0,
'post_type' => 'attachment', 'updated' => 0,
'post_status' => 'inherit', 'skipped' => 0,
'post_mime_type' => 'image', 'errors' => 0,
'fields' => 'ids', ];
'posts_per_page' => absint( $batch_size ),
'orderby' => 'ID',
'order' => 'ASC',
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
array(
'key' => '_zeitfresser_media_optimized',
'compare' => 'NOT EXISTS',
),
),
'no_found_rows' => true,
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
if ( empty( $query->posts ) ) { $query = new WP_Query([
return $results; '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' => '!=',
],
],
]);
foreach ( $query->posts as $attachment_id ) { // Force optimization for manual tool runs, regardless of upload automation setting.
$results['processed']++; $GLOBALS['zeitfresser_force_image_optimization'] = true;
$file = get_attached_file( $attachment_id ); foreach ( $query->posts as $attachment_id ) {
if ( empty( $file ) || ! file_exists( $file ) ) { $results['processed']++;
update_post_meta( $attachment_id, '_zeitfresser_media_optimized', 'missing' );
$results['skipped']++;
continue;
}
$metadata = wp_generate_attachment_metadata( $attachment_id, $file ); $file = get_attached_file( $attachment_id );
if ( is_wp_error( $metadata ) || empty( $metadata ) ) { if ( empty( $file ) || ! file_exists( $file ) ) {
$results['errors']++; update_post_meta( $attachment_id, '_zeitfresser_media_optimized_version', 'missing' );
continue; $results['skipped']++;
} continue;
}
wp_update_attachment_metadata( $attachment_id, $metadata ); if ( ! get_post_meta( $attachment_id, '_zeitfresser_original_file', true ) ) {
update_post_meta( $attachment_id, '_zeitfresser_media_optimized', current_time( 'mysql' ) ); update_post_meta( $attachment_id, '_zeitfresser_original_file', $file );
$results['updated']++; }
}
return $results; $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;
} }
/** /**
* Handle admin actions for performance tools. * AJAX: Optimizer (extended output only)
*
* @return void
*/ */
function zeitfresser_handle_performance_tools_actions() { function zeitfresser_ajax_optimize_images() {
if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
return;
}
if ( empty( $_GET['page'] ) || 'zeitfresser-performance-tools' !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! current_user_can( 'manage_options' ) ) {
return; wp_send_json_error();
} }
if ( empty( $_GET['zeitfresser_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended check_ajax_referer( 'zeitfresser_performance_tools', 'nonce' );
return;
}
check_admin_referer( 'zeitfresser_performance_tools' ); $results = zeitfresser_process_legacy_images_batch( 25 );
$action = sanitize_key( wp_unslash( $_GET['zeitfresser_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended wp_send_json_success([
'processed' => $results['processed'],
if ( 'optimize_legacy_images' === $action ) { 'updated' => $results['updated'],
$results = zeitfresser_process_legacy_images_batch( 25 ); 'pending' => zeitfresser_get_pending_legacy_images_count(),
$args = array( 'total' => zeitfresser_get_total_images_count(),
'page' => 'zeitfresser-performance-tools', ]);
'processed' => $results['processed'],
'updated' => $results['updated'],
'skipped' => $results['skipped'],
'errors' => $results['errors'],
);
wp_safe_redirect( add_query_arg( $args, admin_url( 'themes.php' ) ) );
exit;
}
if ( 'reset_legacy_images' === $action ) {
$query = new WP_Query(
array(
'post_type' => 'attachment',
'post_status' => 'inherit',
'post_mime_type' => 'image',
'fields' => 'ids',
'posts_per_page' => -1,
'meta_key' => '_zeitfresser_media_optimized', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
'no_found_rows' => true,
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
)
);
foreach ( $query->posts as $attachment_id ) {
delete_post_meta( $attachment_id, '_zeitfresser_media_optimized' );
}
wp_safe_redirect(
add_query_arg(
array(
'page' => 'zeitfresser-performance-tools',
'reset' => count( $query->posts ),
),
admin_url( 'themes.php' )
)
);
exit;
}
} }
add_action( 'admin_init', 'zeitfresser_handle_performance_tools_actions' ); add_action( 'wp_ajax_zeitfresser_optimize_images', 'zeitfresser_ajax_optimize_images' );
/** /**
* Render the performance tools page. * AJAX: Delete (extended ONLY)
* */
* @return void function zeitfresser_ajax_delete_originals() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error();
}
check_ajax_referer( 'zeitfresser_performance_tools', 'nonce' );
$deleted = zeitfresser_delete_originals_batch( 10 );
$total = zeitfresser_get_total_originals_count();
$remaining = zeitfresser_get_remaining_originals_count();
wp_send_json_success([
'deleted' => $deleted,
'total' => $total,
'remaining' => $remaining,
'deleted_total' => $total - $remaining,
]);
}
add_action( 'wp_ajax_zeitfresser_delete_originals', 'zeitfresser_ajax_delete_originals' );
/**
* Render UI
*/ */
function zeitfresser_render_performance_tools_page() { function zeitfresser_render_performance_tools_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$pending = zeitfresser_get_pending_legacy_images_count(); if ( ! current_user_can( 'manage_options' ) ) {
$local = function_exists( 'zeitfresser_get_local_webfonts_css' ) ? zeitfresser_get_local_webfont_urls( zeitfresser_get_local_webfonts_css() ) : array(); return;
?> }
<div class="wrap">
<h1><?php esc_html_e( 'Zeitfresser Performance Tools', 'zeitfresser' ); ?></h1>
<p><?php esc_html_e( 'Use these tools after major performance updates to warm local fonts and reprocess older uploads with the current image rules.', 'zeitfresser' ); ?></p>
<?php if ( isset( $_GET['updated'] ) || isset( $_GET['reset'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?> $pending = zeitfresser_get_pending_legacy_images_count();
<div class="notice notice-success is-dismissible"> $total = zeitfresser_get_total_images_count();
<p> $optimized = $total - $pending;
<?php $progress = $total > 0 ? round(($optimized / $total) * 100) : 0;
if ( isset( $_GET['reset'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
printf(
esc_html__( 'Reset complete. %d legacy image markers removed.', 'zeitfresser' ),
absint( $_GET['reset'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
);
} else {
printf(
esc_html__( 'Batch finished. Processed: %1$d, updated: %2$d, skipped: %3$d, errors: %4$d.', 'zeitfresser' ),
absint( $_GET['processed'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
absint( $_GET['updated'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
absint( $_GET['skipped'] ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
absint( $_GET['errors'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
);
}
?>
</p>
</div>
<?php endif; ?>
<div class="card" style="max-width: 880px; padding: 20px;"> // 🔥 NEW: Cleanup counters
<h2><?php esc_html_e( 'Legacy Image Optimization', 'zeitfresser' ); ?></h2> $cleanup_total = zeitfresser_get_total_originals_count();
<p> $cleanup_remaining = zeitfresser_get_remaining_originals_count();
<?php $cleanup_deleted = $cleanup_total - $cleanup_remaining;
printf( $cleanup_progress = $cleanup_total > 0 ? round(($cleanup_deleted / $cleanup_total) * 100) : 0;
esc_html__( '%d image attachments have not been reprocessed with the current size, quality and WebP rules yet.', 'zeitfresser' ), ?>
(int) $pending
);
?>
</p>
<p><?php esc_html_e( 'Run the batch button repeatedly until the counter reaches zero. This keeps each request small and safe on shared hosting.', 'zeitfresser' ); ?></p>
<p>
<a class="button button-primary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'page' => 'zeitfresser-performance-tools', 'zeitfresser_action' => 'optimize_legacy_images' ), admin_url( 'themes.php' ) ), 'zeitfresser_performance_tools' ) ); ?>">
<?php esc_html_e( 'Process Next 25 Images', 'zeitfresser' ); ?>
</a>
<a class="button" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'page' => 'zeitfresser-performance-tools', 'zeitfresser_action' => 'reset_legacy_images' ), admin_url( 'themes.php' ) ), 'zeitfresser_performance_tools' ) ); ?>">
<?php esc_html_e( 'Reset Progress', 'zeitfresser' ); ?>
</a>
</p>
</div>
<div class="card" style="max-width: 880px; padding: 20px; margin-top: 20px;"> <div class="wrap">
<h2><?php esc_html_e( 'Local Font Warmup', 'zeitfresser' ); ?></h2> <h1>Zeitfresser Performance Tools</h1>
<p><?php esc_html_e( 'Zeitfresser now prefers locally cached Google font files for the currently selected font families. When the cache is available, the theme preloads the local files and no longer needs the external stylesheet request.', 'zeitfresser' ); ?></p>
<p> <div class="notice notice-info" style="max-width:800px;margin-top:20px;">
<?php <p>
if ( empty( $local ) ) { <strong>How this tool works</strong><br><br>
esc_html_e( 'Local font files are not cached yet. Visit the front-end once after saving your font settings to warm the cache.', 'zeitfresser' );
} else { This tool helps you optimize your existing media library for better performance.<br><br>
printf(
esc_html__( 'Local font files ready: %d preloadable assets detected.', 'zeitfresser' ), • Images are converted to modern formats (AVIF/WebP) for smaller file sizes.<br>
count( $local ) • The original file path is safely stored before optimization.<br>
); • Once optimized, original images can be deleted to save disk space.<br><br>
}
?> <strong>Automation:</strong><br>
</p> • You can enable automatic optimization on upload in the Customizer under <em>Performance Tools Settings</em>.<br>
</div> • Optionally, original images can also be deleted automatically after successful optimization.<br><br>
</div>
<?php <strong>Safety:</strong><br>
• Images are only processed once per version.<br>
• Original files are only deleted when safe.<br>
• The tool can be run multiple times without side effects.<br><br>
<em><strong>Tip:</strong> You can either automate the process via the Customizer or use this tool manually for full control.</em>
</p>
</div>
<!-- OPTIMIZATION -->
<div class="card" style="max-width:800px;padding:24px;margin-bottom:20px;">
<h2 style="margin-top:0;">🚀 Image Optimization</h2>
<div style="margin-bottom:20px;">
<p><strong>Total Images:</strong> <span id="total"><?php echo $total; ?></span></p>
<p><strong>Optimized:</strong> <span id="optimized"><?php echo $optimized; ?></span></p>
<p><strong>Pending:</strong> <span id="remaining"><?php echo $pending; ?></span></p>
</div>
<div style="margin-bottom:20px;">
<div style="background:#e0e0e0;border-radius:6px;height:12px;">
<div id="progress-bar" style="width:<?php echo $progress; ?>%;background:#4caf50;height:100%;"></div>
</div>
<p><strong>Progress:</strong> <span id="progress"><?php echo $progress; ?></span>%</p>
</div>
<button id="start-btn" class="button button-primary">🚀 Optimize Images</button>
</div>
<!-- CLEANUP -->
<div class="card" style="max-width:800px;padding:24px;">
<h2>🧹 Original Cleanup</h2>
<div style="margin-bottom:20px;">
<p><strong>Total Originals:</strong> <span id="cleanup-total"><?php echo $cleanup_total; ?></span></p>
<p><strong>Deleted:</strong> <span id="cleanup-deleted"><?php echo $cleanup_deleted; ?></span></p>
<p><strong>Remaining:</strong> <span id="cleanup-remaining"><?php echo $cleanup_remaining; ?></span></p>
</div>
<div style="margin-bottom:20px;">
<div style="background:#e0e0e0;border-radius:6px;height:12px;">
<div id="cleanup-bar" style="width:<?php echo $cleanup_progress; ?>%;background:<?php echo $cleanup_progress === 100 ? '#4caf50' : '#ff9800'; ?>;height:100%;"></div>
</div>
<p><strong>Cleanup Progress:</strong> <span id="cleanup-progress"><?php echo $cleanup_progress; ?></span>%</p>
</div>
<button id="delete-btn" class="button">🧹 Delete Originals</button>
</div>
<!-- STATUS -->
<div style="padding:12px;background:#f6f7f7;border-radius:6px;">
<p id="status-opt">🚀 Optimizer: Idle</p>
<p id="status-clean">🧹 Cleanup: Idle</p>
</div>
</div>
<style>
#progress-bar,
#cleanup-bar {
transition: width 0.35s ease, background 0.3s ease;
}
</style>
<script>
let running = false;
let deleting = false;
(function initCleanupBar() {
const bar = document.getElementById('cleanup-bar');
const progress = parseInt(document.getElementById('cleanup-progress').innerText, 10);
if (!bar || isNaN(progress)) return;
if (progress >= 100) {
bar.style.background = '#4caf50';
} else if (progress > 0) {
bar.style.background = '#ff9800';
}
})();
document.getElementById('start-btn').onclick = () => {
running = true;
document.getElementById('start-btn').disabled = true;
document.getElementById('delete-btn').disabled = true;
document.getElementById('start-btn').innerText = '⏳ Running...';
document.getElementById('status-opt').innerText = '🚀 Optimizing images...';
processBatch();
};
document.getElementById('delete-btn').onclick = () => {
deleting = true;
document.getElementById('start-btn').disabled = true;
document.getElementById('delete-btn').disabled = true;
document.getElementById('delete-btn').innerText = '⏳ Running...';
document.getElementById('status-clean').innerText = '🧹 Cleaning originals...';
deleteBatch();
};
function deleteBatch() {
if (!deleting) return;
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'zeitfresser_delete_originals',
nonce: '<?php echo wp_create_nonce('zeitfresser_performance_tools'); ?>'
})
})
.then(res => res.json())
.then(data => {
let total = data.data.total;
let remaining = data.data.remaining;
let deleted = data.data.deleted_total;
let batch = data.data.deleted;
let progress = total > 0 ? Math.round((deleted / total) * 100) : 0;
// Update Cleanup UI
document.getElementById('cleanup-total').innerText = total;
document.getElementById('cleanup-deleted').innerText = deleted;
document.getElementById('cleanup-remaining').innerText = remaining;
document.getElementById('cleanup-progress').innerText = progress;
let bar = document.getElementById('cleanup-bar');
bar.style.width = progress + '%';
if (progress >= 100) {
bar.style.background = '#4caf50';
} else if (progress > 0) {
bar.style.background = '#ff9800';
}
document.getElementById('status-clean').innerText = '🧹 Deleted: ' + batch;
if (remaining > 0) {
setTimeout(deleteBatch, 400);
} else {
deleting = false;
document.getElementById('start-btn').disabled = false;
document.getElementById('delete-btn').disabled = false;
document.getElementById('delete-btn').innerText = '🧹 Delete Originals';
document.getElementById('status-clean').innerText = '✔ Cleanup complete';
}
});
}
function processBatch() {
if (!running) return;
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
action: 'zeitfresser_optimize_images',
nonce: '<?php echo wp_create_nonce('zeitfresser_performance_tools'); ?>'
})
})
.then(res => res.json())
.then(data => {
let pending = data.data.pending;
let total = data.data.total;
let optimized = total - pending;
let progress = total > 0 ? Math.round((optimized / total) * 100) : 0;
// Live update optimization counters
document.getElementById('total').innerText = total;
document.getElementById('optimized').innerText = optimized;
document.getElementById('remaining').innerText = pending;
// Live update progress UI
document.getElementById('progress').innerText = progress;
document.getElementById('progress-bar').style.width = progress + '%';
document.getElementById('status-opt').innerText =
'🚀 Processed: ' + data.data.processed +
' | Updated: ' + data.data.updated;
if (pending > 0) {
setTimeout(processBatch, 400);
} else {
running = false;
document.getElementById('start-btn').disabled = false;
document.getElementById('delete-btn').disabled = false;
document.getElementById('start-btn').innerText = '🚀 Optimize Images';
document.getElementById('status-opt').innerText = '✔ Optimization complete';
}
});
}
</script>
<?php
} }
+1 -1
View File
@@ -5,7 +5,7 @@ Author: Zeitfresser
Author URI: https://ztfr.eu/ Author URI: https://ztfr.eu/
Theme URI: https://ztfr.eu/ Theme URI: https://ztfr.eu/
Description: Zeitfresser Wordpress Theme Description: Zeitfresser Wordpress Theme
Version: 1.6 Version: 1.7
Tested up to: 6.2 Tested up to: 6.2
Requires PHP: 7.0 Requires PHP: 7.0
License: GNU General Public License v2 or later License: GNU General Public License v2 or later
+5 -2
View File
@@ -23,7 +23,10 @@
<?php if ( $show_hide_image && has_post_thumbnail() ) : ?> <?php if ( $show_hide_image && has_post_thumbnail() ) : ?>
<?php $thumbnail_size = get_theme_mod( 'post_snippet_featured_image_size', zeitfresser_get_default_post_snippet_featured_image_size() ); ?> <?php $thumbnail_size = get_theme_mod( 'post_snippet_featured_image_size', zeitfresser_get_default_post_snippet_featured_image_size() ); ?>
<a href="<?php echo esc_url( get_permalink() ); ?>" rel="bookmark" class="featured-image"> <a href="<?php echo esc_url( get_permalink() ); ?>" rel="bookmark" class="featured-image">
<?php the_post_thumbnail( $thumbnail_size ); ?> <?php
$size = ! empty( $thumbnail_size ) ? $thumbnail_size : 'zeitfresser-card';
the_post_thumbnail( $size );
?>
</a> </a>
<?php endif; ?> <?php endif; ?>
<div class="summary"> <div class="summary">
@@ -132,4 +135,4 @@
</div> </div>
</div> </div>
</div> </div>