home_url = \trailingslashit( \get_site_url() );
$this->relative_home_url = \preg_replace( '/https?:/', '', $this->home_url );
$this->home_domain = $this->parse_url( $this->home_url, PHP_URL_HOST );
if ( 'EWWW' === __NAMESPACE__ ) {
$this->content_url = \content_url( 'ewww/' );
$this->content_dir = $this->set_content_dir( '/ewww/' );
$this->version = EWWW_IMAGE_OPTIMIZER_VERSION;
} elseif ( 'EasyIO' === __NAMESPACE__ ) {
$this->content_url = \content_url( 'easyio/' );
$this->content_dir = $this->set_content_dir( '/easyio/' );
$this->version = EASYIO_VERSION;
$this->prefix = 'easyio_';
}
if ( ! $debug ) {
return;
}
// Check to see if we're in the wp-admin to enable debugging temporarily.
// Done after the above, because this means we are constructing the Plugin() object
// which is the very first object initialized.
if (
! self::$temp_debug &&
is_admin() &&
! wp_doing_ajax() &&
! $this->get_option( $this->prefix . 'debug' )
) {
self::$temp_debug = true;
}
$this->debug_message( '' . __METHOD__ . '()' );
$this->debug_message( "plugin (resource) content_url: $this->content_url" );
$this->debug_message( "plugin (resource) content_dir: $this->content_dir" );
$this->debug_message( "home url: $this->home_url" );
$this->debug_message( "relative home url: $this->relative_home_url" );
$this->debug_message( "home domain: $this->home_domain" );
}
/**
* Finds a writable location to store plugin resources.
*
* Checks to see if the wp-content/ directory is writable, and uses the upload dir
* as fall-back. If neither location works, the original wp-content/ folder will be
* used, and other functions will need to make sure the resource folder is writable.
*
* @param string $sub_folder The sub-folder to use for plugin resources, with slashes on both ends.
* @return string The full path to a writable plugin resource folder.
*/
public function set_content_dir( $sub_folder ) {
if (
\defined( 'EWWWIO_CONTENT_DIR' ) &&
\trailingslashit( WP_CONTENT_DIR ) . \trailingslashit( 'ewww' ) !== EWWWIO_CONTENT_DIR
) {
$content_dir = EWWWIO_CONTENT_DIR;
$this->content_url = \str_replace( WP_CONTENT_DIR, WP_CONTENT_URL, $content_dir );
return $content_dir;
}
$content_dir = WP_CONTENT_DIR . $sub_folder;
if ( ! \is_writable( WP_CONTENT_DIR ) || ! empty( $_ENV['PANTHEON_ENVIRONMENT'] ) ) {
$upload_dir = \wp_get_upload_dir();
if ( false === \strpos( $upload_dir['basedir'], '://' ) && \is_writable( $upload_dir['basedir'] ) ) {
$content_dir = $upload_dir['basedir'] . $sub_folder;
// Also need to update the corresponding URL.
$this->content_url = $upload_dir['baseurl'] . $sub_folder;
}
}
return $content_dir;
}
/**
* Get the path to the current debug log, if one exists. Otherwise, generate a new filename.
*
* @return string The full path to the debug log.
*/
public function debug_log_path() {
if ( is_dir( $this->content_dir ) ) {
$potential_logs = \scandir( $this->content_dir );
if ( $this->is_iterable( $potential_logs ) ) {
foreach ( $potential_logs as $potential_log ) {
if ( $this->str_ends_with( $potential_log, '.log' ) && false !== strpos( $potential_log, strtolower( __NAMESPACE__ ) . '-debug-' ) && is_file( $this->content_dir . $potential_log ) ) {
return $this->content_dir . $potential_log;
}
}
}
}
return $this->content_dir . strtolower( __NAMESPACE__ ) . '-debug-' . uniqid() . '.log';
}
/**
* Saves the in-memory debug log to a logfile in the plugin folder.
*/
public function debug_log() {
$debug_log = $this->debug_log_path();
if ( ! \is_dir( $this->content_dir ) && \is_writable( WP_CONTENT_DIR ) ) {
\wp_mkdir_p( $this->content_dir );
}
if (
! empty( self::$debug_data ) &&
empty( self::$temp_debug ) &&
$this->get_option( $this->prefix . 'debug' ) &&
\is_dir( $this->content_dir ) &&
\is_writable( $this->content_dir )
) {
$memory_limit = $this->memory_limit();
\clearstatcache();
$timestamp = \gmdate( 'Y-m-d H:i:s' ) . "\n";
if ( ! \file_exists( $debug_log ) ) {
\touch( $debug_log );
} else {
if ( \filesize( $debug_log ) + 4000000 + \memory_get_usage( true ) > $memory_limit ) {
\unlink( $debug_log );
\clearstatcache();
$debug_log = $this->debug_log_path();
\touch( $debug_log );
}
}
if ( \filesize( $debug_log ) + \strlen( self::$debug_data ) + 4000000 + \memory_get_usage( true ) <= $memory_limit && \is_writable( $debug_log ) ) {
self::$debug_data = \str_replace( '
', "\n", self::$debug_data );
\file_put_contents( $debug_log, $timestamp . self::$debug_data, FILE_APPEND );
}
}
self::$debug_data = '';
}
/**
* Adds information to the in-memory debug log.
*
* @param string $message Debug information to add to the log.
*/
public function debug_message( $message ) {
if ( ! \is_string( $message ) && ! \is_int( $message ) && ! \is_float( $message ) ) {
return;
}
if ( \defined( 'EIO_PHPUNIT' ) && EIO_PHPUNIT ) {
if (
! empty( $_SERVER['argv'] ) &&
( \in_array( '--debug', $_SERVER['argv'], true ) || \in_array( '--verbose', $_SERVER['argv'], true ) )
) {
$message = \str_replace( '
', "\n", $message );
$message = \str_replace( '', '+', $message );
$message = \str_replace( '', '+', $message );
echo \esc_html( $message ) . "\n";
}
}
$message = "$message";
if ( \defined( 'WP_CLI' ) && WP_CLI ) {
\WP_CLI::debug( $message );
return;
}
if ( self::$temp_debug || $this->get_option( $this->prefix . 'debug' ) ) {
$memory_limit = $this->memory_limit();
if ( \strlen( $message ) + 4000000 + \memory_get_usage( true ) <= $memory_limit ) {
$message = \str_replace( "\n\n\n", '
', $message );
$message = \str_replace( "\n\n", '
', $message );
$message = \str_replace( "\n", '
', $message );
self::$debug_data .= "$message
";
} else {
self::$debug_data = "not logging message, memory limit is $memory_limit";
}
}
}
/**
* Clears temp debugging mode and flushes the debug data if needed.
*/
public function temp_debug_end() {
if ( ! $this->get_option( $this->prefix . 'debug' ) ) {
self::$debug_data = '';
}
self::$temp_debug = false;
}
/**
* Escape any spaces in the filename.
*
* @param string $path The path to a binary file.
* @return string The path with spaces escaped.
*/
public function escapeshellcmd( $path ) {
return ( \preg_replace( '/ /', '\ ', $path ) );
}
/**
* Replacement for escapeshellarg() that won't kill non-ASCII characters.
*
* @param string $arg A value to sanitize/escape for commmand-line usage.
* @return string The value after being escaped.
*/
public function escapeshellarg( $arg ) {
if ( PHP_OS === 'WINNT' ) {
$safe_arg = \str_replace( '%', ' ', $arg );
$safe_arg = \str_replace( '!', ' ', $safe_arg );
$safe_arg = \str_replace( '"', ' ', $safe_arg );
return '"' . $safe_arg . '"';
}
$safe_arg = "'" . \str_replace( "'", "'\\''", $arg ) . "'";
return $safe_arg;
}
/**
* Checks if a function is disabled or does not exist.
*
* @param string $function_name The name of a function to test.
* @param bool $debug Whether to output debugging.
* @return bool True if the function is available, False if not.
*/
public function function_exists( $function_name, $debug = false ) {
if ( \function_exists( '\ini_get' ) ) {
$disabled = @\ini_get( 'disable_functions' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( $debug ) {
$this->debug_message( "disable_functions: $disabled" );
}
}
if ( \extension_loaded( 'suhosin' ) && \function_exists( '\ini_get' ) ) {
$suhosin_disabled = @\ini_get( 'suhosin.executor.func.blacklist' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
if ( $debug ) {
$this->debug_message( "suhosin_blacklist: $suhosin_disabled" );
}
if ( ! empty( $suhosin_disabled ) ) {
$suhosin_disabled = \explode( ',', $suhosin_disabled );
$suhosin_disabled = \array_map( 'trim', $suhosin_disabled );
$suhosin_disabled = \array_map( 'strtolower', $suhosin_disabled );
if ( \function_exists( $function_name ) && ! \in_array( \trim( $function_name, '\\' ), $suhosin_disabled, true ) ) {
return true;
}
return false;
}
}
return \function_exists( $function_name );
}
/**
* Check for GD support of both PNG and JPG.
*
* @return string|bool The version of GD if full support is detected, false otherwise.
*/
public function gd_support() {
$this->debug_message( '' . __METHOD__ . '()' );
if ( ! $this->gd_info && $this->function_exists( '\gd_info' ) ) {
$this->gd_info = \gd_info();
$this->debug_message( 'GD found, supports:' );
if ( $this->is_iterable( $this->gd_info ) ) {
foreach ( $this->gd_info as $supports => $supported ) {
$this->debug_message( "$supports: $supported" );
}
if ( ( ! empty( $this->gd_info['JPEG Support'] ) || ! empty( $this->gd_info['JPG Support'] ) ) && ! empty( $this->gd_info['PNG Support'] ) ) {
$this->gd_support = ! empty( $this->gd_info['GD Version'] ) ? $this->gd_info['GD Version'] : '1';
}
}
}
return $this->gd_support;
}
/**
* Check for GMagick support of both PNG and JPG.
*
* @return bool True if full Gmagick support is detected.
*/
public function gmagick_support() {
$this->debug_message( '' . __METHOD__ . '()' );
if ( ! $this->gmagick_info && \extension_loaded( 'gmagick' ) && \class_exists( '\Gmagick' ) ) {
$gmagick = new \Gmagick();
$this->gmagick_info = $gmagick->queryFormats();
$this->debug_message( implode( ',', $this->gmagick_info ) );
if ( \in_array( 'PNG', $this->gmagick_info, true ) && \in_array( 'JPG', $this->gmagick_info, true ) ) {
$this->gmagick_support = true;
} else {
$this->debug_message( 'gmagick found, but PNG or JPG not supported' );
}
}
return $this->gmagick_support;
}
/**
* Check for IMagick support of both PNG and JPG.
*
* @return bool True if full Imagick support is detected.
*/
public function imagick_support() {
$this->debug_message( '' . __METHOD__ . '()' );
if ( ! $this->imagick_info && \extension_loaded( 'imagick' ) && \class_exists( '\Imagick' ) ) {
$imagick = new \Imagick();
$this->imagick_info = $imagick->queryFormats();
$this->debug_message( \implode( ',', $this->imagick_info ) );
if ( \in_array( 'PNG', $this->imagick_info, true ) && \in_array( 'JPG', $this->imagick_info, true ) ) {
$this->imagick_support = true;
} else {
$this->debug_message( 'imagick found, but PNG or JPG not supported' );
}
}
return $this->imagick_support;
}
/**
* Check for GD support of WebP format.
*
* @return bool True if proper WebP support is detected.
*/
public function gd_supports_webp() {
$this->debug_message( '' . __METHOD__ . '()' );
if ( ! $this->gd_supports_webp ) {
$gd_version = $this->gd_support();
if ( $gd_version ) {
if (
\function_exists( '\imagewebp' ) &&
\function_exists( '\imagepalettetotruecolor' ) &&
\function_exists( '\imageistruecolor' ) &&
\function_exists( '\imagealphablending' ) &&
\function_exists( '\imagesavealpha' )
) {
if ( \version_compare( $gd_version, '2.2.5', '>=' ) ) {
$this->debug_message( 'yes it does' );
$this->gd_supports_webp = true;
}
}
}
if ( ! $this->gd_supports_webp ) {
if ( ! \function_exists( '\imagewebp' ) ) {
$this->debug_message( 'imagewebp() missing' );
} elseif ( ! \function_exists( '\imagepalettetotruecolor' ) ) {
$this->debug_message( 'imagepalettetotruecolor() missing' );
} elseif ( \function_exists( '\imageistruecolor' ) ) {
$this->debug_message( 'imageistruecolor() missing' );
} elseif ( \function_exists( '\imagealphablending' ) ) {
$this->debug_message( 'imagealphablending() missing' );
} elseif ( \function_exists( '\imagesavealpha' ) ) {
$this->debug_message( 'imagesavealpha() missing' );
} elseif ( $gd_version ) {
$this->debug_message( "version: $gd_version" );
}
$this->debug_message( 'sorry nope' );
}
}
return $this->gd_supports_webp;
}
/**
* Check for Imagick support of WebP.
*
* @return bool True if WebP support is detected.
*/
public function imagick_supports_webp() {
$this->debug_message( '' . __METHOD__ . '()' );
if ( ! $this->imagick_supports_webp ) {
if ( $this->imagick_support() ) {
if ( \in_array( 'WEBP', $this->imagick_info, true ) ) {
$this->debug_message( 'yes it does' );
$this->imagick_supports_webp = true;
}
}
if ( ! $this->imagick_supports_webp ) {
$this->debug_message( 'sorry nope' );
}
}
return $this->imagick_supports_webp;
}
/**
* Checks if the S3 Uploads plugin is installed and active.
*
* @return bool True if it is fully active and rewriting/offloading media, false otherwise.
*/
public function s3_uploads_enabled() {
// For version 3.x.
if ( \class_exists( '\S3_Uploads\Plugin', false ) && \function_exists( '\S3_Uploads\enabled' ) && \S3_Uploads\enabled() ) {
return true;
}
// Pre version 3.
if ( \class_exists( '\S3_Uploads', false ) && \function_exists( '\s3_uploads_enabled' ) && \s3_uploads_enabled() ) {
return true;
}
return false;
}
/**
* Checks if Easy IO is active in Perfect Images plugin.
*
* @return bool True if Easy IO is enabled via PI, false otherwise.
*/
public function perfect_images_easyio_domain() {
if ( class_exists( '\Meow_WR2X_Core' ) ) {
global $wr2x_core;
if ( is_object( $wr2x_core ) && method_exists( $wr2x_core, 'get_option' ) ) {
if ( ! empty( $wr2x_core->get_option( 'easyio_domain' ) ) ) {
return $wr2x_core->get_option( 'easyio_domain' );
}
}
}
return false;
}
/**
* Sanitize the folders/patterns to exclude from optimization.
*
* @param string $input A list of filesystem paths, from a textarea.
* @return array The sanitized list of paths/patterns to exclude.
*/
public function exclude_paths_sanitize( $input ) {
$this->debug_message( '' . __METHOD__ . '()' );
if ( empty( $input ) ) {
return '';
}
$path_array = array();
if ( \is_array( $input ) ) {
$paths = $input;
} elseif ( \is_string( $input ) ) {
$paths = \explode( "\n", $input );
}
if ( $this->is_iterable( $paths ) ) {
foreach ( $paths as $path ) {
$this->debug_message( "validating path exclusion: $path" );
$path = \trim( \sanitize_text_field( $path ), '*' );
if ( ! empty( $path ) ) {
$path_array[] = $path;
}
}
}
return $path_array;
}
/**
* Retrieve option: use 'site' setting if plugin is network activated, otherwise use 'blog' setting.
*
* Retrieves multi-site and single-site options as appropriate as well as allowing overrides with
* same-named constant. Overrides are only available for integer and boolean options.
*
* @param string $option_name The name of the option to retrieve.
* @param mixed $default_value The default to use if not found/set, defaults to false, but not currently used.
* @param bool $single Use single-site setting regardless of multisite activation. Default is off/false.
* @return mixed The value of the option.
*/
public function get_option( $option_name, $default_value = false, $single = false ) {
$constant_name = \strtoupper( $option_name );
if ( \defined( $constant_name ) && ( \is_int( \constant( $constant_name ) ) || \is_bool( \constant( $constant_name ) ) ) ) {
return \constant( $constant_name );
}
if ( 'ewww_image_optimizer_cloud_key' === $option_name && \defined( $constant_name ) ) {
$option_value = \constant( $constant_name );
if ( \is_string( $option_value ) && ! empty( $option_value ) ) {
return \trim( $option_value );
}
}
if (
(
'ewww_image_optimizer_exclude_paths' === $option_name ||
'exactdn_exclude' === $option_name ||
'easyio_ll_exclude' === $option_name ||
'ewww_image_optimizer_ll_exclude' === $option_name ||
'ewww_image_optimizer_webp_rewrite_exclude' === $option_name
)
&& \defined( $constant_name )
) {
return $this->exclude_paths_sanitize( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_ll_all_things' === $option_name && \defined( $constant_name ) ) {
return \sanitize_text_field( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_aux_paths' === $option_name && \defined( $constant_name ) ) {
return \ewww_image_optimizer_aux_paths_sanitize( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_webp_paths' === $option_name && \defined( $constant_name ) ) {
return \ewww_image_optimizer_webp_paths_sanitize( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_disable_resizes' === $option_name && \defined( $constant_name ) ) {
return \ewww_image_optimizer_disable_resizes_sanitize( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_disable_resizes_opt' === $option_name && \defined( $constant_name ) ) {
return \ewww_image_optimizer_disable_resizes_sanitize( \constant( $constant_name ) );
}
if ( 'ewww_image_optimizer_jpg_background' === $option_name && \defined( $constant_name ) ) {
return \ewww_image_optimizer_jpg_background( \constant( $constant_name ) );
}
// NOTE: For Easy IO, we bail here, because we don't have a network settings page AND because there's no 'allow_multisite_override' option,
// which would slow things down due to lack of auto-loading. If/when we add those things, then we can unlock the multi-site logic below.
if ( 'EasyIO' === __NAMESPACE__ ) {
return \get_option( $option_name );
}
if ( ! \function_exists( 'is_plugin_active_for_network' ) && \is_multisite() ) {
// Need to include the plugin library for the is_plugin_active function.
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if (
! $single &&
\is_multisite() &&
\defined( \strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) &&
\is_plugin_active_for_network( \constant( \strtoupper( $this->prefix ) . 'PLUGIN_FILE_REL' ) ) &&
! \get_site_option( $this->prefix . 'allow_multisite_override' )
) {
$option_value = \get_site_option( $option_name );
} else {
$option_value = \get_option( $option_name );
}
return $option_value;
}
/**
* Implode a multi-dimensional array without throwing errors. Arguments can be reverse order, same as implode().
*
* @param string $delimiter The character to put between the array items (the glue).
* @param array $data The array to output with the glue.
* @return string The array values, separated by the delimiter.
*/
public function implode( $delimiter, $data = '' ) {
if ( \is_array( $delimiter ) ) {
$temp_data = $delimiter;
$delimiter = $data;
$data = $temp_data;
}
if ( \is_array( $delimiter ) ) {
return '';
}
$output = '';
foreach ( $data as $value ) {
if ( \is_string( $value ) || \is_numeric( $value ) ) {
$output .= $value . $delimiter;
} elseif ( \is_bool( $value ) ) {
$output .= ( $value ? 'true' : 'false' ) . $delimiter;
} elseif ( \is_array( $value ) ) {
$output .= 'Array,';
}
}
return \rtrim( $output, ',' );
}
/**
* Checks to see if the current page being output is an AMP page.
*
* @return bool True for an AMP endpoint, false otherwise.
*/
public function is_amp() {
// Just return false if we can't properly check yet.
if ( ! \did_action( 'parse_request' ) ) {
return false;
}
if ( ! \did_action( 'parse_query' ) ) {
return false;
}
if ( ! \did_action( 'wp' ) ) {
return false;
}
if ( \function_exists( '\amp_is_request' ) && \amp_is_request() ) {
return true;
}
if ( \function_exists( '\is_amp_endpoint' ) && \is_amp_endpoint() ) {
return true;
}
if ( \function_exists( '\ampforwp_is_amp_endpoint' ) && \ampforwp_is_amp_endpoint() ) {
return true;
}
return false;
}
/**
* Make sure an array/object can be parsed by a foreach().
*
* @param mixed $value A variable to test for iteration ability.
* @return bool True if the variable is iterable and not empty.
*/
public function is_iterable( $value ) {
return ! empty( $value ) && is_iterable( $value );
}
/**
* Checks to see if the current buffer/output is a JSON-encoded string.
*
* Specifically, we are looking for JSON objects/strings, not just ANY JSON value.
* Thus, the check is rather "loose", only looking for {} or [] at the start/end.
*
* @param string $buffer The content to check for JSON.
* @return bool True for JSON, false for everything else.
*/
public function is_json( $buffer ) {
if ( '{' === \substr( $buffer, 0, 1 ) && '}' === \substr( $buffer, -1 ) ) {
return true;
}
if ( '[' === \substr( $buffer, 0, 1 ) && ']' === \substr( $buffer, -1 ) ) {
return true;
}
return false;
}
/**
* Make sure this is really and truly a "front-end request", excluding page builders and such.
* NOTE: this is not currently used anywhere, each module has it's own list.
*
* @return bool True for front-end requests, false for admin/builder requests.
*/
public function is_frontend() {
if ( \is_admin() ) {
return false;
}
$uri = \add_query_arg( '', '' );
if (
\strpos( $uri, 'cornerstone=' ) !== false ||
\strpos( $uri, 'cornerstone-endpoint' ) !== false ||
\strpos( $uri, 'ct_builder=' ) !== false ||
\did_action( 'cornerstone_boot_app' ) || \did_action( 'cs_before_preview_frame' ) ||
'/print/' === substr( $uri, -7 ) ||
\strpos( $uri, 'elementor-preview=' ) !== false ||
\strpos( $uri, 'et_fb=' ) !== false ||
\strpos( $uri, 'is-editor-iframe=' ) !== false ||
\strpos( $uri, 'vc_editable=' ) !== false ||
\strpos( $uri, 'tatsu=' ) !== false ||
( ! empty( $_POST['action'] ) && 'tatsu_get_concepts' === \sanitize_text_field( \wp_unslash( $_POST['action'] ) ) ) || // phpcs:ignore WordPress.Security.NonceVerification
\strpos( $uri, 'wp-login.php' ) !== false ||
\is_embed() ||
\is_feed() ||
\is_preview() ||
\is_customize_preview() ||
( defined( 'REST_REQUEST' ) && REST_REQUEST )
) {
return false;
}
return true;
}
/**
* Checks if the image URL points to a lazy load placeholder.
*
* @param string $image The image URL (or an image element).
* @return bool True if it matches a known placeholder pattern, false otherwise.
*/
public function is_lazy_placeholder( $image ) {
$this->debug_message( '' . __METHOD__ . '()' );
if (
\strpos( $image, 'base64,R0lGOD' ) ||
\strpos( $image, 'lazy-load/images/1x1' ) ||
\strpos( $image, '/assets/images/lazy' ) ||
\strpos( $image, '/assets/images/dummy.png' ) ||
\strpos( $image, '/assets/images/transparent.png' ) ||
\strpos( $image, '/lazy/placeholder' )
) {
$this->debug_message( 'lazy load placeholder' );
return true;
}
return false;
}
/**
* Check if file exists, and that it is local rather than using a protocol like http:// or phar://
*
* @param string $file The path of the file to check.
* @return bool True if the file exists and is local, false otherwise.
*/
public function is_file( $file ) {
if ( false !== \strpos( $file, '://' ) ) {
return false;
}
if ( false !== \strpos( $file, 'phar://' ) ) {
return false;
}
$file = \realpath( $file );
$wp_dir = \realpath( ABSPATH );
$upload_dir = \wp_get_upload_dir();
$upload_dir = \realpath( $upload_dir['basedir'] );
$content_dir = \realpath( WP_CONTENT_DIR );
if ( empty( $content_dir ) ) {
$content_dir = $wp_dir;
}
if ( empty( $upload_dir ) ) {
$upload_dir = $content_dir;
}
$plugin_dir = \realpath( \constant( \strtoupper( $this->prefix ) . 'PLUGIN_PATH' ) );
if (
false === \strpos( $file, $upload_dir ) &&
false === \strpos( $file, $content_dir ) &&
false === \strpos( $file, $wp_dir ) &&
false === \strpos( $file, $plugin_dir )
) {
return false;
}
return \is_file( $file );
}
/**
* Check if a file/directory is readable.
*
* @param string $file The path to check.
* @return bool True if it is, false if it ain't.
*/
public function is_readable( $file ) {
$this->get_filesystem();
return $this->filesystem->is_readable( $file );
}
/**
* Check filesize, and prevent errors by ensuring file exists, and that the cache has been cleared.
*
* @param string $file The name of the file.
* @return int The size of the file or zero.
*/
public function filesize( $file ) {
$file = \realpath( $file );
if ( $this->is_file( $file ) ) {
$this->get_filesystem();
// Flush the cache for filesize.
\clearstatcache();
// Find out the size of the new PNG file.
return $this->filesystem->size( $file );
} else {
return 0;
}
}
/**
* Check if file is in an approved location and remove it.
*
* @param string $file The path of the file to check.
* @param string $dir The path of the folder constraint. Optional.
* @return bool True if the file was removed, false otherwise.
*/
public function delete_file( $file, $dir = '' ) {
$file = \realpath( $file );
if ( ! empty( $dir ) ) {
return \wp_delete_file_from_directory( $file, $dir );
}
$wp_dir = \realpath( ABSPATH );
$upload_dir = \wp_get_upload_dir();
$upload_dir = \realpath( $upload_dir['basedir'] );
$content_dir = \realpath( WP_CONTENT_DIR );
if ( false !== \strpos( $file, $upload_dir ) ) {
return \wp_delete_file_from_directory( $file, $upload_dir );
}
if ( false !== \strpos( $file, $content_dir ) ) {
return \wp_delete_file_from_directory( $file, $content_dir );
}
if ( false !== \strpos( $file, $wp_dir ) ) {
return \wp_delete_file_from_directory( $file, $wp_dir );
}
return false;
}
/**
* Setup the filesystem class.
*/
public function get_filesystem() {
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
if ( ! defined( 'FS_CHMOD_DIR' ) ) {
\define( 'FS_CHMOD_DIR', ( \fileperms( ABSPATH ) & 0777 | 0755 ) );
}
if ( ! defined( 'FS_CHMOD_FILE' ) ) {
\define( 'FS_CHMOD_FILE', ( \fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
}
if ( ! \is_object( $this->filesystem ) ) {
$this->filesystem = new \WP_Filesystem_Direct( '' );
}
}
/**
* Check the mimetype of the given file with magic mime strings/patterns.
*
* @param string $path The absolute path to the file.
* @param string $category The type of file we are checking. Accepts 'i' for
* images/pdfs or 'b' for binary.
* @return bool|string A valid mime-type or false.
*/
public function mimetype( $path, $category ) {
$this->debug_message( '' . __METHOD__ . '()' );
$this->debug_message( "testing mimetype: $path" );
$type = false;
// For S3 images/files, don't attempt to read the file, just use the quick (filename) mime check.
if ( 'i' === $category && $this->stream_wrapped( $path ) ) {
return $this->quick_mimetype( $path );
}
$path = \realpath( $path );
if ( ! $this->is_file( $path ) ) {
$this->debug_message( "$path is not a file, or out of bounds" );
return $type;
}
if ( ! \is_readable( $path ) ) {
$this->debug_message( "$path is not readable" );
return $type;
}
if ( 'i' === $category ) {
$file_handle = \fopen( $path, 'rb' );
$file_contents = \fread( $file_handle, 4096 );
if ( $file_contents ) {
// Read first 12 bytes, which equates to 24 hex characters.
$magic = \bin2hex( \substr( $file_contents, 0, 12 ) );
$this->debug_message( $magic );
if ( 0 === \strpos( $magic, '52494646' ) && 16 === \strpos( $magic, '57454250' ) ) {
$type = 'image/webp';
$this->debug_message( "ewwwio type: $type" );
return $type;
}
if ( 'ffd8ff' === \substr( $magic, 0, 6 ) ) {
$type = 'image/jpeg';
$this->debug_message( "ewwwio type: $type" );
return $type;
}
if ( '89504e470d0a1a0a' === \substr( $magic, 0, 16 ) ) {
$type = 'image/png';
$this->debug_message( "ewwwio type: $type" );
return $type;
}
if ( '474946383761' === \substr( $magic, 0, 12 ) || '474946383961' === \substr( $magic, 0, 12 ) ) {
$type = 'image/gif';
$this->debug_message( "ewwwio type: $type" );
return $type;
}
if ( '25504446' === \substr( $magic, 0, 8 ) ) {
$type = 'application/pdf';
$this->debug_message( "ewwwio type: $type" );
return $type;
}
if ( \preg_match( '/