File: /usr/home/uyu7574470001/htdocs/wp-content/plugins/tiny-compress-images/src/class-tiny-image.php
<?php
/*
* Tiny Compress Images - WordPress plugin.
* Copyright (C) 2015-2018 Tinify B.V.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
class Tiny_Image {
const ORIGINAL = 0;
/** @var Tiny_Settings */
private $settings;
private $id;
private $name;
private $wp_metadata;
private $sizes = array();
private $statistics = array();
public function __construct(
$settings,
$id,
$wp_metadata = null,
$tiny_metadata = null,
$active_sizes = null,
$active_tinify_sizes = null
) {
$this->settings = $settings;
$this->id = $id;
$this->wp_metadata = $wp_metadata;
$this->parse_wp_metadata();
$this->parse_tiny_metadata( $tiny_metadata );
$this->detect_duplicates( $active_sizes, $active_tinify_sizes );
}
private function parse_wp_metadata() {
if ( ! is_array( $this->wp_metadata ) ) {
$this->wp_metadata = wp_get_attachment_metadata( $this->id );
}
if ( ! is_array( $this->wp_metadata ) || ! isset( $this->wp_metadata['file'] ) ) {
/* No file metadata found, this might be another plugin messing with
metadata. Simply ignore this! */
return;
}
$upload_dir = wp_upload_dir();
$path_prefix = $upload_dir['basedir'] . '/';
$path_info = pathinfo( $this->wp_metadata['file'] );
if ( isset( $path_info['dirname'] ) ) {
$path_prefix .= $path_info['dirname'] . '/';
}
/* Do not use pathinfo for getting the filename.
It doesn't work when the filename starts with a special character. */
$path_parts = explode( '/', $this->wp_metadata['file'] );
$this->name = end( $path_parts );
$filename = $path_prefix . $this->name;
$this->sizes[ self::ORIGINAL ] = new Tiny_Image_Size( $filename );
// Ensure 'sizes' exists and is an array to prevent PHP Warnings
$sizes = isset( $this->wp_metadata['sizes'] ) && is_array( $this->wp_metadata['sizes'] )
? $this->wp_metadata['sizes']
: array();
$sanitized_sizes = array();
foreach ( $sizes as $size_name => $size_info ) {
// size is valid when its an array and has a file
if ( is_array( $size_info ) && isset( $size_info['file'] ) ) {
// Add to sanitized metadata
$sanitized_sizes[ $size_name ] = $size_info;
$this->sizes[ $size_name ] = new Tiny_Image_Size(
$path_prefix . $size_info['file']
);
}
}
// Update the metadata with only the valid sizes found
$this->wp_metadata['sizes'] = $sanitized_sizes;
}
private function detect_duplicates( $active_sizes, $active_tinify_sizes ) {
$filenames = array();
if ( is_array( $this->wp_metadata )
&& isset( $this->wp_metadata['file'] )
&& isset( $this->wp_metadata['sizes'] )
&& is_array( $this->wp_metadata['sizes'] ) ) {
if ( null == $active_sizes ) {
$active_sizes = $this->settings->get_sizes();
}
if ( null == $active_tinify_sizes ) {
$active_tinify_sizes = $this->settings->get_active_tinify_sizes();
}
foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
if ( $this->sizes[ $size_name ]->has_been_compressed()
&& array_key_exists( $size_name, $active_sizes ) ) {
$filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
}
}
foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
$filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
}
}
foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
if ( array_key_exists( $size_name, $active_sizes ) ) {
$filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
}
}
foreach ( $this->wp_metadata['sizes'] as $size_name => $size ) {
$filenames = $this->duplicate_check( $filenames, $size['file'], $size_name );
}
}
}
private function duplicate_check( $filenames, $file, $size_name ) {
if ( isset( $filenames[ $file ] ) ) {
if ( $filenames[ $file ] != $size_name ) {
$this->sizes[ $size_name ]->mark_duplicate( $filenames[ $file ] );
}
} else {
$filenames[ $file ] = $size_name;
}
return $filenames;
}
private function parse_tiny_metadata( $tiny_metadata = null ) {
if ( is_null( $tiny_metadata ) ) {
$tiny_metadata = get_post_meta( $this->id, Tiny_Config::META_KEY, true );
}
if ( $tiny_metadata ) {
foreach ( $tiny_metadata as $size => $meta ) {
if ( ! isset( $this->sizes[ $size ] ) ) {
if ( self::is_retina( $size ) && Tiny_Settings::wr2x_active() ) {
$size_name = rtrim( $size, '_wr2x' );
if ( 'original' === $size_name ) {
$size_name = '0';
}
$retina_path = wr2x_get_retina(
$this->sizes[ $size_name ]->filename
);
$this->sizes[ $size ] = new Tiny_Image_Size( $retina_path );
} else {
$this->sizes[ $size ] = new Tiny_Image_Size();
}
}
$this->sizes[ $size ]->meta = $meta;
}
}
}
public function get_id() {
return $this->id;
}
public function get_name() {
return $this->name;
}
public function get_wp_metadata() {
return $this->wp_metadata;
}
public function file_type_allowed() {
return in_array(
$this->get_mime_type(),
array( 'image/jpeg', 'image/png', 'image/webp' ),
true
);
}
public function get_mime_type() {
return get_post_mime_type( $this->id );
}
public function compress() {
Tiny_Logger::debug(
'compress',
array(
'image_id' => $this->id,
'name' => $this->name,
)
);
if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
return;
}
$success = 0;
$failed = 0;
$active_tinify_sizes = $this->settings->get_active_tinify_sizes();
if ( $this->settings->get_conversion_enabled() ) {
$uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
$unconverted_sizes = $this->filter_image_sizes( 'unconverted', $active_tinify_sizes );
$unprocessed_sizes = $uncompressed_sizes + $unconverted_sizes;
} else {
$unprocessed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
}
$compressor = $this->settings->get_compressor();
$convert_to = $this->convert_to();
foreach ( $unprocessed_sizes as $size_name => $size ) {
if ( ! $size->is_duplicate() ) {
$size->add_tiny_meta_start();
$this->update_tiny_post_meta();
$resize = $this->settings->get_resize_options( $size_name );
$preserve = $this->settings->get_preserve_options( $size_name );
Tiny_Logger::debug(
'compress size',
array(
'image_id' => $this->id,
'size' => $size_name,
'resize' => $resize,
'preserve' => $preserve,
'convert' => $convert_to,
'modified' => $size->modified(),
'filename' => $size->filename,
'is_duplicate' => $size->is_duplicate(),
'exists' => $size->exists(),
'has_been_compressed' => $size->has_been_compressed(),
'filesize' => $size->filesize(),
'mimetype' => $size->mimetype(),
)
);
try {
$response = $compressor->compress_file(
$size->filename,
$resize,
$preserve,
$convert_to
);
// ensure that all conversion are in the same format as the first one
$convert_to = isset( $response['convert'] ) ?
array( $response['convert']['type'] ) :
$convert_to;
$size->add_tiny_meta( $response );
++$success;
Tiny_Logger::debug(
'compress success',
array(
'size' => $size_name,
'image_id' => $this->id,
)
);
} catch ( Tiny_Exception $e ) {
$size->add_tiny_meta_error( $e );
++$failed;
Tiny_Logger::error(
'compress failed',
array(
'error' => $e->get_message(),
'size' => $size_name,
'image_id' => $this->id,
)
);
}
$this->add_wp_metadata( $size_name, $size );
$this->update_tiny_post_meta();
}// End if().
}// End foreach().
/*
Other plugins can hook into this action to execute custom logic
after the image sizes have been compressed, ie. cache flushing.
*/
do_action( 'tiny_image_after_compression', $this->id, $success );
return array(
'success' => $success,
'failed' => $failed,
);
}
public function delete_converted_image() {
$sizes = $this->get_image_sizes();
foreach ( $sizes as $size ) {
$size->delete_converted_image_size();
}
}
public function compress_retina( $size_name, $path ) {
if ( $this->settings->get_compressor() === null || ! $this->file_type_allowed() ) {
return;
}
if ( ! isset( $this->sizes[ $size_name ] ) ) {
$this->sizes[ $size_name ] = new Tiny_Image_Size( $path );
}
$size = $this->sizes[ $size_name ];
$compressor = $this->settings->get_compressor();
$convert_to = $this->convert_to();
if ( ! $size->has_been_compressed() ) {
$size->add_tiny_meta_start();
$this->update_tiny_post_meta();
$preserve = $this->settings->get_preserve_options( $size_name );
try {
$response = $compressor->compress_file( $path, false, $preserve, $convert_to );
$size->add_tiny_meta( $response );
} catch ( Tiny_Exception $e ) {
$size->add_tiny_meta_error( $e );
}
$this->update_tiny_post_meta();
}
}
public function remove_retina_metadata() {
// Remove metadata from all sizes, as this callback only fires when all
// retina sizes are deleted.
foreach ( $this->sizes as $size_name => $size ) {
if ( self::is_retina( $size_name ) ) {
unset( $this->sizes[ $size_name ] );
}
}
$this->update_tiny_post_meta();
}
public function add_wp_metadata( $size_name, $size ) {
if ( self::is_original( $size_name ) ) {
if ( isset( $size->meta['output'] ) ) {
$output = $size->meta['output'];
if ( isset( $output['width'] ) && isset( $output['height'] ) ) {
$this->wp_metadata['width'] = $output['width'];
$this->wp_metadata['height'] = $output['height'];
$this->wp_metadata['filesize'] = $output['size'];
}
}
}
}
public function update_tiny_post_meta() {
$tiny_metadata = array();
foreach ( $this->sizes as $size_name => $size ) {
$tiny_metadata[ $size_name ] = $size->meta;
}
update_post_meta( $this->id, Tiny_Config::META_KEY, $tiny_metadata );
/*
This action is being used by WPML:
https://gist.github.com/srdjan-jcc/5c47685cda4da471dff5757ba3ce5ab1
*/
do_action( 'updated_tiny_postmeta', $this->id, Tiny_Config::META_KEY, $tiny_metadata );
}
public function get_image_sizes() {
$original = isset( $this->sizes[ self::ORIGINAL ] )
? array(
self::ORIGINAL => $this->sizes[ self::ORIGINAL ],
)
: array();
$compressed = array();
$uncompressed = array();
foreach ( $this->sizes as $size_name => $size ) {
if ( self::is_original( $size_name ) ) {
continue;
}
if ( $size->has_been_compressed() ) {
$compressed[ $size_name ] = $size;
} else {
$uncompressed[ $size_name ] = $size;
}
}
ksort( $compressed );
ksort( $uncompressed );
return $original + $compressed + $uncompressed;
}
public function get_image_size( $size = self::ORIGINAL, $create = false ) {
if ( isset( $this->sizes[ $size ] ) ) {
return $this->sizes[ $size ];
} elseif ( $create ) {
return new Tiny_Image_Size();
} else {
return null;
}
}
public function filter_image_sizes( $method, $filter_sizes = null ) {
$selection = array();
if ( is_null( $filter_sizes ) ) {
$filter_sizes = array_keys( $this->sizes );
}
foreach ( $filter_sizes as $size_name ) {
if ( ! isset( $this->sizes[ $size_name ] ) ) {
continue;
}
$tiny_image_size = $this->sizes[ $size_name ];
if ( $tiny_image_size->$method() ) {
$selection[ $size_name ] = $tiny_image_size;
}
}
return $selection;
}
public function get_count( $methods, $count_sizes = null ) {
$stats = array_fill_keys( $methods, 0 );
if ( is_null( $count_sizes ) ) {
$count_sizes = array_keys( $this->sizes );
}
foreach ( $count_sizes as $size ) {
if ( ! isset( $this->sizes[ $size ] ) ) {
continue;
}
foreach ( $methods as $method ) {
if ( $this->sizes[ $size ]->$method() ) {
++$stats[ $method ];
}
}
}
return $stats;
}
public function get_latest_error() {
$active_tinify_sizes = $this->settings->get_active_tinify_sizes();
$error_message = null;
$last_timestamp = null;
foreach ( $this->sizes as $size_name => $size ) {
if ( in_array( $size_name, $active_tinify_sizes, true ) ) {
if ( isset( $size->meta['error'] ) && isset( $size->meta['message'] ) ) {
if (
null === $last_timestamp ||
$last_timestamp < $size->meta['timestamp']
) {
$last_timestamp = $size->meta['timestamp'];
$error_message = Tiny_Helpers::truncate_text(
$size->meta['message'],
140
);
}
}
}
}
return $error_message;
}
public function get_savings( $stats ) {
$before = $stats['initial_total_size'];
$after = $stats['compressed_total_size'];
if ( 0 === $before ) {
$savings = 0;
} else {
$savings = ( $before - $after ) / $before * 100;
}
return '' . number_format( $savings, 1 );
}
public function get_statistics( $active_sizes, $active_tinify_sizes ) {
if ( $this->statistics ) {
error_log( 'Strangely the image statistics are asked for again.' );
return $this->statistics;
}
$this->statistics['initial_total_size'] = 0;
$this->statistics['compressed_total_size'] = 0;
$this->statistics['image_sizes_compressed'] = 0;
$this->statistics['available_uncompressed_sizes'] = 0;
$this->statistics['image_sizes_converted'] = 0;
$this->statistics['available_unconverted_sizes'] = 0;
foreach ( $this->sizes as $size_name => $size ) {
// skip duplicates or inactive sizes
if ( $size->is_duplicate() || ! isset( $active_sizes[ $size_name ] ) ) {
continue;
}
$file_size = $size->filesize();
$is_active_size = in_array( $size_name, $active_tinify_sizes, true );
if ( isset( $size->meta['input'] ) ) {
$input_size = (int) $size->meta['input']['size'];
$this->statistics['initial_total_size'] += $input_size;
if ( isset( $size->meta['output'] ) ) {
$output_size = (int) $size->meta['output']['size'];
if ( $size->modified() ) {
$this->statistics['compressed_total_size'] += $file_size;
if ( $is_active_size ) {
++$this->statistics['available_uncompressed_sizes'];
}
} else {
$this->statistics['compressed_total_size'] += $output_size;
++$this->statistics['image_sizes_compressed'];
}
} else {
$this->statistics['compressed_total_size'] += $input_size;
}
} elseif ( $size->exists() ) {
$this->statistics['initial_total_size'] += $file_size;
$this->statistics['compressed_total_size'] += $file_size;
if ( $is_active_size ) {
++$this->statistics['available_uncompressed_sizes'];
}
}
if ( $is_active_size ) {
if ( $size->has_been_converted() ) {
++$this->statistics['image_sizes_converted'];
} else {
++$this->statistics['available_unconverted_sizes'];
}
}
}// End foreach().
return $this->statistics;
}
public static function is_original( $size ) {
return self::ORIGINAL === $size;
}
public static function is_retina( $size ) {
return strrpos( $size, 'wr2x' ) === strlen( $size ) - strlen( 'wr2x' );
}
public function can_be_converted() {
return $this->settings->get_conversion_enabled() && $this->file_type_allowed();
}
/**
* Get the targeted conversion.
* If original is already converted, then we use the originals' mimetype.
* If nothing is converted yet, we use the settings conversion settings.
*
* @since 3.6.4
*
* @return array{string} mimetypes to which the image should be converted to
*/
private function convert_to() {
$convert_settings = $this->settings->get_conversion_options();
if ( ! $convert_settings['convert'] ) {
// conversion is off so return no mimetypes to convert to
return array();
}
if ( isset( $this->sizes[ self::ORIGINAL ] ) ) {
// original is not in sizes so mimetypes are open
return $convert_settings['convert_to'];
}
$original_img_size = $this->sizes[ self::ORIGINAL ];
if ( $original_img_size->converted() ) {
// original has been convert so use that mimetype to convert to
return array( $original_img_size->meta['convert']['type'] );
}
return $convert_settings['convert_to'];
}
/**
* Marks the image as compressed without actually compressing it.
*
* This method parses existing metadata and delegates to each image size to mark
* itself as compressed. It considers conversion settings when marking the sizes.
* This is useful for images that are already optimized or when you want to skip
* compression while still marking them as processed in the system.
*
* @since 3.0.0
*/
public function mark_as_compressed() {
$this->parse_tiny_metadata();
$conversion_enabled = $this->settings->get_conversion_enabled();
$active_tinify_sizes = $this->settings->get_active_tinify_sizes();
if ( $this->settings->get_conversion_enabled() ) {
$uncompressed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
$unconverted_sizes = $this->filter_image_sizes( 'unconverted', $active_tinify_sizes );
$unprocessed_sizes = $uncompressed_sizes + $unconverted_sizes;
} else {
$unprocessed_sizes = $this->filter_image_sizes( 'uncompressed', $active_tinify_sizes );
}
foreach ( $unprocessed_sizes as $size ) {
$size->mark_as_compressed( $conversion_enabled );
}
$this->update_tiny_post_meta();
}
}