PluginProbe
Squeeze – Image Optimization & Compression, WEBP Conversion / 1.6
Squeeze – Image Optimization & Compression, WEBP Conversion v1.6
1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 trunk 1.0 1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5 1.5.1 1.5.2 1.6 All 42 releases
squeeze / inc / handlers.php

handlers.php in Squeeze – Image Optimization & Compression, WEBP Conversion 1.6, at inc/handlers.php

491 lines 22.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Squeeze;
4
5 // Exit if accessed directly.
6 if ( !defined( 'ABSPATH' ) ) {
7 exit;
8 }
9 class SqueezeHandlers extends SqueezeInit {
10 public function __construct() {
11 add_action( 'wp_ajax_squeeze_update_attachment', [$this, 'update_attachment'] );
12 add_action( 'wp_ajax_squeeze_restore_attachment', [$this, 'restore_attachment'] );
13 add_action( 'wp_ajax_squeeze_get_attachment', [$this, 'get_attachment'] );
14 add_action( 'wp_ajax_squeeze_get_attachment_by_path', [$this, 'get_attachment_by_path'] );
15 add_action( 'wp_ajax_squeeze_get_next_attachments', [$this, 'get_next_attachments'] );
16 add_action( 'wp_ajax_squeeze_get_directories', [$this, 'get_directories'] );
17 add_action( 'delete_attachment', [$this, 'delete_backup_attachment'] );
18 add_action( 'delete_attachment', [$this, 'delete_webp_images'] );
19 add_filter( 'bulk_actions-upload', [$this, 'bulk_actions'] );
20 add_filter(
21 'handle_bulk_actions-upload',
22 [$this, 'handle_bulk_actions'],
23 10,
24 3
25 );
26 add_filter( 'image_size_names_choose', [$this, 'custom_image_sizes'] );
27 add_filter( 'mod_rewrite_rules', [$this, 'add_webp_rewrite_rules'] );
28 add_action( 'pre-html-upload-ui', [$this, 'single_file_upload_notice'], 10 );
29 add_action( 'admin_notices', [$this, 'bulk_action_admin_notice'] );
30 add_action( 'init', [$this, 'output_buffer_start'], 1 );
31 }
32
33 public function update_attachment() {
34 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
35 if ( !current_user_can( 'upload_files' ) ) {
36 wp_send_json_error( '' . esc_html__( 'You do not have permission to upload files', 'squeeze' ) );
37 }
38 if ( !isset( $_POST["base64"] ) || empty( $_POST["base64"] ) ) {
39 wp_send_json_error( '' . esc_html__( 'No image data found', 'squeeze' ) );
40 }
41 $base64 = sanitize_text_field( $_POST["base64"] );
42 $sizes = ( isset( $_POST["base64Sizes"] ) ? $_POST["base64Sizes"] : array() );
43 // DO NOT SANITIZE because it's an array
44 $base64_webp = sanitize_text_field( $_POST["base64Webp"] );
45 $sizes_webp = ( isset( $_POST["base64SizesWebp"] ) ? $_POST["base64SizesWebp"] : array() );
46 $file_format = sanitize_text_field( $_POST["format"] );
47 $filename = sanitize_text_field( $_POST["filename"] );
48 $extension = pathinfo( $filename, PATHINFO_EXTENSION );
49 $image_formats = self::$SqueezeHelpers->get_image_formats();
50 if ( !in_array( $extension, $image_formats ) ) {
51 wp_send_json_error( '' . esc_html__( 'Invalid image format', 'squeeze' ) );
52 }
53 $attach_id = (int) $_POST["attachmentID"];
54 $meta_data = wp_get_attachment_metadata( $attach_id );
55 $url = sanitize_text_field( $_POST["url"] );
56 // sanitize_url() replaces spaces with %20, so we use sanitize_text_field() instead
57 $process = sanitize_text_field( $_POST["process"] );
58 // process: all, uncompressed, path
59 $is_backup_original = self::$SqueezeHelpers->get_option( 'backup_original' );
60 // Upload path.
61 $upload_path = self::$SqueezeHelpers->get_upload_path( $attach_id, $filename, $url );
62 $decoded = self::$SqueezeHelpers->decode_base64_image( $base64, $file_format );
63 if ( $is_backup_original && $process !== 'path' ) {
64 // do not backup for non library images
65 // backup original
66 $backup_original_image = self::$SqueezeHelpers->backup_original_image( $upload_path, $filename );
67 if ( is_wp_error( $backup_original_image ) ) {
68 wp_send_json_error( $backup_original_image->get_error_message() );
69 }
70 }
71 $sizes['original']['original_size'] = filesize( $upload_path . $filename );
72 $sizes['original']['compressed_size'] = strlen( $decoded );
73 // Save the image in the uploads directory.
74 $upload_image = self::$SqueezeHelpers->upload_image( $upload_path, $filename, $decoded );
75 if ( is_wp_error( $upload_image ) ) {
76 wp_send_json_error( $upload_image->get_error_message() );
77 }
78 if ($base64_webp) {
79 $upload_webp = self::$SqueezeHelpers->upload_webp($upload_path, $base64_webp, $filename);
80
81 if (is_wp_error( $upload_webp )) {
82 wp_send_json_error( $upload_webp->get_error_message() );
83 }
84
85 self::$SqueezeHelpers->upload_webp_thumbs($upload_path, $sizes_webp);
86
87 // skip handling errors for webp thumbs, because they are not always required
88 }
89 // upload thumbnails
90 if ( $process !== 'path' ) {
91 $sizes = self::$SqueezeHelpers->upload_image_thumbs( $upload_path, $sizes, $file_format );
92 if ( is_wp_error( $sizes ) ) {
93 wp_send_json_error( $sizes->get_error_message() );
94 }
95 update_post_meta( $attach_id, "squeeze_is_compressed", true );
96 $response_msg = self::$SqueezeHelpers->get_comparison_table( $sizes );
97 $response_msg = '<strong>�
98 ' . esc_html__( 'Squeezed successfully', 'squeeze' ) . '!</strong> ' . $response_msg;
99 $uncompressed_images = self::$SqueezeHelpers->get_stats_option( 'uncompressed_images' );
100 $uncompressed_images--;
101 update_option( 'squeeze_stats', array(
102 'uncompressed_images' => $uncompressed_images,
103 ) );
104 wp_send_json_success( $response_msg );
105 } else {
106 wp_send_json_success( '�
107 ' . esc_html__( 'Squeezed successfully', 'squeeze' ) );
108 }
109 wp_die();
110 }
111
112 public function restore_attachment() {
113 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
114 if ( !isset( $_POST["attachmentID"] ) || empty( $_POST["attachmentID"] ) || !wp_get_attachment_url( $_POST["attachmentID"] ) ) {
115 wp_send_json_error( '' . esc_html__( 'Attachment not found', 'squeeze' ) );
116 }
117 $attach_id = (int) $_POST["attachmentID"];
118 $can_restore = self::$SqueezeHelpers->can_restore( $attach_id );
119 if ( $can_restore ) {
120 $is_restore_attachment = self::$SqueezeHelpers->restore_attachment( $attach_id );
121 if ( $is_restore_attachment ) {
122 wp_send_json_success( '�
123 ' . esc_html__( 'Restored successfully', 'squeeze' ) );
124 } else {
125 wp_send_json_error( ' ' . esc_html__( 'Attachment not restored', 'squeeze' ) );
126 }
127 }
128 wp_die();
129 }
130
131 public function get_attachment() {
132 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
133 if ( !isset( $_POST["attachmentID"] ) || empty( $_POST["attachmentID"] ) || !wp_get_attachment_url( $_POST["attachmentID"] ) ) {
134 wp_send_json_error( ' ' . esc_html__( 'Attachment not found', 'squeeze' ) );
135 }
136 $attach_id = (int) $_POST["attachmentID"];
137 $sizes = wp_get_attachment_metadata( $attach_id );
138 $sizes = $sizes['sizes'];
139 $full_image = wp_get_attachment_image_src( $attach_id, 'full' );
140 $sizes['full'] = array(
141 'url' => $full_image[0],
142 'width' => $full_image[1],
143 'height' => $full_image[2],
144 );
145 foreach ( $sizes as $size_name => $size_data ) {
146 $sizes[$size_name]['url'] = wp_get_attachment_image_url( $attach_id, $size_name );
147 }
148 $attach_data = array(
149 'id' => $attach_id,
150 'url' => wp_get_original_image_url( $attach_id ),
151 'mime' => get_post_mime_type( $attach_id ),
152 'name' => get_the_title( $attach_id ),
153 'filename' => basename( wp_get_original_image_path( $attach_id ) ),
154 'sizes' => $sizes,
155 );
156 wp_send_json_success( $attach_data );
157 wp_die();
158 }
159
160 public function get_attachment_by_path() {
161 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
162 if ( !isset( $_POST["path"] ) || empty( $_POST["path"] ) ) {
163 wp_send_json_error( ' ' . esc_html__( 'Path not found', 'squeeze' ) );
164 }
165 $pathes = sanitize_text_field( $_POST["path"] );
166 $pathes = json_decode( stripslashes( $pathes ), true );
167 $attach_data = array();
168 $image_formats = self::$SqueezeHelpers->get_image_formats();
169 $image_formats = implode( ',', $image_formats );
170 foreach ( $pathes as $path ) {
171 $path = str_replace( ['\\\\', '\\'], '/', $path );
172 // replace double and then single backslashes with slashes
173 if ( substr( $path, -1 ) !== '/' ) {
174 $path .= '/';
175 }
176 if ( substr( $path, 0, 1 ) !== '/' ) {
177 $path = '/' . $path;
178 }
179 $images = glob( ABSPATH . $path . '*.{' . $image_formats . '}', GLOB_BRACE );
180 if ( empty( $images ) ) {
181 continue;
182 }
183 foreach ( $images as $image ) {
184 $attach_id = attachment_url_to_postid( $image );
185 $attach_mime = image_type_to_mime_type( exif_imagetype( $image ) );
186 $attach_url = str_replace( ABSPATH, home_url(), $image );
187 $attach_name = pathinfo( $image, PATHINFO_FILENAME );
188 $attach_data[] = array(
189 'id' => $attach_id,
190 'url' => $attach_url,
191 'mime' => $attach_mime,
192 'name' => $attach_name,
193 'filename' => basename( $image ),
194 );
195 }
196 }
197 // Save pathes to cache
198 set_transient( 'squeeze_bulk_path', $pathes, MONTH_IN_SECONDS );
199 if ( empty( $attach_data ) ) {
200 wp_send_json_error( ' ' . esc_html__( 'Images were not found in the selected directories', 'squeeze' ) );
201 }
202 wp_send_json_success( $attach_data );
203 wp_die();
204 }
205
206 public function delete_backup_attachment( $attach_id ) {
207 $original_img_path = wp_get_original_image_path( (int) $attach_id );
208 $backup_img_path = preg_replace( "/(\\.(?!.*\\.))/", '.bak.', $original_img_path );
209 if ( file_exists( $backup_img_path ) ) {
210 return wp_delete_file( $backup_img_path );
211 }
212 return false;
213 }
214
215 public function delete_webp_images( $attach_id ) {
216 $original_img_path = wp_get_original_image_path( (int) $attach_id );
217 $attach_data = wp_get_attachment_metadata( $attach_id );
218 $delete_webp_images = self::$SqueezeHelpers->delete_webp_images( $original_img_path, $attach_data );
219 return $delete_webp_images;
220 }
221
222 public function bulk_actions( $actions ) {
223 $actions['squeeze_bulk_restore'] = esc_html__( 'Restore Original Image', 'squeeze' );
224 $actions['squeeze_bulk_compress'] = esc_html__( 'Squeeze Image', 'squeeze' );
225 $actions['squeeze_bulk_delete_backup'] = esc_html__( 'Delete Backup Image', 'squeeze' );
226 $actions['squeeze_bulk_delete_webp'] = esc_html__( 'Delete WEBP Image', 'squeeze' );
227 return $actions;
228 }
229
230 public function handle_bulk_actions( $redirect_to, $doaction, $post_ids ) {
231 if ( $doaction === 'squeeze_bulk_restore' ) {
232 $restored_ids_count = 0;
233 foreach ( $post_ids as $post_id ) {
234 $can_restore = self::$SqueezeHelpers->can_restore( $post_id );
235 if ( $can_restore ) {
236 $is_restore_attachment = self::$SqueezeHelpers->restore_attachment( $post_id, true );
237 if ( $is_restore_attachment ) {
238 $restored_ids_count += 1;
239 }
240 }
241 }
242 $redirect_to = add_query_arg( 'squeeze_bulk_restored', $restored_ids_count, $redirect_to );
243 }
244 if ( $doaction === 'squeeze_bulk_compress' ) {
245 foreach ( $post_ids as $post_id ) {
246 $redirect_to = add_query_arg( 'squeeze_bulk_compressed', count( $post_ids ), $redirect_to );
247 }
248 }
249 if ( $doaction === 'squeeze_bulk_delete_backup' ) {
250 $deleted_ids_count = 0;
251 foreach ( $post_ids as $post_id ) {
252 $is_delete_backup = $this->delete_backup_attachment( $post_id );
253 if ( $is_delete_backup ) {
254 $deleted_ids_count += 1;
255 }
256 }
257 $redirect_to = add_query_arg( 'squeeze_bulk_deleted', $deleted_ids_count, $redirect_to );
258 }
259 if ( $doaction === 'squeeze_bulk_delete_webp' ) {
260 $deleted_ids_count = 0;
261 foreach ( $post_ids as $post_id ) {
262 $is_delete_webp = $this->delete_webp_images( $post_id );
263 if ( $is_delete_webp ) {
264 $deleted_ids_count += 1;
265 }
266 }
267 $redirect_to = add_query_arg( 'squeeze_bulk_webp_deleted', $deleted_ids_count, $redirect_to );
268 }
269 return $redirect_to;
270 }
271
272 public function bulk_action_admin_notice() {
273 if ( !empty( $_REQUEST['squeeze_bulk_restored'] ) ) {
274 $message = sprintf(
275 /* translators: %d: number of attachments restored */
276 _n(
277 '%d attachment restored.',
278 '%d attachments restored.',
279 $_REQUEST['squeeze_bulk_restored'],
280 'squeeze'
281 ),
282 number_format_i18n( $_REQUEST['squeeze_bulk_restored'] )
283 );
284 printf( '<div class="notice notice-success is-dismissible"><p>%s</p></div>', esc_html( $message ) );
285 }
286 if ( !empty( $_REQUEST['squeeze_bulk_compressed'] ) ) {
287 $message = sprintf(
288 /* translators: %d: number of attachments squeezed */
289 _n(
290 '%d attachment squeezed.',
291 '%d attachments squeezed.',
292 $_REQUEST['squeeze_bulk_compressed'],
293 'squeeze'
294 ),
295 number_format_i18n( $_REQUEST['squeeze_bulk_compressed'] )
296 );
297 printf( '<div class="notice notice-success is-dismissible"><p>%s</p></div>', esc_html( $message ) );
298 }
299 if ( !empty( $_REQUEST['squeeze_bulk_deleted'] ) ) {
300 $message = sprintf(
301 /* translators: %d: number of backup images deleted */
302 _n(
303 '%d backup image deleted.',
304 '%d backup images deleted.',
305 $_REQUEST['squeeze_bulk_deleted'],
306 'squeeze'
307 ),
308 number_format_i18n( $_REQUEST['squeeze_bulk_deleted'] )
309 );
310 printf( '<div class="notice notice-success is-dismissible"><p>%s</p></div>', esc_html( $message ) );
311 }
312 if ( !empty( $_REQUEST['squeeze_bulk_webp_deleted'] ) ) {
313 $message = sprintf(
314 /* translators: %d: number of webp images deleted */
315 _n(
316 '%d WEBP image deleted.',
317 '%d WEBP images deleted.',
318 $_REQUEST['squeeze_bulk_webp_deleted'],
319 'squeeze'
320 ),
321 number_format_i18n( $_REQUEST['squeeze_bulk_webp_deleted'] )
322 );
323 printf( '<div class="notice notice-success is-dismissible"><p>%s</p></div>', esc_html( $message ) );
324 }
325 }
326
327 public function custom_image_sizes( $sizes ) {
328 $available_sizes = wp_get_registered_image_subsizes();
329 foreach ( $available_sizes as $size_name => $size_data ) {
330 $sizes[$size_name] = $size_data['width'] . 'x' . $size_data['height'];
331 }
332 return $sizes;
333 }
334
335 public function get_next_attachments() {
336 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
337 $per_page = self::$MEDIA_PER_PAGE;
338 $page = ( isset( $_POST['page'] ) ? (int) $_POST['page'] : 1 );
339 $type = ( isset( $_POST['type'] ) ? sanitize_text_field( $_POST['type'] ) : 'uncompressed' );
340 if ( $type === 'uncompressed' ) {
341 $next_images = self::$SqueezeHelpers->get_uncompressed_images( $page );
342 } else {
343 $next_images = self::$SqueezeHelpers->get_total_images( $page );
344 }
345 wp_send_json_success( $next_images );
346 }
347
348 public function single_file_upload_notice() {
349 global $current_screen;
350 if ( $current_screen->id === 'media' ) {
351 ?>
352 <div class="notice notice-info hide-if-js squeeze-single-file-upload-notice">
353 <p><?php
354 esc_html_e( 'Single file upload is not supported for the image compression by Squeeze. Please use multi-file uploader or bulk squeeze.', 'squeeze' );
355 ?></p>
356 </div>
357 <?php
358 }
359 }
360
361 public function get_directories() {
362 check_ajax_referer( 'squeeze-nonce', '_ajax_nonce' );
363 $parent_directory = ( isset( $_POST['parentDir'] ) ? sanitize_text_field( $_POST['parentDir'] ) : '' );
364 $base_dir = ( $parent_directory ? ABSPATH . $parent_directory : WP_CONTENT_DIR );
365 $directories = scandir( $base_dir );
366 if ( !$parent_directory ) {
367 $directories[] = $base_dir;
368 }
369 $result = array_filter( $directories, function ( $dir ) use($base_dir) {
370 if ( $dir === $base_dir ) {
371 return true;
372 }
373 return is_dir( $base_dir . '/' . $dir ) && !in_array( $dir, ['.', '..'] );
374 } );
375 $output = array_map( function ( $dir ) use($base_dir) {
376 if ( $dir === 'squeeze-webp' ) {
377 return [
378 'name' => '',
379 'path' => '',
380 'is_writeable' => false,
381 'parent' => '',
382 ];
383 }
384 if ( $dir === $base_dir ) {
385 $path = str_replace( ABSPATH, '/', $dir . '/' );
386 $parent_path = dirname( $base_dir );
387 $parent_path = str_replace( ABSPATH, '/', $parent_path . '/' );
388 return [
389 'name' => 'wp-content',
390 'path' => $path,
391 'is_writeable' => false,
392 'parent' => $parent_path,
393 ];
394 }
395 $path = str_replace( ABSPATH, '/', $base_dir . '/' . $dir . '/' );
396 $parent_path = dirname( $base_dir . '/' . $dir );
397 $parent_path = str_replace( ABSPATH, '/', $parent_path . '/' );
398 // Remove double slashes from path
399 $path = preg_replace( '/\\/+/', '/', $path );
400 $parent_path = preg_replace( '/\\/+/', '/', $parent_path );
401 return [
402 'name' => $dir,
403 'path' => $path,
404 'is_writeable' => wp_is_writable( $base_dir . '/' . $dir ),
405 'parent' => $parent_path,
406 ];
407 }, $result );
408 usort( $output, function ( $a, $b ) {
409 if ( $a['name'] === 'wp-content' ) {
410 return -1;
411 } elseif ( $b['name'] === 'wp-content' ) {
412 return 1;
413 }
414 return strcmp( $a['name'], $b['name'] );
415 } );
416 wp_send_json( $output );
417 }
418
419 public function add_webp_rewrite_rules( $rules ) {
420 $is_auto_webp = self::$SqueezeHelpers->get_option( 'auto_webp' );
421 // Get the WordPress installation subdirectory, if applicable
422 $wordpress_subdirectory = wp_parse_url( home_url(), PHP_URL_PATH );
423 // Check if WordPress is installed in a subdirectory (not just the root)
424 if ( strlen( $wordpress_subdirectory ) > 1 ) {
425 // Ensure the subdirectory is used correctly in the rules
426 $rewrite_base = $wordpress_subdirectory . '/';
427 } else {
428 // If WordPress is installed in the root, no subdirectory path is needed
429 $rewrite_base = '/';
430 }
431 $webp_rules = "\n# Serve WebP images from the wp-content/squeeze-webp folder if available\n";
432 $webp_rules .= "RewriteCond %{HTTP_ACCEPT} image/webp\n";
433 // Check if browser supports WebP
434 $webp_rules .= "RewriteCond %{REQUEST_URI} \\.(jpg|jpeg|png)\$ [NC]\n";
435 // Check if request is for JPG, JPEG, or PNG
436 $webp_rules .= "RewriteCond %{DOCUMENT_ROOT}" . $rewrite_base . "wp-content/squeeze-webp/\$1.\$2.webp -f\n";
437 // Check if WebP file exists
438 $webp_rules .= "RewriteRule ^wp-content/(.+)\\.(jpg|jpeg|png)\$ wp-content/squeeze-webp/\$1.\$2.webp [T=image/webp,E=webp_request,L]\n";
439 // Serve WebP file
440 $webp_rules .= "\n";
441 if ( !$is_auto_webp ) {
442 // If auto WebP conversion is disabled, return the original rules and replace the WebP rules if they exist
443 $rules = preg_replace( '/# Serve WebP images from the wp-content\\/squeeze-webp folder if available.*?# Serve WebP images from the wp-content\\/squeeze-webp folder if available/s', '', $rules );
444 return $rules;
445 }
446 // Check if the server is Apache and htaccess is writable
447 if ( !function_exists( 'apache_get_modules' ) || !in_array( 'mod_rewrite', apache_get_modules() ) ) {
448 return $rules;
449 }
450 return $webp_rules . $rules;
451 }
452
453 public function output_buffer_start() {
454 if ( !is_admin() && (!isset( $_SERVER['HTTP_X_WP_REMOTE_REQUEST'] ) || $_SERVER['HTTP_X_WP_REMOTE_REQUEST'] !== 'true') || function_exists( "wp_doing_ajax" ) && wp_doing_ajax() || defined( 'DOING_AJAX' ) && DOING_AJAX ) {
455 ob_start( [$this, 'replace_image_urls_with_webp'] );
456 }
457 }
458
459 // Other helper functions
460 public function replace_image_urls_with_webp( $content ) {
461 if ( !self::$SqueezeHelpers->is_webp_replace_urls() ) {
462 return $content;
463 }
464 // Regular expression to find JPG and PNG images in src and srcset attributes.
465 $pattern = '/(\\/\\/.*?\\/wp-content\\/)([^"\\s]+\\.(jpg|jpeg|png))(\\?[^"\\s]*)?/i';
466 // Callback function to replace the URLs.
467 $callback = function ( $matches ) {
468 $file_extension = strtolower( pathinfo( $matches[0], PATHINFO_EXTENSION ) );
469 if ( $file_extension === 'webp' ) {
470 return $matches[0];
471 }
472 $protocol = ( is_ssl() ? 'https:' : 'http:' );
473 $webp_url = self::$SqueezeHelpers->convert_image_path_to_webp_path( $matches[0] ) . '.webp';
474 // WebP URL like 'example.com/wp-content/squeeze-webp/uploads/2024/12/test.jpg.webp'
475 // Check if the WEBP file exists on the server.
476 $webp_file_path = str_replace( home_url(), ABSPATH, $protocol . $webp_url );
477 // Convert URL to file path.
478 //return $webp_file_path.'::'.$webp_url;
479 if ( file_exists( $webp_file_path ) ) {
480 return $webp_url;
481 // Use WEBP version if it exists.
482 }
483 return $matches[0];
484 // Fallback to the original URL if WEBP file doesn't exist.
485 };
486 // Replace URLs in the content, including src and srcset attributes.
487 $content = preg_replace_callback( $pattern, $callback, $content );
488 return $content;
489 }
490
491 }