Delete-unused-images

                        
1// Add the buttons to the Media Library toolbar
2function add_image_management_buttons() {
3 if (!current_user_can('manage_options')) {
4 return;
5 }
6 
7 echo '<div class="alignright actions">';
8 echo '<button class="button button-primary" id="mark-unused-images-button">';
9 echo 'Mark Unused Images';
10 echo '</button>';
11 echo '<button class="button button-secondary" id="remove-delete-prefix-button">';
12 echo 'Remove Delete_ Prefix';
13 echo '</button>';
14 echo '</div>';
15 
16 // Add inline script for AJAX functionality
17 ?>
18 <script>
19 jQuery(document).ready(function ($) {
20 $("#mark-unused-images-button").on("click", function () {
21 const button = $(this);
22 button.prop("disabled", true).text("Processing...");
23 
24 $.ajax({
25 url: "<?php echo admin_url('admin-ajax.php'); ?>",
26 type: "POST",
27 dataType: "json",
28 data: {
29 action: "mark_unused_images",
30 security: "<?php echo wp_create_nonce('mark_unused_images_action'); ?>"
31 },
32 success: function (response) {
33 if (response.success) {
34 alert(response.data.message + "\nUsed Images: " + response.data.used_count + "\nUnused Images: " + response.data.unused_count);
35 location.reload();
36 } else {
37 alert("Error: " + response.data.message);
38 }
39 button.prop("disabled", false).text("Mark Unused Images");
40 },
41 error: function () {
42 alert("An error occurred while processing. Please check the console for details.");
43 button.prop("disabled", false).text("Mark Unused Images");
44 },
45 });
46 });
47 
48 $("#remove-delete-prefix-button").on("click", function () {
49 const button = $(this);
50 button.prop("disabled", true).text("Processing...");
51 
52 $.ajax({
53 url: "<?php echo admin_url('admin-ajax.php'); ?>",
54 type: "POST",
55 dataType: "json",
56 data: {
57 action: "remove_delete_prefix",
58 security: "<?php echo wp_create_nonce('remove_delete_prefix_action'); ?>"
59 },
60 success: function (response) {
61 if (response.success) {
62 alert(response.data.message + "\nUpdated Images: " + response.data.updated_count);
63 location.reload();
64 } else {
65 alert("Error: " + response.data.message);
66 }
67 button.prop("disabled", false).text("Remove Delete_ Prefix");
68 },
69 error: function () {
70 alert("An error occurred while processing. Please check the console for details.");
71 button.prop("disabled", false).text("Remove Delete_ Prefix");
72 },
73 });
74 });
75 });
76 </script>
77 <?php
78}
79add_action('restrict_manage_posts', 'add_image_management_buttons');
80 
81// Ensure the buttons only appear in the Media Library
82function show_button_in_media_library($post_type) {
83 if ($post_type !== 'attachment') {
84 remove_action('restrict_manage_posts', 'add_image_management_buttons');
85 }
86}
87add_action('load-upload.php', function () {
88 show_button_in_media_library(get_current_screen()->post_type);
89});
90 
91// AJAX handler: Mark unused images
92function mark_unused_images_ajax_handler() {
93 check_ajax_referer('mark_unused_images_action', 'security');
94 
95 if (!current_user_can('manage_options')) {
96 wp_send_json_error(['message' => 'You do not have sufficient permissions.']);
97 }
98 
99 global $wpdb;
100 
101 $used_images = []; // Initialize array to track used images
102 $unused_count = 0; // Counter for unused images
103 $used_count = 0; // Counter for used images
104 
105 // Site logo and favicon
106 $site_logo_id = get_theme_mod('custom_logo');
107 $site_icon_id = get_option('site_icon');
108 $ids_to_include = array_filter([$site_logo_id, $site_icon_id]);
109 
110 foreach ($ids_to_include as $id) {
111 $url = untrailingslashit(esc_url_raw(wp_get_attachment_url($id)));
112 if ($url) $used_images[] = $url;
113 }
114 
115 // WooCommerce images
116 if (class_exists('WooCommerce')) {
117 $products = wc_get_products(['limit' => -1]);
118 foreach ($products as $product) {
119 $used_images[] = untrailingslashit(esc_url_raw(wp_get_attachment_url(get_post_thumbnail_id($product->get_id()))));
120 $gallery_image_ids = $product->get_gallery_image_ids();
121 foreach ($gallery_image_ids as $id) {
122 $used_images[] = untrailingslashit(esc_url_raw(wp_get_attachment_url($id)));
123 }
124 }
125 }
126 
127 // Posts and Pages including drafts and private posts
128 $posts = $wpdb->get_results("SELECT ID, post_content FROM $wpdb->posts WHERE post_status IN ('publish', 'draft', 'private')", ARRAY_A);
129 foreach ($posts as $post) {
130 preg_match_all('/https?:\/\/[^\s"]+\.(jpg|jpeg|png|gif|webp)/i', $post['post_content'], $matches);
131 if (!empty($matches[0])) $used_images = array_merge($used_images, $matches[0]);
132 
133 $thumbnail_id = get_post_thumbnail_id($post['ID']);
134 if ($thumbnail_id) {
135 $used_images[] = untrailingslashit(esc_url_raw(wp_get_attachment_url($thumbnail_id)));
136 }
137 
138 // Elementor data in meta fields
139 $meta_fields = $wpdb->get_results($wpdb->prepare(
140 "SELECT meta_value FROM $wpdb->postmeta WHERE post_id = %d",
141 $post['ID']
142 ), ARRAY_A);
143 
144 foreach ($meta_fields as $meta) {
145 $meta_data = maybe_unserialize($meta['meta_value']);
146 if (is_string($meta_data) && json_decode($meta_data, true)) {
147 $decoded_data = json_decode($meta_data, true);
148 $used_images = array_merge($used_images, extract_image_urls_from_elementor_data($decoded_data));
149 }
150 }
151 }
152 
153 // Post Categories and Product Categories
154 $taxonomies = ['category', 'product_cat'];
155 foreach ($taxonomies as $taxonomy) {
156 $terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false]);
157 foreach ($terms as $term) {
158 $thumbnail_id = get_term_meta($term->term_id, 'thumbnail_id', true);
159 if ($thumbnail_id) {
160 $used_images[] = untrailingslashit(esc_url_raw(wp_get_attachment_url($thumbnail_id)));
161 }
162 }
163 }
164 
165 // Mark unused images in Media Library
166 $media_query = new WP_Query([
167 'post_type' => 'attachment',
168 'post_status' => 'inherit',
169 'post_mime_type' => 'image',
170 'posts_per_page' => -1,
171 ]);
172 
173 if ($media_query->have_posts()) {
174 while ($media_query->have_posts()) {
175 $media_query->the_post();
176 $media_id = get_the_ID();
177 $media_url = untrailingslashit(esc_url_raw(wp_get_attachment_url($media_id)));
178 $current_title = get_the_title($media_id);
179 
180 if (!in_array($media_url, $used_images)) {
181 if (strpos($current_title, 'Delete_') !== 0) {
182 wp_update_post(['ID' => $media_id, 'post_title' => "Delete_" . $current_title]);
183 $unused_count++;
184 }
185 } else {
186 $used_count++;
187 }
188 }
189 }
190 
191 wp_reset_postdata();
192 
193 wp_send_json_success([
194 'message' => 'Image analysis complete.',
195 'used_count' => $used_count,
196 'unused_count' => $unused_count,
197 ]);
198}
199add_action('wp_ajax_mark_unused_images', 'mark_unused_images_ajax_handler');
200 
201// AJAX handler: Remove Delete_ prefix
202function remove_delete_prefix_ajax_handler() {
203 check_ajax_referer('remove_delete_prefix_action', 'security');
204 
205 if (!current_user_can('manage_options')) {
206 wp_send_json_error(['message' => 'You do not have sufficient permissions.']);
207 }
208 
209 global $wpdb;
210 
211 $updated_count = 0;
212 
213 $media_query = new WP_Query([
214 'post_type' => 'attachment',
215 'post_status' => 'inherit',
216 'post_mime_type' => 'image',
217 'posts_per_page' => -1,
218 ]);
219 
220 if ($media_query->have_posts()) {
221 while ($media_query->have_posts()) {
222 $media_query->the_post();
223 $media_id = get_the_ID();
224 $current_title = get_the_title($media_id);
225 
226 if (strpos($current_title, 'Delete_') === 0) {
227 $new_title = substr($current_title, 7); // Remove "Delete_" prefix
228 wp_update_post(['ID' => $media_id, 'post_title' => $new_title]);
229 $updated_count++;
230 }
231 }
232 }
233 
234 wp_reset_postdata();
235 
236 wp_send_json_success([
237 'message' => 'Delete_ prefix removal complete.',
238 'updated_count' => $updated_count,
239 ]);
240}
241add_action('wp_ajax_remove_delete_prefix', 'remove_delete_prefix_ajax_handler');
242 
243// Helper function to extract image URLs from Elementor data
244function extract_image_urls_from_elementor_data($data) {
245 $urls = [];
246 foreach ($data as $element) {
247 if (isset($element['settings'])) {
248 foreach ($element['settings'] as $key => $value) {
249 if (is_string($value) && preg_match('/https?:\/\/[^\s"]+\.(jpg|jpeg|png|gif|webp)/i', $value)) {
250 $urls[] = $value;
251 }
252 if (is_array($value) && isset($value['url'])) {
253 $urls[] = $value['url'];
254 }
255 }
256 }
257 if (isset($element['elements']) && is_array($element['elements'])) {
258 $urls = array_merge($urls, extract_image_urls_from_elementor_data($element['elements']));
259 }
260 }
261 return $urls;
262}

