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}