PluginProbe ʕ •ᴥ•ʔ
EWWW Image Optimizer / 8.5.0
EWWW Image Optimizer v8.5.0
8.7.0 8.6.0 trunk 5.6.2 5.7.1 5.8.2 6.0.3 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.1.9 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3.0 6.4.0 6.4.1 6.4.2 6.5.0 6.5.1 6.5.2 6.6.0 6.7.0 6.8.0 6.9.0 6.9.1 6.9.2 6.9.3 7.0.0 7.0.1 7.0.2 7.1.0 7.2.0 7.2.1 7.2.2 7.2.3 7.3.0 7.4.0 7.5.0 7.6.0 7.7.0 7.8.0 7.9.0 7.9.1 8.0.0 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.1 8.3.0 8.3.1 8.4.0 8.4.1 8.5.0
ewww-image-optimizer / aux-optimize.php
ewww-image-optimizer Last commit date
bin 9 years ago binaries 2 years ago classes 2 months ago docs 7 years ago images 3 months ago includes 2 months ago tests 2 months ago vendor 10 months ago .travis.yml 5 months ago aux-optimize.php 2 months ago bulk.php 2 months ago changelog.txt 2 months ago common.php 2 months ago composer.json 10 months ago composer.lock 3 years ago ewww-image-optimizer.php 2 months ago functions.php 6 months ago license.txt 7 years ago mwebp.php 2 months ago phpcs.ruleset.xml 6 months ago phpunit.xml 6 years ago readme.txt 2 months ago uninstall.php 7 years ago unique.php 3 months ago
aux-optimize.php
2259 lines
1 <?php
2 /**
3 * Functions for dealing with auxiliary images
4 *
5 * This file contains functions for bulk optimizing images outside the Media
6 * Library, and AJAX hooks for handling the image status table on the bulk
7 * optimize page.
8 *
9 * @link https://ewww.io
10 * @package EWWW_Image_Optimizer
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Displays 50 records from the images table.
19 *
20 * Called via AJAX to find 50 records from the images table and display them
21 * with alternating row style.
22 *
23 * @global object $wpdb
24 */
25 function ewww_image_optimizer_aux_images_table() {
26 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
27 // Verify that an authorized user has called function.
28 $permissions = apply_filters( 'ewww_image_optimizer_bulk_permissions', '' );
29 if (
30 empty( $_REQUEST['ewww_wpnonce'] ) ||
31 (
32 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) &&
33 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-bulk' )
34 ) ||
35 ! current_user_can( $permissions )
36 ) {
37 ewwwio_ob_clean();
38 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
39 }
40 global $wpdb;
41 $per_page = 50;
42 $offset = empty( $_POST['ewww_offset'] ) ? 0 : $per_page * abs( (int) $_POST['ewww_offset'] );
43 $search = empty( $_POST['ewww_search'] ) ? '' : sanitize_text_field( wp_unslash( $_POST['ewww_search'] ) );
44 $pending = empty( $_POST['ewww_pending'] ) ? 0 : 1;
45 if ( ! empty( $_POST['ewww_skew'] ) ) {
46 $skew = (int) $_POST['ewww_skew'];
47 if ( $skew > 0 ) {
48 $offset = $offset - $per_page + $skew;
49 }
50 if ( $offset < 0 ) {
51 $offset = 0;
52 }
53 }
54
55 $output = array();
56
57 $output['show_pending_button'] = false;
58 if ( ! $pending ) {
59 $output['show_pending_button'] = ewww_image_optimizer_aux_images_table_count_pending() > 0;
60 }
61
62 if ( $pending ) {
63 $sort_column = 'id';
64 $sort_direction = 'DESC';
65 $size_sort_class = '';
66 $attachment_sort = true;
67 if ( ! empty( $_POST['ewww_size_sort'] ) && 'asc' === $_POST['ewww_size_sort'] ) {
68 $sort_column = 'orig_size';
69 $sort_direction = 'ASC';
70 $size_sort_class = 'ewww-size-asc';
71 $attachment_sort = false;
72 } elseif ( ! empty( $_POST['ewww_size_sort'] ) && 'desc' === $_POST['ewww_size_sort'] ) {
73 $sort_column = 'orig_size';
74 $sort_direction = 'DESC';
75 $size_sort_class = 'ewww-size-desc';
76 $attachment_sort = false;
77 }
78 if ( ! empty( $search ) ) {
79 if ( 'ASC' === $sort_direction ) {
80 $already_optimized = $wpdb->get_results(
81 $wpdb->prepare(
82 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 AND path LIKE %s ORDER BY %i ASC LIMIT %d,%d',
83 $wpdb->ewwwio_images,
84 '%' . $wpdb->esc_like( $search ) . '%',
85 $sort_column,
86 $offset,
87 $per_page
88 ),
89 ARRAY_A
90 );
91 } elseif ( ! $attachment_sort ) {
92 $already_optimized = $wpdb->get_results(
93 $wpdb->prepare(
94 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 AND path LIKE %s ORDER BY %i DESC LIMIT %d,%d',
95 $wpdb->ewwwio_images,
96 '%' . $wpdb->esc_like( $search ) . '%',
97 $sort_column,
98 $offset,
99 $per_page
100 ),
101 ARRAY_A
102 );
103 } else {
104 $already_optimized = $wpdb->get_results(
105 $wpdb->prepare(
106 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 AND path LIKE %s ORDER BY attachment_id DESC, %i DESC LIMIT %d,%d',
107 $wpdb->ewwwio_images,
108 '%' . $wpdb->esc_like( $search ) . '%',
109 $sort_column,
110 $offset,
111 $per_page
112 ),
113 ARRAY_A
114 );
115 }
116 $search_count = $wpdb->get_var(
117 $wpdb->prepare(
118 "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE pending=1 AND path LIKE %s",
119 '%' . $wpdb->esc_like( $search ) . '%'
120 )
121 );
122 if ( $search_count < $per_page ) {
123 /* translators: %d: number of image records found */
124 $output['search_result'] = sprintf( esc_html__( '%d items found', 'ewww-image-optimizer' ), count( $already_optimized ) );
125 } else {
126 /* translators: 1: number of image records displayed, 2: number of total records found */
127 $output['search_result'] = sprintf( esc_html__( '%1$d items displayed of %2$s records found', 'ewww-image-optimizer' ), count( $already_optimized ), number_format_i18n( $search_count ) );
128 }
129 $total = ceil( $search_count / $per_page );
130 } else {
131 if ( 'ASC' === $sort_direction ) {
132 $already_optimized = $wpdb->get_results(
133 $wpdb->prepare(
134 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 ORDER BY %i ASC LIMIT %d,%d',
135 $wpdb->ewwwio_images,
136 $sort_column,
137 $offset,
138 $per_page
139 ),
140 ARRAY_A
141 );
142 } elseif ( ! $attachment_sort ) {
143 $already_optimized = $wpdb->get_results(
144 $wpdb->prepare(
145 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 ORDER BY %i DESC LIMIT %d,%d',
146 $wpdb->ewwwio_images,
147 $sort_column,
148 $offset,
149 $per_page
150 ),
151 ARRAY_A
152 );
153 } else {
154 $already_optimized = $wpdb->get_results(
155 $wpdb->prepare(
156 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=1 ORDER BY attachment_id DESC, %i DESC LIMIT %d,%d',
157 $wpdb->ewwwio_images,
158 $sort_column,
159 $offset,
160 $per_page
161 ),
162 ARRAY_A
163 );
164 }
165 $search_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE pending=1" );
166 $total = ceil( $search_count / $per_page );
167 /* translators: %d: number of image records found */
168 $output['search_result'] = sprintf( esc_html__( '%d items displayed', 'ewww-image-optimizer' ), count( $already_optimized ) );
169 }
170 } elseif ( ! empty( $search ) ) {
171 $already_optimized = $wpdb->get_results(
172 $wpdb->prepare(
173 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=0 AND image_size > 0 AND path LIKE %s ORDER BY id DESC LIMIT %d,%d',
174 $wpdb->ewwwio_images,
175 '%' . $wpdb->esc_like( $search ) . '%',
176 $offset,
177 $per_page
178 ),
179 ARRAY_A
180 );
181 $search_count = $wpdb->get_var(
182 $wpdb->prepare(
183 'SELECT COUNT(*) FROM %i WHERE pending=0 AND image_size > 0 AND path LIKE %s',
184 $wpdb->ewwwio_images,
185 '%' . $wpdb->esc_like( $search ) . '%'
186 )
187 );
188 if ( $search_count < $per_page ) {
189 /* translators: %d: number of image records found */
190 $output['search_result'] = sprintf( esc_html__( '%d items found', 'ewww-image-optimizer' ), count( $already_optimized ) );
191 } else {
192 /* translators: 1: number of image records displayed, 2: number of total records found */
193 $output['search_result'] = sprintf( esc_html__( '%1$d items displayed of %2$s records found', 'ewww-image-optimizer' ), count( $already_optimized ), number_format_i18n( $search_count ) );
194 }
195 $total = ceil( $search_count / $per_page );
196 } else {
197 $already_optimized = $wpdb->get_results(
198 $wpdb->prepare(
199 'SELECT path,orig_size,image_size,id,backup,attachment_id,gallery,resize_error,webp_size,webp_error,updates,UNIX_TIMESTAMP(updated) AS updated FROM %i WHERE pending=0 AND image_size > 0 ORDER BY id DESC LIMIT %d,%d',
200 $wpdb->ewwwio_images,
201 $offset,
202 $per_page
203 ),
204 ARRAY_A
205 );
206 $search_count = $wpdb->get_var(
207 $wpdb->prepare(
208 'SELECT COUNT(*) FROM %i WHERE pending=0 AND image_size > 0',
209 $wpdb->ewwwio_images
210 )
211 );
212 $total = ceil( $search_count / $per_page );
213 if ( empty( $output['search_result'] ) ) {
214 /* translators: %d: number of image records found */
215 $output['search_result'] = sprintf( esc_html__( '%d items displayed', 'ewww-image-optimizer' ), count( $already_optimized ) );
216 }
217 }
218
219 $output['pagination'] = sprintf(
220 /* translators: 1: current page in list of images 2: total pages for list of images */
221 esc_html__( 'page %1$s of %2$s', 'ewww-image-optimizer' ),
222 '<span class="current-page">' . ( (int) $_POST['ewww_offset'] + 1 ) . '</span>',
223 '<span class="total-pages">' . esc_html( number_format_i18n( $total ) ) . '</span>'
224 );
225
226 $output['search_count'] = count( $already_optimized );
227 $output['total_images'] = $search_count;
228 $output['total_pages'] = $total;
229
230 $output['total_images_text'] = sprintf(
231 /* translators: %d: number of images */
232 esc_html__( '%s total images', 'ewww-image-optimizer' ),
233 '<span class="total-images">' . number_format_i18n( $search_count ) . '</span>'
234 );
235
236 $output['table'] = '<table class="wp-list-table widefat media" cellspacing="0"><thead><tr><th>&nbsp;</th>' .
237 '<th>' . esc_html__( 'Filename', 'ewww-image-optimizer' ) . '</th>' .
238 '<th style="width:120px;">' . esc_html__( 'Image Type', 'ewww-image-optimizer' ) . '</th>' .
239 '<th style="width:120px;">' . esc_html__( 'Last Optimized', 'ewww-image-optimizer' ) . '</th>';
240 if ( $pending ) {
241 $output['table'] .= '<th class="' . esc_attr( $size_sort_class ) . '"><a class="ewww-sort-size">' .
242 esc_html__( 'Image Size', 'ewww-image-optimizer' ) .
243 '<span class="ewww-sort-grid"><span class="ewww-sort-asc dashicons dashicons-arrow-up"></span><span class="ewww-sort-desc dashicons dashicons-arrow-down"></span></span>' .
244 '</a></th></tr></thead>';
245 } else {
246 $output['table'] .= '<th>' . esc_html__( 'Results', 'ewww-image-optimizer' ) . '</th></tr></thead>';
247 }
248
249 $output['table'] .= '<tbody>';
250
251 if ( empty( $already_optimized ) ) {
252 $output['pagination'] = sprintf(
253 /* translators: 1: current page in list of images 2: total pages for list of images */
254 esc_html__( 'page %1$d of %2$s', 'ewww-image-optimizer' ),
255 1,
256 '<span class="total-pages">1</span>'
257 );
258 if ( $pending ) {
259 $output['table'] .= '<tr class="ewww-no-images"><td colspan="5">' . esc_html__( 'No images in queue.', 'ewww-image-optimizer' ) . '</td></tr>';
260 } else {
261 $output['table'] .= '<tr class="ewww-no-images"><td colspan="5">' . esc_html__( 'No images optimized!', 'ewww-image-optimizer' ) . '</td></tr>';
262 }
263 } else {
264 $alternate = true;
265 foreach ( $already_optimized as $optimized_image ) {
266 $optimized_image['pending'] = $pending;
267 $output['table'] .= ewww_image_optimizer_get_image_table_row( $optimized_image, $alternate );
268 $alternate = ! $alternate;
269 } // End foreach().
270 }
271
272 $output['table'] .= '</tbody></table>';
273 die( wp_json_encode( $output ) );
274 }
275
276 /**
277 * Render (and return) the output for a row in the image results table.
278 *
279 * @param array $optimized_image An image record from the database.
280 * @param bool $alternate Whether this is an alternate row, used to add the 'alternate' class for styling.
281 * @param bool $show_links Whether to include remove/restore links in the output. Optional, defaults to true.
282 * @return string The HTML for the image table row.
283 */
284 function ewww_image_optimizer_get_image_table_row( $optimized_image, $alternate, $show_links = true ) {
285 ob_start();
286 global $eio_backup;
287 $file = ewww_image_optimizer_absolutize_path( $optimized_image['path'] );
288 $image_name = str_replace( ABSPATH, '', $file );
289 $thumb_url = '';
290 $image_url = '';
291 ewwwio_debug_message( "name is $image_name after replacing ABSPATH" );
292 if ( 'media' === $optimized_image['gallery'] && ! empty( $optimized_image['attachment_id'] ) ) {
293 $thumb_url = wp_get_attachment_image_url( $optimized_image['attachment_id'] );
294 }
295 if ( $file !== $image_name ) {
296 $image_url = site_url( $image_name );
297 } else {
298 $image_name = str_replace( WP_CONTENT_DIR, '', $file );
299 if ( $file !== $image_name ) {
300 $image_url = content_url( $image_name );
301 }
302 }
303 if ( empty( $thumb_url ) && ! empty( $image_url ) ) {
304 $thumb_url = $image_url;
305 } elseif ( empty( $thumb_url ) ) {
306 $thumb_url = site_url( 'wp-includes/images/media/default.png' );
307 }
308 // Retrieve the mimetype of the attachment.
309 $type = ewww_image_optimizer_quick_mimetype( $file, 'i' );
310
311 $savings = '';
312 if ( $optimized_image['image_size'] ) {
313 $savings = esc_html( ewww_image_optimizer_image_results( $optimized_image['orig_size'], $optimized_image['image_size'] ) );
314 }
315 if ( 946684800 > $optimized_image['updated'] ) {
316 $last_updated = '';
317 } elseif ( $optimized_image['pending'] && empty( $optimized_image['image_size'] ) ) {
318 $last_updated = '';
319 } elseif ( ! $show_links ) {
320 $last_updated = gmdate( 'M j, H:i:s', $optimized_image['updated'] );
321 } else {
322 $last_updated = human_time_diff( $optimized_image['updated'] );
323 }
324
325 // Get a human readable filesize.
326 $file_size = ewww_image_optimizer_size_format( $optimized_image['image_size'] );
327 if ( empty( $optimized_image['image_size'] ) ) {
328 if ( ! empty( $optimized_image['orig_size'] ) ) {
329 $file_size = ewww_image_optimizer_size_format( $optimized_image['orig_size'] );
330 } elseif ( ewwwio_is_file( $file ) ) {
331 $file_size = ewww_image_optimizer_size_format( ewww_image_optimizer_filesize( $file ) );
332 } else {
333 $file_size = __( 'unknown', 'ewww-image-optimizer' );
334 }
335 }
336 /* translators: %s: human-readable filesize */
337 $size_string = sprintf( __( 'Image Size: %s', 'ewww-image-optimizer' ), $file_size );
338
339 $remove_from_text = __( 'Remove from history', 'ewww-image-optimizer' );
340 if ( $optimized_image['pending'] ) {
341 $remove_from_text = __( 'Remove from queue', 'ewww-image-optimizer' );
342 }
343 ?>
344 <tr class="ewww-image-<?php echo (int) $optimized_image['id'] . ( $alternate ? ' alternate' : '' ); ?>">
345 <td style="width:50px;" class="column-icon"><img style="width:50px;height:50px;object-fit:contain;" loading="lazy" src="<?php echo esc_url( $thumb_url ); ?>" /></td>
346 <td class="title">
347 <?php echo esc_html( $image_name ); ?>
348 <?php if ( $show_links ) : ?>
349 <br><a class="ewww-remove-image" data-id="<?php echo (int) $optimized_image['id']; ?>"><?php echo esc_html( $remove_from_text ); ?></a>
350 <?php if ( $optimized_image['pending'] ) : ?>
351 | <a class="ewww-exclude-image" data-id="<?php echo (int) $optimized_image['id']; ?>"><?php esc_html_e( 'Add exclusion', 'ewww-image-optimizer' ); ?></a>
352 <?php endif; ?>
353 <?php if ( ! $optimized_image['pending'] && $eio_backup->is_backup_available( $optimized_image['path'], $optimized_image ) ) : ?>
354 | <a class="ewww-restore-image" data-id="<?php echo (int) $optimized_image['id']; ?>"><?php esc_html_e( 'Restore original', 'ewww-image-optimizer' ); ?></a>
355 <?php endif; ?>
356 <?php endif; ?>
357 <?php if ( ! empty( $optimized_image['debug'] ) ) : ?>
358 <?php $debug_id = uniqid(); ?>
359 <br><a class="ewww-show-debug-meta" href="#" data-id="<?php echo esc_attr( $debug_id ); ?>"><?php esc_html_e( 'Show Debug Output', 'ewww-image-optimizer' ); ?></a>
360 <div class="ewww-bulk-single-debug <?php echo esc_attr( "ewww-debug-meta-$debug_id" ); ?>" style="background-color:#f1f1f1;display:none;"><?php echo wp_kses_post( $optimized_image['debug'] ); ?></div>
361 <?php endif; ?>
362 </td>
363 <td><?php echo esc_html( $type ); ?></td>
364 <td><?php echo esc_html( $last_updated ); ?></td>
365 <td>
366 <?php if ( $optimized_image['pending'] ) : ?>
367 <?php echo esc_html( $file_size ); ?>
368 <?php else : ?>
369 <?php echo esc_html( $savings ); ?>
370 <br>
371 <?php echo esc_html( $size_string ); ?>
372 <?php
373 // Check for WebP results.
374 $webp_error = '';
375 $webpurl = '';
376 list( $webpfile, $oldwebpfile ) = ewww_image_optimizer_get_all_webp_paths( $file );
377 $webp_size = ewww_image_optimizer_filesize( $webpfile );
378 $resize_status = ewww_image_optimizer_resize_results_message( $optimized_image['path'], $optimized_image['resize_error'] );
379 ?>
380 <?php if ( $show_links && ! str_ends_with( $file, '.webp' ) && ! ewww_image_optimizer_easy_active() && ewwwio_is_file( $oldwebpfile ) && current_user_can( apply_filters( 'ewww_image_optimizer_admin_permissions', '' ) ) ) : ?>
381 <br><a href="<?php echo esc_url( admin_url( 'options.php?page=ewww-image-optimizer-webp-migrate' ) ); ?>"><?php esc_html_e( 'Run WebP renaming', 'ewww-image-optimizer' ); ?></a>
382 <?php endif; ?>
383 <?php
384 if ( ! $webp_size ) {
385 if ( ! empty( $optimized_image['webp_size'] ) ) {
386 $webp_size = $optimized_image['webp_size'];
387 } elseif ( ! empty( $optimized_image['webp_error'] ) && 2 !== (int) $optimized_image['webp_error'] ) {
388 $webp_error = ewww_image_optimizer_webp_error_message( $optimized_image['webp_error'] );
389 }
390 }
391 if ( $webp_size ) {
392 // Get a human readable filesize.
393 $webp_size = ewww_image_optimizer_size_format( $webp_size );
394 if ( $image_url ) {
395 $webpurl = ewww_image_optimizer_get_webp_url( $file, $image_url );
396 }
397 }
398 ?>
399 <?php if ( $webp_size ) : ?>
400 <?php if ( $webpurl ) : ?>
401 <br>WebP: <a href="<?php echo esc_url( $webpurl ); ?>" target="_blank"><?php echo esc_html( $webp_size ); ?></a>
402 <?php else : ?>
403 <br>WebP: <?php echo esc_html( $webp_size ); ?>
404 <?php endif; ?>
405 <?php elseif ( $webp_error ) : ?>
406 <br><?php echo esc_html( $webp_error ); ?>
407 <?php endif; ?>
408 <?php if ( ! empty( $resize_status ) ) : ?>
409 <br><?php echo esc_html( $resize_status ); ?>
410 <?php endif; ?>
411 <?php if ( ! empty( $optimized_image['elapsed'] ) ) : ?>
412 <?php /* translators: %s: number of seconds */ ?>
413 <br><?php printf( esc_html( _n( 'Elapsed: %s second', 'Elapsed: %s seconds', $optimized_image['elapsed'], 'ewww-image-optimizer' ) ), esc_html( number_format_i18n( $optimized_image['elapsed'], 1 ) ) ); ?>
414 <?php endif; ?>
415 <?php endif; ?>
416 </td>
417 </tr>
418 <?php
419 return \ob_get_clean();
420 }
421 /**
422 * Excludes an image from the images table.
423 *
424 * Called via AJAX, this function will add an exclusion based on the record provided by the
425 * POST variable 'ewww_image_id' and return a '1' if successful. It will also toggle the pending
426 * indicator, and remove an image if it has not been optimized yet.
427 *
428 * @global object $wpdb
429 */
430 function ewww_image_optimizer_aux_images_exclude() {
431 // Verify that an authorized user has called function.
432 $permissions = apply_filters( 'ewww_image_optimizer_bulk_permissions', '' );
433 if (
434 empty( $_REQUEST['ewww_wpnonce'] ) ||
435 (
436 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) &&
437 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-bulk' )
438 ) ||
439 ! current_user_can( $permissions )
440 ) {
441 ewwwio_ob_clean();
442 die( esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) );
443 }
444 ewwwio_ob_clean();
445 global $wpdb;
446 if ( empty( $_POST['ewww_image_id'] ) ) {
447 die();
448 } else {
449 $id = (int) $_POST['ewww_image_id'];
450 }
451 $image = new \EWWW_Image( $id );
452 ewww_image_optimizer_add_file_exclusion( $image->file );
453 ewwwio_debug_message( "excluding {$image->file}" );
454 if ( empty( $image->opt_size ) ) {
455 ewwwio_debug_message( 'not optimized, removing record' );
456 if ( $wpdb->delete(
457 $wpdb->ewwwio_images,
458 array(
459 'id' => $id,
460 )
461 ) ) {
462 echo '1';
463 }
464 } else {
465 ewwwio_debug_message( 'previously optimized, toggle pending record' );
466 if ( $wpdb->update(
467 $wpdb->ewwwio_images,
468 array(
469 'pending' => 0,
470 ),
471 array(
472 'id' => $id,
473 )
474 ) ) {
475 echo '1';
476 }
477 }
478 die();
479 }
480
481 /**
482 * Removes an image from the auxiliary images table.
483 *
484 * Called via AJAX, this function will remove the record provided by the
485 * POST variable 'ewww_image_id' and return a '1' if successful.
486 *
487 * @global object $wpdb
488 */
489 function ewww_image_optimizer_aux_images_remove() {
490 // Verify that an authorized user has called function.
491 $permissions = apply_filters( 'ewww_image_optimizer_bulk_permissions', '' );
492 if (
493 empty( $_REQUEST['ewww_wpnonce'] ) ||
494 (
495 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) &&
496 ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-bulk' )
497 ) ||
498 ! current_user_can( $permissions )
499 ) {
500 ewwwio_ob_clean();
501 die( esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) );
502 }
503 ewwwio_ob_clean();
504 global $wpdb;
505 if ( empty( $_POST['ewww_image_id'] ) ) {
506 die();
507 } else {
508 $id = (int) $_POST['ewww_image_id'];
509 }
510 if ( empty( $_POST['ewww_pending'] ) ) {
511 ewwwio_debug_message( "removing record for $id" );
512 if ( $wpdb->delete(
513 $wpdb->ewwwio_images,
514 array(
515 'id' => $id,
516 )
517 ) ) {
518 echo '1';
519 }
520 } else {
521 ewwwio_debug_message( "toggle pending for $id" );
522 if ( $wpdb->update(
523 $wpdb->ewwwio_images,
524 array(
525 'pending' => 0,
526 ),
527 array(
528 'id' => $id,
529 )
530 ) ) {
531 echo '1';
532 }
533 }
534 ewwwio_memory( __FUNCTION__ );
535 die();
536 }
537
538 /**
539 * Removes all images from the auxiliary images table.
540 *
541 * Called via AJAX, this function will return a '1' if successful.
542 *
543 * @global object $wpdb
544 */
545 function ewww_image_optimizer_aux_images_clear_all() {
546 // Verify that an authorized user has called function.
547 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
548 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
549 ewwwio_ob_clean();
550 die( esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) );
551 }
552 ewwwio_ob_clean();
553 global $wpdb;
554 if ( $wpdb->query( "TRUNCATE $wpdb->ewwwio_images" ) ) {
555 die( esc_html__( 'All records have been removed from the optimization history.', 'ewww-image-optimizer' ) );
556 }
557 ewwwio_memory( __FUNCTION__ );
558 die();
559 }
560
561 /**
562 * Reset the progress/position of the WebP cleanup routine.
563 */
564 function ewww_image_optimizer_reset_webp_clean() {
565 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
566 check_admin_referer( 'ewww-image-optimizer-tools' );
567 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
568 if ( ! current_user_can( $permissions ) ) {
569 wp_die( esc_html__( 'Access denied.', 'ewww-image-optimizer' ) );
570 }
571 delete_option( 'ewww_image_optimizer_webp_clean_position' );
572 wp_safe_redirect( wp_get_referer() );
573 exit;
574 }
575
576 /**
577 * Reset the progress/position of the bulk restore routine.
578 */
579 function ewww_image_optimizer_reset_bulk_restore() {
580 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
581 check_admin_referer( 'ewww-image-optimizer-tools' );
582 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
583 if ( ! current_user_can( $permissions ) ) {
584 wp_die( esc_html__( 'Access denied.', 'ewww-image-optimizer' ) );
585 }
586 delete_option( 'ewww_image_optimizer_bulk_restore_position' );
587 wp_safe_redirect( wp_get_referer() );
588 exit;
589 }
590
591 /**
592 * Restore backups for images using records from the ewwwio_images table.
593 *
594 * @global object $wpdb
595 * @global object $eio_backup
596 */
597 function ewww_image_optimizer_bulk_restore_handler() {
598 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
599
600 session_write_close();
601 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
602 if ( ! current_user_can( $permissions ) ) {
603 ewwwio_ob_clean();
604 wp_die( wp_json_encode( array( 'error' => esc_html__( 'You do not have permission to optimize images.', 'ewww-image-optimizer' ) ) ) );
605 }
606 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) ) {
607 ewwwio_ob_clean();
608 wp_die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
609 }
610
611 global $eio_backup;
612 global $wpdb;
613
614 $completed = 0;
615 $position = (int) get_option( 'ewww_image_optimizer_bulk_restore_position' );
616 $per_page = (int) apply_filters( 'ewww_image_optimizer_bulk_restore_batch_size', 20 );
617 $started = time();
618
619 ewwwio_debug_message( "searching for $per_page records starting at $position" );
620 $optimized_images = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->ewwwio_images WHERE id > %d AND pending = 0 AND image_size > 0 AND updates > 0 ORDER BY id LIMIT %d", $position, $per_page ), ARRAY_A );
621
622 if ( empty( $optimized_images ) || ! is_countable( $optimized_images ) || 0 === count( $optimized_images ) ) {
623 ewwwio_debug_message( 'no more images, all done!' );
624 delete_option( 'ewww_image_optimizer_bulk_restore_position' );
625 ewwwio_ob_clean();
626 wp_die( wp_json_encode( array( 'finished' => 1 ) ) );
627 }
628
629 // Because some plugins might have loose filters (looking at you WPML).
630 remove_all_filters( 'wp_delete_file' );
631
632 $messages = '';
633 foreach ( $optimized_images as $optimized_image ) {
634 ++$completed;
635 ewwwio_debug_message( "submitting {$optimized_image['id']} to be restored" );
636 $eio_backup->restore_file( $optimized_image );
637 $error_message = $eio_backup->get_error();
638 if ( $error_message ) {
639 $messages .= esc_html( $error_message ) . '<br>';
640 }
641 update_option( 'ewww_image_optimizer_bulk_restore_position', $optimized_image['id'], false );
642 if ( time() > $started + 20 ) {
643 break;
644 }
645 } // End foreach().
646
647 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
648
649 ewwwio_ob_clean();
650 wp_die(
651 wp_json_encode(
652 array(
653 'completed' => $completed,
654 'messages' => $messages,
655 'new_nonce' => $new_nonce,
656 )
657 )
658 );
659 }
660
661 /**
662 * Check a nonce to see if it is on it's last leg/tick. If so, create a new one!
663 *
664 * @param string $current_nonce The existing nonce value for AJAX verification.
665 * @param string $action The handle connected to the nonce that indicates the context of the action performed.
666 * @return string A new nonce, or an empty value.
667 */
668 function ewwwio_maybe_get_new_nonce( $current_nonce, $action ) {
669 $tick = wp_verify_nonce( $current_nonce, $action );
670 if ( 2 === $tick ) {
671 return wp_create_nonce( $action );
672 }
673 return '';
674 }
675
676 /**
677 * Find the number of converted images in the ewwwio_images table.
678 *
679 * @global object $wpdb
680 */
681 function ewww_image_optimizer_aux_images_count_converted() {
682 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
683 // Verify that an authorized user has called function.
684 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
685 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
686 ewwwio_ob_clean();
687 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
688 }
689 ewwwio_ob_clean();
690 global $wpdb;
691 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE converted != ''" );
692 die( wp_json_encode( array( 'total_converted' => $count ) ) );
693 }
694
695 /**
696 * Cleanup originals of converted images using records from the ewwwio_images table.
697 *
698 * @global object $wpdb
699 */
700 function ewww_image_optimizer_aux_images_converted_clean() {
701 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
702 // Verify that an authorized user has called function.
703 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
704 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
705 ewwwio_ob_clean();
706 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
707 }
708 global $wpdb;
709 $completed = 0;
710 $per_page = 50;
711
712 $converted_images = $wpdb->get_results( $wpdb->prepare( "SELECT path,converted,id FROM $wpdb->ewwwio_images WHERE converted != '' ORDER BY id DESC LIMIT %d", $per_page ), ARRAY_A );
713
714 if ( empty( $converted_images ) || ! is_countable( $converted_images ) || 0 === count( $converted_images ) ) {
715 die( wp_json_encode( array( 'finished' => 1 ) ) );
716 }
717
718 // Because some plugins might have loose filters (looking at you WPML).
719 remove_all_filters( 'wp_delete_file' );
720
721 $messages = '';
722 foreach ( $converted_images as $optimized_image ) {
723 ++$completed;
724 $file = ewww_image_optimizer_absolutize_path( $optimized_image['converted'] );
725 ewwwio_debug_message( "$file was converted, checking if it still exists" );
726 if ( ! ewww_image_optimizer_stream_wrapped( $file ) && ewwwio_is_file( $file ) ) {
727 ewwwio_debug_message( "removing original: $file" );
728 if ( ewwwio_delete_file( $file ) ) {
729 ewwwio_debug_message( "removed $file" );
730 /* translators: %s: file name */
731 $messages .= sprintf( esc_html__( 'Deleted %s', 'ewww-image-optimizer' ), esc_html( $file ) ) . '<br>';
732 } else {
733 /* translators: %s: file name */
734 die( wp_json_encode( array( 'error' => sprintf( esc_html__( 'Could not delete %s, please remove manually or fix permissions and try again.', 'ewww-image-optimizer' ), esc_html( $file ) ) ) ) );
735 }
736 }
737 $wpdb->update(
738 $wpdb->ewwwio_images,
739 array(
740 'converted' => '',
741 ),
742 array(
743 'id' => $optimized_image['id'],
744 )
745 );
746 } // End foreach().
747
748 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
749
750 die(
751 wp_json_encode(
752 array(
753 'messages' => $messages,
754 'completed' => $completed,
755 'new_nonce' => $new_nonce,
756 )
757 )
758 );
759 }
760
761 /**
762 * Cleanup WebP images using records from the ewwwio_images table.
763 *
764 * @global object $wpdb
765 */
766 function ewww_image_optimizer_aux_images_webp_clean_handler() {
767 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
768 // Verify that an authorized user has called function.
769 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
770 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
771 ewwwio_ob_clean();
772 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
773 }
774 global $wpdb;
775 $completed = 0;
776 $per_page = 50;
777 $resume = get_option( 'ewww_image_optimizer_webp_clean_position' );
778 $position = is_array( $resume ) && ! empty( $resume['stage2'] ) ? (int) $resume['stage2'] : 0;
779 if ( ! is_array( $resume ) ) {
780 $resume = array();
781 }
782
783 ewwwio_debug_message( "searching for $per_page records starting at $position" );
784 $optimized_images = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->ewwwio_images WHERE id > %d AND pending = 0 AND image_size > 0 AND updates > 0 ORDER BY id LIMIT %d", $position, $per_page ), ARRAY_A );
785
786 if ( empty( $optimized_images ) || ! is_countable( $optimized_images ) || 0 === count( $optimized_images ) ) {
787 delete_option( 'ewww_image_optimizer_webp_clean_position' );
788 die( wp_json_encode( array( 'finished' => 1 ) ) );
789 }
790
791 // Because some plugins might have loose filters (looking at you WPML).
792 remove_all_filters( 'wp_delete_file' );
793
794 $removed = 0;
795 foreach ( $optimized_images as $optimized_image ) {
796 ++$completed;
797 $removed += ewww_image_optimizer_aux_images_webp_clean( $optimized_image );
798 }
799
800 $resume['stage2'] = $optimized_image['id'];
801 update_option( 'ewww_image_optimizer_webp_clean_position', $resume, false );
802
803 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
804
805 die(
806 wp_json_encode(
807 array(
808 'completed' => $completed,
809 'removed' => $removed,
810 'new_nonce' => $new_nonce,
811 )
812 )
813 );
814 }
815
816 /**
817 * Remove WebP images via db record.
818 *
819 * @param array $optimized_image The database record for an image from the optimization history.
820 * @return int Number of images removed.
821 */
822 function ewww_image_optimizer_aux_images_webp_clean( $optimized_image ) {
823 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
824 $file = ewww_image_optimizer_absolutize_path( $optimized_image['path'] );
825 $removed = 0;
826 ewwwio_debug_message( "looking for $file.webp" );
827 if ( ! ewww_image_optimizer_stream_wrapped( $file ) && ewwwio_is_file( $file ) && ewwwio_is_file( $file . '.webp' ) ) {
828 ewwwio_debug_message( "removing: $file.webp" );
829 if ( ewwwio_delete_file( $file . '.webp' ) ) {
830 ewwwio_debug_message( "removed $file.webp" );
831 ++$removed;
832 } else {
833 if ( wp_doing_ajax() ) {
834 /* translators: %s: file name */
835 die( wp_json_encode( array( 'error' => sprintf( esc_html__( 'Could not delete %s, please remove manually or fix permissions and try again.', 'ewww-image-optimizer' ), esc_html( $file . '.webp' ) ) ) ) );
836 } elseif ( defined( 'WP_CLI' ) && WP_CLI ) {
837 WP_CLI::error(
838 sprintf(
839 /* translators: %s: file name */
840 esc_html__( 'Could not delete %s, please remove manually or fix permissions and try again.', 'ewww-image-optimizer' ),
841 esc_html( $file . '.webp' )
842 )
843 );
844 }
845 }
846 }
847 if ( ! empty( $optimized_image['converted'] ) ) {
848 $file = ewww_image_optimizer_absolutize_path( $optimized_image['converted'] );
849 ewwwio_debug_message( "$file was converted, checking if webp version exists" );
850 if ( ! ewww_image_optimizer_stream_wrapped( $file ) && ewwwio_is_file( $file ) && ewwwio_is_file( $file . '.webp' ) ) {
851 ewwwio_debug_message( "removing: $file.webp" );
852 if ( ewwwio_delete_file( $file . '.webp' ) ) {
853 ewwwio_debug_message( "removed $file.webp" );
854 ++$removed;
855 } else {
856 if ( wp_doing_ajax() ) {
857 /* translators: %s: file name */
858 die( wp_json_encode( array( 'error' => sprintf( esc_html__( 'Could not delete %s, please remove manually or fix permissions and try again.', 'ewww-image-optimizer' ), esc_html( $file . '.webp' ) ) ) ) );
859 } elseif ( defined( 'WP_CLI' ) && WP_CLI ) {
860 WP_CLI::error(
861 sprintf(
862 /* translators: %s: file name */
863 esc_html__( 'Could not delete %s, please remove manually or fix permissions and try again.', 'ewww-image-optimizer' ),
864 esc_html( $file . '.webp' )
865 )
866 );
867 }
868 }
869 }
870 }
871 return $removed;
872 }
873
874 /**
875 * Cleanup WebP images via AJAX for a particular attachment.
876 *
877 * @global object $wpdb
878 */
879 function ewww_image_optimizer_delete_webp_handler() {
880 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
881 // Verify that an authorized user has called function.
882 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
883 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
884 ewwwio_ob_clean();
885 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
886 }
887 global $wpdb;
888 $resume = get_option( 'ewww_image_optimizer_webp_clean_position' );
889 $position = is_array( $resume ) && ! empty( $resume['stage1'] ) ? (int) $resume['stage1'] : 0;
890 if ( ! is_array( $resume ) ) {
891 $resume = array();
892 }
893
894 $id = (int) $wpdb->get_var(
895 $wpdb->prepare(
896 "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = 'attachment' AND (post_mime_type LIKE %s OR post_mime_type LIKE %s) ORDER BY ID LIMIT 1",
897 (int) $position,
898 '%image%',
899 '%pdf%'
900 )
901 );
902 if ( ! $id ) {
903 die( wp_json_encode( array( 'finished' => 1 ) ) );
904 }
905
906 // Because some plugins might have loose filters (looking at you WPML).
907 remove_all_filters( 'wp_delete_file' );
908
909 $removed = ewww_image_optimizer_delete_webp( $id );
910 $resume['stage1'] = (int) $id;
911 update_option( 'ewww_image_optimizer_webp_clean_position', $resume, false );
912
913 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
914
915 die(
916 wp_json_encode(
917 array(
918 'completed' => 1,
919 'removed' => $removed,
920 'new_nonce' => $new_nonce,
921 )
922 )
923 );
924 }
925
926 /**
927 * Cleanup WebP images for a particular attachment.
928 *
929 * @global object $wpdb
930 *
931 * @param int $id Attachment ID number for an image.
932 * @return int Number of images removed.
933 */
934 function ewww_image_optimizer_delete_webp( $id ) {
935 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
936 global $wpdb;
937
938 $removed = 0;
939 // Finds non-meta images to remove from disk, and from db, as well as converted originals.
940 $optimized_images = $wpdb->get_results(
941 $wpdb->prepare(
942 "SELECT path,converted FROM $wpdb->ewwwio_images WHERE attachment_id = %d AND gallery = 'media'",
943 $id
944 ),
945 ARRAY_A
946 );
947 if ( $optimized_images ) {
948 if ( ewww_image_optimizer_iterable( $optimized_images ) ) {
949 foreach ( $optimized_images as $image ) {
950 if ( ! empty( $image['path'] ) ) {
951 $image['path'] = ewww_image_optimizer_absolutize_path( $image['path'] );
952 }
953 if ( ! empty( $image['path'] ) ) {
954 ewwwio_debug_message( 'looking for: ' . $image['path'] . '.webp' );
955 if ( ewwwio_is_file( $image['path'] ) && ewwwio_is_file( $image['path'] . '.webp' ) ) {
956 ewwwio_debug_message( 'removing: ' . $image['path'] . '.webp' );
957 if ( ewwwio_delete_file( $image['path'] . '.webp' ) ) {
958 ++$removed;
959 }
960 }
961 $webpfileold = preg_replace( '/\.\w+$/', '.webp', $image['path'] );
962 if (
963 ! preg_match( '/\.webp$/', $image['path'] ) &&
964 ewwwio_is_file( $image['path'] ) &&
965 ewwwio_is_file( $webpfileold )
966 ) {
967 ewwwio_debug_message( 'removing: ' . $webpfileold );
968 if ( ewwwio_delete_file( $webpfileold ) ) {
969 ++$removed;
970 }
971 }
972 }
973 if ( ! empty( $image['converted'] ) && ewwwio_is_file( $image['converted'] ) && ewwwio_is_file( $image['converted'] . '.webp' ) ) {
974 ewwwio_debug_message( 'removing: ' . $image['converted'] . '.webp' );
975 if ( ewwwio_delete_file( $image['converted'] . '.webp' ) ) {
976 ++$removed;
977 }
978 }
979 }
980 }
981 }
982 $s3_path = false;
983 $s3_dir = false;
984 if ( ewww_image_optimizer_s3_uploads_enabled() ) {
985 $s3_path = get_attached_file( $id );
986 if ( 0 === strpos( $s3_path, 's3://' ) ) {
987 $webp_paths = ewww_image_optimizer_get_all_webp_paths( $s3_path );
988 foreach ( $webp_paths as $webp_path ) {
989 if ( ewwwio_is_file( $webp_path ) ) {
990 ewwwio_debug_message( 'removing: ' . $webp_path );
991 unlink( $webp_path );
992 }
993 }
994 }
995 $s3_dir = trailingslashit( dirname( $s3_path ) );
996 }
997 // Retrieve the image metadata.
998 $meta = wp_get_attachment_metadata( $id );
999 // If the attachment has an original file set.
1000 if ( ! empty( $meta['orig_file'] ) ) {
1001 // Get the filepath from the metadata.
1002 $file_path = $meta['orig_file'];
1003 // Get the base filename.
1004 $filename = wp_basename( $file_path );
1005 // Delete any residual webp versions.
1006 $webpfile = $file_path . '.webp';
1007 $webpfileold = preg_replace( '/\.\w+$/', '.webp', $file_path );
1008 if ( ewwwio_is_file( $file_path ) && ewwwio_is_file( $webpfile ) ) {
1009 ewwwio_debug_message( 'removing: ' . $webpfile );
1010 if ( ewwwio_delete_file( $webpfile ) ) {
1011 ++$removed;
1012 }
1013 }
1014 if (
1015 ! preg_match( '/\.webp$/', $file_path ) &&
1016 ewwwio_is_file( $file_path ) &&
1017 ewwwio_is_file( $webpfileold )
1018 ) {
1019 ewwwio_debug_message( 'removing: ' . $webpfileold );
1020 if ( ewwwio_delete_file( $webpfileold ) ) {
1021 ++$removed;
1022 }
1023 }
1024 }
1025 $file_path = get_attached_file( $id );
1026 // If the attachment has an original file set.
1027 if ( ! empty( $meta['original_image'] ) ) {
1028 // One way or another, $file_path is now set, and we can get the base folder name.
1029 $base_dir = dirname( $file_path ) . '/';
1030 // Get the original filename from the metadata.
1031 $orig_path = $base_dir . wp_basename( $meta['original_image'] );
1032 // Delete any residual webp versions.
1033 $webpfile = $orig_path . '.webp';
1034 if ( ewwwio_is_file( $orig_path ) && ewwwio_is_file( $webpfile ) ) {
1035 ewwwio_debug_message( 'removing: ' . $webpfile );
1036 if ( ewwwio_delete_file( $webpfile ) ) {
1037 ++$removed;
1038 }
1039 }
1040 if ( $s3_path && $s3_dir && wp_basename( $meta['original_image'] ) ) {
1041 ewwwio_debug_message( 'removing: ' . $s3_dir . wp_basename( $meta['original_image'] ) . '.webp' );
1042 if ( unlink( $s3_dir . wp_basename( $meta['original_image'] ) . '.webp' ) ) {
1043 ++$removed;
1044 }
1045 }
1046 }
1047 // Resized versions, so we can continue.
1048 if ( isset( $meta['sizes'] ) && ewww_image_optimizer_iterable( $meta['sizes'] ) ) {
1049 // One way or another, $file_path is now set, and we can get the base folder name.
1050 $base_dir = dirname( $file_path ) . '/';
1051 foreach ( $meta['sizes'] as $size => $data ) {
1052 if ( empty( $data['file'] ) ) {
1053 continue;
1054 }
1055 // Delete any residual webp versions.
1056 $webpfile = $base_dir . wp_basename( $data['file'] ) . '.webp';
1057 $webpfileold = preg_replace( '/\.\w+$/', '.webp', $base_dir . wp_basename( $data['file'] ) );
1058 if ( ewwwio_is_file( $base_dir . wp_basename( $data['file'] ) ) && ewwwio_is_file( $webpfile ) ) {
1059 ewwwio_debug_message( 'removing: ' . $webpfile );
1060 if ( ewwwio_delete_file( $webpfile ) ) {
1061 ++$removed;
1062 }
1063 }
1064 if (
1065 ! preg_match( '/\.webp$/', $base_dir . wp_basename( $data['file'] ) ) &&
1066 ewwwio_is_file( $base_dir . wp_basename( $data['file'] ) ) &&
1067 ewwwio_is_file( $webpfileold )
1068 ) {
1069 ewwwio_debug_message( 'removing: ' . $webpfileold );
1070 if ( ewwwio_delete_file( $webpfileold ) ) {
1071 ++$removed;
1072 }
1073 }
1074 if ( $s3_path && $s3_dir && wp_basename( $data['file'] ) ) {
1075 ewwwio_debug_message( 'removing: ' . $s3_dir . wp_basename( $data['file'] ) . '.webp' );
1076 if ( unlink( $s3_dir . wp_basename( $data['file'] ) . '.webp' ) ) {
1077 ++$removed;
1078 }
1079 }
1080 // If the original resize is set, and still exists.
1081 if (
1082 ! empty( $data['orig_file'] ) &&
1083 ewwwio_is_file( $base_dir . $data['orig_file'] ) &&
1084 ewwwio_is_file( $base_dir . $data['orig_file'] . '.webp' )
1085 ) {
1086 ewwwio_debug_message( 'removing: ' . $base_dir . $data['orig_file'] . '.webp' );
1087 if ( ewwwio_delete_file( $base_dir . $data['orig_file'] . '.webp' ) ) {
1088 ++$removed;
1089 }
1090 }
1091 }
1092 }
1093 ewwwio_debug_message( "looking for: $file_path.webp" );
1094 if ( ewwwio_is_file( $file_path ) && ewwwio_is_file( $file_path . '.webp' ) ) {
1095 ewwwio_debug_message( 'removing: ' . $file_path . '.webp' );
1096 if ( ewwwio_delete_file( $file_path . '.webp' ) ) {
1097 ++$removed;
1098 }
1099 }
1100 $webpfileold = preg_replace( '/\.\w+$/', '.webp', $file_path );
1101 if (
1102 ! preg_match( '/\.webp$/', $file_path ) &&
1103 ewwwio_is_file( $file_path ) &&
1104 ewwwio_is_file( $webpfileold )
1105 ) {
1106 ewwwio_debug_message( 'removing: ' . $webpfileold );
1107 if ( ewwwio_delete_file( $webpfileold ) ) {
1108 ++$removed;
1109 }
1110 }
1111
1112 // Remove WebP notification from displaying in media library.
1113 $wpdb->update(
1114 $wpdb->ewwwio_images,
1115 array(
1116 'webp_size' => 0,
1117 'webp_error' => 0,
1118 ),
1119 array(
1120 'attachment_id' => $id,
1121 )
1122 );
1123 return $removed;
1124 }
1125
1126 /**
1127 * Cleans up original_image via AJAX for a particular attachment.
1128 */
1129 function ewww_image_optimizer_ajax_delete_original() {
1130 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1131 // Verify that an authorized user has called function.
1132 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
1133 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
1134 ewwwio_ob_clean();
1135 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
1136 }
1137 if ( ! empty( $_POST['delete_originals_done'] ) ) {
1138 delete_option( 'ewww_image_optimizer_delete_originals_resume' );
1139 die( wp_json_encode( array( 'done' => 1 ) ) );
1140 }
1141 if ( empty( $_POST['attachment_id'] ) ) {
1142 die( wp_json_encode( array( 'error' => esc_html__( 'Missing attachment ID number.', 'ewww-image-optimizer' ) ) ) );
1143 }
1144
1145 // Because some plugins might have loose filters (looking at you WPML).
1146 remove_all_filters( 'wp_delete_file' );
1147
1148 $count = 0;
1149 if ( ! empty( $_POST['completed'] ) ) {
1150 $count = (int) $_POST['completed'] + 1;
1151 }
1152
1153 $total = 0;
1154 if ( ! empty( $_POST['total'] ) ) {
1155 $total = (int) $_POST['total'];
1156 }
1157
1158 $deleted = false;
1159 $id = (int) $_POST['attachment_id'];
1160 $old_meta = wp_get_attachment_metadata( $id );
1161 $new_meta = ewwwio_remove_original_image( $id, $old_meta );
1162 if ( ewww_image_optimizer_iterable( $new_meta ) ) {
1163 $deleted_image = ewwwio_get_original_image_path( $id, '', $old_meta );
1164 wp_update_attachment_metadata( $id, $new_meta );
1165 /* translators: %s: filename of deleted image */
1166 $deleted = sprintf( esc_html__( 'Deleted %s', 'ewww-image-optimizer' ), esc_html( $deleted_image ) );
1167 }
1168
1169 update_option( 'ewww_image_optimizer_delete_originals_resume', $id, false );
1170
1171 /* translators: 1: number of images scanned so far, 2: total number of images to scan */
1172 $progress = sprintf( esc_html__( '%1$s / %2$s images checked', 'ewww-image-optimizer' ), number_format_i18n( $count ), number_format_i18n( $total ) );
1173 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
1174
1175 die(
1176 wp_json_encode(
1177 array(
1178 'progress' => $progress,
1179 'deleted' => $deleted,
1180 'new_nonce' => $new_nonce,
1181 )
1182 )
1183 );
1184 }
1185
1186 /**
1187 * Cleanup duplicate and unreferenced records from the images table.
1188 *
1189 * Called via AJAX to find records from the images table and checks them for duplicates and
1190 * references to non-existent files.
1191 *
1192 * @global object $wpdb
1193 */
1194 function ewww_image_optimizer_aux_images_clean() {
1195 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1196 // Verify that an authorized user has called function.
1197 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
1198 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
1199 ewwwio_ob_clean();
1200 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
1201 }
1202 global $wpdb;
1203 $per_page = 500;
1204 $offset = empty( $_POST['ewww_offset'] ) ? 0 : $per_page * (int) $_POST['ewww_offset'];
1205
1206 $already_optimized = $wpdb->get_results( $wpdb->prepare( "SELECT path,orig_size,image_size,id,backup,updated FROM $wpdb->ewwwio_images WHERE pending=0 AND image_size > 0 ORDER BY id DESC LIMIT %d,%d", $offset, $per_page ), ARRAY_A );
1207
1208 foreach ( $already_optimized as $optimized_image ) {
1209 $file = ewww_image_optimizer_absolutize_path( $optimized_image['path'] );
1210 ewwwio_debug_message( "checking $file for duplicates and dereferences" );
1211 // Will remove duplicates.
1212 ewww_image_optimizer_find_already_optimized( $file );
1213 if ( ! ewww_image_optimizer_stream_wrapped( $file ) && ! ewwwio_is_file( $file ) ) {
1214 ewwwio_debug_message( "removing defunct record for $file" );
1215 $wpdb->delete(
1216 $wpdb->ewwwio_images,
1217 array(
1218 'id' => $optimized_image['id'],
1219 ),
1220 array( '%d' )
1221 );
1222 }
1223 } // End foreach().
1224
1225 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
1226
1227 die(
1228 wp_json_encode(
1229 array(
1230 'success' => 1,
1231 'new_nonce' => $new_nonce,
1232 )
1233 )
1234 );
1235 }
1236
1237 /**
1238 * Cleanup and migrate optimization data from wp_postmeta to the images table.
1239 *
1240 * @global object $wpdb
1241 */
1242 function ewww_image_optimizer_aux_meta_clean() {
1243 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1244
1245 // Verify that an authorized user has called function.
1246 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
1247 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
1248 ewwwio_ob_clean();
1249 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
1250 }
1251
1252 global $wpdb;
1253 $per_page = 50;
1254 $offset = empty( $_POST['ewww_offset'] ) ? 0 : (int) $_POST['ewww_offset'];
1255 ewwwio_debug_message( "getting $per_page attachments, starting at $offset" );
1256
1257 $attachments = $wpdb->get_col(
1258 $wpdb->prepare(
1259 "SELECT ID FROM $wpdb->posts WHERE post_type = 'attachment' AND (post_mime_type LIKE %s OR post_mime_type LIKE %s) ORDER BY ID ASC LIMIT %d,%d",
1260 '%image%',
1261 '%pdf%',
1262 $offset,
1263 $per_page
1264 )
1265 );
1266
1267 if ( empty( $attachments ) ) {
1268 die( wp_json_encode( array( 'done' => 1 ) ) );
1269 }
1270
1271 foreach ( $attachments as $attachment_id ) {
1272 ewwwio_debug_message( "checking $attachment_id for migration" );
1273 $meta = wp_get_attachment_metadata( $attachment_id );
1274 if ( is_array( $meta ) ) {
1275 ewww_image_optimizer_migrate_meta_to_db( $attachment_id, $meta );
1276 }
1277 }
1278
1279 $new_nonce = ewwwio_maybe_get_new_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' );
1280
1281 die(
1282 wp_json_encode(
1283 array(
1284 'success' => $per_page,
1285 'new_nonce' => $new_nonce,
1286 )
1287 )
1288 );
1289 }
1290
1291 /**
1292 * Find the number of optimized images in the ewwwio_images table.
1293 *
1294 * @param bool $cached Whether to use a cached value.
1295 * @global object $wpdb
1296 * @return int The total number of records in the images table that are not pending and have a
1297 * valid file-size.
1298 */
1299 function ewww_image_optimizer_aux_images_table_count( $cached = false ) {
1300 if ( $cached ) {
1301 $count = get_transient( 'ewwwio_images_table_count' );
1302 if ( $count ) {
1303 return (int) $count;
1304 }
1305 }
1306 global $wpdb;
1307 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE pending=0 AND image_size > 0 AND updates > 0" );
1308 // Verify that an authorized user has called function.
1309 $permissions = apply_filters( 'ewww_image_optimizer_bulk_permissions', '' );
1310 if ( ! empty( $_REQUEST['ewww_inline'] ) &&
1311 ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) )
1312 ) {
1313 die();
1314 } elseif ( ! empty( $_REQUEST['ewww_inline'] ) ) {
1315 ewwwio_ob_clean();
1316 echo (int) $count;
1317 ewwwio_memory( __FUNCTION__ );
1318 die();
1319 }
1320 if ( $cached && $count ) {
1321 set_transient( 'ewwwio_images_table_count', (int) $count, HOUR_IN_SECONDS );
1322 }
1323 ewwwio_memory( __FUNCTION__ );
1324 return (int) $count;
1325 }
1326
1327 /**
1328 * Find the number of un-optimized images in the ewwwio_images table.
1329 *
1330 * @global object $wpdb
1331 * @return int Number of pending images in queue.
1332 */
1333 function ewww_image_optimizer_aux_images_table_count_pending() {
1334 global $wpdb;
1335 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE pending=1" );
1336 return $count;
1337 }
1338
1339 /**
1340 * Find the number of un-optimized (media) images in the ewwwio_images table.
1341 *
1342 * This is useful to know if we need to alert the user when the bulk attachments array is empty.
1343 *
1344 * @global object $wpdb
1345 * @return int Number of pending media images in queue.
1346 */
1347 function ewww_image_optimizer_aux_images_table_count_pending_media() {
1348 global $wpdb;
1349 $count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->ewwwio_images WHERE pending=1 AND gallery='media'" );
1350 return $count;
1351 }
1352
1353 /**
1354 * Set a batch of images to pending.
1355 *
1356 * @global object $wpdb
1357 *
1358 * @param array $reset_images A list of images to reset in the ewwwio_images table.
1359 */
1360 function ewww_image_optimizer_reset_images( $reset_images ) {
1361 if ( ! ewww_image_optimizer_iterable( $reset_images ) ) {
1362 return;
1363 }
1364 array_walk( $reset_images, 'intval' );
1365 global $wpdb;
1366 /**
1367 * Set a maximum for a query, 1k less than WPE's 16k limit, just to be safe.
1368 *
1369 * @param int 15000 The maximum query length.
1370 */
1371 $max_query_length = apply_filters( 'ewww_image_optimizer_max_query_length', 15000 );
1372 $reset_images_sql = '';
1373 foreach ( $reset_images as $reset_image ) {
1374 if ( strlen( $reset_images_sql ) > $max_query_length ) {
1375 $reset_images_sql = rtrim( $reset_images_sql, ',' );
1376 $updated = $wpdb->query( "UPDATE $wpdb->ewwwio_images SET pending = 1, updated = updated WHERE id IN ($reset_images_sql)" ); // phpcs:ignore WordPress.DB.PreparedSQL
1377 if ( ! $updated ) {
1378 ewwwio_debug_message( 'db error: ' . $wpdb->last_error );
1379 }
1380 $reset_images_sql = '';
1381 }
1382 $reset_images_sql .= $reset_image . ',';
1383 }
1384 $reset_images_sql = rtrim( $reset_images_sql, ',' );
1385 $updated = $wpdb->query( "UPDATE $wpdb->ewwwio_images SET pending = 1, updated = updated WHERE id IN ($reset_images_sql)" ); // phpcs:ignore WordPress.DB.PreparedSQL
1386 if ( ! $updated ) {
1387 ewwwio_debug_message( 'db error: ' . $wpdb->last_error );
1388 }
1389 }
1390
1391 /**
1392 * Remove all un-optimized images from the ewwwio_images table.
1393 *
1394 * @global object $wpdb
1395 */
1396 function ewww_image_optimizer_delete_pending() {
1397 global $wpdb;
1398 $wpdb->query( "DELETE from $wpdb->ewwwio_images WHERE pending=1 AND (image_size IS NULL OR image_size = 0)" );
1399 $wpdb->update(
1400 $wpdb->ewwwio_images,
1401 array(
1402 'pending' => 0,
1403 ),
1404 array(
1405 'pending' => 1,
1406 )
1407 );
1408 }
1409
1410 /**
1411 * Remove an un-optimized image from the ewwwio_images table.
1412 *
1413 * If the image was previously optimized, then simply toggle the pending column.
1414 *
1415 * @param int $id The ID of the pending image in the db.
1416 * @global object $wpdb
1417 */
1418 function ewww_image_optimizer_delete_pending_image( $id ) {
1419 global $wpdb;
1420 $deleted = $wpdb->query( $wpdb->prepare( "DELETE from $wpdb->ewwwio_images WHERE id=%d AND pending=1 AND (image_size IS NULL OR image_size = 0)", $id ) );
1421 if ( ! $deleted ) {
1422 ewww_image_optimizer_toggle_pending_image( $id );
1423 }
1424 }
1425
1426 /**
1427 * Toggle the pending flag for an image in the ewwwio_images table.
1428 *
1429 * @param int $id The ID of the pending image in the db.
1430 * @global object $wpdb
1431 */
1432 function ewww_image_optimizer_toggle_pending_image( $id ) {
1433 global $wpdb;
1434 $wpdb->update(
1435 $wpdb->ewwwio_images,
1436 array(
1437 'pending' => 0,
1438 'retrieve' => '',
1439 ),
1440 array(
1441 'id' => $id,
1442 )
1443 );
1444 }
1445
1446 /**
1447 * Retrieve the number images from the ewwwio_queue table.
1448 *
1449 * @since 4.6.0
1450 *
1451 * @global object $wpdb
1452 */
1453 function ewww_image_optimizer_count_attachments() {
1454 global $wpdb;
1455 $count = $wpdb->get_var( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_type = 'attachment' AND (post_mime_type LIKE '%%image%%' OR post_mime_type LIKE '%%pdf%%') ORDER BY ID DESC" );
1456 return $count;
1457 }
1458
1459 /**
1460 * Retrieve the total number of images from the ewwwio_queue table.
1461 *
1462 * @since 8.5.0
1463 *
1464 * @param string $gallery The type of attachments to count from the queue.
1465 * @global object $wpdb
1466 */
1467 function ewww_image_optimizer_count_queued_attachments( $gallery = 'media' ) {
1468 global $wpdb;
1469 $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->ewwwio_queue WHERE gallery = %s", $gallery ) );
1470 return $count;
1471 }
1472
1473 /**
1474 * Retrieve the number of scanned images from the ewwwio_queue table.
1475 *
1476 * @since 8.5.0
1477 *
1478 * @param string $gallery The type of attachments to count from the queue.
1479 * @global object $wpdb
1480 */
1481 function ewww_image_optimizer_count_scanned_attachments( $gallery = 'media' ) {
1482 global $wpdb;
1483 $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->ewwwio_queue WHERE gallery = %s AND scanned = 1", $gallery ) );
1484 return $count;
1485 }
1486
1487 /**
1488 * Retrieve the number of un-scanned images from the ewwwio_queue table.
1489 *
1490 * @since 4.6.0
1491 *
1492 * @param string $gallery The type of attachments to count from the queue.
1493 * @global object $wpdb
1494 */
1495 function ewww_image_optimizer_count_unscanned_attachments( $gallery = 'media' ) {
1496 global $wpdb;
1497 $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->ewwwio_queue WHERE gallery = %s AND scanned = 0", $gallery ) );
1498 return $count;
1499 }
1500
1501 /**
1502 * Retrieve unscanned images from the ewwwio_queue table.
1503 *
1504 * @since 4.6.0
1505 *
1506 * @param string $gallery The type of attachments for which to search.
1507 * @param int $limit The maximum number of unscanned attachments to retrieve.
1508 * @return array A list of unscanned attachments. Will always be an array of integers.
1509 * @global object $wpdb
1510 */
1511 function ewww_image_optimizer_get_unscanned_attachments( $gallery, $limit = 1000 ) {
1512 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1513 global $wpdb;
1514 // Retrieve the attachment IDs that were pre-loaded in the database.
1515 $selected_ids = $wpdb->get_col( $wpdb->prepare( "SELECT attachment_id FROM $wpdb->ewwwio_queue WHERE gallery = %s AND scanned = 0 LIMIT %d", $gallery, $limit ) );
1516 if ( empty( $selected_ids ) ) {
1517 ewwwio_debug_message( 'no attachments found for scanning' );
1518 return array();
1519 }
1520 array_walk( $selected_ids, 'intval' );
1521 ewwwio_debug_message( 'selected items: ' . count( $selected_ids ) );
1522 return $selected_ids;
1523 }
1524
1525 /**
1526 * Check to see if an attachment has any more sizes that are pending.
1527 *
1528 * @param int $id The ID of the attachment/image.
1529 * @param string $gallery The type of image to look for. Optional, default is 'media'.
1530 * @return int The number of pending sizes in the database.
1531 * @global object $wpdb
1532 */
1533 function ewww_image_optimizer_attachment_has_pending_sizes( $id, $gallery = 'media' ) {
1534 global $wpdb;
1535 $id = (int) $id;
1536 return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM $wpdb->ewwwio_images WHERE attachment_id = %d AND gallery = %s AND pending = 1", $id, $gallery ) );
1537 }
1538
1539 /**
1540 * Check to see if an image is in the queue.
1541 *
1542 * @param int $id The ID of the attachment/image.
1543 * @param string $gallery The type of image to look for. Optional.
1544 * @return bool True if it's still in the queue.
1545 * @global object $wpdb
1546 */
1547 function ewww_image_optimizer_image_is_pending( $id, $gallery = 'media' ) {
1548 global $wpdb;
1549 $id = (int) $id;
1550 return $wpdb->get_var( $wpdb->prepare( "SELECT attachment_id FROM $wpdb->ewwwio_queue WHERE attachment_id = %d AND gallery = %s LIMIT 1", $id, $gallery ) );
1551 }
1552
1553 /**
1554 * Count all the images (and PDFs) from the wp_posts table.
1555 *
1556 * @global object $wpdb
1557 *
1558 * @return int The number of image and PDF attachments in the posts table.
1559 */
1560 function ewww_image_optimizer_count_all_attachments() {
1561 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1562 global $wpdb;
1563 return $wpdb->get_var(
1564 $wpdb->prepare(
1565 "SELECT count(ID) FROM $wpdb->posts WHERE post_type = 'attachment' AND (post_mime_type LIKE %s OR post_mime_type LIKE %s)",
1566 '%image%',
1567 '%pdf%'
1568 )
1569 );
1570 }
1571
1572 /**
1573 * Retrieve all the images (and PDFs) from the wp_posts table.
1574 *
1575 * @global object $wpdb
1576 */
1577 function ewww_image_optimizer_get_all_attachments() {
1578 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1579 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
1580 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
1581 ewwwio_ob_clean();
1582 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
1583 }
1584 $start_id = get_option( 'ewww_image_optimizer_delete_originals_resume', 0 );
1585 global $wpdb;
1586 $attachments = $wpdb->get_col(
1587 $wpdb->prepare(
1588 "SELECT ID FROM $wpdb->posts WHERE ID > %d AND post_type = 'attachment' AND (post_mime_type LIKE %s OR post_mime_type LIKE %s) ORDER BY ID DESC",
1589 (int) $start_id,
1590 '%image%',
1591 '%pdf%'
1592 )
1593 );
1594 if ( empty( $attachments ) || ! is_countable( $attachments ) || 0 === count( $attachments ) ) {
1595 delete_option( 'ewww_image_optimizer_delete_originals_resume' );
1596 die( wp_json_encode( array( 'error' => esc_html__( 'No media uploads found.', 'ewww-image-optimizer' ) ) ) );
1597 }
1598 ewwwio_debug_message( gettype( $attachments ) );
1599 die( wp_json_encode( $attachments ) );
1600 }
1601
1602 /**
1603 * Count all the image (and PDF) attachments that are remaining for WebP cleanup.
1604 *
1605 * @global object $wpdb
1606 */
1607 function ewww_image_optimizer_webp_attachment_count() {
1608 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1609 $permissions = apply_filters( 'ewww_image_optimizer_admin_permissions', '' );
1610 if ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-tools' ) || ! current_user_can( $permissions ) ) {
1611 ewwwio_ob_clean();
1612 die( wp_json_encode( array( 'error' => esc_html__( 'Access token has expired, please reload the page.', 'ewww-image-optimizer' ) ) ) );
1613 }
1614 $resume = get_option( 'ewww_image_optimizer_webp_clean_position' );
1615 $start_id = is_array( $resume ) && ! empty( $resume['stage1'] ) ? (int) $resume['stage1'] : 0;
1616
1617 global $wpdb;
1618 $total_attachments = (int) $wpdb->get_var(
1619 $wpdb->prepare(
1620 "SELECT count(ID) FROM $wpdb->posts WHERE ID > %d AND post_type = 'attachment' AND (post_mime_type LIKE %s OR post_mime_type LIKE %s)",
1621 (int) $start_id,
1622 '%image%',
1623 '%pdf%'
1624 )
1625 );
1626 die( wp_json_encode( array( 'total' => (int) $total_attachments ) ) );
1627 }
1628
1629 /**
1630 * Retrieve an image ID from the ewwwio_queue table.
1631 *
1632 * @since 4.6.0
1633 *
1634 * @param string $gallery The type of attachment to find.
1635 * @param int $limit The maximum number of unscanned attachments to retrieve.
1636 * @return array The ID list for queued/scanned attachments.
1637 * @global object $wpdb
1638 */
1639 function ewww_image_optimizer_get_queued_attachments( $gallery, $limit = 100 ) {
1640 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1641 global $wpdb;
1642 // Retrieve the attachment IDs that were pre-loaded in the database.
1643 $selected_ids = $wpdb->get_col( $wpdb->prepare( "SELECT attachment_id FROM $wpdb->ewwwio_queue WHERE gallery = %s AND scanned = 1 ORDER BY attachment_id DESC LIMIT %d", $gallery, $limit ) );
1644 if ( empty( $selected_ids ) ) {
1645 ewwwio_debug_message( 'no attachments found in queue' );
1646 return array( 0 );
1647 }
1648 array_walk( $selected_ids, 'intval' );
1649 ewwwio_debug_message( 'selected items: ' . count( $selected_ids ) );
1650 return $selected_ids;
1651 }
1652
1653 /**
1654 * Insert a batch of attachment IDs into the ewwwio_queue table.
1655 *
1656 * @since 4.6.0
1657 *
1658 * @param array $ids The list of attachment IDs to insert.
1659 * @param string $gallery The type of attachments to insert. Defaults to media library.
1660 * @global object $wpdb
1661 */
1662 function ewww_image_optimizer_insert_unscanned( $ids, $gallery = 'media' ) {
1663 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1664 global $wpdb;
1665 $images = array();
1666 $id = array_shift( $ids );
1667 while ( ! empty( $id ) ) {
1668 $images[] = array(
1669 'attachment_id' => (int) $id,
1670 'gallery' => $gallery,
1671 'force_reopt' => (int) ewwwio()->force,
1672 'force_smart' => (int) ewwwio()->force_smart,
1673 'webp_only' => (int) ewwwio()->webp_only,
1674 );
1675 $id = array_shift( $ids );
1676 }
1677 if ( $images ) {
1678 $result = ewww_image_optimizer_mass_insert( $wpdb->ewwwio_queue, $images );
1679 }
1680 }
1681
1682 /**
1683 * Update an image in the queue after it has been scanned.
1684 *
1685 * @since 4.6.0
1686 *
1687 * @param int $ids The attachment IDs to update.
1688 * @param string $gallery The type of attachment to update. Defaults to media library.
1689 * @global object $wpdb
1690 */
1691 function ewww_image_optimizer_update_scanned_images( $ids, $gallery = 'media' ) {
1692 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1693 if ( ! ewww_image_optimizer_iterable( $ids ) ) {
1694 return;
1695 }
1696 global $wpdb;
1697
1698 array_walk( $ids, 'intval' );
1699 $ids_sql = '(' . implode( ',', $ids ) . ')';
1700
1701 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->ewwwio_queue SET scanned = 1 WHERE gallery = %s AND attachment_id IN $ids_sql", $gallery ) ); // phpcs:ignore WordPress.DB.PreparedSQL
1702 }
1703
1704 /**
1705 * Remove a batch of images from the ewwwio_queue table (usually when we are done with it).
1706 *
1707 * @since 4.6.0
1708 *
1709 * @param int $ids The attachment IDs to remove.
1710 * @param string $gallery The type of attachment to remove. Defaults to media library.
1711 * @global object $wpdb
1712 */
1713 function ewww_image_optimizer_delete_queued_images( $ids, $gallery = 'media' ) {
1714 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1715 if ( ! ewww_image_optimizer_iterable( $ids ) ) {
1716 return;
1717 }
1718 global $wpdb;
1719
1720 array_walk( $ids, 'intval' );
1721 $ids_sql = '(' . implode( ',', $ids ) . ')';
1722
1723 $wpdb->query( $wpdb->prepare( "DELETE from $wpdb->ewwwio_queue WHERE gallery = %s AND attachment_id IN $ids_sql", $gallery ) ); // phpcs:ignore WordPress.DB.PreparedSQL
1724 }
1725
1726 /**
1727 * Remove all images from the ewwwio_queue table for the given 'gallery'.
1728 *
1729 * @since 4.6.0
1730 *
1731 * @param string $gallery The type of attachments to clear from the queue. Default media library.
1732 * @global object $wpdb
1733 */
1734 function ewww_image_optimizer_delete_queue_images( $gallery = 'media' ) {
1735 global $wpdb;
1736 $wpdb->query( $wpdb->prepare( "DELETE from $wpdb->ewwwio_queue WHERE gallery = %s", $gallery ) );
1737 }
1738
1739 /**
1740 * Searches for images to optimize in a specific folder.
1741 *
1742 * Scan a folder for images and mark unoptimized images in the database
1743 * (inserts new records as necessary).
1744 *
1745 * @global object $wpdb
1746 * @global array|string $optimized_list An associative array containing information from the images
1747 * table, or 'low_memory', 'large_list', 'small_scan'.
1748 *
1749 * @param string $dir The absolute path of the folder to be scanned for unoptimized images.
1750 * @param int $started Optional. The number of seconds since the overall scanning process started. Default 0.
1751 */
1752 function ewww_image_optimizer_image_scan( $dir, $started = 0 ) {
1753 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
1754 if ( ! is_dir( $dir ) ) {
1755 ewwwio_debug_message( "$dir is not a directory, or unreadable" );
1756 return;
1757 }
1758 $folders_completed = get_option( 'ewww_image_optimizer_aux_folders_completed' );
1759 if ( ! is_array( $folders_completed ) ) {
1760 $folders_completed = array();
1761 }
1762 if ( in_array( $dir, $folders_completed, true ) ) {
1763 ewwwio_debug_message( "$dir already completed" );
1764 return;
1765 }
1766 global $wpdb;
1767 global $optimized_list;
1768 global $ewww_scan;
1769 $images = array();
1770 $reset_images = array();
1771 ewwwio_debug_message( "scanning folder for images: $dir" );
1772 $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ), RecursiveIteratorIterator::CHILD_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD );
1773 $start = microtime( true );
1774 if ( empty( $optimized_list ) || ! is_array( $optimized_list ) ) {
1775 ewww_image_optimizer_optimized_list();
1776 }
1777 $file_counter = 0; // Used to track total files overall.
1778 $image_count = 0; // Used to track number of files since last queue update.
1779 if ( ewww_image_optimizer_stl_check() ) {
1780 set_time_limit( 0 );
1781 }
1782
1783 $supported_types = ewwwio()->get_supported_types();
1784 $webp_types = ewwwio()->get_webp_types();
1785
1786 foreach ( $iterator as $file ) {
1787 if ( get_transient( 'ewww_image_optimizer_aux_iterator' ) && get_transient( 'ewww_image_optimizer_aux_iterator' ) > $file_counter ) {
1788 continue;
1789 }
1790 if (
1791 $started &&
1792 'scheduled' !== $ewww_scan &&
1793 0 === $file_counter % 100 &&
1794 microtime( true ) - $started > apply_filters( 'ewww_image_optimizer_timeout', 15 )
1795 ) {
1796 ewww_image_optimizer_reset_images( $reset_images );
1797 if ( ! empty( $images ) ) {
1798 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $images );
1799 }
1800 set_transient( 'ewww_image_optimizer_aux_iterator', $file_counter - 20, 300 ); // Keep track of where we left off, minus 20 to be safe.
1801 $loading_image = plugins_url( '/images/wpspin.gif', __FILE__ );
1802 ewwwio_ob_clean();
1803 die(
1804 wp_json_encode(
1805 array(
1806 'remaining' => esc_html__( 'Searching additional folders for images to optimize...', 'ewww-image-optimizer' ),
1807 'notice' => '',
1808 )
1809 )
1810 );
1811 } elseif (
1812 $started &&
1813 'scheduled' === $ewww_scan &&
1814 0 === $file_counter % 100 &&
1815 microtime( true ) - $started > apply_filters( 'ewww_image_optimizer_timeout', 15 )
1816 ) {
1817 ewwwio_debug_message( 'ending current scan iteration, will fire off a new one shortly' );
1818 ewww_image_optimizer_reset_images( $reset_images );
1819 if ( ! empty( $images ) ) {
1820 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $images );
1821 }
1822 set_transient( 'ewww_image_optimizer_aux_iterator', $file_counter - 20, 300 ); // Keep track of where we left off, minus 20 to be safe.
1823 ewwwio()->async_scan->data(
1824 array(
1825 'ewww_scan' => 'scheduled',
1826 )
1827 )->dispatch();
1828 die();
1829 } elseif ( 'scheduled' === $ewww_scan && get_option( 'ewwwio_stop_scheduled_scan' ) ) {
1830 ewwwio_debug_message( 'ending current scan iteration because of stop_scan' );
1831 delete_option( 'ewwwio_stop_scheduled_scan' );
1832 die();
1833 }
1834 if ( $ewww_scan && 0 === $file_counter % 100 && ! ewwwio_check_memory_available( 2097000 ) ) {
1835 ewwwio_debug_message( 'ending current scan iteration because of memory constraints' );
1836 if ( $file_counter < 100 ) {
1837 ewwwio_ob_clean();
1838 die(
1839 wp_json_encode(
1840 array(
1841 'error' => esc_html__( 'Image search unable to complete due to memory restrictions. Please increase the memory_limit setting for PHP and try again.', 'ewww-image-optimizer' ),
1842 )
1843 )
1844 );
1845 }
1846 ewww_image_optimizer_reset_images( $reset_images );
1847 if ( ! empty( $images ) ) {
1848 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $images );
1849 }
1850 set_transient( 'ewww_image_optimizer_aux_iterator', $file_counter - 20, 300 ); // Keep track of where we left off, minus 20 to be safe.
1851 $loading_image = plugins_url( '/images/wpspin.gif', __FILE__ );
1852 ewwwio_ob_clean();
1853 die(
1854 wp_json_encode(
1855 array(
1856 'remaining' => esc_html__( 'Searching additional folders for images to optimize...', 'ewww-image-optimizer' ),
1857 'notice' => '',
1858 )
1859 )
1860 );
1861 }
1862 ++$file_counter;
1863 if ( $file->isFile() ) {
1864 $path = $file->getPathname();
1865 if ( preg_match( '/(\/|\\\\)\./', $path ) && apply_filters( 'ewww_image_optimizer_ignore_hidden_files', true ) ) {
1866 continue;
1867 }
1868 if ( defined( 'EWWW_IMAGE_OPTIMIZER_REAL_MIME' ) && EWWW_IMAGE_OPTIMIZER_REAL_MIME ) {
1869 $mime = ewww_image_optimizer_mimetype( $path, 'i' );
1870 } else {
1871 $mime = ewww_image_optimizer_quick_mimetype( $path );
1872 }
1873 if ( ! in_array( $mime, $supported_types, true ) ) {
1874 continue;
1875 }
1876 if ( ewwwio()->webp_only && ! in_array( $mime, $webp_types, true ) ) {
1877 continue;
1878 }
1879 if ( apply_filters( 'ewww_image_optimizer_bypass', false, $path ) === true ) {
1880 ewwwio_debug_message( "skipping $path as instructed" );
1881 continue;
1882 }
1883
1884 $already_optimized = false;
1885 if ( ! is_array( $optimized_list ) && is_string( $optimized_list ) ) {
1886 $already_optimized = ewww_image_optimizer_find_already_optimized( $path );
1887 } elseif ( is_array( $optimized_list ) && isset( $optimized_list[ $path ] ) && ! empty( $optimized_list[ $path ] ) ) {
1888 $already_optimized = $optimized_list[ $path ];
1889 }
1890
1891 $should_resize = ewww_image_optimizer_should_resize( $path );
1892 if ( is_array( $already_optimized ) && ! empty( $already_optimized ) ) {
1893 if ( ! empty( $already_optimized['pending'] ) ) {
1894 ewwwio_debug_message( "pending record for $path" );
1895 continue;
1896 }
1897 $image_size = $file->getSize();
1898 if ( $image_size < ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_size' ) ) {
1899 ewwwio_debug_message( "file skipped due to filesize: $path" );
1900 continue;
1901 }
1902 if ( 'image/png' === $mime && ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) && $image_size > ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) ) {
1903 ewwwio_debug_message( "file skipped due to PNG filesize: $path" );
1904 continue;
1905 }
1906 $compression_level = ewww_image_optimizer_get_level( $mime );
1907 if ( ! empty( ewwwio()->force_smart ) && ewww_image_optimizer_level_mismatch( $already_optimized['level'], $compression_level ) ) {
1908 $reset_images[] = (int) $already_optimized['id'];
1909 ewwwio_debug_message( "smart re-opt found level mismatch for $path, db says " . $already_optimized['level'] . " vs. current $compression_level" );
1910 } elseif ( $should_resize ) {
1911 $reset_images[] = (int) $already_optimized['id'];
1912 ewwwio_debug_message( "resize other existing found candidate for scaling: $path" );
1913 } elseif ( (int) $already_optimized['image_size'] === $image_size && empty( ewwwio()->force ) ) {
1914 ewwwio_debug_message( "match found for $path" );
1915 continue;
1916 } else {
1917 $reset_images[] = (int) $already_optimized['id'];
1918 ewwwio_debug_message( "mismatch found for $path, db says " . $already_optimized['image_size'] . " vs. current $image_size" );
1919 }
1920 } else {
1921 $image_size = $file->getSize();
1922 if ( $image_size < ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_size' ) ) {
1923 ewwwio_debug_message( "file skipped due to filesize: $path" );
1924 continue;
1925 }
1926 if ( 'image/png' === $mime && ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) && $image_size > ewww_image_optimizer_get_option( 'ewww_image_optimizer_skip_png_size' ) ) {
1927 ewwwio_debug_message( "file skipped due to PNG filesize: $path" );
1928 continue;
1929 }
1930 ewwwio_debug_message( "queuing $path" );
1931 $path = ewww_image_optimizer_relativize_path( $path );
1932 $utf8_file_path = ewwwio()->ensure_utf8_path( $path );
1933 $images[] = array(
1934 'path' => $utf8_file_path,
1935 'orig_size' => $image_size,
1936 'pending' => 1,
1937 );
1938 ++$image_count;
1939 } // End if().
1940 if ( false && $image_count > 1000 ) { // Disabled, should no longer be needed, as the mass_insert() function limits queries to 16k.
1941 // Let's dump what we have so far to the db.
1942 $image_count = 0;
1943 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $images );
1944 $images = array();
1945 }
1946 } // End if().
1947 } // End foreach().
1948 if ( ! empty( $images ) ) {
1949 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $images );
1950 }
1951 set_transient( 'ewww_image_optimizer_aux_iterator', 0 );
1952 ewww_image_optimizer_reset_images( $reset_images );
1953 delete_transient( 'ewww_image_optimizer_aux_iterator' );
1954 $end = microtime( true ) - $start;
1955 ewwwio_debug_message( "query time for $file_counter files (seconds): $end" );
1956 clearstatcache();
1957 ewwwio_memory( __FUNCTION__ );
1958 $folders_completed[] = $dir;
1959 update_option( 'ewww_image_optimizer_aux_folders_completed', $folders_completed, false );
1960 }
1961
1962 /**
1963 * Convert all records in table to use filesize rather than md5sum.
1964 *
1965 * @global object $wpdb
1966 */
1967 function ewww_image_optimizer_aux_images_convert() {
1968 global $wpdb;
1969 $old_records = $wpdb->get_results( "SELECT id,path,image_md5 FROM $wpdb->ewwwio_images", ARRAY_A );
1970 foreach ( $old_records as $record ) {
1971 if ( empty( $record['image_md5'] ) ) {
1972 continue;
1973 }
1974 $record['path'] = ewww_image_optimizer_absolutize_path( $record['path'] );
1975 if ( false !== strpos( $record['path'], 'phar://' ) ) {
1976 continue;
1977 }
1978 $image_md5 = md5_file( $record['path'] );
1979 if ( $image_md5 === $record['image_md5'] ) {
1980 $filesize = ewww_image_optimizer_filesize( $record['path'] );
1981 $wpdb->update(
1982 $wpdb->ewwwio_images,
1983 array(
1984 'image_md5' => null,
1985 'image_size' => $filesize,
1986 ),
1987 array(
1988 'id' => $record['id'],
1989 )
1990 );
1991 } else {
1992 $wpdb->delete(
1993 $wpdb->ewwwio_images,
1994 array(
1995 'id' => $record['id'],
1996 )
1997 );
1998 }
1999 }
2000 }
2001
2002 /**
2003 * Searches for images to optimize.
2004 *
2005 * Scans all auxiliary folders, including some predefined ones, and those configured by the user.
2006 * Used for the main bulk tool, and the scheduled optimization.
2007 *
2008 * @param string $hook Optional. Indicates if scheduled optimization is running.
2009 * @global object $wpdb
2010 * @return int Number of images ready to optimize.
2011 */
2012 function ewww_image_optimizer_aux_images_scan( $hook = '' ) {
2013 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
2014 session_write_close();
2015 // Retrieve the time when the scan starts.
2016 $started = microtime( true );
2017 if ( ! get_transient( 'ewww_image_optimizer_skip_aux' ) ) {
2018 update_option( 'ewww_image_optimizer_aux_resume', 'scanning' );
2019 set_transient( 'ewww_image_optimizer_aux_lock', time(), 60 );
2020 $scan_args = get_option( 'ewww_image_optimizer_scan_args' );
2021 if ( is_array( $scan_args ) && ! empty( $scan_args ) ) {
2022 ewwwio_debug_message( 'scan args retrieved from db' );
2023 if ( ! empty( $scan_args['force_reopt'] ) ) {
2024 ewwwio_debug_message( 'force re-opt enabled' );
2025 ewwwio()->force = true;
2026 }
2027 if ( ! empty( $scan_args['force_smart'] ) ) {
2028 ewwwio_debug_message( 'smart re-opt enabled' );
2029 ewwwio()->force_smart = true;
2030 }
2031 if ( ! empty( $scan_args['webp_only'] ) ) {
2032 ewwwio_debug_message( 'webp-only enabled' );
2033 ewwwio()->webp_only = true;
2034 }
2035 }
2036 ewwwio_debug_message( 'getting fresh list of files to optimize' );
2037 // Collect a list of images from the current theme (and parent theme if applicable).
2038 $child_path = get_stylesheet_directory();
2039 $parent_path = get_template_directory();
2040 ewww_image_optimizer_image_scan( $child_path, $started );
2041 if ( $child_path !== $parent_path ) {
2042 ewww_image_optimizer_image_scan( $parent_path, $started );
2043 }
2044 if ( ! function_exists( 'is_plugin_active' ) ) {
2045 // Need to include the plugin library for the is_plugin_active function.
2046 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2047 }
2048 ewwwio_debug_message( 'checking for commonly-used folders' );
2049 $upload_dir = wp_get_upload_dir();
2050 ewww_image_optimizer_image_scan( $upload_dir['basedir'] . '/avatars', $started );
2051 ewww_image_optimizer_image_scan( $upload_dir['basedir'] . '/group-avatars', $started );
2052 ewww_image_optimizer_image_scan( $upload_dir['basedir'] . '/bpfb', $started );
2053 ewww_image_optimizer_image_scan( $upload_dir['basedir'] . '/buddypress', $started );
2054 ewww_image_optimizer_image_scan( WP_CONTENT_DIR . '/grand-media', $started );
2055 if ( is_plugin_active( 'wp-symposium/wp-symposium.php' ) || is_plugin_active_for_network( 'wp-symposium/wp-symposium.php' ) ) {
2056 ewww_image_optimizer_image_scan( get_option( 'symposium_img_path' ), $started );
2057 }
2058 if ( defined( 'WPS_CORE_PLUGINS' ) ) {
2059 ewww_image_optimizer_image_scan( WP_CONTENT_DIR . '/wps-pro-content', $started );
2060 }
2061 if ( ewww_image_optimizer_get_option( 'ewww_image_optimizer_lazy_load' ) ) {
2062 ewww_image_optimizer_image_scan( WP_CONTENT_DIR . '/ewww/lazy/', $started );
2063 }
2064 if ( is_plugin_active( 'ml-slider/ml-slider.php' ) || is_plugin_active_for_network( 'ml-slider/ml-slider.php' ) ) {
2065 global $wpdb;
2066 $slide_paths = array();
2067 $slides = $wpdb->get_col(
2068 "
2069 SELECT wpposts.ID
2070 FROM $wpdb->posts wpposts
2071 INNER JOIN $wpdb->term_relationships term_relationships
2072 ON wpposts.ID = term_relationships.object_id
2073 INNER JOIN $wpdb->terms wpterms
2074 ON term_relationships.term_taxonomy_id = wpterms.term_id
2075 INNER JOIN $wpdb->term_taxonomy term_taxonomy
2076 ON wpterms.term_id = term_taxonomy.term_id
2077 WHERE term_taxonomy.taxonomy = 'ml-slider'
2078 AND wpposts.post_type = 'attachment'
2079 "
2080 );
2081 if ( ewww_image_optimizer_iterable( $slides ) ) {
2082 foreach ( $slides as $slide ) {
2083 $type = get_post_meta( $slide, 'ml-slider_type', true );
2084 $type = $type ? $type : 'image'; // For backwards compatibility, fall back to 'image'.
2085 if ( 'image' !== $type ) {
2086 continue;
2087 }
2088 $backup_sizes = get_post_meta( $slide, '_wp_attachment_backup_sizes', true );
2089 if ( ewww_image_optimizer_iterable( $backup_sizes ) ) {
2090 foreach ( $backup_sizes as $backup_size => $meta ) {
2091 if ( preg_match( '/resized-/', $backup_size ) ) {
2092 $path = $meta['path'];
2093 if ( ! ewwwio_is_file( $path ) ) {
2094 continue;
2095 }
2096 $image_size = ewww_image_optimizer_filesize( $path );
2097 if ( ! $image_size ) {
2098 continue;
2099 }
2100 $already_optimized = ewww_image_optimizer_find_already_optimized( $path );
2101 // A pending record already present.
2102 if ( ! empty( $already_optimized ) && empty( $already_optimized['image_size'] ) ) {
2103 continue;
2104 }
2105 $mimetype = ewww_image_optimizer_mimetype( $path, 'i' );
2106 // This is a brand new image.
2107 if ( preg_match( '/^image\/(jpeg|png|gif|svg\+xml)/', $mimetype ) && empty( $already_optimized ) ) {
2108 $slide_paths[] = array(
2109 'path' => ewww_image_optimizer_relativize_path( $path ),
2110 'orig_size' => $image_size,
2111 );
2112 // This is a changed image.
2113 } elseif ( preg_match( '/^image\/(jpeg|png|gif|svg\+xml)/', $mimetype ) && ! empty( $already_optimized ) && (int) $already_optimized['image_size'] !== $image_size ) {
2114 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->ewwwio_images SET pending = 1 WHERE id = %d", $already_optimized['id'] ) );
2115 }
2116 }
2117 }
2118 }
2119 }
2120 } // End if().
2121 if ( ! empty( $slide_paths ) ) {
2122 ewww_image_optimizer_mass_insert( $wpdb->ewwwio_images, $slide_paths );
2123 }
2124 } // End if().
2125 // Collect a list of images in auxiliary folders provided by user.
2126 $aux_paths = ewww_image_optimizer_get_option( 'ewww_image_optimizer_aux_paths' );
2127 if ( $aux_paths ) {
2128 if ( ewww_image_optimizer_iterable( $aux_paths ) ) {
2129 foreach ( $aux_paths as $aux_path ) {
2130 ewww_image_optimizer_image_scan( $aux_path, $started );
2131 }
2132 }
2133 }
2134 // Scan images in two most recent media library folders if the option is enabled, and this is a scheduled optimization.
2135 if ( 'ewww-image-optimizer-auto' === $hook && ewww_image_optimizer_get_option( 'ewww_image_optimizer_include_media_paths' ) ) {
2136 // Retrieve the location of the WordPress upload folder.
2137 $upload_dir = wp_get_upload_dir();
2138 // Retrieve the path of the upload folder.
2139 $upload_path = $upload_dir['basedir'];
2140 $this_month = gmdate( 'm' );
2141 $this_year = gmdate( 'Y' );
2142 ewww_image_optimizer_image_scan( "$upload_path/$this_year/$this_month/", $started );
2143 if ( class_exists( 'DateTime' ) ) {
2144 $date = new DateTime();
2145 $date->sub( new DateInterval( 'P1M' ) );
2146 $last_year = $date->format( 'Y' );
2147 $last_month = $date->format( 'm' );
2148 ewww_image_optimizer_image_scan( "$upload_path/$last_year/$last_month/", $started );
2149 }
2150 }
2151 } else {
2152 ewwwio_debug_message( 'skipping scan because of skip_aux transient' );
2153 } // End if().
2154 $image_count = (int) ewww_image_optimizer_aux_images_table_count_pending();
2155 ewwwio_debug_message( "found $image_count images to optimize while scanning" );
2156 update_option( 'ewww_image_optimizer_aux_folders_completed', array(), false );
2157 update_option( 'ewww_image_optimizer_scan_args', '' );
2158 update_option( 'ewww_image_optimizer_aux_resume', '' );
2159 update_option( 'ewww_image_optimizer_bulk_resume', '' );
2160 update_option( 'ewww_image_optimizer_bulk_foreground', '' );
2161 delete_transient( 'ewww_image_optimizer_aux_lock' );
2162 delete_transient( 'ewww_image_optimizer_skip_aux' );
2163 if ( wp_doing_ajax() && 'ewww-image-optimizer-auto' !== $hook && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) {
2164 $verify_cloud = ewww_image_optimizer_cloud_verify( ewww_image_optimizer_get_option( 'ewww_image_optimizer_cloud_key' ), false );
2165 $usage = false;
2166 if ( preg_match( '/great/', $verify_cloud ) ) {
2167 $usage = ewww_image_optimizer_cloud_quota( true );
2168 }
2169 ewwwio_memory( __FUNCTION__ );
2170 /* translators: %s: number of images */
2171 $ready_msg = sprintf( esc_html( _n( 'There is %s image ready to optimize.', 'There are %s images ready to optimize.', $image_count, 'ewww-image-optimizer' ) ), '<span class="ready-to-optimize-count" style="font-weight:bold">' . number_format_i18n( $image_count ) . '</span>' );
2172 if ( is_array( $usage ) && ! $usage['metered'] && ! $usage['unlimited'] ) {
2173 $credits_available = $usage['licensed'] - $usage['consumed'];
2174 if ( $credits_available < $image_count ) {
2175 $ready_msg .= ' ' . esc_html__( 'You do not appear to have enough image credits to complete this operation.', 'ewww-image-optimizer' );
2176 }
2177 }
2178 if ( $image_count > 1000 ) {
2179 $ready_msg .= ' <a href="https://docs.ewww.io/article/20-why-do-i-have-so-many-images-on-my-site" target="_blank" data-beacon-article="58598744c697912ffd6c3eb4">' . esc_html__( 'Why are there so many images?', 'ewww-image-optimizer' ) . '</a>';
2180 }
2181
2182 $buttons = '<a class="ewww-queue-controls ewww-start-optimization button-secondary" href="#">' . esc_html__( 'Start optimizing', 'ewww-image-optimizer' ) . '</a>';
2183 $buttons .= '<a class="ewww-queue-controls ewww-pause-optimization button-secondary" style="display:none" href="#">' . esc_html__( 'Pause Optimization', 'ewww-image-optimizer' ) . '</a>';
2184
2185 if ( empty( $image_count ) ) {
2186 $ready_msg = esc_html__( 'There are no images to optimize.', 'ewww-image-optimizer' );
2187 }
2188 ewwwio_ob_clean();
2189 die(
2190 wp_json_encode(
2191 array(
2192 'ready' => $image_count,
2193 'message' => $ready_msg,
2194 )
2195 )
2196 );
2197 } elseif ( 'ewww-image-optimizer-auto' === $hook ) {
2198 ewwwio_debug_message( 'retrieving images for scheduled queue' );
2199 global $wpdb;
2200 $images_queued = $wpdb->get_col( "SELECT id FROM $wpdb->ewwwio_images WHERE pending=1" );
2201 if ( ewww_image_optimizer_iterable( $images_queued ) ) {
2202 foreach ( $images_queued as $id ) {
2203 ewwwio()->background_image->push_to_queue(
2204 array(
2205 'id' => $id,
2206 'new' => 0,
2207 )
2208 );
2209 }
2210 }
2211 ewwwio()->background_image->dispatch();
2212 }
2213 ewwwio_memory( __FUNCTION__ );
2214 return $image_count;
2215 }
2216
2217 /**
2218 * Called by scheduled optimization to cleanup after ourselves.
2219 *
2220 * @param bool $auto Indicates whether or not the function is called from scheduled (auto) optimization mode.
2221 */
2222 function ewww_image_optimizer_aux_images_cleanup( $auto = false ) {
2223 ewwwio_debug_message( '<b>' . __FUNCTION__ . '()</b>' );
2224 // Verify that an authorized user has started the optimizer.
2225 $permissions = apply_filters( 'ewww_image_optimizer_bulk_permissions', '' );
2226 if ( ! $auto && ( empty( $_REQUEST['ewww_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['ewww_wpnonce'] ), 'ewww-image-optimizer-bulk' ) || ! current_user_can( $permissions ) ) ) {
2227 ewwwio_ob_clean();
2228 die( esc_html__( 'Access denied.', 'ewww-image-optimizer' ) );
2229 }
2230 // All done, so we can update the bulk options with empty values.
2231 update_option( 'ewww_image_optimizer_aux_resume', '', false );
2232 if ( ! $auto ) {
2233 ewwwio_ob_clean();
2234 // And let the user know we are done.
2235 echo '<p><b>' . esc_html__( 'Finished', 'ewww-image-optimizer' ) . '</b></p>';
2236 ewwwio_memory( __FUNCTION__ );
2237 die();
2238 }
2239 }
2240
2241 add_action( 'wp_ajax_bulk_aux_images_table', 'ewww_image_optimizer_aux_images_table' );
2242 add_action( 'wp_ajax_bulk_aux_images_table_count', 'ewww_image_optimizer_aux_images_table_count' );
2243 add_action( 'wp_ajax_bulk_aux_images_table_clear', 'ewww_image_optimizer_aux_images_clear_all' );
2244 add_action( 'wp_ajax_bulk_aux_images_exclude', 'ewww_image_optimizer_aux_images_exclude' );
2245 add_action( 'wp_ajax_bulk_aux_images_remove', 'ewww_image_optimizer_aux_images_remove' );
2246 add_action( 'wp_ajax_bulk_aux_images_restore_original', 'ewww_image_optimizer_bulk_restore_handler' );
2247 add_action( 'wp_ajax_bulk_aux_images_count_converted', 'ewww_image_optimizer_aux_images_count_converted' );
2248 add_action( 'wp_ajax_bulk_aux_images_converted_clean', 'ewww_image_optimizer_aux_images_converted_clean' );
2249 add_action( 'wp_ajax_bulk_aux_images_table_clean', 'ewww_image_optimizer_aux_images_clean' );
2250 add_action( 'wp_ajax_bulk_aux_images_meta_clean', 'ewww_image_optimizer_aux_meta_clean' );
2251 add_action( 'wp_ajax_bulk_aux_images_webp_clean', 'ewww_image_optimizer_aux_images_webp_clean_handler' );
2252 add_action( 'wp_ajax_bulk_aux_images_delete_webp', 'ewww_image_optimizer_delete_webp_handler' );
2253 add_action( 'wp_ajax_bulk_aux_images_delete_original', 'ewww_image_optimizer_ajax_delete_original' );
2254 add_action( 'wp_ajax_ewwwio_get_all_attachments', 'ewww_image_optimizer_get_all_attachments' );
2255 add_action( 'wp_ajax_ewwwio_webp_attachment_count', 'ewww_image_optimizer_webp_attachment_count' );
2256 // Non-AJAX handler(s) to reset tool resume option/placeholder.
2257 add_action( 'admin_action_ewww_image_optimizer_reset_bulk_restore', 'ewww_image_optimizer_reset_bulk_restore' );
2258 add_action( 'admin_action_ewww_image_optimizer_reset_webp_clean', 'ewww_image_optimizer_reset_webp_clean' );
2259