: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @author Umesh Kumar <umesh@incsub.com>
* @copyright (c) 2017, Incsub (http://incsub.com)
use Smush\Core\Media\Media_Item_Cache;
use Smush\Core\Media\Media_Item_Stats;
use Smush\Core\Png2Jpg\Png2Jpg_Optimization;
if ( ! defined( 'WPINC' ) ) {
* We use this instead of WP_Object_Cache to avoid save data to memory cache (persistent caching).
* And to avoid it take memory space, we also reset the group cache each we get a new key,
* it means one group only has one key.
* It's useful when we want to save result a function.
* Leave group is null to set and get the value by unique key.
* It's useful to avoid checking something multiple times.
private static $temp_cache = array();
* Get WDEV_Logger instance.
public static function logger() {
if ( null === self::$logger ) {
// On MU site, move all log files into the log folder [wp-content/uploads/smush] on the main site.
if ( is_multisite() && ! is_main_site() ) {
switch_to_blog( get_main_site_id() );
$upload_dir = wp_get_upload_dir();
if ( false !== strpos( $upload_dir['basedir'], WP_CONTENT_DIR ) ) {
$log_dir = str_replace( trailingslashit( WP_CONTENT_DIR ), '', $upload_dir['basedir'] ) . '/smush';
self::$logger = WDEV_Logger::create(
'is_global_module' => true,
'integrations' => array(),
* @param string $file File path.
public static function clean_file_path( $file ) {
return str_replace( WP_CONTENT_DIR, '', $file );
* Get value from temporary cache.
* @param string $key Key name.
* @param string|null $group Group name.
* @param mixed $default Default value, default is NULL.
* if( null !== Helper::cache_get( 'your_key', 'your_group' ) ){
* // Do your something with temporary cache value.
* // Maybe setting it with Helper::cache_set.
* @return mixed The cached result.
public static function cache_get( $key, $group = null, $default = null ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
// Get cache for current blog.
if ( isset( self::$temp_cache[ $current_blog_id ] ) ) {
$temp_cache = self::$temp_cache[ $current_blog_id ];
* Add a filter to force cache.
* It might be helpful when we debug.
if ( apply_filters( 'wp_smush_force_cache', false, $key, $group, $temp_cache ) ) {
// Required for cache png2jpg()->can_be_converted() before resizing.
'png2jpg_can_be_converted',
// Required for cache unique file name of png2jpg()->convert_to_jpg().
if ( ! in_array( $group, $locked_groups, true ) ) {
if ( isset( $temp_cache[ $group ][ $key ] ) ) {
$value = $temp_cache[ $group ][ $key ];
} elseif ( isset( $temp_cache[ $group ] ) ) {
// Get a new key, reset group.
unset( $temp_cache[ $group ] );
} elseif ( isset( $temp_cache[ $key ] ) ) {
$value = $temp_cache[ $key ];
* Save value to temporary cache.
* @param string $key Key name.
* @param mixed $value Data to cache.
* @param string|null $group Group name.
* Note, we return the provided value to use it inside some methods.
* @return mixed Returns the provided value.
public static function cache_set( $key, $value, $group = null ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
// Reset group and set the value.
self::$temp_cache[ $current_blog_id ][ $group ] = array( $key => $value );
// Save value by unique key.
self::$temp_cache[ $current_blog_id ][ $key ] = $value;
* Clear cache by group or key.
* @param string $cache_key Group name or unique key name.
public static function cache_delete( $cache_key ) {
// Add current blog id to support MU site.
$current_blog_id = get_current_blog_id();
// Delete temp cache by cache key.
if ( isset( $cache_key, self::$temp_cache[ $current_blog_id ][ $cache_key ] ) ) {
unset( self::$temp_cache[ $current_blog_id ][ $cache_key ] );
* Get mime type for file.
* @since 3.1.0 Moved here as a helper function.
* @param string $path Image path.
public static function get_mime_type( $path ) {
// These mime functions only work on local files/streams.
if ( ! stream_is_local( $path ) ) {
if ( class_exists( 'finfo' ) ) {
$file_info = new finfo( FILEINFO_MIME_TYPE );
$mime = file_exists( $path ) ? $file_info->file( $path ) : '';
} elseif ( function_exists( 'mime_content_type' ) ) {
$mime = mime_content_type( $path );
* Filter the Posts object as per mime type.
* @param array $posts Object of Posts.
* @return array Array of post IDs.
public static function filter_by_mime( $posts ) {
foreach ( $posts as $post_k => $post ) {
if ( ! isset( $post->post_mime_type ) || ! in_array( $post->post_mime_type, Core::$mime_types, true ) ) {
unset( $posts[ $post_k ] );
$posts[ $post_k ] = $post->ID;
* Iterate over PNG->JPG Savings to return cummulative savings for an image
* @param string $attachment_id Attachment ID.
public static function get_pngjpg_savings( $attachment_id = '' ) {
$media_item = Media_Item_Cache::get_instance()->get( $attachment_id );
$png2jpg_optimization = new Png2Jpg_Optimization( $media_item );
$stats = $png2jpg_optimization->is_optimized()
? $png2jpg_optimization->get_stats() :
return $stats->to_array();
* Get the link to the media library page for the image.
* @param int $id Image ID.
* @param string $name Image file name.
* @param bool $src Return only src. Default - return link.
public static function get_image_media_link( $id, $name, $src = false ) {
$mode = get_user_option( 'media_library_mode' );
if ( 'grid' === $mode ) {
$link = admin_url( "upload.php?item=$id" );
$link = admin_url( "post.php?post=$id&action=edit" );
return "<a href='$link'>$name</a>";
* Returns current user name to be displayed
public static function get_user_name() {
$current_user = wp_get_current_user();
return ! empty( $current_user->first_name ) ? $current_user->first_name : $current_user->display_name;
* Allows to filter the error message sent to the user
* @param string $error Error message.
* @param string $attachment_id Attachment ID.
* @return mixed|null|string
public static function filter_error( $error = '', $attachment_id = '' ) {
* Replace the 500 server error with a more appropriate error message.
if ( false !== strpos( $error, '500 Internal Server Error' ) ) {
$error = esc_html__( "Couldn't process image due to bad headers. Try re-saving the image in an image editor, then upload it again.", 'wp-smushit' );
} elseif ( strpos( $error, 'timed out' ) ) {
$error = esc_html__( "Timeout error. You can increase the request timeout to make sure Smush has enough time to process larger files. `define('WP_SMUSH_TIMEOUT', 150);`", 'wp-smushit' );
* Used internally to modify the error message
return apply_filters( 'wp_smush_error', $error, $attachment_id );
* Format metadata from $_POST request.
* Post request in WordPress will convert all values
* to string. Make sure image height and width are int.
* This is required only when Async requests are used.
* See - https://wordpress.org/support/topic/smushit-overwrites-image-meta-crop-sizes-as-string-instead-of-int/
* @param array $meta Metadata of attachment.
public static function format_meta_from_post( $meta = array() ) {
// Do not continue in case meta is empty.
// If metadata is array proceed.
if ( is_array( $meta ) ) {
// Walk through each items and format.
array_walk_recursive( $meta, array( self::class, 'format_attachment_meta_item' ) );
* If current item is width or height, make sure it is int.
* @param mixed $value Meta item value.
* @param string $key Meta item key.
public static function format_attachment_meta_item( &$value, $key ) {
if ( 'height' === $key || 'width' === $key ) {
* Allows to format single item in meta.
* This filter will be used only for Async, post requests.
* @param mixed $value Meta item value.
* @param string $key Meta item key.
$value = apply_filters( 'wp_smush_format_attachment_meta_item', $value, $key );
* Check to see if file is animated.
* @since 3.0 Moved from class-resize.php
* @since 3.9.6 Add a new param $mime_type.
* @param string $file_path Image file path.
* @param int $id Attachment ID.
* @param false|string $mime_type Mime type.
public static function check_animated_status( $file_path, $id, $mime_type = false ) {
$media_item = Media_Item_Cache::get_instance()->get( $id );
return $media_item->is_animated();
public static function check_animated_file_contents( $file_path ) {
$filecontents = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
// There is no point in continuing after we find a 2nd frame.
$where1 = strpos( $filecontents, "\x00\x21\xF9\x04", $str_loc );
if ( false === $where1 ) {
$where2 = strpos( $filecontents, "\x00\x2C", $str_loc );
if ( false === $where2 ) {
if ( $where2 === $where1 + 8 ) {
* Verify the file size limit.
* @param int $attachment_id Attachment ID.
* Note: We only use this method to verify an image before smushing it,
* we still need to verify the file size of every thumbnail files while smushing them.
* @return bool|int Return the file size if the size limit is exceeded, otherwise return FALSE.
public static function size_limit_exceeded( $attachment_id ) {
$original_file_path = self::get_attached_file( $attachment_id, 'original' );
if ( ! file_exists( $original_file_path ) ) {
$original_file_path = self::get_attached_file( $attachment_id );
if ( ! file_exists( $original_file_path ) ) {
$max_file_size = WP_Smush::is_pro() ? WP_SMUSH_PREMIUM_MAX_BYTES : WP_SMUSH_MAX_BYTES;
$file_size = filesize( $original_file_path );
return $file_size > $max_file_size ? $file_size : false;
* @param string $original_file Original file.
* @return string File Path
public static function original_file( $original_file = '' ) {
$uploads = wp_get_upload_dir();
$upload_path = $uploads['basedir'];
return path_join( $upload_path, $original_file );
* Gets the WPMU DEV API key.