Skip to content

Instantly share code, notes, and snippets.

@Jany-M
Created June 1, 2025 00:45
Show Gist options
  • Save Jany-M/a57d880c65fe5a87a779bacb6655c39e to your computer and use it in GitHub Desktop.
Save Jany-M/a57d880c65fe5a87a779bacb6655c39e to your computer and use it in GitHub Desktop.
[WP] WordPress utility for cleaning up generated image variations (thumbnails) garbage in bulk, and for regenerating thumbnails for specific images. With every deletion or regeneration, it also updates the attachment metadata in the database to remove references to deleted variations.
<?php
/*
WordPress utility for cleaning up generated image variations (thumbnails) in bulk, in a wp-content/uploads/{year} directory (all months), and for regenerating thumbnails for specific images.
With every deletion or regeneration, it also updates the attachment metadata in the database to remove references to deleted variations.
Drop the file in the ROOT of your website (not in plugins) and access it via browser directly after you have logged into your WordPress dashboard first. It only works for administrator users.
ALWAYS backup your database AND files before using it. Delete the file after use.
Made to work on WP Engine, but should work on any host.
Last Update: 1 June 2025
Version: 1.0
Developed by Shambix, a web & mobile development agency with 10+ experience in WordPress.
https://www.shambix.com
[email protected]
*/
?>
<?php
define('SHAMBIX_BATCH_SIZE', 100); // Unified batch size for all batch operations
/**
* WordPress Generated Image Variation Cleaner for WP Engine
* Place this file in your WordPress *root* directory and access via browser
* Delete the file after you're done for safety reasons
*/
// =====================================================================
// 1. HANDLE BATCH PROCESSING FIRST (BEFORE ANY OUTPUT)
// =====================================================================
if (isset($_POST['action']) && $_POST['action'] === 'process_batch') {
ob_start();
require_once('wp-load.php');
if (!current_user_can('manage_options')) {
wp_die('Access Denied: You must be an administrator to run this script.');
}
set_time_limit(0);
ini_set('memory_limit', '1024M');
ignore_user_abort(true);
try {
$year = isset($_POST['year']) ? sanitize_text_field($_POST['year']) : '2025';
$offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
$dry_run = isset($_POST['dry_run']) ? boolval($_POST['dry_run']) : true;
$YEAR_DIR = ABSPATH . 'wp-content/uploads/' . $year;
if (!is_dir($YEAR_DIR)) {
throw new Exception("Directory does not exist: $YEAR_DIR");
}
$BATCH_SIZE = SHAMBIX_BATCH_SIZE;
// Accept matching_files from POST (JSON encoded array)
$matching_files = null;
if (!empty($_POST['matching_files'])) {
$matching_files = json_decode(stripslashes($_POST['matching_files']), true);
if (!is_array($matching_files)) $matching_files = null;
}
// If not provided, build the list (first batch)
if ($matching_files === null) {
$matching_files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($YEAR_DIR, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$filename = $file->getFilename();
if (preg_match('/^(.+)-(\d+)x(\d+)\.(jpe?g|png|webp|gif)$/i', $filename)) {
$matching_files[] = $file->getPathname();
}
}
}
$total_files = count($matching_files);
list($deleted, $skipped, $metadata_updated, $output, $current) = process_directory_batch(
$YEAR_DIR,
$dry_run,
$offset,
$BATCH_SIZE,
$matching_files
);
$finished = ($deleted + $skipped == 0) || ($current >= $total_files);
ob_end_clean();
header('Content-Type: application/json');
echo json_encode([
'output' => $output,
'current' => $current,
'total' => $total_files,
'finished' => $finished,
'deleted' => $deleted,
'skipped' => $skipped,
'metadata_updated' => $metadata_updated,
// Always return the file list in every batch
'matching_files' => $matching_files
]);
exit;
} catch (Exception $e) {
ob_end_clean();
header('Content-Type: application/json');
echo json_encode([
'error' => 'Error: ' . $e->getMessage(),
'output' => 'Exception occurred during processing'
]);
exit;
}
}
// =====================================================================
// 2. REGULAR PAGE LOAD - REST OF THE SCRIPT
// =====================================================================
require_once('wp-load.php'); // Load WordPress environment
// Only allow administrators to run this script
if (!current_user_can('manage_options')) {
wp_die('Access Denied: You must be an administrator to run this script.');
}
// Only run if we're in a controlled environment
$allowed_ips = ['127.0.0.1', '::1'];
$server_ip = $_SERVER['REMOTE_ADDR'] ?? '';
if (!in_array($server_ip, $allowed_ips) && !current_user_can('manage_options')) {
wp_die('<h1>Access Denied</h1><p>This script can only be run by administrators.</p>');
}
// Disable time limits and memory limits
set_time_limit(0);
ini_set('memory_limit', '1024M');
ignore_user_abort(true);
// Main processing function
function process_directory($dir, $dry_run = true) {
if (!is_dir($dir)) return [0, 0, 0, ''];
$deleted = 0;
$skipped = 0;
$metadata_updated = 0;
$output = '';
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$original_files = [];
$attachments_to_update = [];
// First pass: Collect all original files
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$filename = $file->getFilename();
if (preg_match('/^(.+)\.(jpe?g|png|webp|gif)$/i', $filename)) {
$original_files[strtolower($file->getPathname())] = true;
}
}
// Second pass: Process files
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$path = $file->getPathname();
$filename = $file->getFilename();
// Match generated variations
if (preg_match('/^(.+)-(\d+)x(\d+)\.(jpe?g|png|webp|gif)$/i', $filename, $matches)) {
$original_filename = $matches[1] . '.' . $matches[4];
$original_path = $file->getPath() . DIRECTORY_SEPARATOR . $original_filename;
// Check if original exists (case-insensitive)
if (isset($original_files[strtolower($original_path)])) {
$deleted++;
if (!$dry_run) {
if (@unlink($path)) {
$output .= "Deleted: " . str_replace(ABSPATH, '', $path) . "\n";
// Track attachments to update metadata
$attachments_to_update[$original_path] = $original_path;
} else {
$output .= "Error deleting: " . str_replace(ABSPATH, '', $path) . "\n";
}
} else {
$output .= "Would delete: " . str_replace(ABSPATH, '', $path) . "\n";
}
} else {
$skipped++;
$output .= "Skipped (no original): " . str_replace(ABSPATH, '', $path) . "\n";
}
}
}
// Update attachment metadata for processed files
if (!$dry_run && !empty($attachments_to_update)) {
foreach ($attachments_to_update as $original_path) {
$attachment_id = attachment_url_to_postid(
content_url(str_replace(wp_normalize_path(WP_CONTENT_DIR), '', wp_normalize_path($original_path)))
);
if ($attachment_id) {
$result = clean_attachment_metadata($attachment_id, $dry_run);
if ($result) {
$metadata_updated++;
$output .= "Updated metadata for attachment ID: $attachment_id\n";
}
}
}
}
return [$deleted, $skipped, $metadata_updated, $output];
}
// Batch processing function
function process_directory_batch($dir, $dry_run = true, $offset = 0, $batch_size = null, $matching_files = null) {
if ($batch_size === null) $batch_size = SHAMBIX_BATCH_SIZE;
if (!is_dir($dir)) return [0, 0, 0, '', 0];
$deleted = 0;
$skipped = 0;
$metadata_updated = 0;
$output = '';
$processed_count = 0;
// If matching_files is not provided, build it (for backward compatibility)
if ($matching_files === null) {
$matching_files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$filename = $file->getFilename();
if (preg_match('/^(.+)-(\d+)x(\d+)\.(jpe?g|png|webp|gif)$/i', $filename)) {
$matching_files[] = $file->getPathname();
}
}
}
// Only process the current batch slice
$batch_files = array_slice($matching_files, $offset, $batch_size);
$attachments_to_update = [];
$original_files = [];
// Build original files list for existence check
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isDir()) continue;
$filename = $file->getFilename();
if (preg_match('/^(.+)\.(jpe?g|png|webp|gif)$/i', $filename)) {
$original_files[strtolower($file->getPathname())] = true;
}
}
foreach ($batch_files as $path) {
$filename = basename($path);
if (preg_match('/^(.+)-(\d+)x(\d+)\.(jpe?g|png|webp|gif)$/i', $filename, $matches)) {
$original_filename = $matches[1] . '.' . $matches[4];
$original_path = dirname($path) . DIRECTORY_SEPARATOR . $original_filename;
$original_path_norm = wp_normalize_path(strtolower($original_path));
$original_files_norm = array_map('wp_normalize_path', array_map('strtolower', array_keys($original_files)));
if (in_array($original_path_norm, $original_files_norm)) {
$deleted++;
if (!$dry_run) {
if (@unlink($path)) {
$output .= "Deleted: " . str_replace(ABSPATH, '', $path) . "\n";
$attachments_to_update[$original_path] = $original_path;
} else {
$output .= "Error deleting (permission denied?): " . str_replace(ABSPATH, '', $path) . "\n";
}
} else {
$output .= "Would delete: " . str_replace(ABSPATH, '', $path) . "\n";
}
} else {
if (!file_exists($original_path)) {
$output .= "Skipped (original missing): " . str_replace(ABSPATH, '', $path) . "\n";
} else {
$output .= "Skipped (original exists but not matched, check case/path): " . str_replace(ABSPATH, '', $path) . "\n";
}
$skipped++;
}
}
$processed_count++;
}
// Update attachment metadata for processed files
if (!$dry_run && !empty($attachments_to_update)) {
foreach ($attachments_to_update as $original_path) {
$attachment_id = attachment_url_to_postid(
content_url(str_replace(wp_normalize_path(WP_CONTENT_DIR), '', wp_normalize_path($original_path)))
);
if ($attachment_id) {
$result = clean_attachment_metadata($attachment_id, $dry_run);
if ($result) {
$metadata_updated++;
$output .= "Updated metadata for attachment ID: $attachment_id\n";
}
}
}
}
// $current is offset + processed_count (number of matching files processed so far)
return [$deleted, $skipped, $metadata_updated, $output, $offset + $processed_count];
}
// Clean attachment metadata by removing references to missing files
function clean_attachment_metadata($attachment_id, $dry_run = true) {
$metadata = wp_get_attachment_metadata($attachment_id);
if (empty($metadata['sizes'])) return false;
$original_file = get_attached_file($attachment_id);
if (!$original_file || !file_exists($original_file)) return false;
$base_dir = dirname($original_file);
$updated = false;
$valid_sizes = [];
foreach ($metadata['sizes'] as $size_name => $size_data) {
$file_path = path_join($base_dir, $size_data['file']);
// Check if file exists and has valid dimensions
if (file_exists($file_path)) {
$valid_sizes[$size_name] = $size_data;
} else {
$updated = true;
}
}
if ($updated && !$dry_run) {
$metadata['sizes'] = $valid_sizes;
wp_update_attachment_metadata($attachment_id, $metadata);
}
return $updated;
}
// Regenerate thumbnails for a specific attachment
function regenerate_attachment_thumbs($filename, $dry_run = true) {
$output = '';
$success = false;
// Find attachment by filename
$attachments = get_posts([
'post_type' => 'attachment',
'post_status' => 'inherit',
'posts_per_page' => -1,
'meta_query' => [[
'key' => '_wp_attached_file',
'value' => $filename,
'compare' => 'LIKE'
]]
]);
if (empty($attachments)) {
$output .= "No attachment found for filename: $filename\n";
return [false, $output];
}
// Handle multiple matches
if (count($attachments) > 1) {
$output .= "Found multiple attachments matching filename: $filename\n";
$output .= "Please use the exact relative path (e.g., '2025/05/your-image.jpg')\n";
$output .= "Matching attachments:\n";
foreach ($attachments as $attachment) {
$file = get_post_meta($attachment->ID, '_wp_attached_file', true);
$output .= "- ID {$attachment->ID}: $file\n";
}
return [false, $output];
}
$attachment = reset($attachments);
$attachment_id = $attachment->ID;
$original_file = get_attached_file($attachment_id);
$relative_path = get_post_meta($attachment_id, '_wp_attached_file', true);
if (!$original_file || !file_exists($original_file)) {
$output .= "Original file not found for attachment ID: $attachment_id\n";
$output .= "Path: $original_file\n";
return [false, $output];
}
$output .= "Processing attachment ID: $attachment_id\n";
$output .= "Original file: $relative_path\n";
if (!$dry_run) {
// Regenerate thumbnails
require_once(ABSPATH . 'wp-admin/includes/image.php');
// First clean up existing thumbnails
$metadata = wp_get_attachment_metadata($attachment_id);
$deleted_thumbs = 0;
if (isset($metadata['sizes']) && is_array($metadata['sizes'])) {
$base_dir = dirname($original_file);
foreach ($metadata['sizes'] as $size_name => $size_data) {
$thumb_path = path_join($base_dir, $size_data['file']);
if (file_exists($thumb_path)) {
if (unlink($thumb_path)) {
$output .= "Deleted old thumbnail: " . $size_data['file'] . "\n";
$deleted_thumbs++;
}
}
}
}
if ($deleted_thumbs > 0) {
$output .= "Deleted $deleted_thumbs old thumbnails\n";
} else {
$output .= "No existing thumbnails found to delete\n";
}
// Regenerate metadata
$new_metadata = wp_generate_attachment_metadata($attachment_id, $original_file);
if (is_wp_error($new_metadata)) {
$output .= "Error regenerating metadata: " . $new_metadata->get_error_message() . "\n";
} else {
wp_update_attachment_metadata($attachment_id, $new_metadata);
$output .= "Successfully regenerated thumbnails!\n";
$success = true;
// List new thumbnails
if (!empty($new_metadata['sizes'])) {
$output .= "Generated thumbnails:\n";
foreach ($new_metadata['sizes'] as $size => $size_data) {
$output .= "- {$size}: {$size_data['width']}x{$size_data['height']} ({$size_data['file']})\n";
}
} else {
$output .= "No thumbnails generated (check registered image sizes)\n";
}
}
} else {
$output .= "Would regenerate thumbnails for attachment ID: $attachment_id\n";
$output .= "Relative path: $relative_path\n";
$success = true; // For dry run reporting
}
return [$success, $output];
}
// Start HTML output
?>
<!DOCTYPE html>
<html>
<head>
<title>Image Variation Manager</title>
<style>
body { font-family: sans-serif; line-height: 1.6; margin: 2em; }
pre { background: #f5f5f5; padding: 1em; overflow: auto; max-height: 400px; }
.success { color: #2e7d32; }
.error { color: #c62828; }
.warning { color: #f57f17; }
.tabs { display: flex; margin-bottom: 1em; }
.tab { padding: 10px 20px; cursor: pointer; border: 1px solid #ccc; border-bottom: none; }
.tab.active { background: #f5f5f5; font-weight: bold; }
.tab-content { display: none; border: 1px solid #ccc; padding: 20px; }
.tab-content.active { display: block; }
.progress-container { width: 100%; background: #f1f1f1; margin: 20px 0; }
.progress-bar { width: 0%; height: 30px; background: #4CAF50; text-align: center; line-height: 30px; color: white; }
.btn { padding: 10px 15px; background: #2196F3; color: white; border: none; cursor: pointer; }
.btn:disabled { background: #cccccc; }
.batch-section { margin-top: 30px; padding-top: 20px; }
</style>
<script>
// Initialize variables for batch processing
let paused = false;
let currentOffset = 0;
let totalFiles = 0;
let dryRun = true;
let processingYear = '';
let matchingFiles = null; // Store the list of files for batching
function openTab(evt, tabName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tab-content");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].classList.remove("active");
}
tablinks = document.getElementsByClassName("tab");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].classList.remove("active");
}
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
// Reset batch UI when switching to batch tab
if (tabName === 'cleaner-batch') {
document.getElementById('batch-output').innerHTML = '';
document.getElementById('progress-bar').style.width = '0%';
document.getElementById('progress-bar').innerHTML = '0%';
document.getElementById('progress-bar').style.backgroundColor = '#4CAF50';
document.getElementById('progress-text').innerHTML = 'Processed: 0/0 files';
currentOffset = 0;
}
}
function startBatchProcessing() {
processingYear = document.getElementById('batch_year').value;
dryRun = !document.getElementById('batch_confirm').checked;
paused = false;
document.getElementById('batch-output').innerHTML = '';
document.getElementById('progress-bar').style.width = '0%';
document.getElementById('progress-bar').innerHTML = '0%';
document.getElementById('progress-bar').style.backgroundColor = '#4CAF50';
document.getElementById('progress-text').innerHTML = 'Processed: 0/0 files';
currentOffset = 0;
matchingFiles = null; // Reset file list on new run
document.getElementById('start-btn').disabled = true;
document.getElementById('pause-btn').disabled = false;
document.getElementById('resume-btn').disabled = true;
processBatch();
}
function pauseProcessing() {
paused = true;
document.getElementById('pause-btn').disabled = true;
document.getElementById('resume-btn').disabled = false;
}
function resumeProcessing() {
paused = false;
document.getElementById('pause-btn').disabled = false;
document.getElementById('resume-btn').disabled = true;
processBatch();
}
function processBatch() {
if (paused) return;
const outputElement = document.getElementById('batch-output');
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const xhr = new XMLHttpRequest();
xhr.open('POST', '<?php echo $_SERVER['PHP_SELF']; ?>', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.error) {
outputElement.innerHTML += '<br><span class="error">' + response.error + '</span>';
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
return;
}
// Update output
if (response.output) {
outputElement.innerHTML += response.output.replace(/\n/g, '<br>');
outputElement.scrollTop = outputElement.scrollHeight;
}
// Always update the file list from the response
if (response.matching_files && Array.isArray(response.matching_files)) {
matchingFiles = response.matching_files;
}
// Only set totalFiles on the first batch
if (totalFiles === 0 && response.total > 0) {
totalFiles = response.total;
}
const progress = totalFiles > 0 ? Math.round((response.current / totalFiles) * 100) : 0;
progressBar.style.width = progress + '%';
progressBar.innerHTML = progress + '%';
progressText.innerHTML = `Processed: ${response.current}/${totalFiles} files`;
currentOffset = response.current;
// Continue processing if not finished
if (response.finished) {
progressBar.style.backgroundColor = '#4CAF50';
progressBar.innerHTML = 'Completed!';
progressText.innerHTML = `Finished! Processed ${totalFiles} files`;
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
outputElement.innerHTML += `<br><br><b>Summary:</b><br>Files deleted: ${response.deleted}<br>Files skipped: ${response.skipped}<br>Metadata updated: ${response.metadata_updated}`;
} else if (!paused) {
setTimeout(processBatch, 500);
}
} catch (e) {
outputElement.innerHTML += '<br><span class="error">Error parsing response: ' + e.message + '</span>';
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
}
} else {
outputElement.innerHTML += '<br><span class="error">Server Error: ' + xhr.statusText + '</span>';
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
}
}
};
xhr.onerror = function() {
outputElement.innerHTML += '<br><span class="error">Request failed. Check console for details.</span>';
document.getElementById('start-btn').disabled = false;
document.getElementById('pause-btn').disabled = true;
};
// Send request, always include matchingFiles if available
let postData = `action=process_batch&year=${processingYear}&offset=${currentOffset}&dry_run=${dryRun ? 1 : 0}`;
if (matchingFiles !== null) {
postData += `&matching_files=${encodeURIComponent(JSON.stringify(matchingFiles))}`;
}
xhr.send(postData);
}
</script>
</head>
<body>
<h2>WordPress Thumbs Cleaner - by <a href="https://www.shambix.com" target="_blank">Shambix</a></h2>
<p>Use this tool to clean up generated image variations and regenerate thumbnails.</p>
<p class="warning"><strong>Please ensure you have a backup of your site before proceeding, the deletions are irreversible. Use the script at your own risk and delete it after use.</strong></p>
<hr/>
<div class="tabs">
<div class="tab active" onclick="openTab(event, 'cleaner-standard')">Clean Variations (Standard)</div>
<div class="tab" onclick="openTab(event, 'cleaner-batch')">Clean Variations (Batch)</div>
<div class="tab" onclick="openTab(event, 'regenerate')">Regenerate Thumbnails</div>
</div>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'regenerate') {
// Handle thumbnail regeneration
$dry_run = empty($_POST['confirm']);
$filename = isset($_POST['filename']) ? sanitize_text_field($_POST['filename']) : '';
echo "<div id='regenerate' class='tab-content active'>";
echo "<h2>Regenerate Thumbnails</h2>";
if (empty($filename)) {
echo "<p class='error'>Please enter a filename</p>";
} else {
list($success, $output) = regenerate_attachment_thumbs($filename, $dry_run);
echo "<p>Target file: $filename</p>";
echo "<p>Mode: " . ($dry_run ? "DRY RUN (no changes)" : "LIVE REGENERATION") . "</p>";
echo "<hr>";
echo "<pre>$output</pre>";
echo "<hr>";
if ($success) {
echo "<p class='success'>Operation completed successfully!</p>";
} else {
echo "<p class='error'>There were errors during processing</p>";
}
}
echo '<p><a href="'.esc_url($_SERVER['REQUEST_URI']).'">Run again</a></p>';
echo "</div>";
} else if (isset($_POST['action']) && $_POST['action'] === 'clean_standard') {
// Handle standard image cleanup
$dry_run = empty($_POST['confirm']);
$year = isset($_POST['year']) ? sanitize_text_field($_POST['year']) : '2025';
$YEAR_DIR = ABSPATH . 'wp-content/uploads/' . $year;
echo "<div id='cleaner-standard' class='tab-content active'>";
echo "<h2>Processing: $year</h2>";
echo "<p>Target directory: " . str_replace(ABSPATH, '', $YEAR_DIR) . "</p>";
echo "<p>Mode: " . ($dry_run ? "DRY RUN (no changes)" : "LIVE DELETION") . "</p>";
echo "<hr>";
list($total_deleted, $total_skipped, $metadata_updated, $output) = process_directory($YEAR_DIR, $dry_run);
echo "<pre>$output</pre>";
echo "<hr>";
echo "<h3>Summary:</h3>";
echo "<p>Total variations deleted: $total_deleted</p>";
echo "<p>Total variations skipped: $total_skipped</p>";
echo "<p>Metadata updated (unique attachments ID): $metadata_updated</p>";
echo "<p>Original files preserved</p>";
if ($dry_run) {
echo '<p class="warning">NOTE: Run in LIVE mode to perform actual deletion and metadata updates</p>';
}
echo '<p><a href="'.esc_url($_SERVER['REQUEST_URI']).'">Run again</a></p>';
echo "</div>";
}
} else {
// Show forms
?>
<!-- Standard Cleaner Tab -->
<div id="cleaner-standard" class="tab-content active">
<h2>Clean Image Variations (Standard)</h2>
<form method="post">
<input type="hidden" name="action" value="clean_standard">
<p>
<label for="standard_year">Year to process:</label>
<input type="text" id="standard_year" name="year" value="2025" required>
<span>(e.g., 2025)</span>
</p>
<p>
<label>
<input type="checkbox" name="confirm" value="1">
Check to confirm actual deletion (otherwise dry run)
</label>
</p>
<p>
<input type="submit" value="Process Images" class="btn">
</p>
</form>
<div class="warning">
<h3>Important Notes:</h3>
<ul>
<li>Best for small to medium directories</li>
<li>Backup your site before running this script</li>
<li>Dry run mode is enabled by default</li>
<li>Only checks image variations in the specified year directory</li>
<li>Will not delete original images</li>
<li>Will automatically update attachment metadata</li>
<li>Process might time out for large directories</li>
</ul>
</div>
</div>
<!-- Batch Cleaner Tab -->
<div id="cleaner-batch" class="tab-content">
<h2>Clean Image Variations (Batch)</h2>
<p>
<label for="batch_year">Year to process:</label>
<input type="text" id="batch_year" name="year" value="2025" required>
<span>(e.g., 2025)</span>
</p>
<p>
<label>
<input type="checkbox" id="batch_confirm" name="confirm" value="1">
Check to confirm actual deletion (otherwise dry run)
</label>
</p>
<div class="batch-section">
<div class="progress-container">
<div id="progress-bar" class="progress-bar">0%</div>
</div>
<p id="progress-text">Processed: 0/0 files</p>
<div id="batch-output" style="height: 300px; overflow: auto; border: 1px solid #ccc; padding: 10px; margin: 20px 0;"></div>
<button id="start-btn" class="btn" onclick="startBatchProcessing()">Start Batch Processing</button>
<button id="pause-btn" class="btn" onclick="pauseProcessing()" disabled>Pause</button>
<button id="resume-btn" class="btn" onclick="resumeProcessing()" disabled>Resume</button>
</div>
<div class="warning">
<h3>Important Notes:</h3>
<ul>
<li>For large directories (1000+ files)</li>
<li>Processes in batches to prevent timeouts</li>
<li>Backup your site before running this script</li>
<li>Dry run mode is enabled by default</li>
<li>Only checks image variations in the specified year directory</li>
<li>Will not delete original images</li>
<li>Will automatically update attachment metadata</li>
</ul>
</div>
</div>
<!-- Regenerate Thumbnails Tab -->
<div id="regenerate" class="tab-content">
<h2>Regenerate Thumbnails</h2>
<form method="post">
<input type="hidden" name="action" value="regenerate">
<p>
<label for="filename">File path:</label>
<input type="text" id="filename" name="filename" required>
<span>(e.g., 2025/05/ABB25_2500x650.jpg)</span>
</p>
<p class="warning">
<strong>Important:</strong> Use the relative path as shown in Media Library<br>
(e.g., "2025/05/your-image.jpg" or just "your-image.jpg" if unique)
</p>
<p>
<label>
<input type="checkbox" name="confirm" value="1">
Check to confirm actual regeneration (otherwise dry run)
</label>
</p>
<p>
<input type="submit" value="Regenerate Thumbnails" class="btn">
</p>
</form>
<div class="warning">
<h3>Important Notes:</h3>
<ul>
<li>Enter the exact filename as it appears in the Media Library</li>
<li>For best results, use the full relative path (e.g., 2025/05/filename.jpg)</li>
<li>If multiple matches found, the script will show options</li>
<li>This will delete existing thumbnails and generate new ones</li>
<li>Dry run mode will show what would be generated</li>
<li>Thumbnails will be created for all registered image sizes</li>
</ul>
</div>
</div>
<?php
}
?>
<hr/>
<p>This <small><a href="https://gist.github.com/Jany-M/a57d880c65fe5a87a779bacb6655c39e" target="_blank">Gist</a> is on Github, <a href="https://www.shambix.com" target="_blank">contact the developers</a> if you need help with your WordPress site's regular maintenance, custom plugins, features and themes, but also for custom web or mobile app development and cloud setup services.</small></p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment