File Manager
<?php
/**
* PNG to JPG conversion: Png2jpg class
*
* @package Smush\Core\Modules
*
* @version 2.4
*
* @author Umesh Kumar <umesh@incsub.com>
*
* @copyright (c) 2016, Incsub (http://incsub.com)
*/
namespace Smush\Core\Modules;
use Exception;
use Imagick;
use Smush\Core\Helper;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Png2jpg
*/
class Png2jpg extends Abstract_Module {
/**
* Module slug.
*
* @var string
*/
protected $slug = 'png_to_jpg';
/**
* Whether module is pro or not.
*
* @var string
*/
protected $is_pro = true;
/**
* Init method.
*
* @since 3.0
*/
public function init() {
}
/**
* Check if Imagick is available or not
*
* @return bool True/False Whether Imagick is available or not
*/
private function supports_imagick() {
if ( ! class_exists( '\Imagick' ) ) {
return false;
}
return true;
}
/**
* Check if GD is loaded
*
* @return bool True/False Whether GD is available or not
*/
private function supports_gd() {
if ( ! function_exists( 'gd_info' ) ) {
return false;
}
return true;
}
/**
* Save can be converted status before resizing the image,
* because the the image might be lost the undefined-transparent behavior after resizing.
*
* @see WP_Image_Editor_Imagick::thumbnail_image()
* WP Resize function will convert Imagick::ALPHACHANNEL_UNDEFINED -> Imagick::ALPHACHANNEL_OPAQUE.
*
* @param array $sizes Array of sizes containing max width and height for all the uploaded images.
* @param string $file_path Image file path.
* @param int $id Image id.
*
* @return array the Original $sizes.
*
* @since 3.9.6
*/
public function cache_can_be_converted_status( $sizes, $file_path, $id ) {
// Call can_be_converted and cache the status.
$this->can_be_converted( $id, 'full', '', $file_path );
// Always return $sizes.
return $sizes;
}
/**
* Checks if the Given PNG file is transparent or not
*
* @param string $id Attachment ID.
* @param string $file File path for the attachment.
*
* @return bool|int
*/
private function is_transparent( $id = '', $file = '' ) {
// No attachment id/ file path, return.
if ( empty( $id ) && empty( $file ) ) {
return false;
}
if ( empty( $file ) ) {
// This downloads the file from S3 when S3 is enabled.
$file = Helper::get_attached_file( $id );
}
// Check if File exists.
if ( empty( $file ) || ! file_exists( $file ) ) {
Helper::logger()->png2jpg()->info( sprintf( 'File [%s(%d)] is empty or does not exist.', Helper::clean_file_path( $file ), $id ) );
return false;
}
$transparent = '';
// Try to get transparency using Imagick.
if ( $this->supports_imagick() ) {
try {
$im = new Imagick( $file );
return $im->getImageAlphaChannel();
} catch ( Exception $e ) {
Helper::logger()->png2jpg()->error( 'Imagick: Error in checking PNG transparency ' . $e->getMessage() );
}
} else {
// Simple check.
// Src: http://camendesign.com/code/uth1_is-png-32bit.
if ( ord( file_get_contents( $file, false, null, 25, 1 ) ) & 4 ) {
Helper::logger()->png2jpg()->info( sprintf( 'File [%s(%d)] is an PNG 32-bit.', Helper::clean_file_path( $file ), $id ) );
return true;
}
// Src: http://www.jonefox.com/blog/2011/04/15/how-to-detect-transparency-in-png-images/.
$contents = file_get_contents( $file );
if ( stripos( $contents, 'PLTE' ) !== false && stripos( $contents, 'tRNS' ) !== false ) {
Helper::logger()->png2jpg()->info( sprintf( 'File [%s(%d)] is an PNG 8-bit.', Helper::clean_file_path( $file ), $id ) );
return true;
}
// If both the conditions failed, that means not transparent.
return false;
}
// If Imagick is installed, and the code exited due to some error.
// Src: StackOverflow.
if ( empty( $transparent ) && $this->supports_gd() ) {
// Check for transparency using GD.
$i = imagecreatefrompng( $file );
$palette = ( imagecolortransparent( $i ) < 0 );
if ( $palette ) {
return true;
}
}
return false;
}
/**
* Check if given attachment id can be converted to JPEG or not
*
* @param string $id Atachment ID.
* @param string $size Image size.
* @param string $mime Mime type.
* @param string $file File.
*
* @since 3.9.6 We removed the private method should_convert
* and we also handled the case which we need to delete the download file inside S3.
*
* Note, we also used this for checking resmush, so we only download the attached file (s3)
* if it's necessary (self::is_transparent()). Check the comment on self::__construct() for detail.
*
* @return bool True/False Can be converted or not
*/
public function can_be_converted( $id = '', $size = 'full', $mime = '', $file = '' ) {
// If PNG2JPG not enabled, or is not smushable, return.
if ( ! $this->is_active() || ! Helper::is_smushable( $id ) ) {
return false;
}
// Check it from the cache for full size.
if ( 'full' === $size && null !== Helper::cache_get( $id, 'png2jpg_can_be_converted' ) ) {
return Helper::cache_get( $id, 'png2jpg_can_be_converted' );
}
// False if not a PNG.
$mime = empty( $mime ) ? get_post_mime_type( $id ) : $mime;
if ( 'image/png' !== $mime && 'image/x-png' !== $mime ) {
return false;
}
// Return if Imagick and GD is not available.
if ( ! $this->supports_imagick() && ! $this->supports_gd() ) {
Helper::logger()->png2jpg()->warning( 'The site does not support Imagick or GD.' );
return false;
}
// If already tried the conversion.
if ( get_post_meta( $id, 'wp-smush-pngjpg_savings', true ) ) {
Helper::logger()->png2jpg()->info( sprintf( 'File [%s(%d)] already tried the conversion.', Helper::clean_file_path( $file ), $id ) );
return false;
}
// Check if registered size is supposed to be converted or not.
if ( 'full' !== $size && WP_Smush::get_instance()->core()->mod->smush->skip_image_size( $size ) ) {
return false;
}
// Make sure $file is not empty.
if ( empty( $file ) ) {
$file = Helper::get_attached_file( $id );// S3+.
}
/**
* Filter whether to convert the PNG to JPG or not
*
* @since 2.4
*
* @param bool $should_convert Current choice for image conversion
*
* @param int $id Attachment id
*
* @param string $file File path for the image
*
* @param string $size Image size being converted
*/
$should_convert = apply_filters( 'wp_smush_convert_to_jpg', ! $this->is_transparent( $id, $file ), $id, $file, $size );
if ( 'full' === $size ) {
/**
* We used this method inside Backup::create_backup(), Smush function, and filter wp_smush_resize_sizes,
* so cache the result to avoid to check it again.
*/
Helper::cache_set( $id, $should_convert, 'png2jpg_can_be_converted' );
}
return $should_convert;
}
/**
* Check whether to resmush image or not.
*
* @since 3.9.6
*
* @usedby Smush\App\Ajax::scan_images()
*
* @param bool $should_resmush Current status.
* @param int $attachment_id Attachment ID.
* @return bool|string png2jpg|TRUE|FALSE
*/
public function should_resmush( $should_resmush, $attachment_id ) {
if ( ! $should_resmush && $this->can_be_converted( $attachment_id ) ) {
$should_resmush = 'png2jpg';
}
return $should_resmush;
}
/**
* Update the image URL, MIME Type, Attached File, file path in Meta, URL in post content
*
* @param string $id Attachment ID.
* @param string $o_file Original File Path that has to be replaced.
* @param string $n_file New File Path which replaces the old file.
* @param string $meta Attachment Meta.
* @param string $size_k Image Size.
* @param string $o_type Operation Type "conversion", "restore".
*
* @return array Attachment Meta with updated file path.
*/
public function update_image_path( $id, $o_file, $n_file, $meta, $size_k, $o_type = 'conversion' ) {
// Upload Directory.
$upload_dir = wp_upload_dir();
// Upload Path.
$upload_path = trailingslashit( $upload_dir['basedir'] );
$dir_name = pathinfo( $o_file, PATHINFO_DIRNAME );
// Full Path to new file.
$n_file_path = path_join( $dir_name, $n_file );
// Current URL for image.
$o_url = wp_get_attachment_url( $id );
// Update URL for image size.
if ( 'full' !== $size_k ) {
$base_url = dirname( $o_url );
$o_url = $base_url . '/' . basename( $o_file );
}
// Update File path, Attached File, GUID.
$meta = empty( $meta ) ? wp_get_attachment_metadata( $id ) : $meta;
$mime = Helper::get_mime_type( $n_file_path );
/**
* If there's no fileinfo extension installed, the mime type will be returned as false.
* As a fallback, we set it manually.
*
* @since 3.8.3
*/
if ( false === $mime ) {
$mime = 'conversion' === $o_type ? 'image/jpeg' : 'image/png';
}
$del_file = true;
// Update File Path, Attached file, Mime Type for Image.
if ( 'full' === $size_k ) {
if ( ! empty( $meta ) ) {
$new_file = str_replace( $upload_path, '', $n_file_path );
$meta['file'] = $new_file;
// Update Attached File.
if ( ! update_attached_file( $id, $meta['file'] ) ) {
$del_file = false;
}
}
// Update Mime type.
if ( ! wp_update_post(
array(
'ID' => $id,
'post_mime_type' => $mime,
)
) ) {
$del_file = false;
}
} else {
$meta['sizes'][ $size_k ]['file'] = basename( $n_file );
$meta['sizes'][ $size_k ]['mime-type'] = $mime;
}
// To be called after the attached file key is updated for the image.
if ( ! $this->update_image_url( $id, $size_k, $n_file, $o_url ) ) {
$del_file = false;
}
/**
* Delete the Original files if backup not enabled
* We only delete the file if we don't have any issues while updating the DB.
* SMUSH-1088?focusedCommentId=92914.
*/
if ( $del_file && 'conversion' === $o_type ) {
// We might need to backup the full size file, will delete it later if we don't need to use it for backup.
if ( 'full' !== $size_k ) {
/**
* We only need to keep the original file as a backup file.
* and try to delete this file on cloud too, e.g S3.
*/
Helper::delete_permanently( $o_file, $id );
}
}
return $meta;
}
/**
* Replace the file if there are savings, and return savings
*
* @param string $file Original File Path.
* @param array $result Array structure.
* @param string $n_file Updated File path.
*
* @return array
*/
private function replace_file( $file = '', $result = array(), $n_file = '' ) {
if ( empty( $file ) || empty( $n_file ) ) {
return $result;
}
// Get the file size of original image.
$o_file_size = filesize( $file );
$n_file = path_join( dirname( $file ), $n_file );
$n_file_size = filesize( $n_file );
// If there aren't any savings return.
if ( $n_file_size >= $o_file_size ) {
// Delete the JPG image and return.
unlink( $n_file );
Helper::logger()->png2jpg()->notice( sprintf( 'The new file [%s](%s) is larger than the original file [%s](%s).', Helper::clean_file_path( $n_file ), size_format( $n_file_size ), Helper::clean_file_path( $file ), size_format( $o_file_size ) ) );
return $result;
}
// Get the savings.
$savings = $o_file_size - $n_file_size;
// Store Stats.
$savings = array(
'bytes' => $savings,
'size_before' => $o_file_size,
'size_after' => $n_file_size,
);
$result['savings'] = $savings;
return $result;
}
/**
* Perform the conversion process, using WordPress Image Editor API
*
* @param string $id Attachment ID.
* @param string $file Attachment File path.
* @param string $meta Attachment meta.
* @param string $size Image size, default empty for full image.
*
* @return array $result array(
* 'meta' => array Update Attachment metadata
* 'savings' => Reduction of Image size in bytes
* )
*/
private function convert_to_jpg( $id = '', $file = '', $meta = '', $size = 'full' ) {
$result = array(
'meta' => $meta,
'savings' => '',
);
// Flag: Whether the image was converted or not.
if ( 'full' === $size ) {
$result['converted'] = false;
}
// If any of the values is not set.
if ( empty( $id ) || empty( $file ) || empty( $meta ) || ! file_exists( $file ) ) {
Helper::logger()->png2jpg()->info( sprintf( 'Meta file [%s(%d)] is empty or file not found.', Helper::clean_file_path( $file ), $id ) );
return $result;
}
$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) ) {
// Use custom method maybe.
Helper::logger()->png2jpg()->error( sprintf( 'Image Editor cannot load file [%s(%d)]: %s.', Helper::clean_file_path( $file ), $id, $editor->get_error_message() ) );
return $result;
}
$n_file = pathinfo( $file );
if ( ! empty( $n_file['filename'] ) && $n_file['dirname'] ) {
// Get a unique File name.
$file_detail = Helper::cache_get( $id, 'convert_to_jpg' );
if ( $file_detail ) {
list( $old_main_filename, $new_main_filename ) = $file_detail;
/**
* Thumbnail name.
* E.g.
* test-150x150.jpg
* test-1-150x150.jpg
*/
if ( $old_main_filename !== $new_main_filename ) {
$n_file['filename'] = str_replace( $old_main_filename, $new_main_filename, $n_file['filename'] );
}
$n_file['filename'] .= '.jpg';
} else {
$org_filename = $n_file['filename'];
/**
* Get unique file name for the main file.
* E.g.
* test.png => test.jpg
* test.png => test-1.jpg
*/
$n_file['filename'] = wp_unique_filename( $n_file['dirname'], $org_filename . '.jpg' );
Helper::cache_set( $id, array( $org_filename, pathinfo( $n_file['filename'], PATHINFO_FILENAME ) ), 'convert_to_jpg' );
}
$n_file = path_join( $n_file['dirname'], $n_file['filename'] );
} else {
Helper::logger()->png2jpg()->error( sprintf( 'Cannot retrieve the path info of file [%s(%d)].', Helper::clean_file_path( $file ), $id ) );
return $result;
}
// Save PNG as JPG.
$new_image_info = $editor->save( $n_file, 'image/jpeg' );
// If image editor was unable to save the image, return.
if ( is_wp_error( $new_image_info ) ) {
return $result;
}
$n_file = ! empty( $new_image_info ) ? $new_image_info['file'] : '';
// Replace file, and get savings.
$result = $this->replace_file( $file, $result, $n_file );
if ( ! empty( $result['savings'] ) ) {
if ( 'full' === $size ) {
$result['converted'] = true;
}
// Update the File Details. and get updated meta.
$result['meta'] = $this->update_image_path( $id, $file, $n_file, $meta, $size );
/**
* Perform a action after the image URL is updated in post content
*/
do_action( 'wp_smush_image_url_changed', $id, $file, $n_file, $size );
}
return $result;
}
/**
* Convert a PNG to JPG, Lossless Conversion, if we have any savings
*
* @param string $id Image ID.
* @param string|array $meta Image meta.
*
* @uses Backup::add_to_image_backup_sizes()
*
* @return mixed|string
*
* TODO: Save cumulative savings
*/
public function png_to_jpg( $id = '', $meta = '' ) {
// If we don't have meta or ID, or if not a premium user.
if ( empty( $id ) || empty( $meta ) || ! $this->is_active() || ! Helper::is_smushable( $id ) ) {
return $meta;
}
$file = Helper::get_attached_file( $id );// S3+.
// Whether to convert to jpg or not.
$should_convert = $this->can_be_converted( $id, 'full', '', $file );
if ( ! $should_convert ) {
return $meta;
}
$result['meta'] = $meta;
/**
* Allow to force convert the PNG to JPG via filter wp_smush_convert_to_jpg.
*
* @since 3.9.6
* @see self::can_be_converted()
*/
// Perform the conversion, and update path.
$result = $this->convert_to_jpg( $id, $file, $result['meta'] );
$savings['full'] = ! empty( $result['savings'] ) ? $result['savings'] : '';
// If original image was converted and other sizes are there for the image, Convert all other image sizes.
if ( $result['converted'] ) {
if ( ! empty( $meta['sizes'] ) ) {
$converted_thumbs = array();
foreach ( $meta['sizes'] as $size_k => $data ) {
// Some thumbnail sizes are using the same image path, so check if the thumbnail file is converted.
if ( isset( $converted_thumbs[ $data['file'] ] ) ) {
// Update converted thumbnail size.
$result['meta']['sizes'][ $size_k ]['file'] = $result['meta']['sizes'][ $converted_thumbs[ $data['file'] ] ]['file'];
$result['meta']['sizes'][ $size_k ]['mime-type'] = $result['meta']['sizes'][ $converted_thumbs[ $data['file'] ] ]['mime-type'];
continue;
}
$s_file = path_join( dirname( $file ), $data['file'] );
/**
* Check if the file exists on the server,
* if not, might try to download it from the cloud (s3).
*
* @since 3.9.6
*/
if ( ! Helper::exists_or_downloaded( $s_file, $id ) ) {
continue;
}
/**
* Since these sizes are derived from the main png file,
* We can safely perform the conversion.
*/
$result = $this->convert_to_jpg( $id, $s_file, $result['meta'], $size_k );
if ( ! empty( $result['savings'] ) ) {
$savings[ $size_k ] = $result['savings'];
/**
* Save converted thumbnail file, allow to try to convert the thumbnail to PNG again if it was failed.
*/
$converted_thumbs[ $data['file'] ] = $size_k;
}
}
}
$mod = WP_Smush::get_instance()->core()->mod;
// Save the original File URL.
/**
* Filter: wp_smush_png2jpg_enable_backup
*
* Whether to backup the PNG before converting it to JPG or not
*
* It's safe when we try to backup the PNG file before converting it to JPG when disabled backup option.
* But if exists the backup file, we can delete the PNG file to free up space.
* Note, if enabling resize the image, the backup file is a file that has already been resized, not the original file.
* Use this filter to disable this option:
* add_filter('wp_smush_png2jpg_enable_backup', '__return_false' );
*/
if ( $mod->backup->is_active() || apply_filters( 'wp_smush_png2jpg_enable_backup', ! $mod->backup->is_active(), $id, $file ) ) {
if ( ! $mod->backup->maybe_backup_image( $id, $file ) ) {
/**
* Delete the original file if the backup file exists.
*
* Note, we use size key smush-png2jpg-full for PNG2JPG file to support S3 private media,
* to remove converted JPG file after restoring in private folder.
*
* @see Smush\Core\Integrations\S3::get_object_key()
*/
Helper::delete_permanently( array( 'smush-png2jpg-full' => $file ), $id );// S3+.
}
}
// Remove webp images created from the png version, if any.
$mod->webp->delete_images( $id, true, $file );
/**
* Do action, if the PNG to JPG conversion was successful
*/
do_action( 'wp_smush_png_jpg_converted', $id, $meta, $savings );
/**
* The file converted to JPG,
* we can clear the temp cache related to this converting.
*/
Helper::cache_delete( 'png2jpg_can_be_converted' );
Helper::cache_delete( 'convert_to_jpg' );
}
// Update the Final Stats.
update_post_meta( $id, 'wp-smush-pngjpg_savings', $savings );
return $result['meta'];
}
/**
* Get JPG quality from WordPress Image Editor
*
* @param string $file File.
*
* @return int Quality for JPEG images
*/
private function get_quality( $file ) {
if ( empty( $file ) ) {
return 82;
}
$editor = wp_get_image_editor( $file );
if ( ! is_wp_error( $editor ) ) {
$quality = $editor->get_quality();
} else {
Helper::logger()->png2jpg()->error( sprintf( 'Image Editor cannot load image [%s].', Helper::clean_file_path( $file ) ) );
}
// Choose the default quality if we didn't get it.
if ( ! isset( $quality ) || $quality < 1 || $quality > 100 ) {
// The default quality.
$quality = 82;
}
return $quality;
}
/**
* Check whether the given attachment was converted from PNG to JPG.
*
* @param int $id Attachment ID.
*
* @since 3.9.6 Use this function to check if an image is converted from PNG to JPG.
* @see Backup::get_backup_file() To check the backup file.
*
* @return int|false False if the image id is empty.
* 0 Not yet converted, -1 Tried to convert but it failed or not saving, 1 Convert successfully.
*/
public function is_converted( $id ) {
if ( empty( $id ) ) {
return false;
}
$savings = get_post_meta( $id, 'wp-smush-pngjpg_savings', true );
$is_converted = 0;
if ( ! empty( $savings ) ) {
$is_converted = -1;// The image was tried to convert to JPG but it failed or larger than the original file.
if ( ! empty( $savings['full'] ) ) {
$is_converted = 1;// The image was converted to JPG successfully.
}
}
return $is_converted;
}
/**
* Update Image URL in post content
*
* @param string $id Attachment ID.
* @param string $size_k Image Size.
* @param string $n_file New File Path which replaces the old file.
* @param string $o_url URL to search for.
*/
private function update_image_url( $id, $size_k, $n_file, $o_url ) {
if ( 'full' === $size_k ) {
// Get the updated image URL.
$n_url = wp_get_attachment_url( $id );
} else {
$n_url = trailingslashit( dirname( $o_url ) ) . basename( $n_file );
}
// Update In Post Content, Loop Over a set of posts to avoid the query failure for large sites.
global $wpdb;
// Get existing Images with current URL.
$wild = '%';
$o_url_like = $wild . $wpdb->esc_like( $o_url ) . $wild;
$query = $wpdb->prepare(
"SELECT ID, post_content FROM $wpdb->posts WHERE post_content LIKE %s",
$o_url_like
);
$rows = $wpdb->get_results( $query, ARRAY_A );
if ( empty( $rows ) || ! is_array( $rows ) ) {
return true;
}
// Iterate over rows to update post content.
$total = count( $rows );
foreach ( $rows as $row ) {
// replace old URLs with new URLs.
$post_content = $row['post_content'];
$post_content = str_replace( $o_url, $n_url, $post_content );
// Update Post content.
if ( $wpdb->update(
$wpdb->posts,
array(
'post_content' => $post_content,
),
array(
'ID' => $row['ID'],
)
) ) {
$total --;
}
clean_post_cache( $row['ID'] );
}
// SMUSH-1088?focusedCommentId=92914.
return 0 === $total;
}
}
File Manager Version 1.0, Coded By Lucas
Email: hehe@yahoo.com