PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.2.9
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.2.9
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / inc / classes / class-imagify-files-scan.php

class-imagify-files-scan.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 2.2.9, at inc/classes/class-imagify-files-scan.php

717 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class handling everything that is related to "custom folders optimization".
5 *
6 * @since 1.7
7 * @author Grégory Viguier
8 */
9 class Imagify_Files_Scan {
10
11 /**
12 * Class version.
13 *
14 * @var string
15 * @since 1.7
16 * @author Grégory Viguier
17 */
18 const VERSION = '1.1.1';
19
20 /**
21 * Get files (optimizable by Imagify) recursively from a specific folder.
22 *
23 * @since 1.7
24 * @access public
25 * @author Grégory Viguier
26 *
27 * @param string $folder An absolute path to a folder.
28 * @return array|object An array of absolute paths. A WP_Error object on error.
29 */
30 public static function get_files_from_folder( $folder ) {
31 $filesystem = imagify_get_filesystem();
32
33 // Formate and validate the folder path.
34 if ( ! is_string( $folder ) ) {
35 return new WP_Error( 'invalid_folder', __( 'Invalid folder.', 'imagify' ) );
36 }
37
38 $folder = realpath( $folder );
39
40 if ( ! $folder ) {
41 return new WP_Error( 'folder_not_exists', __( 'This folder does not exist.', 'imagify' ) );
42 }
43
44 if ( ! $filesystem->is_dir( $folder ) ) {
45 return new WP_Error( 'not_a_folder', __( 'This file is not a folder.', 'imagify' ) );
46 }
47
48 if ( self::is_path_forbidden( trailingslashit( $folder ) ) ) {
49 return new WP_Error( 'folder_forbidden', __( 'This folder is not allowed.', 'imagify' ) );
50 }
51
52 // Finally we made all our validations.
53 if ( $filesystem->is_site_root( $folder ) ) {
54 // For the site's root, we don't look in sub-folders.
55 $dir = new DirectoryIterator( $folder );
56 $dir = new Imagify_Files_Iterator( $dir, false );
57 $images = [];
58
59 foreach ( new IteratorIterator( $dir ) as $file ) {
60 $images[] = $file->getPathname();
61 }
62
63 return $images;
64 }
65
66 /**
67 * 4096 stands for FilesystemIterator::SKIP_DOTS, which was introduced in php 5.3.0.
68 * 8192 stands for FilesystemIterator::UNIX_PATHS, which was introduced in php 5.3.0.
69 */
70 $dir = new RecursiveDirectoryIterator( $folder, 4096 | 8192 );
71 $dir = new Imagify_Files_Recursive_Iterator( $dir );
72 $images = new RecursiveIteratorIterator( $dir );
73 $images = array_keys( iterator_to_array( $images ) );
74
75 return $images;
76 }
77
78
79 /** ----------------------------------------------------------------------------------------- */
80 /** FORBIDDEN FOLDERS AND FILES ============================================================= */
81 /** ----------------------------------------------------------------------------------------- */
82
83 /**
84 * Tell if a path is autorized.
85 * When testing a folder, the path MUST have a trailing slash.
86 *
87 * @since 1.7.1
88 * @since 1.8 The path must have a trailing slash if for a folder.
89 * @access public
90 * @author Grégory Viguier
91 *
92 * @param string $file_path A file or folder absolute path.
93 * @return bool
94 */
95 public static function is_path_autorized( $file_path ) {
96 return ! self::is_path_forbidden( $file_path );
97 }
98
99 /**
100 * Tell if a path is forbidden.
101 * When testing a folder, the path MUST have a trailing slash.
102 *
103 * @since 1.7
104 * @since 1.8 The path must have a trailing slash if for a folder.
105 * @access public
106 * @author Grégory Viguier
107 *
108 * @param string $file_path A file or folder absolute path.
109 * @return bool
110 */
111 public static function is_path_forbidden( $file_path ) {
112 static $folders;
113
114 $filesystem = imagify_get_filesystem();
115
116 if ( self::is_filename_forbidden( $filesystem->file_name( $file_path ) ) ) {
117 return true;
118 }
119
120 if ( $filesystem->is_symlinked( $file_path ) ) {
121 // Files outside the site's folder are forbidden.
122 return true;
123 }
124
125 if ( ! isset( $folders ) ) {
126 $folders = self::get_forbidden_folders();
127 $folders = array_map( 'strtolower', $folders );
128 $folders = array_flip( $folders );
129 }
130
131 $file_path = self::normalize_path_for_comparison( $file_path );
132
133 if ( isset( $folders[ $file_path ] ) ) {
134 return true;
135 }
136
137 $delim = Imagify_Filesystem::PATTERN_DELIMITER;
138
139 foreach ( self::get_forbidden_folder_patterns() as $pattern ) {
140 if ( preg_match( $delim . '^' . $pattern . $delim, $file_path ) ) {
141 return true;
142 }
143 }
144
145 foreach ( $folders as $folder => $i ) {
146 if ( strpos( $file_path, $folder ) === 0 ) {
147 return true;
148 }
149 }
150
151 return false;
152 }
153
154 /**
155 * Get the list of folders where Imagify won't look for files to optimize.
156 *
157 * @since 1.7
158 * @access public
159 * @author Grégory Viguier
160 *
161 * @return array A list of absolute paths.
162 */
163 public static function get_forbidden_folders() {
164 static $folders;
165
166 if ( isset( $folders ) ) {
167 return $folders;
168 }
169
170 $filesystem = imagify_get_filesystem();
171 $site_root = $filesystem->get_site_root();
172 $abspath = $filesystem->get_abspath();
173 $folders = [
174 // Server.
175 $site_root . 'cgi-bin', // `cgi-bin`
176 // WordPress.
177 $abspath . 'wp-admin',
178 $abspath . WPINC,
179 WP_CONTENT_DIR . '/mu-plugins', // MU plugins.
180 WP_CONTENT_DIR . '/upgrade', // Upgrade.
181 // Plugins.
182 WP_CONTENT_DIR . '/bps-backup', // BulletProof Security.
183 self::get_ewww_tools_path(), // EWWW: /wp-content/ewww.
184 WP_CONTENT_DIR . '/ngg', // NextGen Gallery.
185 WP_CONTENT_DIR . '/ngg_styles', // NextGen Gallery.
186 WP_CONTENT_DIR . '/w3tc-config', // W3 Total Cache.
187 WP_CONTENT_DIR . '/wfcache', // WP Fastest Cache.
188 WP_CONTENT_DIR . '/wp-rocket-config', // WP Rocket.
189 Imagify_Custom_Folders::get_backup_dir_path(), // Imagify "Custom folders" backup: /imagify-backup.
190 IMAGIFY_PATH, // Imagify plugin: /wp-content/plugins/imagify.
191 self::get_shortpixel_path(), // ShortPixel: /wp-content/uploads/ShortpixelBackups.
192 ];
193
194 if ( ! is_multisite() ) {
195 $uploads_dir = $filesystem->get_upload_basedir( true );
196 $ngg_galleries = self::get_ngg_galleries_path();
197
198 if ( $ngg_galleries ) {
199 $folders[] = $ngg_galleries; // NextGen Gallery: /wp-content/gallery.
200 }
201
202 $folders[] = $uploads_dir . 'formidable'; // Formidable Forms: /wp-content/uploads/formidable.
203 $folders[] = get_imagify_backup_dir_path( true ); // Imagify Media Library backup: /wp-content/uploads/backup.
204 $folders[] = self::get_wc_logs_path(); // WooCommerce Logs: /wp-content/uploads/wc-logs.
205 $folders[] = $uploads_dir . 'woocommerce_uploads'; // WooCommerce uploads: /wp-content/uploads/woocommerce_uploads.
206 }
207
208 $folders = array_map( [ $filesystem, 'normalize_dir_path' ], $folders );
209
210 /**
211 * Add folders to the list of forbidden ones.
212 *
213 * @since 1.7
214 * @author Grégory Viguier
215 *
216 * @param array $added_folders List of absolute paths.
217 * @param array $folders List of folders already forbidden.
218 */
219 $added_folders = apply_filters( 'imagify_add_forbidden_folders', [], $folders );
220 $added_folders = array_filter( (array) $added_folders );
221 $added_folders = array_filter( $added_folders, 'is_string' );
222
223 if ( ! $added_folders ) {
224 return $folders;
225 }
226
227 $added_folders = array_map( [ $filesystem, 'normalize_dir_path' ], $added_folders );
228
229 $folders = array_merge( $folders, $added_folders );
230 $folders = array_flip( array_flip( $folders ) );
231
232 return $folders;
233 }
234
235 /**
236 * Get the list of folder patterns where Imagify won't look for files to optimize. This is meant for paths that are dynamic.
237 * `^` will be prepended to each pattern (aka, the pattern must match an absolute path).
238 * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
239 * Paths tested against these patterns are lower-cased.
240 *
241 * @since 1.7
242 * @access public
243 * @author Grégory Viguier
244 *
245 * @return array A list of regex patterns.
246 */
247 public static function get_forbidden_folder_patterns() {
248 static $folders;
249
250 if ( isset( $folders ) ) {
251 return $folders;
252 }
253
254 $folders = [];
255
256 // Media Library: /wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/.
257 $folders[] = self::get_media_library_pattern();
258
259 if ( is_multisite() ) {
260 /**
261 * On multisite we can't exclude Imagify's library backup folders, or any other folder located in the uploads folders (created by other plugins): there are too many ways it can fail.
262 * Only exception we're aware of so far is NextGen Gallery, because it provides a clear pattern to use.
263 */
264 $ngg_galleries = self::get_ngg_galleries_multisite_pattern();
265
266 if ( $ngg_galleries ) {
267 // NextGen Gallery: /wp\-content/uploads/sites/\d+/nggallery/.
268 $folders[] = $ngg_galleries;
269 }
270 }
271
272 /**
273 * Add folder patterns to the list of forbidden ones.
274 * Don't forget to use `Imagify_Files_Scan::normalize_path_for_regex( $path )`!
275 *
276 * @since 1.7
277 * @author Grégory Viguier
278 *
279 * @param array $added_folders List of patterns.
280 * @param array $folders List of patterns already forbidden.
281 */
282 $added_folders = apply_filters( 'imagify_add_forbidden_folder_patterns', [], $folders );
283 $added_folders = array_filter( (array) $added_folders );
284 $added_folders = array_filter( $added_folders, 'is_string' );
285
286 if ( ! $added_folders ) {
287 return $folders;
288 }
289
290 $folders = array_merge( $folders, $added_folders );
291 $folders = array_flip( array_flip( $folders ) );
292
293 return $folders;
294 }
295
296 /**
297 * Tell if a file/folder name is forbidden.
298 *
299 * @since 1.7
300 * @access public
301 * @author Grégory Viguier
302 *
303 * @param string $file_name A file or folder name.
304 * @return bool
305 */
306 public static function is_filename_forbidden( $file_name ) {
307 static $file_names;
308
309 if ( ! isset( $file_names ) ) {
310 $file_names = array_flip( self::get_forbidden_file_names() );
311 }
312
313 return isset( $file_names[ strtolower( $file_name ) ] );
314 }
315
316 /**
317 * Get the list of file names that Imagify won't optimize.
318 * It can contain folder names. Names are case-lowered.
319 *
320 * @since 1.7
321 * @access public
322 * @author Grégory Viguier
323 *
324 * @return array A list of file names
325 */
326 public static function get_forbidden_file_names() {
327 static $file_names;
328
329 if ( isset( $file_names ) ) {
330 return $file_names;
331 }
332
333 $file_names = [
334 '.',
335 '..',
336 '.DS_Store',
337 '.git',
338 '.svn',
339 'backup',
340 'backups',
341 'cache',
342 'lang',
343 'langs',
344 'languages',
345 'node_modules',
346 'Thumbs.db',
347 ];
348 $file_names = array_map( 'strtolower', $file_names );
349
350 /**
351 * Add file names to the list of forbidden ones.
352 *
353 * @since 1.7
354 * @author Grégory Viguier
355 *
356 * @param array $added_file_names List of file names.
357 * @param array $file_names List of file names already forbidden.
358 */
359 $added_file_names = apply_filters( 'imagify_add_forbidden_file_names', [], $file_names );
360
361 if ( ! $added_file_names || ! is_array( $added_file_names ) ) {
362 return $file_names;
363 }
364
365 $added_file_names = array_filter( $added_file_names, 'is_string' );
366 $added_file_names = array_map( 'strtolower', $added_file_names );
367
368 $file_names = array_merge( $file_names, $added_file_names );
369 $file_names = array_flip( array_flip( $file_names ) );
370
371 return $file_names;
372 }
373
374
375 /** ----------------------------------------------------------------------------------------- */
376 /** PLACEHOLDERS ============================================================================ */
377 /** ----------------------------------------------------------------------------------------- */
378
379 /**
380 * Add a placeholder to a path.
381 *
382 * @since 1.7
383 * @access public
384 * @author Grégory Viguier
385 *
386 * @param string $file_path An absolute path.
387 * @return string A "placeholdered" path.
388 */
389 public static function add_placeholder( $file_path ) {
390 $file_path = wp_normalize_path( $file_path );
391 $locations = self::get_placeholder_paths();
392
393 foreach ( $locations as $placeholder => $location_path ) {
394 if ( strpos( $file_path, $location_path ) === 0 ) {
395 return preg_replace( '@^' . preg_quote( $location_path, '@' ) . '@', $placeholder, $file_path );
396 }
397 }
398
399 // Should not happen.
400 return $file_path;
401 }
402
403 /**
404 * Change a path with a placeholder into a real path or URL.
405 *
406 * @since 1.7
407 * @access public
408 * @author Grégory Viguier
409 *
410 * @param string $file_path A path with a placeholder.
411 * @param string $type What to return: 'path' or 'url'.
412 * @return string An absolute path or a URL.
413 */
414 public static function remove_placeholder( $file_path, $type = 'path' ) {
415 if ( 'path' === $type ) {
416 $locations = self::get_placeholder_paths();
417 } else {
418 $locations = self::get_placeholder_urls();
419 }
420
421 foreach ( $locations as $placeholder => $location_path ) {
422 if ( strpos( $file_path, $placeholder ) === 0 ) {
423 return preg_replace( '@^' . preg_quote( $placeholder, '@' ) . '@', $location_path, $file_path );
424 }
425 }
426
427 // Should not happen.
428 return $file_path;
429 }
430
431 /**
432 * Get array of pairs of placeholder => corresponding path.
433 *
434 * @since 1.7
435 * @access public
436 * @author Grégory Viguier
437 *
438 * @return array
439 */
440 public static function get_placeholder_paths() {
441 static $replacements;
442
443 if ( isset( $replacements ) ) {
444 return $replacements;
445 }
446
447 $filesystem = imagify_get_filesystem();
448 $replacements = [
449 '{{PLUGINS}}/' => WP_PLUGIN_DIR,
450 '{{MU_PLUGINS}}/' => WPMU_PLUGIN_DIR,
451 '{{THEMES}}/' => WP_CONTENT_DIR . '/themes',
452 '{{UPLOADS}}/' => $filesystem->get_main_upload_basedir(),
453 '{{CONTENT}}/' => WP_CONTENT_DIR,
454 '{{ROOT}}/' => $filesystem->get_site_root(),
455 ];
456 $replacements = array_map( [ $filesystem, 'normalize_dir_path' ], $replacements );
457
458 return $replacements;
459 }
460
461 /**
462 * Get array of pairs of placeholder => corresponding URL.
463 *
464 * @since 1.7
465 * @access public
466 * @author Grégory Viguier
467 *
468 * @return array
469 */
470 public static function get_placeholder_urls() {
471 static $replacements;
472
473 if ( isset( $replacements ) ) {
474 return $replacements;
475 }
476
477 $filesystem = imagify_get_filesystem();
478 $replacements = [
479 '{{PLUGINS}}/' => plugins_url( '/' ),
480 '{{MU_PLUGINS}}/' => plugins_url( '/', WPMU_PLUGIN_DIR . '/.' ),
481 '{{THEMES}}/' => content_url( 'themes/' ),
482 '{{UPLOADS}}/' => $filesystem->get_main_upload_baseurl(),
483 '{{CONTENT}}/' => content_url( '/' ),
484 '{{ROOT}}/' => $filesystem->get_site_root_url(),
485 ];
486
487 return $replacements;
488 }
489
490 /**
491 * A file_exists() for paths with a placeholder.
492 *
493 * @since 1.7
494 * @access public
495 * @author Grégory Viguier
496 *
497 * @param string $file_path The file path.
498 * @return bool
499 */
500 public static function placeholder_path_exists( $file_path ) {
501 return imagify_get_filesystem()->is_readable( self::remove_placeholder( $file_path ) );
502 }
503
504
505 /** ----------------------------------------------------------------------------------------- */
506 /** PATHS =================================================================================== */
507 /** ----------------------------------------------------------------------------------------- */
508
509 /**
510 * Get the path to NextGen galleries on monosites.
511 *
512 * @since 1.7
513 * @access public
514 * @author Grégory Viguier
515 *
516 * @return string|bool An absolute path. False if it can't be retrieved.
517 */
518 public static function get_ngg_galleries_path() {
519 $galleries_path = get_site_option( 'ngg_options' );
520
521 if ( empty( $galleries_path['gallerypath'] ) ) {
522 return false;
523 }
524
525 $filesystem = imagify_get_filesystem();
526 $galleries_path = $filesystem->normalize_dir_path( $galleries_path['gallerypath'] );
527 $galleries_path = trim( $galleries_path, '/' ); // Something like `wp-content/gallery`.
528
529 $ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
530
531 if ( $galleries_path && 'content' === $ngg_root ) {
532 $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
533 $ngg_root = trim( $ngg_root, '/' ); // Something like `abs-path/to/wp-content`.
534
535 $exploded_root = explode( '/', $ngg_root );
536 $exploded_galleries = explode( '/', $galleries_path );
537 $first_gallery_dirname = reset( $exploded_galleries );
538 $last_root_dirname = end( $exploded_root );
539
540 if ( $last_root_dirname === $first_gallery_dirname ) {
541 array_shift( $exploded_galleries );
542 $galleries_path = implode( '/', $exploded_galleries );
543 }
544 }
545
546 if ( 'content' === $ngg_root ) {
547 $ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
548 } else {
549 $ngg_root = $filesystem->get_abspath();
550 }
551
552 if ( strpos( $galleries_path, $ngg_root ) !== 0 ) {
553 $galleries_path = $ngg_root . $galleries_path;
554 }
555
556 return $galleries_path . '/';
557 }
558
559 /**
560 * Get the path to WooCommerce logs on monosites.
561 *
562 * @since 1.7
563 * @access public
564 * @author Grégory Viguier
565 *
566 * @return string An absolute path.
567 */
568 public static function get_wc_logs_path() {
569 if ( defined( 'WC_LOG_DIR' ) ) {
570 return WC_LOG_DIR;
571 }
572
573 return imagify_get_filesystem()->get_upload_basedir( true ) . 'wc-logs/';
574 }
575
576 /**
577 * Get the path to EWWW optimization tools.
578 * It is the same for all sites on multisite.
579 *
580 * @since 1.7
581 * @access public
582 * @author Grégory Viguier
583 *
584 * @return string An absolute path.
585 */
586 public static function get_ewww_tools_path() {
587 if ( defined( 'EWWW_IMAGE_OPTIMIZER_TOOL_PATH' ) ) {
588 return EWWW_IMAGE_OPTIMIZER_TOOL_PATH;
589 }
590
591 return WP_CONTENT_DIR . '/ewww/';
592 }
593
594 /**
595 * Get the path to ShortPixel backup folder.
596 * It is the same for all sites on multisite (and yes, you'll get a surprise if your upload base dir -aka uploads/sites/12/- is not 2 folders deeper than theuploads folder).
597 *
598 * @since 1.8
599 * @access public
600 * @author Grégory Viguier
601 *
602 * @return string An absolute path.
603 */
604 public static function get_shortpixel_path() {
605 if ( defined( 'SHORTPIXEL_BACKUP_FOLDER' ) ) {
606 return trailingslashit( SHORTPIXEL_BACKUP_FOLDER );
607 }
608
609 $filesystem = imagify_get_filesystem();
610 $path = $filesystem->get_upload_basedir( true );
611 $path = is_main_site() ? $path : $filesystem->dir_path( $filesystem->dir_path( $path ) );
612
613 return $path . 'ShortpixelBackups/';
614 }
615
616
617 /** ----------------------------------------------------------------------------------------- */
618 /** REGEX PATTERNS ========================================================================== */
619 /** ----------------------------------------------------------------------------------------- */
620
621 /**
622 * Get the regex pattern used to match the paths to the media library.
623 * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
624 * Paths tested against these patterns are lower-cased.
625 *
626 * @since 1.8
627 * @access public
628 * @author Grégory Viguier
629 *
630 * @return string Something like `/wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/`.
631 */
632 public static function get_media_library_pattern() {
633 $filesystem = imagify_get_filesystem();
634 $uploads_dir = self::normalize_path_for_regex( $filesystem->get_main_upload_basedir() );
635
636 if ( ! is_multisite() ) {
637 if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
638 // In year/month folders.
639 return $uploads_dir . '\d{4}/\d{2}/';
640 }
641
642 // Not in year/month folders.
643 return $uploads_dir . '[^/]+$';
644 }
645
646 $pattern = $filesystem->get_multisite_uploads_subdir_pattern();
647
648 if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
649 // In year/month folders.
650 return $uploads_dir . '(' . $pattern . ')?\d{4}/\d{2}/';
651 }
652
653 // Not in year/month folders.
654 return $uploads_dir . '(' . $pattern . ')?[^/]+$';
655 }
656
657 /**
658 * Get the regex pattern used to match the paths to NextGen galleries on multisite.
659 * Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
660 * Paths tested against these patterns are lower-cased.
661 *
662 * @since 1.8
663 * @access public
664 * @author Grégory Viguier
665 *
666 * @return string|bool Something like `/wp-content/uploads/sites/\d+/nggallery/`. False if it can't be retrieved.
667 */
668 public static function get_ngg_galleries_multisite_pattern() {
669 $galleries_path = self::get_ngg_galleries_path(); // Something like `wp-content/uploads/sites/%BLOG_ID%/nggallery/`.
670
671 if ( ! $galleries_path ) {
672 return false;
673 }
674
675 $galleries_path = self::normalize_path_for_regex( $galleries_path );
676 $galleries_path = str_replace( [ '%blog_name%', '%blog_id%' ], [ '.+', '\d+' ], $galleries_path );
677
678 return $galleries_path;
679 }
680
681
682 /** ----------------------------------------------------------------------------------------- */
683 /** NORMALIZATION TOOLS ===================================================================== */
684 /** ----------------------------------------------------------------------------------------- */
685
686 /**
687 * Normalize a file path, aiming for path comparison.
688 * The path is normalized and case-lowered.
689 *
690 * @since 1.7
691 * @since 1.8 No trailing slash anymore, because it can be used for files.
692 * @access public
693 * @author Grégory Viguier
694 *
695 * @param string $file_path The file path.
696 * @return string The normalized file path.
697 */
698 public static function normalize_path_for_comparison( $file_path ) {
699 return strtolower( wp_normalize_path( $file_path ) );
700 }
701
702 /**
703 * Normalize a file path, aiming for use in a regex pattern.
704 * The path is normalized, case-lowered, and escaped.
705 *
706 * @since 1.8
707 * @access public
708 * @author Grégory Viguier
709 *
710 * @param string $file_path The file path.
711 * @return string The normalized file path.
712 */
713 public static function normalize_path_for_regex( $file_path ) {
714 return preg_quote( imagify_get_filesystem()->normalize_path_for_comparison( $file_path ), Imagify_Filesystem::PATTERN_DELIMITER );
715 }
716 }
717