PluginProbe
Squeeze – Image Optimization & Compression, WEBP Conversion / 1.7.9
Squeeze – Image Optimization & Compression, WEBP Conversion v1.7.9
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 / helpers.php

helpers.php in Squeeze – Image Optimization & Compression, WEBP Conversion 1.7.9, at inc/helpers.php

804 lines 26.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace SqueezeFree;
3
4 // Exit if accessed directly.
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 class SqueezeHelpers extends SqueezeInit {
10 // Cache squeeze_options array per request to avoid repeated database queries
11 private static $cached_squeeze_options = null;
12
13 /** @var string[]|null */
14 private static $cached_excluded_images = null;
15
16 public function __construct() {
17 //parent::__construct(); // will cause infinite loop in SquuezeInit
18
19 add_filter( 'posts_where', [$this, 'get_images_from_last_id'], 10, 2 );
20 }
21
22 public function get_upload_path($attach_id, $filename, $url) {
23 if ($attach_id > 0) {
24 $upload_dir = str_replace($filename, "", wp_get_original_image_path($attach_id));
25 } else {
26 $upload_url = str_replace($filename, '', $url);
27 if (strpos($upload_url, '//') === 0) {
28 $upload_url = (is_ssl() ? 'https:' : 'http:') . $upload_url;
29 }
30 $resolved = $this->resolve_media_url_to_abspath($upload_url);
31 $upload_dir = $resolved !== '' ? $resolved : str_replace(home_url('/'), ABSPATH, $upload_url);
32 }
33 return str_replace('/', DIRECTORY_SEPARATOR, $upload_dir);
34 }
35
36 public function create_backup_filename($filename) {
37 $backup_filename = preg_replace("/(\.(?!.*\.))/", '.bak.', $filename);
38 return $backup_filename;
39 }
40
41 public function backup_original_image($upload_path, $filename, $original_file = null) {
42 if ( ! function_exists( 'WP_Filesystem' ) ) {
43 require_once ABSPATH . 'wp-admin/includes/file.php';
44 }
45
46 global $wp_filesystem;
47
48 // Initialize the filesystem (this populates $wp_filesystem)
49 WP_Filesystem();
50
51 if ( ! $wp_filesystem || ! method_exists( $wp_filesystem, 'copy' ) ) {
52 return new \WP_Error( 'squeeze_filesystem_api_error', 'Filesystem API is not available or failed to initialize.' );
53 }
54
55 $backup_filename = $this->create_backup_filename($filename);
56 if (!file_exists($upload_path . $backup_filename)) {
57 try {
58 if (!$original_file) {
59 $upload_backup_file = $wp_filesystem->copy($upload_path . $filename, $upload_path . $backup_filename, true);
60 } else {
61 $upload_backup_file = move_uploaded_file($original_file, $upload_path . $backup_filename);
62 }
63 } catch (\Exception $e) {
64 return new \WP_Error('squeeze_backup_original_image_failed', ''.esc_html__('Backup original image failed', 'squeeze') . ': '. $upload_path . $backup_filename);
65 }
66 }
67
68 return true;
69 }
70
71 public function decode_base64_image($base64, $file_format) {
72 $img = str_replace('data:image/'.$file_format.';base64,', '', $base64);
73 $img = str_replace(' ', '+', $img);
74 return base64_decode($img);
75 }
76
77 public function upload_image($upload_path, $filename, $decoded_image, $is_file = false) {
78 if ( ! function_exists( 'WP_Filesystem' ) ) {
79 require_once ABSPATH . 'wp-admin/includes/file.php';
80 }
81
82 global $wp_filesystem;
83
84 // Initialize the filesystem (this populates $wp_filesystem)
85 WP_Filesystem();
86
87 if ( ! $wp_filesystem || ! method_exists( $wp_filesystem, 'copy' ) ) {
88 return new \WP_Error('squeeze_filesystem_api_error', 'Filesystem API is not available or failed to initialize.');
89 }
90
91 if ($is_file) {
92 $upload_file = move_uploaded_file($decoded_image, $upload_path . $filename);
93 } else {
94 $upload_file = $wp_filesystem->put_contents($upload_path . $filename, $decoded_image);
95 }
96 if (!$upload_file) {
97 return new \WP_Error('squeeze_upload_image_failed', ''.esc_html__('Upload image failed', 'squeeze') . ': <br>upload_path: ' . $upload_path . '<br>filename: ' . $filename);
98 }
99
100 return $upload_file;
101 }
102
103 public function upload_image_thumbs($upload_path, $sizes, $file_format, $filename = '') {
104 if (!is_array($sizes) || empty($sizes)) {
105 return new \WP_Error('squeeze_upload_image_thumbs_failed', ''.esc_html__('No image data found', 'squeeze'));
106 }
107
108 if ( ! function_exists( 'WP_Filesystem' ) ) {
109 require_once ABSPATH . 'wp-admin/includes/file.php';
110 }
111
112 global $wp_filesystem;
113
114 // Initialize the filesystem (this populates $wp_filesystem)
115 WP_Filesystem();
116
117 if ( ! $wp_filesystem || ! method_exists( $wp_filesystem, 'copy' ) ) {
118 return new \WP_Error('squeeze_filesystem_api_error', 'Filesystem API is not available or failed to initialize.');
119 }
120
121 $is_direct_webp = $this->get_option('direct_webp');
122 foreach ($sizes as $size_name => $size_data) {
123 if ($size_name === 'original') {
124 continue;
125 }
126 $new_size_url = '';
127 if ($file_format === 'webp' && $is_direct_webp && $filename) {
128 //$size_data['url'] = preg_replace('/\.[^.]+$/', '.webp', $size_data['url']);
129
130 // remove extension from filename if it exists
131 //$origin_filename = $filename;
132 $new_filename = pathinfo($filename, PATHINFO_FILENAME); // DO NOT appennd to the $filename, causes bug with complex filenames
133
134
135 // replace the filename in the URL before the last dash
136 // e.g. 'http://localhost/test/wp-content/uploads/2025/08/image-thumbxsize.jpg' becomes
137 // 'http://localhost/test/wp-content/uploads/2025/08/$new_filename-thumbxsize.webp'
138
139 // 1. Match and capture path, base filename, suffix, and extension
140 //if (preg_match('/^(.*\/)(.+)-([^-\/]+)\.((?:jpe?g|png))$/i', $size_data['url'], $m)) {
141 if (preg_match('#^(?P<path>.*/wp-content/uploads/.*/)(?P<base>[^/]+?)(?P<suffix>-(?:\d+x\d+|scaled|rotated))?\.(?P<ext>jpe?g|png)$#i', $size_data['url'], $m)) {
142 // $m[1] = the full path including trailing slash
143 // $m[2] = the base filename (before the dash)
144 // $m[3] = the suffix (between dash and extension)
145 // $m[4] = the original extension
146
147 $path = $m[1];
148 //$new_filename = $m[2]; // <-- this is your variable
149 $suffix = $m[3];
150
151 // 2. Rebuild URL with .webp
152 $new_size_url = "{$path}{$new_filename}{$suffix}.webp";
153 }
154 }
155 $size_base64 = sanitize_text_field($size_data['base64']);
156 $size_decoded = $this->decode_base64_image($size_base64, $file_format);
157 $size_filename = basename(sanitize_url($new_size_url ? $new_size_url : $size_data['url']));
158 if ($size_name === 'full') {
159 $size_name = 'scaled';
160 unset($sizes['full']);
161 }
162 $original_size = file_exists($upload_path . $size_filename) ? wp_filesize($upload_path . $size_filename) : 0;
163 $compressed_size = strlen($size_decoded);
164 $sizes[$size_name]['original_size'] = $original_size;
165
166 // if compressed size is larger than original, skip uploading and keep original
167 if ($original_size > 0 && $compressed_size > $original_size) {
168 $sizes[$size_name]['compressed_size'] = $original_size;
169 continue;
170 }
171 $upload_size_file = $wp_filesystem->put_contents($upload_path . $size_filename, $size_decoded);
172 if (!$upload_size_file) {
173 return new \WP_Error('squeeze_upload_image_thumbs_failed', ''.esc_html__('Upload image failed', 'squeeze') . ': <br>upload_path: ' . $upload_path . '<br>filename: ' . $size_filename);
174 } else {
175 $sizes[$size_name]['compressed_size'] = $compressed_size;
176 }
177 }
178 return $sizes;
179 }
180
181 public function upload_webp($upload_path, $base64_webp, $filename, $is_file = false) {
182 if (!$base64_webp) {
183 return new \WP_Error('squeeze_upload_webp_failed', ''.esc_html__('No WebP data found', 'squeeze'));
184 }
185
186 $upload_webp_path = $this->convert_image_path_to_webp_path($upload_path);
187 if (!file_exists($upload_webp_path)) {
188 wp_mkdir_p($upload_webp_path);
189 }
190 $decoded_webp = $is_file ? $base64_webp : $this->decode_base64_image($base64_webp, 'webp');
191 $filename_webp = $filename.'.webp';
192 $upload_file_webp = $this->upload_image($upload_webp_path, $filename_webp, $decoded_webp, $is_file);
193
194 return $upload_file_webp;
195 }
196
197 public function upload_webp_thumbs($upload_path, $sizes_webp) {
198 if (!is_array($sizes_webp) || empty($sizes_webp)) {
199 return new \WP_Error('squeeze_upload_webp_thumbs_failed', ''.esc_html__('No WebP data found', 'squeeze'));
200 }
201
202 $upload_webp_path = $this->convert_image_path_to_webp_path($upload_path);
203 if (!file_exists($upload_webp_path)) {
204 wp_mkdir_p($upload_webp_path);
205 }
206 foreach ($sizes_webp as $size_name => $size_data) {
207 if ($size_name === 'original') {
208 continue;
209 }
210 $size_base64 = sanitize_text_field($size_data['base64']);
211 $size_decoded = $this->decode_base64_image($size_base64, 'webp');
212 $size_filename = basename(sanitize_url($size_data['url']));
213 $size_filename = $size_filename.'.webp';
214 $upload_size_file = $this->upload_image($upload_webp_path, $size_filename, $size_decoded);
215 }
216 return $sizes_webp;
217 }
218
219 public function convert_image_path_to_webp_path($image_path) {
220 //$webp_path = preg_replace('/wp-content[\/\\\\]/', 'wp-content/squeeze-webp/', $image_path, 1);
221 //return str_replace(['/', '\\'], '/', $webp_path);
222
223 // Grab your actual content folder name (e.g. "wp-content" or custom)
224 $content_folder = basename( WP_CONTENT_DIR );
225
226 // Build a regex to match that folder plus a slash or backslash
227 $pattern = sprintf(
228 '#%s[\\/\\\\]#',
229 preg_quote( $content_folder, '#' )
230 );
231 // Replace with "{folder}/squeeze-webp/"
232 $replacement = $content_folder . '/squeeze-webp/';
233
234 // Do the one-time replacement…
235 $webp_path = preg_replace( $pattern, $replacement, $image_path, 1 );
236
237 // Normalize backslashes to forward slashes and return
238 return str_replace( '\\', '/', $webp_path );
239 }
240
241 /**
242 * Map a full media URL (scheme or protocol-relative) to a path under ABSPATH.
243 * Strips the configured CDN base URL first when set, then home_url(), matching premium behavior.
244 *
245 * @param string $absolute_url Full URL, e.g. https://cdn.example.com/wp-content/uploads/...
246 * @return string Absolute filesystem path, or empty string if the URL does not map to this site.
247 */
248 public function resolve_media_url_to_abspath( $absolute_url ) {
249 if ( ! is_string( $absolute_url ) || $absolute_url === '' ) {
250 return '';
251 }
252 if ( strpos( $absolute_url, '//' ) === 0 ) {
253 $absolute_url = ( is_ssl() ? 'https:' : 'http:' ) . $absolute_url;
254 }
255 $cdn = trim( (string) $this->get_option( 'cdn_url' ) );
256 $bases = $cdn !== '' ? array( $cdn, home_url() ) : array( home_url() );
257 $rel = str_replace( $bases, '', $absolute_url );
258 $rel = ltrim( str_replace( '\\', '/', $rel ), '/' );
259 if ( $rel === '' || strpos( $rel, '..' ) !== false ) {
260 return '';
261 }
262 $content_folder = basename( WP_CONTENT_DIR );
263 if ( stripos( $rel, $content_folder . '/' ) !== 0 && stripos( $rel, $content_folder ) !== 0 ) {
264 return '';
265 }
266 return ABSPATH . $rel;
267 }
268
269 public function can_restore($attach_id) {
270 $original_img_path = wp_get_original_image_path((int) $attach_id);
271 $backup_img_path = preg_replace("/(\.(?!.*\.))/", '.bak.', $original_img_path);
272 $can_restore = file_exists($backup_img_path);
273
274 return $can_restore;
275 }
276
277 public function restore_attachment($attach_id, $is_bulk = false) {
278 if ( ! function_exists( 'WP_Filesystem' ) ) {
279 require_once ABSPATH . 'wp-admin/includes/file.php';
280 }
281
282 global $wp_filesystem;
283
284 // Initialize the filesystem (this populates $wp_filesystem)
285 WP_Filesystem();
286
287 if ( ! $wp_filesystem || ! method_exists( $wp_filesystem, 'copy' ) ) {
288 if ($is_bulk) {
289 wp_die( 'Filesystem API is not available or failed to initialize.' );
290 } else {
291 return new \WP_Error('squeeze_filesystem_api_error', 'Filesystem API is not available or failed to initialize.');
292 }
293 }
294
295 $original_img_path = wp_get_original_image_path($attach_id);
296 $backup_img_path = preg_replace("/(\.(?!.*\.))/", '.bak.', $original_img_path);
297
298 if (!file_exists($backup_img_path)) {
299 $error_message = '' . esc_html__('Backup image not found', 'squeeze');
300 if ($is_bulk) {
301 wp_die(esc_html($error_message));
302 } else {
303 return new \WP_Error('squeeze_restore_attachment_failed', $error_message);
304 }
305 return false;
306 }
307
308 $backup_img = $wp_filesystem->copy($backup_img_path, $original_img_path, true);
309 if (!$backup_img) {
310 $error_message = '' . esc_html__('Restore original image failed', 'squeeze');
311 if ($is_bulk) {
312 wp_die(esc_html($error_message));
313 } else {
314 return new \WP_Error('squeeze_restore_attachment_failed', $error_message);
315 }
316 return false;
317 }
318
319 $attachment_data = wp_create_image_subsizes($original_img_path, $attach_id);
320 if (!delete_post_meta($attach_id, "squeeze_is_compressed")) {
321 return false;
322 }
323
324 wp_delete_file($backup_img_path);
325 $this->delete_webp_images($original_img_path, $attachment_data);
326
327 $uncompressed_images = $this->get_stats_option('uncompressed_images');
328 update_option('squeeze_stats', array('uncompressed_images' => ++$uncompressed_images));
329 return true;
330 }
331
332 public function delete_webp_images($original_img_path, $attachment_data) {
333 $result = false;
334
335 if (!is_array($attachment_data) || empty($attachment_data)) {
336 return $result;
337 }
338
339 $original_filename = pathinfo($original_img_path, PATHINFO_BASENAME);
340
341 $webp_path = $this->convert_image_path_to_webp_path($original_img_path);
342 $result = wp_delete_file($webp_path . '.webp');
343
344 foreach ($attachment_data['sizes'] as $size_data) {
345 $webp_thumb_path = str_replace($original_filename, $size_data['file'] . '.webp', $original_img_path);
346 $result = wp_delete_file($this->convert_image_path_to_webp_path($webp_thumb_path));
347 }
348
349 $webp_scaled_filename = pathinfo($attachment_data['file'], PATHINFO_BASENAME);
350 $webp_scaled_path = $this->convert_image_path_to_webp_path(str_replace($original_filename, $webp_scaled_filename . '.webp', $original_img_path));
351
352 if (file_exists($webp_scaled_path)) {
353 $result = wp_delete_file($webp_scaled_path);
354 }
355
356 return $result;
357 }
358
359 public function get_stats_option($option) {
360 $stats = get_option('squeeze_stats');
361 $option_value = isset($stats[$option]) ? $stats[$option] : 0;
362
363 return $option_value;
364 }
365
366 public function get_option($option) {
367 // Cache squeeze_options array per request to avoid repeated database queries
368 // This prevents hundreds of get_option('squeeze_options') calls during bulk operations
369 if (self::$cached_squeeze_options === null) {
370 self::$cached_squeeze_options = get_option('squeeze_options');
371 }
372
373 $options = self::$cached_squeeze_options;
374 $option_value = isset($options[$option]) ? $options[$option] : $this->get_default_value($option);
375
376 return $option_value;
377 }
378
379 public function set_options($options) {
380 $default_options = $this->get_default_value('all', true);
381 $options = wp_parse_args($options, $default_options);
382 $result = update_option('squeeze_options', $options);
383
384 // Clear the cache when options are updated
385 if ($result) {
386 self::$cached_squeeze_options = $options;
387 }
388
389 return $result;
390 }
391
392 public function get_comparison_table($sizes) {
393 if (!is_array($sizes) || empty($sizes)) {
394 return '';
395 }
396
397 //$table = print_r($sizes, true);
398 $table = '<div class="squeeze-comparison-table">';
399 $table .= '<table class="wp-list-table widefat striped">';
400 $table .= '<thead><tr><th>'.esc_html__('Size Name', 'squeeze').'</th><th>'.esc_html__('Original Size', 'squeeze').'</th><th>'.esc_html__('Squeezed Size', 'squeeze').'</th><th>'.esc_html__('Savings', 'squeeze').' (%)</th></tr></thead>';
401 $table .= '<tbody>';
402
403 foreach ($sizes as $size_name => $size_data) {
404 /*if (!isset($size_data['url'])) {
405 continue;
406 }*/
407 //$size_filename = basename(sanitize_url($size_data['url']));
408 $original_size = $size_data['original_size'];
409 $compressed_size = $size_data['compressed_size'];
410 $savings = $original_size - $compressed_size;
411 $savings_percent = round(($savings / $original_size) * 100, 2);
412 $savings_class = $savings > 0 ? 'squeeze-savings-positive' : 'squeeze-savings-negative';
413
414 $table .= '<tr>';
415 $table .= '<td><strong>'.$size_name.'</strong></td>';
416 $table .= '<td>'.size_format($original_size, 0).'</td>';
417 $table .= '<td>'.size_format($compressed_size, 0).'</td>';
418 $table .= '<td><span class="squeeze-savings-label '.$savings_class.'">'.$savings_percent.'%</span></td>';
419 $table .= '</tr>';
420 }
421
422 $table .= '</tbody></table></div>';
423
424 return $table;
425 }
426
427 public function is_webp_replace_urls() {
428 $is_auto_webp = $this->get_option('auto_webp');
429 $is_webp_replace_urls = $this->get_option('webp_replace_urls');
430
431 if (!$is_auto_webp || !$is_webp_replace_urls) {
432 return false;
433 }
434
435 return true;
436 }
437
438 public function get_image_formats($return_mimes = false, $custom_formats = []) {
439 $allowed_image_formats = empty($custom_formats) ? $this->get_option('compress_formats') : $custom_formats;
440 $allowed_image_formats = array_keys($allowed_image_formats);
441
442 // make values the same as keys
443 $allowed_image_formats = array_combine($allowed_image_formats, $allowed_image_formats);
444
445 if ($return_mimes) {
446 $allowed_image_formats = array_map(function($format) {
447 $format = $format === 'jpg' ? 'jpeg' : $format; // handle jpg/jpeg mime type
448 return 'image/'.$format;
449 }, $allowed_image_formats);
450 }
451
452 return $allowed_image_formats;
453 }
454
455 public function get_total_images_count() {
456 $total_images = $this->get_stats_option('total_images');
457
458 if ($total_images > 0) {
459 return $total_images;
460 }
461
462 $query_all = new \WP_Query(array(
463 'post_type' => 'attachment',
464 'post_status' => 'inherit',
465 'post_mime_type' => $this->get_image_formats(true),
466 'posts_per_page' => -1,
467 'fields' => 'ids',
468 ));
469
470 $total_images = $query_all->found_posts;
471
472 $stats['total_images'] = $total_images;
473 update_option('squeeze_stats', $stats);
474
475 return $total_images;
476 }
477
478 public function get_uncompressed_images_count() {
479 $uncompressed_images = $this->get_stats_option('uncompressed_images');
480
481 if ($uncompressed_images > 0) {
482 return $uncompressed_images;
483 }
484
485 $query_uncompressed = new \WP_Query(array(
486 'post_type' => 'attachment',
487 'post_status' => 'inherit',
488 'post_mime_type' => $this->get_image_formats(true),
489 'posts_per_page' => -1,
490 'fields' => 'ids',
491 'meta_query' => array(
492 'relation' => 'OR',
493 array(
494 'key' => 'squeeze_is_compressed',
495 'compare' => 'NOT EXISTS',
496 ),
497 array(
498 'key' => 'squeeze_is_compressed',
499 'compare' => '!=',
500 'value' => '1',
501 ),
502 ),
503 ));
504
505 $uncompressed_images = $query_uncompressed->found_posts;
506
507 $stats['uncompressed_images'] = $uncompressed_images;
508 update_option('squeeze_stats', $stats);
509
510 return $uncompressed_images;
511 }
512
513 public function get_images_from_last_id($where, $wp_query) {
514 if ( $wp_query->get( 'squeeze_last_id' ) !== null && $wp_query->get( 'squeeze_last_id' ) > 0 ) {
515 global $wpdb;
516 $last = intval( $wp_query->get( 'squeeze_last_id' ) );
517 $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID < %d", $last );
518 }
519 return $where;
520 }
521
522 public function get_uncompressed_images($last_id = 0) {
523
524 $query_uncompressed = new \WP_Query(array(
525 'post_type' => 'attachment',
526 'post_status' => 'inherit',
527 'post_mime_type' => $this->get_image_formats(true),
528 'posts_per_page' => self::$MEDIA_PER_PAGE,
529 'fields' => 'ids',
530 'orderby' => 'ID',
531 'order' => 'DESC',
532 'meta_query' => array(
533 'relation' => 'OR',
534 array(
535 'key' => 'squeeze_is_compressed',
536 'compare' => 'NOT EXISTS',
537 ),
538 array(
539 'key' => 'squeeze_is_compressed',
540 'compare' => '!=',
541 'value' => '1',
542 ),
543 ),
544 'squeeze_last_id' => $last_id,
545 ));
546
547 return $query_uncompressed->posts;
548 }
549
550 public function get_total_images($paged = 1) {
551 $args = array(
552 'post_type' => 'attachment',
553 'post_status' => 'inherit',
554 'post_mime_type' => $this->get_image_formats(true),
555 'posts_per_page' => self::$MEDIA_PER_PAGE,
556 'paged' => $paged,
557 'fields' => 'ids',
558 );
559
560 $query_all = new \WP_Query($args);
561
562 return $query_all->posts;
563 }
564
565 public function get_hint($hint, $class = 'squeeze-hint') {
566 return '<span class="' . esc_attr($class) . '">' . esc_html($hint) . '</span>';
567 }
568
569 /**
570 * Parsed list of exclusion patterns (one per line in settings). Cached per request.
571 *
572 * @return string[]
573 */
574 public function get_excluded_images() {
575 if ( null !== self::$cached_excluded_images ) {
576 return self::$cached_excluded_images;
577 }
578
579 $raw = $this->get_option( 'excluded_images' );
580 if ( ! is_string( $raw ) || $raw === '' ) {
581 self::$cached_excluded_images = array();
582 return self::$cached_excluded_images;
583 }
584
585 $lines = explode( "\n", $raw );
586 $lines = array_filter(
587 array_map( 'trim', $lines ),
588 static function ( $line ) {
589 return $line !== '';
590 }
591 );
592
593 self::$cached_excluded_images = array_values( $lines );
594 return self::$cached_excluded_images;
595 }
596
597 /**
598 * Whether a URL or path matches any exclusion pattern (substring match, case-insensitive).
599 *
600 * @param string|null $image_path URL or path fragment.
601 * @param string[]|null $excluded_images Patterns from get_excluded_images(); null loads from options.
602 * @return array{is_excluded: true, exclude_reason: string}|false
603 */
604 public function is_excluded_image( $image_path, $excluded_images = null ) {
605 if ( ! $image_path ) {
606 return false;
607 }
608
609 if ( null === $excluded_images ) {
610 $excluded_images = $this->get_excluded_images();
611 }
612
613 if ( empty( $excluded_images ) ) {
614 return false;
615 }
616
617 foreach ( $excluded_images as $excluded_image ) {
618 $excluded_image = trim( $excluded_image );
619 if ( $excluded_image === '' ) {
620 continue;
621 }
622 if ( stripos( $image_path, $excluded_image ) !== false ) {
623 return array(
624 'is_excluded' => true,
625 'exclude_reason' => $excluded_image,
626 );
627 }
628 }
629
630 return false;
631 }
632
633 public function get_default_value ( $option, $all = false ) {
634 $options_defaults = apply_filters('squeeze_options_default',
635 array(
636 // JPEG settings
637 'jpeg_quality' => 80,
638 'jpeg_baseline' => false,
639 //'jpeg_arithmetic' => false,
640 'jpeg_progressive' => true,
641 'jpeg_optimize_coding' => true,
642 'jpeg_smoothing' => 0,
643 'jpeg_color_space' => 3,
644 'jpeg_quant_table' => 3,
645 'jpeg_trellis_multipass' => false,
646 'jpeg_trellis_opt_zero' => false,
647 'jpeg_trellis_opt_table' => false,
648 'jpeg_trellis_loops' => 1,
649 'jpeg_auto_subsample' => true,
650 'jpeg_chroma_subsample' => 2,
651 'jpeg_separate_chroma_quality' => false,
652 'jpeg_chroma_quality' => 75,
653
654 // PNG settings
655 'png_level' => 2,
656 'png_interlace' => false,
657 'png_quality' => 0.7,
658
659 // WEBP settings
660 'webp_method' => 4,
661 'webp_quality' => 80,
662 'webp_lossless' => false,
663 'webp_near_lossless' => 100,
664
665 // AVIF settings
666 'avif_cqLevel' => 70,
667
668 // General settings
669 'auto_compress' => true,
670 'auto_webp' => false, // needs to be false by default, because user has to save settings first in order to flush rewrite rules
671 'webp_replace_urls' => false,
672 'direct_webp' => true,
673 'cdn_url' => '',
674 'backup_original' => true,
675 'compress_formats' => self::ALLOWED_IMAGE_FORMATS,
676 'compress_thumbs' => array('large' => 'on', 'full' => 'on'),
677 'max_width' => '',
678 'max_height' => '',
679 'excluded_images' => '',
680 'timeout' => 60,
681 'restore_defaults' => false, // special option to trigger restore defaults
682 )
683 );
684 if ($all) {
685 return $options_defaults;
686 }
687 return in_array($option, array_keys($options_defaults)) ? $options_defaults[ $option ] : false;
688 }
689
690 public function get_thumb_sizes() {
691 $sizes = wp_get_registered_image_subsizes();
692 if ( !empty( $sizes ) && is_array( $sizes ) ) {
693 // Add the scaled image size option if it fits the image dimensions
694 $big_image_size_threshold = apply_filters('big_image_size_threshold', 2560);
695 if ( $big_image_size_threshold ) {
696 $sizes['full'] = array(
697 'width' => $big_image_size_threshold,
698 'height' => $big_image_size_threshold,
699 'crop' => false,
700 );
701 }
702 }
703 return $sizes;
704 }
705
706 public function is_rest_enabled() {
707 // Check if REST API is enabled
708 if ( !function_exists( 'rest_get_server' ) || !rest_get_server() ) {
709 return false;
710 }
711
712 return (bool) apply_filters( 'rest_enabled', true );
713 }
714
715 public function apache_get_modules() {
716 if (!function_exists('apache_get_modules') || !in_array('mod_rewrite', apache_get_modules())) {
717 return false;
718 }
719 return apache_get_modules();
720 }
721
722 /**
723 * Convert a filesystem directory under ABSPATH to site-root-relative form (/wp-content/foo/).
724 *
725 * @param string $filesystem_dir Absolute directory path.
726 * @return string Leading slash, forward slashes, trailing slash (or '/' for site root).
727 */
728 public function bulk_directory_uri_from_filesystem( $filesystem_dir ) {
729 $abs_base = wp_normalize_path( ABSPATH );
730 $full = wp_normalize_path( rtrim( (string) $filesystem_dir, '/\\' ) );
731 if ( strpos( $full, $abs_base ) !== 0 ) {
732 $slash = preg_replace( '#/+#', '/', str_replace( '\\', '/', str_replace( ABSPATH, '/', (string) $filesystem_dir ) ) );
733 if ( '/' === $slash || '' === $slash ) {
734 return '/';
735 }
736 if ( '/' !== substr( $slash, 0, 1 ) ) {
737 $slash = '/' . ltrim( $slash, '/' );
738 }
739 return substr( $slash, -1 ) === '/' ? $slash : $slash . '/';
740 }
741 $rel = substr( $full, strlen( $abs_base ) );
742 $rel = trim( str_replace( '\\', '/', $rel ), '/' );
743 return '' === $rel ? '/' : '/' . $rel . '/';
744 }
745
746 /**
747 * Turn browse API parentDir into a path relative to WP_CONTENT_DIR (uploads, themes/foo, or '').
748 *
749 * @param string $parent_directory Raw POST value (e.g. /wp-content/uploads/ or wp-content/uploads).
750 * @return string No leading/trailing slashes.
751 */
752 public function bulk_parent_relative_to_content_dir( $parent_directory ) {
753 $parent_directory = preg_replace( '#/+#', '/', str_replace( '\\', '/', (string) $parent_directory ) );
754 $parent_directory = trim( $parent_directory, '/' );
755 if ( '' === $parent_directory ) {
756 return '';
757 }
758 if ( 'wp-content' === $parent_directory ) {
759 return '';
760 }
761 if ( 0 === strpos( $parent_directory, 'wp-content/' ) ) {
762 return trim( substr( $parent_directory, strlen( 'wp-content/' ) ), '/' );
763 }
764 return $parent_directory;
765 }
766
767 /**
768 * Normalize directory paths for bulk UI and JSON: never show filesystem absolute paths.
769 *
770 * @param string $path Raw path from transient, manual entry, etc.
771 * @return string Site-relative path like /wp-content/uploads/.
772 */
773 public function normalize_bulk_directory_storage_path( $path ) {
774 $path = trim( (string) $path );
775 if ( '' === $path ) {
776 return '/';
777 }
778 if ( preg_match( '#^https?://#i', $path ) ) {
779 $parsed = wp_parse_url( $path );
780 $path = isset( $parsed['path'] ) ? $parsed['path'] : '/';
781 $site = wp_parse_url( home_url( '/' ), PHP_URL_PATH );
782 if ( is_string( $site ) && strlen( $site ) > 1 ) {
783 $site = untrailingslashit( $site );
784 if ( $site && strpos( $path, $site . '/' ) === 0 ) {
785 $path = substr( $path, strlen( $site ) );
786 }
787 }
788 }
789 $clean = preg_replace( '#/+#', '/', str_replace( '\\', '/', $path ) );
790 $norm = wp_normalize_path( $clean );
791 $base = wp_normalize_path( ABSPATH );
792 if ( ( strlen( $norm ) >= 2 && ctype_alpha( $norm[0] ) && ':' === $norm[1] ) || ( strpos( $norm, $base ) === 0 ) ) {
793 return $this->bulk_directory_uri_from_filesystem( $clean );
794 }
795 if ( '/' !== substr( $clean, 0, 1 ) ) {
796 $clean = '/' . ltrim( $clean, '/' );
797 }
798 if ( '/' !== substr( $clean, -1 ) ) {
799 $clean .= '/';
800 }
801 return $clean;
802 }
803 }
804