51

AI Verification Score:
3.5/5
Show details

Snippet Description:

This snippet does not have a description.

Instructions:

1. Install Code Snippets Plugin 2. Add New Code Snippet 3. Paste the Code Watch this video first: https://youtu.be/x4ueJJRZ548?feature=shared

Type:

PHP

Status:

AI VERIFIED

Snippet Details:

Created by:
WebSquadron
Category:
WordPress Admin
Rating:
Voted: 51 by 51 users
Wordpress Compatibility:
6.4
More Information:
Click here
Author Website:

Not specified

Credits:

Not specified

Last Updated:
7 months ago

Comments

  • J

    John

    1 year ago

    Unfortunately, it looks like this marks images that are inserted into Blog Posts (via the Add Media button) to be deleted. Would be great to see that adjusted to take that into account.
  • KO

    Kelvin Ong

    1 year ago

    Tested on my staging woocommerce site. It didn't work. Error message:
    "An error occurred while processing. Please check the console for details."
    What console?
  • M

    Manager

    1 year ago

    Hey, Thanks Imran
    The code is tested - not working on hello elementor theme
    
    would try and update a version which works with hello elementor
    cheers mate!
    
  • IS

    Imran Siddiq

    1 year ago

    Thanks all - with regards to comments;
    
    @All - this is is not a plugin with vested resources, so I will revisit when I can - but the code is there or others to investigate and improve upon. No images are deleted unless you manually delete them, thus you're in control.
    
    @John - when testing images that I inserted into Classic WordPress Editor it was fine though false positives have been reported by others. Due to variation in how we build it's difficult to be sure why this issue occurred for you. 
    
    @Kelvin Ong - I've not experienced that issue. Sounds like something else on your site may be impacting on the snippet.
    
    @Manafer - it works on my site with the Hello theme. Are you referring to the Site Logo and Favicon?
    
  • DS

    Derek Short

    1 year ago

    Awesome! Thanks!
  • DS

    Derek Short

    1 year ago

    This snippet was downloaded as a JSON file. Do I need the Code Snippets plugin to use it, or can I add the code to the functions PHP file?
  • IS

    Imran Siddiq

    1 year ago

    1. Install Code Snippets Plugin
    2. Add New Code Snippet
    3. Paste the Code
    
    Watch this video first: https://youtu.be/x4ueJJRZ548?feature=shared
    
  • AS

    Ahmed Siddieg

    1 year ago

    it give me an Error , did not work
    wp version 6.7.1
  • H

    Hossam

    1 year ago

    when trying to mark unused images I got an error (An error occurred while processing. Please check the console for details.) . BTW we have more than 28K images in our store
  • SW

    Siteuri Web

    7 months ago

    Hello,
    It is not compatible with ACF gallery.

Login or create account to submit Comment