<?php if (! defined('ABSPATH')) { exit; // Exit if accessed directly. } use \Resmush\ShortPixelLogger\ShortPixelLogger as Log; /** * ReSmushit * * * @package Resmush.it * @subpackage Controller * @author Charles Bourgeaux <contact@resmush.it> */ Class reSmushit { const MAX_FILESIZE = 5242880; const MAX_ATTACHMENTS_REQ = 1000; /** * * returns the list of supported extensions by the API * * @return array List of extensions */ public static function authorizedExtensions() { return array('jpg', 'jpeg', 'gif', 'png'); } /** * * Optimize an image according to a filepath. * * @param string $file_path the path to the file on the server * @return bool TRUE if the resmush operation worked */ public static function getPictureQualitySetting() { if(get_option( 'resmushit_qlty' )) { return apply_filters('resmushit_image_quality', get_option( 'resmushit_qlty')); } else { if (!defined('RESMUSHIT_QLTY')) { return RESMUSHIT_DEFAULT_QLTY; } return RESMUSHIT_QLTY; } } /** * * Optimize an image according to a filepath. * * @param string $file_path the path to the file on the server * @param boolean The is_original param doesn't make any sense, since it's always true. Also before it was hooked to regenerate attachment metadata, hoping it wouldnt send a second param. This whole thing can probl. go * * @return bool TRUE if the resmush operation worked */ public static function optimize($file_path = NULL, $is_original = TRUE) { global $wp_version; if(!file_exists($file_path) OR !is_file($file_path)) { Log::addError('Error! Picture ' . str_replace(ABSPATH, '/', $file_path) . ' cannot be optimized, file is not found on disk.'); return false; } if(filesize($file_path) > self::MAX_FILESIZE){ Log::addError('Error! Picture ' . str_replace(ABSPATH, '/', $file_path) . ' cannot be optimized, file size is above 5MB ('. reSmushitUI::sizeFormat(filesize($file_path)) .')'); return false; } if(! in_array('curl', get_loaded_extensions())){ return false; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, RESMUSHIT_ENDPOINT); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, RESMUSHIT_TIMEOUT); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_USERAGENT, "Wordpress $wp_version/Resmush.it " . RESMUSHIT_VERSION . ' - ' . get_bloginfo('wpurl') ); if (!class_exists('CURLFile')) { $arg = array('files' => '@' . $file_path); } else { $cfile = new CURLFile($file_path); $arg = array( 'files' => $cfile, ); } if(get_option( 'resmushit_preserve_exif' ) && get_option( 'resmushit_preserve_exif' ) == 1) { $arg['exif'] = 'true'; } $arg['qlty'] = self::getPictureQualitySetting(); curl_setopt($ch, CURLOPT_POSTFIELDS, $arg); $data = curl_exec($ch); curl_close($ch); $json = json_decode($data); if($json){ if (!isset($json->error)) { if (ini_get('allow_url_fopen')) { $arrContextOptions= array("ssl" => array("verify_peer" => false,"verify_peer_name" => false)); $data = file_get_contents( $json->dest, false, stream_context_create($arrContextOptions) ); } else { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $json->dest); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $data = curl_exec($ch); curl_close($ch); } if ($data) { if($is_original && get_option('resmushit_remove_unsmushed') == 0){ $originalFile = pathinfo($file_path); $newPath = $originalFile['dirname'] . '/' . $originalFile['filename'] . '-unsmushed.' . $originalFile['extension']; copy($file_path, $newPath); } file_put_contents($file_path, $data); Log::addDebug("Optimized file " . str_replace(ABSPATH, '/', $file_path) . " from " . reSmushitUI::sizeFormat($json->src_size) . " to " . reSmushitUI::sizeFormat($json->dest_size)); return $json; } } else { Log::addError("Webservice returned the following error while optimizing $file_path : Code #" . $json->error . " - " . $json->error_long); } } else { Log::addError("Cannot establish connection with reSmush.it webservice while optimizing $file_path (timeout of " . RESMUSHIT_TIMEOUT . "sec.)"); } return false; } /** * * Revert original file and regenerates attachment thumbnails * * @param int $attachment_id ID of the attachment to revert * @return none */ public static function revert($id, $generateThumbnails = true) { global $wp_version; global $attachment_id; $attachment_id = $id; delete_post_meta($attachment_id, 'resmushed_quality'); delete_post_meta($attachment_id, 'resmushed_cumulated_original_sizes'); delete_post_meta($attachment_id, 'resmushed_cumulated_optimized_sizes'); $basepath = dirname(get_attached_file( $attachment_id )) . '/'; $fileInfo = pathinfo(get_attached_file( $attachment_id )); $originalFile = $basepath . $fileInfo['filename'] . '-unsmushed.' . $fileInfo['extension']; Log::addDebug('Revert original image for : ' . str_replace(ABSPATH, '/', get_attached_file( $attachment_id ))); if(file_exists($originalFile)) { copy($originalFile, get_attached_file( $attachment_id )); } //Regenerate thumbnails if($generateThumbnails) { wp_generate_attachment_metadata($attachment_id, get_attached_file( $attachment_id )); } return self::wasSuccessfullyUpdated( $attachment_id ); } /** * * Delete Original file (-unsmushed) * * @param int $attachment_id ID of the attachment * @return none */ public static function deleteOriginalFile($attachment_id) { $basepath = dirname(get_attached_file( $attachment_id )) . '/'; $fileInfo = pathinfo(get_attached_file( $attachment_id )); $originalFile = $basepath . $fileInfo['filename'] . '-unsmushed.' . $fileInfo['extension']; Log::addDebug('Delete original image for : ' . get_attached_file( $attachment_id )); if(file_exists($originalFile)) unlink($originalFile); } /** * Checking that we have backup * * @param int $attachment_id ID of the attachment * @return bool TRUE if there is the original file, FALSE otherwise */ public static function hasBackup($attachment_id) { $basepath = dirname(get_attached_file($attachment_id)) . '/'; $fileInfo = pathinfo(get_attached_file($attachment_id)); $originalFile = $basepath . $fileInfo['filename'] . '-unsmushed.' . $fileInfo['extension']; return file_exists($originalFile); } /** * * Detect if optimization process was already launched one time * * @return boolean */ public static function hasAlreadyRunOnce(){ global $wpdb; $query = $wpdb->prepare( "select count($wpdb->posts.ID) as count from $wpdb->posts inner join $wpdb->postmeta on $wpdb->posts.ID = $wpdb->postmeta.post_id and $wpdb->postmeta.meta_key = %s $extraSQL limit 1", array('resmushed_cumulated_original_sizes') ); return (boolean)$wpdb->get_var($query); } /** * * Return optimization statistics * * @param int $attachment_id (optional) * @return array of statistics */ public static function getStatistics($attachment_id = null){ global $wpdb; $output = array(); $extraSQL = null; if($attachment_id) $extraSQL = "where $wpdb->postmeta.post_id = ". (int)($attachment_id); $query = $wpdb->prepare( "select $wpdb->posts.ID as ID, $wpdb->postmeta.meta_value from $wpdb->posts inner join $wpdb->postmeta on $wpdb->posts.ID = $wpdb->postmeta.post_id and $wpdb->postmeta.meta_key = %s $extraSQL", array('resmushed_cumulated_original_sizes') ); $original_sizes = $wpdb->get_results($query); $total_original_size = 0; foreach($original_sizes as $s){ $total_original_size += $s->meta_value; } $query = $wpdb->prepare( "select $wpdb->posts.ID as ID, $wpdb->postmeta.meta_value from $wpdb->posts inner join $wpdb->postmeta on $wpdb->posts.ID = $wpdb->postmeta.post_id and $wpdb->postmeta.meta_key = %s $extraSQL", array('resmushed_cumulated_optimized_sizes') ); $optimized_sizes = $wpdb->get_results($query); $total_optimized_size = 0; foreach($optimized_sizes as $s){ $total_optimized_size += $s->meta_value; } $output['total_original_size'] = $total_original_size; $output['total_optimized_size'] = $total_optimized_size; $output['total_saved_size'] = $total_original_size - $total_optimized_size; $output['total_original_size_nice'] = reSmushitUI::sizeFormat($total_original_size); $output['total_optimized_size_nice'] = reSmushitUI::sizeFormat($total_optimized_size); $output['total_saved_size_nice'] = reSmushitUI::sizeFormat($total_original_size - $total_optimized_size); if($total_original_size == 0) $output['percent_reduction'] = 0; else $output['percent_reduction'] = 100*round(($total_original_size - $total_optimized_size)/$total_original_size,4) . ' %'; //number of thumbnails + original image $output['files_optimized'] = sizeof($optimized_sizes); $output['files_optimized_with_thumbnails'] = sizeof($optimized_sizes) * (sizeof(get_intermediate_image_sizes()) + 1); if(!$attachment_id){ $output['total_optimizations'] = get_option('resmushit_total_optimized'); $output['total_pictures'] = self::getCountAllPictures(); $output['total_pictures_with_thumbnails'] = self::getCountAllPictures() * (sizeof(get_intermediate_image_sizes()) + 1); } return $output; } /** * * Get the count of all images * * @param none * @return json of unsmushed image attachment IDs */ public static function getCountAllPictures(){ global $wpdb; $queryAllPictures = $wpdb->prepare( "select Count($wpdb->posts.ID) as count from $wpdb->posts inner join $wpdb->postmeta on $wpdb->posts.ID = $wpdb->postmeta.post_id and $wpdb->postmeta.meta_key = %s where $wpdb->posts.post_type = %s and $wpdb->posts.post_mime_type like %s and ($wpdb->posts.post_mime_type = 'image/jpeg' OR $wpdb->posts.post_mime_type = 'image/gif' OR $wpdb->posts.post_mime_type = 'image/png')", array('_wp_attachment_metadata','attachment', 'image%') ); $data = $wpdb->get_results($queryAllPictures); if(isset($data[0])) $data = $data[0]; if(!isset($data->count)) return 0; return $data->count; } /** * * Get a list of unoptimized images * * @param none * @return json of unsmushed image attachment IDs */ public static function getNonOptimizedPictures($id_only = FALSE){ global $wpdb; $tmp = array(); $unsmushed_images = array(); $files_too_big = array(); $already_optimized_images_array = array(); $disabled_images_array = array(); $files_not_found = array(); $extra_select = ""; if($id_only == FALSE) { $extra_select = ",POSTS.guid as guid, METAATTACH.meta_value as file_meta"; } $queryUnoptimizedPicture = $wpdb->prepare( "SELECT ATTACHMENTS.* FROM ( select POSTS.ID as ID, METAQLTY.meta_value as qlty, METADISABLED.meta_value as disabled $extra_select from $wpdb->posts as POSTS inner join $wpdb->postmeta as METAATTACH on POSTS.ID = METAATTACH.post_id and METAATTACH.meta_key = %s left join $wpdb->postmeta as METAQLTY on POSTS.ID = METAQLTY.post_id and METAQLTY.meta_key = %s left join $wpdb->postmeta as METADISABLED on POSTS.ID = METADISABLED.post_id and METADISABLED.meta_key = %s where POSTS.post_type = %s and (POSTS.post_mime_type = 'image/jpeg' OR POSTS.post_mime_type = 'image/gif' OR POSTS.post_mime_type = 'image/png') ORDER BY POSTS.post_date desc ) as ATTACHMENTS WHERE (ATTACHMENTS.qlty != '%s' OR ATTACHMENTS.qlty IS NULL) AND ATTACHMENTS.disabled IS NULL LIMIT %d", array('_wp_attachment_metadata','resmushed_quality','resmushed_disabled','attachment', self::getPictureQualitySetting(), self::MAX_ATTACHMENTS_REQ) ); // Get the images in the attachement table // Log::addTemp('UnOptimizedPictures Query' . $queryUnoptimizedPicture, debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)); $all_images = $wpdb->get_results($queryUnoptimizedPicture); foreach($all_images as $image){ $tmp = array(); $tmp['ID'] = $image->ID; $tmp['attachment_metadata'] = isset($image->file_meta) ? unserialize($image->file_meta) : array(); if( !file_exists(get_attached_file( $image->ID )) ) { $files_not_found[] = $tmp; continue; } //If filesize > 5MB, we do not optimize this image if( filesize(get_attached_file( $image->ID )) > self::MAX_FILESIZE ){ $files_too_big[] = $tmp; continue; } $unsmushed_images[] = $tmp; } return json_encode(array('nonoptimized' => $unsmushed_images, 'filestoobig' => $files_too_big, 'filesnotfound' => $files_not_found, 'totalresult' => count($all_images) )); } /** * * Return the number of unoptimized images * * @param none * @return number of unoptimized images to the current quality factor */ public static function getCountNonOptimizedPictures(){ $data = json_decode(self::getNonOptimizedPictures()); $output = array(); $output['nonoptimized'] = is_array($data->nonoptimized) ? sizeof($data->nonoptimized) : 0; $output['filesnotfound'] = is_array($data->filesnotfound) ? sizeof($data->filesnotfound) : 0; $output['filestoobig'] = is_array($data->filestoobig) ? sizeof($data->filestoobig) : 0; $output['totalresult'] = property_exists($data, 'totalresult') ? : 0; return $output; } /** * * Record in DB new status for optimization disabled state * * @param int $id ID of postID * @param string $state state of disable * @return none */ public static function updateDisabledState($id, $state){ //if we do not want this attachment to be resmushed. if($state == "true"){ update_post_meta($id, 'resmushed_disabled', 'disabled'); self::revert($id); return 'true'; } else { delete_post_meta($id, 'resmushed_disabled'); return 'false'; } } /** * * Get Disabled State * * @param int $attachment_id Post ID * @return boolean true if attachment is disabled */ public static function getDisabledState($attachment_id){ if(get_post_meta($attachment_id, 'resmushed_disabled')) return true; return false; } /** * * Detect unsmushed files by browsing the library directory * * @param none * @return none */ public static function detect_unsmushed_files() { $wp_upload_dir=wp_upload_dir(); return self::glob_recursive($wp_upload_dir['basedir'] . '/*-unsmushed.*'); } /** * * Find recursively files based on pattern * * @param string $pattern file search * @param boolean $flags * @return array * @author Mike * @link https://www.php.net/manual/en/function.glob.php#106595 */ protected static function glob_recursive($pattern, $flags = 0) { $files = glob($pattern, $flags); foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { $files = array_merge($files, self::glob_recursive($dir.'/'.basename($pattern), $flags)); } return $files; } /** * * retrieve Attachment ID from Path * from : https://pippinsplugins.com/retrieve-attachment-id-from-image-url/ * * @param imageURL * @return json object */ public static function resmushit_get_image_id($image_url) { global $wpdb; $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid='%s';", $image_url )); Log::addTemp('Attachment', $attachment); if (! isset($attachment[0])) { return false; } return $attachment[0]; } /** * * Get Last Quality Factor attached to an image * * @param int $attachment_id Post ID * @return int quality setting for this attachment */ public static function getAttachmentQuality($attachment_id){ $attachmentQuality = get_post_meta($attachment_id, 'resmushed_quality'); if(isset($attachmentQuality[0])) return $attachmentQuality[0]; return null; } /** * * Check if this Attachment was successfully optimized * * @param int $attachment_id Post ID * @return string $status */ public static function wasSuccessfullyUpdated($attachment_id){ if( self::getDisabledState( $attachment_id )) return 'disabled'; if (!file_exists(get_attached_file( $attachment_id ))) { Log::addError("Error! File " . get_attached_file( $attachment_id ) . " not found on disk."); return 'file_not_found'; } if( filesize(get_attached_file( $attachment_id )) > self::MAX_FILESIZE){ Log::addDebug('File too big' . $attachment_id); return 'file_too_big'; } if( self::getPictureQualitySetting() != self::getAttachmentQuality( $attachment_id )) { Log::addDebug('Quality setting failed'); return 'failed'; } return 'success'; } }