Files
medicalalert-web-reloaded/wp/wp-content/plugins/imagify/inc/classes/class-imagify-files-scan.php
Rachit Bhargava 5d0f0734d8 first commit
2023-07-21 17:12:10 -04:00

718 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
defined( 'ABSPATH' ) || die( 'Cheatin uh?' );
/**
* Class handling everything that is related to "custom folders optimization".
*
* @since 1.7
* @author Grégory Viguier
*/
class Imagify_Files_Scan {
/**
* Class version.
*
* @var string
* @since 1.7
* @author Grégory Viguier
*/
const VERSION = '1.1.1';
/**
* Get files (optimizable by Imagify) recursively from a specific folder.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $folder An absolute path to a folder.
* @return array|object An array of absolute paths. A WP_Error object on error.
*/
public static function get_files_from_folder( $folder ) {
$filesystem = imagify_get_filesystem();
// Formate and validate the folder path.
if ( ! is_string( $folder ) ) {
return new WP_Error( 'invalid_folder', __( 'Invalid folder.', 'imagify' ) );
}
$folder = realpath( $folder );
if ( ! $folder ) {
return new WP_Error( 'folder_not_exists', __( 'This folder does not exist.', 'imagify' ) );
}
if ( ! $filesystem->is_dir( $folder ) ) {
return new WP_Error( 'not_a_folder', __( 'This file is not a folder.', 'imagify' ) );
}
if ( self::is_path_forbidden( trailingslashit( $folder ) ) ) {
return new WP_Error( 'folder_forbidden', __( 'This folder is not allowed.', 'imagify' ) );
}
// Finally we made all our validations.
if ( $filesystem->is_site_root( $folder ) ) {
// For the site's root, we don't look in sub-folders.
$dir = new DirectoryIterator( $folder );
$dir = new Imagify_Files_Iterator( $dir, false );
$images = array();
foreach ( new IteratorIterator( $dir ) as $file ) {
$images[] = $file->getPathname();
}
return $images;
}
/**
* 4096 stands for FilesystemIterator::SKIP_DOTS, which was introduced in php 5.3.0.
* 8192 stands for FilesystemIterator::UNIX_PATHS, which was introduced in php 5.3.0.
*/
$dir = new RecursiveDirectoryIterator( $folder, 4096 | 8192 );
$dir = new Imagify_Files_Recursive_Iterator( $dir );
$images = new RecursiveIteratorIterator( $dir );
$images = array_keys( iterator_to_array( $images ) );
return $images;
}
/** ----------------------------------------------------------------------------------------- */
/** FORBIDDEN FOLDERS AND FILES ============================================================= */
/** ----------------------------------------------------------------------------------------- */
/**
* Tell if a path is autorized.
* When testing a folder, the path MUST have a trailing slash.
*
* @since 1.7.1
* @since 1.8 The path must have a trailing slash if for a folder.
* @access public
* @author Grégory Viguier
*
* @param string $file_path A file or folder absolute path.
* @return bool
*/
public static function is_path_autorized( $file_path ) {
return ! self::is_path_forbidden( $file_path );
}
/**
* Tell if a path is forbidden.
* When testing a folder, the path MUST have a trailing slash.
*
* @since 1.7
* @since 1.8 The path must have a trailing slash if for a folder.
* @access public
* @author Grégory Viguier
*
* @param string $file_path A file or folder absolute path.
* @return bool
*/
public static function is_path_forbidden( $file_path ) {
static $folders;
$filesystem = imagify_get_filesystem();
if ( self::is_filename_forbidden( $filesystem->file_name( $file_path ) ) ) {
return true;
}
if ( $filesystem->is_symlinked( $file_path ) ) {
// Files outside the site's folder are forbidden.
return true;
}
if ( ! isset( $folders ) ) {
$folders = self::get_forbidden_folders();
$folders = array_map( 'strtolower', $folders );
$folders = array_flip( $folders );
}
$file_path = self::normalize_path_for_comparison( $file_path );
if ( isset( $folders[ $file_path ] ) ) {
return true;
}
$delim = Imagify_Filesystem::PATTERN_DELIMITER;
foreach ( self::get_forbidden_folder_patterns() as $pattern ) {
if ( preg_match( $delim . '^' . $pattern . $delim, $file_path ) ) {
return true;
}
}
foreach ( $folders as $folder => $i ) {
if ( strpos( $file_path, $folder ) === 0 ) {
return true;
}
}
return false;
}
/**
* Get the list of folders where Imagify won't look for files to optimize.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array A list of absolute paths.
*/
public static function get_forbidden_folders() {
static $folders;
if ( isset( $folders ) ) {
return $folders;
}
$filesystem = imagify_get_filesystem();
$site_root = $filesystem->get_site_root();
$abspath = $filesystem->get_abspath();
$folders = array(
// Server.
$site_root . 'cgi-bin', // `cgi-bin`
// WordPress.
$abspath . 'wp-admin', // `wp-admin`
$abspath . WPINC, // `wp-includes`
WP_CONTENT_DIR . '/mu-plugins', // MU plugins.
WP_CONTENT_DIR . '/upgrade', // Upgrade.
// Plugins.
WP_CONTENT_DIR . '/bps-backup', // BulletProof Security.
self::get_ewww_tools_path(), // EWWW: /wp-content/ewww.
WP_CONTENT_DIR . '/ngg', // NextGen Gallery.
WP_CONTENT_DIR . '/ngg_styles', // NextGen Gallery.
WP_CONTENT_DIR . '/w3tc-config', // W3 Total Cache.
WP_CONTENT_DIR . '/wfcache', // WP Fastest Cache.
WP_CONTENT_DIR . '/wp-rocket-config', // WP Rocket.
Imagify_Custom_Folders::get_backup_dir_path(), // Imagify "Custom folders" backup: /imagify-backup.
IMAGIFY_PATH, // Imagify plugin: /wp-content/plugins/imagify.
self::get_shortpixel_path(), // ShortPixel: /wp-content/uploads/ShortpixelBackups.
);
if ( ! is_multisite() ) {
$uploads_dir = $filesystem->get_upload_basedir( true );
$ngg_galleries = self::get_ngg_galleries_path();
if ( $ngg_galleries ) {
$folders[] = $ngg_galleries; // NextGen Gallery: /wp-content/gallery.
}
$folders[] = $uploads_dir . 'formidable'; // Formidable Forms: /wp-content/uploads/formidable.
$folders[] = get_imagify_backup_dir_path( true ); // Imagify Media Library backup: /wp-content/uploads/backup.
$folders[] = self::get_wc_logs_path(); // WooCommerce Logs: /wp-content/uploads/wc-logs.
$folders[] = $uploads_dir . 'woocommerce_uploads'; // WooCommerce uploads: /wp-content/uploads/woocommerce_uploads.
}
$folders = array_map( array( $filesystem, 'normalize_dir_path' ), $folders );
/**
* Add folders to the list of forbidden ones.
*
* @since 1.7
* @author Grégory Viguier
*
* @param array $added_folders List of absolute paths.
* @param array $folders List of folders already forbidden.
*/
$added_folders = apply_filters( 'imagify_add_forbidden_folders', array(), $folders );
$added_folders = array_filter( (array) $added_folders );
$added_folders = array_filter( $added_folders, 'is_string' );
if ( ! $added_folders ) {
return $folders;
}
$added_folders = array_map( array( $filesystem, 'normalize_dir_path' ), $added_folders );
$folders = array_merge( $folders, $added_folders );
$folders = array_flip( array_flip( $folders ) );
return $folders;
}
/**
* Get the list of folder patterns where Imagify won't look for files to optimize. This is meant for paths that are dynamic.
* `^` will be prepended to each pattern (aka, the pattern must match an absolute path).
* Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
* Paths tested against these patterns are lower-cased.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array A list of regex patterns.
*/
public static function get_forbidden_folder_patterns() {
static $folders;
if ( isset( $folders ) ) {
return $folders;
}
$folders = array();
// Media Library: /wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/.
$folders[] = self::get_media_library_pattern();
if ( is_multisite() ) {
/**
* 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.
* Only exception we're aware of so far is NextGen Gallery, because it provides a clear pattern to use.
*/
$ngg_galleries = self::get_ngg_galleries_multisite_pattern();
if ( $ngg_galleries ) {
// NextGen Gallery: /wp\-content/uploads/sites/\d+/nggallery/.
$folders[] = $ngg_galleries;
}
}
/**
* Add folder patterns to the list of forbidden ones.
* Don't forget to use `Imagify_Files_Scan::normalize_path_for_regex( $path )`!
*
* @since 1.7
* @author Grégory Viguier
*
* @param array $added_folders List of patterns.
* @param array $folders List of patterns already forbidden.
*/
$added_folders = apply_filters( 'imagify_add_forbidden_folder_patterns', array(), $folders );
$added_folders = array_filter( (array) $added_folders );
$added_folders = array_filter( $added_folders, 'is_string' );
if ( ! $added_folders ) {
return $folders;
}
$folders = array_merge( $folders, $added_folders );
$folders = array_flip( array_flip( $folders ) );
return $folders;
}
/**
* Tell if a file/folder name is forbidden.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $file_name A file or folder name.
* @return bool
*/
public static function is_filename_forbidden( $file_name ) {
static $file_names;
if ( ! isset( $file_names ) ) {
$file_names = array_flip( self::get_forbidden_file_names() );
}
return isset( $file_names[ strtolower( $file_name ) ] );
}
/**
* Get the list of file names that Imagify won't optimize.
* It can contain folder names. Names are case-lowered.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array A list of file names
*/
public static function get_forbidden_file_names() {
static $file_names;
if ( isset( $file_names ) ) {
return $file_names;
}
$file_names = array(
'.',
'..',
'.DS_Store',
'.git',
'.svn',
'backup',
'backups',
'cache',
'lang',
'langs',
'languages',
'node_modules',
'Thumbs.db',
);
$file_names = array_map( 'strtolower', $file_names );
/**
* Add file names to the list of forbidden ones.
*
* @since 1.7
* @author Grégory Viguier
*
* @param array $added_file_names List of file names.
* @param array $file_names List of file names already forbidden.
*/
$added_file_names = apply_filters( 'imagify_add_forbidden_file_names', array(), $file_names );
if ( ! $added_file_names || ! is_array( $added_file_names ) ) {
return $file_names;
}
$added_file_names = array_filter( $added_file_names, 'is_string' );
$added_file_names = array_map( 'strtolower', $added_file_names );
$file_names = array_merge( $file_names, $added_file_names );
$file_names = array_flip( array_flip( $file_names ) );
return $file_names;
}
/** ----------------------------------------------------------------------------------------- */
/** PLACEHOLDERS ============================================================================ */
/** ----------------------------------------------------------------------------------------- */
/**
* Add a placeholder to a path.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $file_path An absolute path.
* @return string A "placeholdered" path.
*/
public static function add_placeholder( $file_path ) {
$file_path = wp_normalize_path( $file_path );
$locations = self::get_placeholder_paths();
foreach ( $locations as $placeholder => $location_path ) {
if ( strpos( $file_path, $location_path ) === 0 ) {
return preg_replace( '@^' . preg_quote( $location_path, '@' ) . '@', $placeholder, $file_path );
}
}
// Should not happen.
return $file_path;
}
/**
* Change a path with a placeholder into a real path or URL.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $file_path A path with a placeholder.
* @param string $type What to return: 'path' or 'url'.
* @return string An absolute path or a URL.
*/
public static function remove_placeholder( $file_path, $type = 'path' ) {
if ( 'path' === $type ) {
$locations = self::get_placeholder_paths();
} else {
$locations = self::get_placeholder_urls();
}
foreach ( $locations as $placeholder => $location_path ) {
if ( strpos( $file_path, $placeholder ) === 0 ) {
return preg_replace( '@^' . preg_quote( $placeholder, '@' ) . '@', $location_path, $file_path );
}
}
// Should not happen.
return $file_path;
}
/**
* Get array of pairs of placeholder => corresponding path.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_placeholder_paths() {
static $replacements;
if ( isset( $replacements ) ) {
return $replacements;
}
$filesystem = imagify_get_filesystem();
$replacements = array(
'{{PLUGINS}}/' => WP_PLUGIN_DIR,
'{{MU_PLUGINS}}/' => WPMU_PLUGIN_DIR,
'{{THEMES}}/' => WP_CONTENT_DIR . '/themes',
'{{UPLOADS}}/' => $filesystem->get_main_upload_basedir(),
'{{CONTENT}}/' => WP_CONTENT_DIR,
'{{ROOT}}/' => $filesystem->get_site_root(),
);
$replacements = array_map( array( $filesystem, 'normalize_dir_path' ), $replacements );
return $replacements;
}
/**
* Get array of pairs of placeholder => corresponding URL.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return array
*/
public static function get_placeholder_urls() {
static $replacements;
if ( isset( $replacements ) ) {
return $replacements;
}
$filesystem = imagify_get_filesystem();
$replacements = array(
'{{PLUGINS}}/' => plugins_url( '/' ),
'{{MU_PLUGINS}}/' => plugins_url( '/', WPMU_PLUGIN_DIR . '/.' ),
'{{THEMES}}/' => content_url( 'themes/' ),
'{{UPLOADS}}/' => $filesystem->get_main_upload_baseurl(),
'{{CONTENT}}/' => content_url( '/' ),
'{{ROOT}}/' => $filesystem->get_site_root_url(),
);
return $replacements;
}
/**
* A file_exists() for paths with a placeholder.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @param string $file_path The file path.
* @return bool
*/
public static function placeholder_path_exists( $file_path ) {
return imagify_get_filesystem()->is_readable( self::remove_placeholder( $file_path ) );
}
/** ----------------------------------------------------------------------------------------- */
/** PATHS =================================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the path to NextGen galleries on monosites.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return string|bool An absolute path. False if it can't be retrieved.
*/
public static function get_ngg_galleries_path() {
$galleries_path = get_site_option( 'ngg_options' );
if ( empty( $galleries_path['gallerypath'] ) ) {
return false;
}
$filesystem = imagify_get_filesystem();
$galleries_path = $filesystem->normalize_dir_path( $galleries_path['gallerypath'] );
$galleries_path = trim( $galleries_path, '/' ); // Something like `wp-content/gallery`.
$ngg_root = defined( 'NGG_GALLERY_ROOT_TYPE' ) ? NGG_GALLERY_ROOT_TYPE : 'site';
if ( $galleries_path && 'content' === $ngg_root ) {
$ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
$ngg_root = trim( $ngg_root, '/' ); // Something like `abs-path/to/wp-content`.
$exploded_root = explode( '/', $ngg_root );
$exploded_galleries = explode( '/', $galleries_path );
$first_gallery_dirname = reset( $exploded_galleries );
$last_root_dirname = end( $exploded_root );
if ( $last_root_dirname === $first_gallery_dirname ) {
array_shift( $exploded_galleries );
$galleries_path = implode( '/', $exploded_galleries );
}
}
if ( 'content' === $ngg_root ) {
$ngg_root = $filesystem->normalize_dir_path( WP_CONTENT_DIR );
} else {
$ngg_root = $filesystem->get_abspath();
}
if ( strpos( $galleries_path, $ngg_root ) !== 0 ) {
$galleries_path = $ngg_root . $galleries_path;
}
return $galleries_path . '/';
}
/**
* Get the path to WooCommerce logs on monosites.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return string An absolute path.
*/
public static function get_wc_logs_path() {
if ( defined( 'WC_LOG_DIR' ) ) {
return WC_LOG_DIR;
}
return imagify_get_filesystem()->get_upload_basedir( true ) . 'wc-logs/';
}
/**
* Get the path to EWWW optimization tools.
* It is the same for all sites on multisite.
*
* @since 1.7
* @access public
* @author Grégory Viguier
*
* @return string An absolute path.
*/
public static function get_ewww_tools_path() {
if ( defined( 'EWWW_IMAGE_OPTIMIZER_TOOL_PATH' ) ) {
return EWWW_IMAGE_OPTIMIZER_TOOL_PATH;
}
return WP_CONTENT_DIR . '/ewww/';
}
/**
* Get the path to ShortPixel backup folder.
* 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).
*
* @since 1.8
* @access public
* @author Grégory Viguier
*
* @return string An absolute path.
*/
public static function get_shortpixel_path() {
if ( defined( 'SHORTPIXEL_BACKUP_FOLDER' ) ) {
return trailingslashit( SHORTPIXEL_BACKUP_FOLDER );
}
$filesystem = imagify_get_filesystem();
$path = $filesystem->get_upload_basedir( true );
$path = is_main_site() ? $path : $filesystem->dir_path( $filesystem->dir_path( $path ) );
return $path . 'ShortpixelBackups/';
}
/** ----------------------------------------------------------------------------------------- */
/** REGEX PATTERNS ========================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Get the regex pattern used to match the paths to the media library.
* Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
* Paths tested against these patterns are lower-cased.
*
* @since 1.8
* @access public
* @author Grégory Viguier
*
* @return string Something like `/wp\-content/uploads/(sites/\d+/)?\d{4}/\d{2}/`.
*/
public static function get_media_library_pattern() {
$filesystem = imagify_get_filesystem();
$uploads_dir = self::normalize_path_for_regex( $filesystem->get_main_upload_basedir() );
if ( ! is_multisite() ) {
if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
// In year/month folders.
return $uploads_dir . '\d{4}/\d{2}/';
}
// Not in year/month folders.
return $uploads_dir . '[^/]+$';
}
$pattern = $filesystem->get_multisite_uploads_subdir_pattern();
if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
// In year/month folders.
return $uploads_dir . '(' . $pattern . ')?\d{4}/\d{2}/';
}
// Not in year/month folders.
return $uploads_dir . '(' . $pattern . ')?[^/]+$';
}
/**
* Get the regex pattern used to match the paths to NextGen galleries on multisite.
* Pattern delimiter is `Imagify_Filesystem::PATTERN_DELIMITER`.
* Paths tested against these patterns are lower-cased.
*
* @since 1.8
* @access public
* @author Grégory Viguier
*
* @return string|bool Something like `/wp-content/uploads/sites/\d+/nggallery/`. False if it can't be retrieved.
*/
public static function get_ngg_galleries_multisite_pattern() {
$galleries_path = self::get_ngg_galleries_path(); // Something like `wp-content/uploads/sites/%BLOG_ID%/nggallery/`.
if ( ! $galleries_path ) {
return false;
}
$galleries_path = self::normalize_path_for_regex( $galleries_path );
$galleries_path = str_replace( array( '%blog_name%', '%blog_id%' ), array( '.+', '\d+' ), $galleries_path );
return $galleries_path;
}
/** ----------------------------------------------------------------------------------------- */
/** NORMALIZATION TOOLS ===================================================================== */
/** ----------------------------------------------------------------------------------------- */
/**
* Normalize a file path, aiming for path comparison.
* The path is normalized and case-lowered.
*
* @since 1.7
* @since 1.8 No trailing slash anymore, because it can be used for files.
* @access public
* @author Grégory Viguier
*
* @param string $file_path The file path.
* @return string The normalized file path.
*/
public static function normalize_path_for_comparison( $file_path ) {
return strtolower( wp_normalize_path( $file_path ) );
}
/**
* Normalize a file path, aiming for use in a regex pattern.
* The path is normalized, case-lowered, and escaped.
*
* @since 1.8
* @access public
* @author Grégory Viguier
*
* @param string $file_path The file path.
* @return string The normalized file path.
*/
public static function normalize_path_for_regex( $file_path ) {
return preg_quote( imagify_get_filesystem()->normalize_path_for_comparison( $file_path ), Imagify_Filesystem::PATTERN_DELIMITER );
}
}