: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace Smush\Core\Media;
use Smush\Core\Animated_Status_Controller;
use Smush\Core\Array_Utils;
use Smush\Core\Backup_Size;
use Smush\Core\File_System;
use Smush\Core\Smush_File;
class Media_Item extends Smush_File {
const ANIMATED_META_KEY = 'wp-smush-animated';
const TRANSPARENT_META_KEY = 'wp-smush-transparent';
const IGNORED_META_KEY = 'wp-smush-ignore-bulk';
const SIZE_KEY_SCALED = 'wp_scaled';
const SIZE_KEY_FULL = 'full';
const BACKUP_SIZES_META_KEY = '_wp_attachment_backup_sizes';
const DEFAULT_BACKUP_KEY = 'smush-full';
private $plugin_settings;
private $animated_mime_types = array( 'image/gif' );
private $smushable_sizes;
private $mime_type_supported;
private $reset_properties = array(
private $original_image_path;
private $registered_wp_sizes;
private $outdated_meta_values = array();
public function __construct( $id ) {
$this->set_settings( Settings::get_instance() );
$size_limit = WP_Smush::is_pro()
? WP_SMUSH_PREMIUM_MAX_BYTES
$this->set_size_limit( $size_limit );
$this->array_utils = new Array_Utils();
$this->fs = new File_System();
public function size_limit_exceeded() {
foreach ( $this->get_smushable_sizes() as $size ) {
if ( $size->exceeds_size_limit() ) {
private function get_file_name_exceeding_limit() {
foreach ( $this->get_smushable_sizes() as $size ) {
if ( $size->exceeds_size_limit() ) {
return $size->get_file_name();
public function set_size_limit( $size_limit ) {
$this->size_limit = $size_limit;
public function get_size_limit() {
return $this->size_limit;
public function get_human_size_limit() {
return size_format( $this->get_size_limit() );
public function get_id() {
* Checks whether important metadata exists for the media item.
* Missing metadata for a size does not mean there is a problem, for example data
* is not generated if the image is too small to generate a 'large' version.
* So we don't check metadata for sizes.
public function has_wp_metadata() {
$metadata = $this->get_wp_metadata();
return is_array( $metadata )
&& ! empty( $metadata['file'] );
private function get_missing_sizes() {
if ( is_null( $this->missing_sizes ) ) {
$this->missing_sizes = wp_get_missing_image_subsizes( $this->get_id() );
return $this->missing_sizes;
* TODO: maybe add an error for this
public function has_missing_sizes() {
return ! empty( $this->get_missing_sizes() );
public function get_wp_metadata() {
if ( empty( $this->metadata ) ) {
$this->metadata = $this->fetch_wp_metadata();
private function fetch_wp_metadata() {
$attachment_metadata = wp_get_attachment_metadata( $this->get_id() );
return $this->array_utils->ensure_array( $attachment_metadata );
private function update_wp_metadata() {
$updated_attachment_meta = $this->make_attachment_meta();
if ( ! $this->arrays_same( $this->get_wp_metadata(), $updated_attachment_meta ) ) {
wp_update_attachment_metadata( $this->get_id(), $updated_attachment_meta );
private function file_name_from_path( $file_path ) {
return wp_basename( $file_path );
* TODO: use this instead of Helper::get_image_media_link
public function get_edit_link() {
if ( is_null( $this->edit_link ) ) {
$this->edit_link = $this->prepare_edit_link();
private function prepare_edit_link() {
// TODO: copy implementation from Helper::get_image_media_link
* File dir relative to the uploads directory e.g. 2023/05/. Includes trailing slash.
public function get_relative_file_dir() {
$relative_file_dir = dirname( $this->get_relative_file_path() );
if ( '.' === $relative_file_dir ) {
return trailingslashit( $relative_file_dir );
* The relative file path e.g. 2023/05/image.png
public function get_relative_file_path() {
if ( is_null( $this->file ) ) {
$this->file = $this->prepare_relative_file_path();
private function prepare_relative_file_path() {
$file = (string) $this->get_array_value( $this->get_wp_metadata(), 'file' );
* If metadata is missing we still want some of our functions to work, e.g. backup and restore
* Using _wp_attached_file meta because:
* 1. get_attached_file returns the full path but the _wp_attached_file meta has the relative path we need
* 2. get_attached_file is filtered which can interfere with our code e.g. the S3 module changes attached file, but we don't want that
$file = $this->get_post_meta( '_wp_attached_file' );
private function get_post_meta( $key ) {
return get_post_meta( $this->get_id(), $key, true );
public function has_size( $key ) {
$sizes = $this->get_sizes();
return ! empty( $sizes[ $key ] );
public function has_scaled_size() {
return $this->has_size( self::SIZE_KEY_SCALED );
public function has_full_size() {
return $this->has_size( self::SIZE_KEY_FULL );
* @return Media_Item_Size
public function get_size( $key ) {
return $this->get_array_value( $this->get_sizes(), $key );
public function get_scaled_size() {
return $this->get_size( self::SIZE_KEY_SCALED );
public function get_full_size() {
return $this->get_size( self::SIZE_KEY_FULL );
public function get_sizes() {
if ( is_null( $this->sizes ) ) {
$this->sizes = $this->prepare_sizes();
* The 'main' size is the size has get_attached_file as the file path
* @return Media_Item_Size
public function get_main_size() {
return $this->get_scaled_or_full_size();
private function prepare_sizes() {
$media_item_sizes = array();
$metadata_sizes = $this->get_wp_metadata_sizes();
foreach ( $metadata_sizes as $size_key => $metadata_size ) {
$registered_size = $this->array_utils->ensure_array( $this->get_registered_wp_size( $size_key ) );
$metadata_size = $this->array_utils->ensure_array( $metadata_size );
$size = $this->initialize_size( $size_key, array_merge( $registered_size, $metadata_size ) );
$media_item_sizes[ $size_key ] = $size;
$scaled_size = $this->prepare_scaled_size();
$media_item_sizes[ self::SIZE_KEY_SCALED ] = $scaled_size;
$full_size = $this->prepare_full_size();
$media_item_sizes[ self::SIZE_KEY_FULL ] = $full_size;
return $media_item_sizes;
public function prepare_scaled_size() {
$file = $this->get_attached_file();
if ( $file && $this->separate_original_image_path_exists() ) {
$wp_size_metadata = $this->attachment_metadata_as_size_metadata( $file );
return $this->initialize_size( self::SIZE_KEY_SCALED, $wp_size_metadata );
private function separate_original_image_path_exists() {
$original_image = $this->get_original_image_path();
$main_file = $this->get_attached_file();
return $original_image !== $main_file;
public function prepare_full_size() {
$original_image_exists = $this->separate_original_image_path_exists();
if ( $original_image_exists ) {
$original_image_file = $this->get_original_image_path();
$image_size = $this->fs->file_exists( $original_image_file ) ?
$this->fs->getimagesize( $original_image_file ) : false;
return $this->initialize_size( self::SIZE_KEY_FULL, array(
'file' => $this->file_name_from_path( $original_image_file ),
'width' => $image_size[0],
'height' => $image_size[1],
'mime-type' => $this->get_mime_type(),
'filesize' => $this->fs->filesize( $original_image_file ),
$main_file = $this->get_attached_file();
$wp_size_metadata = $this->attachment_metadata_as_size_metadata( $main_file );
return $this->initialize_size( self::SIZE_KEY_FULL, $wp_size_metadata );
public function has_smushable_sizes() {
return ! empty( $this->get_smushable_sizes() );
* @return Media_Item_Size[]
public function get_smushable_sizes() {
if ( is_null( $this->smushable_sizes ) ) {
$this->smushable_sizes = $this->prepare_smushable_sizes();
return $this->smushable_sizes;
private function prepare_smushable_sizes() {
foreach ( $this->get_sizes() as $size_key => $size ) {
if ( $size->is_smushable() ) {
$sizes[ $size_key ] = $size;
private function get_array_value( $array, $key ) {
return $array && isset( $array[ $key ] )
private function get_wp_metadata_sizes() {
// TODO: media items created before a certain WP version might not have the scaled size so that needs to be normalized for all wp versions
$metadata = $this->get_wp_metadata();
return empty( $metadata['sizes'] )
private function get_wp_metadata_size( $size_key ) {
$metadata = $this->get_wp_metadata_sizes();
return empty( $metadata[ $size_key ] )
: $metadata[ $size_key ];
public function is_skipped() {
return $this->is_ignored() ||
public function is_mime_type_supported() {
if ( is_null( $this->mime_type_supported ) ) {
$this->mime_type_supported = $this->check_is_mime_type_supported();
return $this->mime_type_supported;
private function check_is_mime_type_supported() {
$mime_type = $this->get_mime_type();
$supported = in_array( $mime_type, $this->get_supported_mime_types(), true );
return apply_filters( 'wp_smush_resmush_mime_supported', $supported, $mime_type );
public function is_image() {
if ( is_null( $this->is_image ) ) {