first
This commit is contained in:
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||||
|
||||
class ApiConverter extends MediaLibraryConverter
|
||||
{
|
||||
|
||||
const CONVERTABLE_EXTENSIONS = array( 'heic');
|
||||
|
||||
protected $requestAPIthumbnails = true;
|
||||
|
||||
|
||||
public function isConvertable()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$extension = $this->imageModel->getExtension();
|
||||
|
||||
// If extension is in list of allowed Api Converts.
|
||||
if (in_array($extension, static::CONVERTABLE_EXTENSIONS) && $extension !== 'png')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If file has not been converted in terms of file, but has a placeholder - process ongoing, so continue;
|
||||
if (false === $this->imageModel->getMeta()->convertMeta()->isConverted() && true === $this->imageModel->getMeta()->convertMeta()->hasPlaceHolder())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// File has been converted, not converting again.
|
||||
if (true === $this->imageModel->getMeta()->convertMeta()->isConverted())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create placeholder here.
|
||||
public function convert($args = array())
|
||||
{
|
||||
$defaults = array(
|
||||
'runReplacer' => true, // The replacer doesn't need running when the file is just uploaded and doing in handle upload hook.
|
||||
);
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$placeholderFile = $fs->getFile(\wpSPIO()->plugin_path('res/img/fileformat-heic-placeholder.jpg'));
|
||||
|
||||
// Convert runs when putting imageModel to queue format in the Queue classs. This could run without optimization (before) taking place and when accidentally running it more than once results in duplicate files / backups (img-1, img-2 etc). Check placeholder and baseName to prevent this. Assume already done when it has it .
|
||||
if ($this->imageModel->getMeta()->convertMeta()->hasPlaceHolder() && $this->imageModel->getMeta()->convertMeta()->getReplacementImageBase() !== false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// @todo Check replacementpath here. Rename main file - and backup - if numeration is needed.
|
||||
// @todo Also placeholder probably needs to be done each time to block current job in progress.
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replaceFile = $fs->getFile($replacementPath);
|
||||
// If filebase (filename without extension) is not the same, this indicates that a double is there and it's enumerated. Move backup accordingly.
|
||||
|
||||
$destinationFile = $fs->getFile($replacementPath);
|
||||
$copyok = $placeholderFile->copy($destinationFile);
|
||||
|
||||
if ($copyok)
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setFileFormat('heic');
|
||||
$this->imageModel->getMeta()->convertMeta()->setPlaceHolder(true);
|
||||
$this->imageModel->getMeta()->convertMeta()->setReplacementImageBase($destinationFile->getFileBase());
|
||||
$this->imageModel->saveMeta();
|
||||
|
||||
|
||||
// @todo Wip . Moved from handleConverted.
|
||||
// Backup basically. Do this first.
|
||||
$conversion_args = array('replacementPath' => $replacementPath);
|
||||
$prepared = $this->imageModel->conversionPrepare($conversion_args);
|
||||
if (false === $prepared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setTarget($destinationFile);
|
||||
// $params = array('success' => true, 'generate_metadata' => false);
|
||||
// $this->updateMetaData($params);
|
||||
|
||||
$fs->flushImage($this->imageModel);
|
||||
|
||||
if (true === $args['runReplacer'])
|
||||
{
|
||||
$result = $this->replacer->replace();
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
Log::addError('Failed to copy placeholder');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Restore from original file. Search and replace everything else to death.
|
||||
public function restore()
|
||||
{
|
||||
/*$params = array('restore' => true);
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$newExtension = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
|
||||
$oldFileName = $this->imageModel->getFileName(); // Old File Name, Still .jpg
|
||||
$newFileName = $this->imageModel->getFileBase() . '.' . $newExtension;
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$oldFileName = $this->imageModel->getOriginalFile()->getFileName();
|
||||
$newFileName = $this->imageModel->getOriginalFile()->getFileBase() . '.' . $newExtension;
|
||||
}
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$this->newFile = $fsNewFile;
|
||||
$this->setTarget($fsNewFile);
|
||||
|
||||
$this->updateMetaData($params);
|
||||
// $result = $this->replacer->replace();
|
||||
|
||||
$fs->flushImageCache(); */
|
||||
}
|
||||
|
||||
public function getCheckSum()
|
||||
{
|
||||
return 1; // done or not.
|
||||
}
|
||||
|
||||
public function handleConverted($optimizeData)
|
||||
{
|
||||
$this->setupReplacer();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
|
||||
/* $replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replacementFile = $fs->getFile($replacementPath);
|
||||
*/
|
||||
$replacementBase = $this->imageModel->getMeta()->convertMeta()->getReplacementImageBase();
|
||||
if (false === $replacementBase)
|
||||
{
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
$replacementFile = $fs->getFile($replacementPath);
|
||||
}
|
||||
else {
|
||||
$replacementPath = $replacementBase . '.jpg';
|
||||
$replacementFile = $fs->getFile($this->imageModel->getFileDir() . $replacementPath);
|
||||
}
|
||||
|
||||
// If -sigh- file has a placeholder, then do something with that.
|
||||
if (true === $this->imageModel->getMeta()->convertMeta()->hasPlaceHolder())
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setPlaceHolder(false);
|
||||
// $this->imageModel->getMeta()->convertMeta()->setReplacementImageBase(false);
|
||||
|
||||
// $attach_id = $this->imageModel->get('id');
|
||||
|
||||
// ReplacementFile as source should not point to the placeholder file
|
||||
$this->source_url = $fs->pathToUrl($replacementFile);
|
||||
$this->replacer->setSource($this->source_url);
|
||||
|
||||
$replacementFile->delete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (isset($optimizeData['files']) && isset($optimizeData['data']))
|
||||
{
|
||||
$files = $optimizeData['files'];
|
||||
$data = $optimizeData['data'];
|
||||
}
|
||||
else {
|
||||
Log::addError('Something went wrong with handleOptimized', $optimizeData);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mainImageKey = $this->imageModel->get('mainImageKey');
|
||||
$mainFile = (isset($files) && isset($files[$mainImageKey])) ? $files[$mainImageKey] : false;
|
||||
|
||||
if (false === $mainFile)
|
||||
{
|
||||
Log::addError('MainFile not set during success Api Conversion');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! isset($mainFile['image']) || ! isset($mainFile['image']['file']))
|
||||
{
|
||||
Log::addError('Optimizer didn\'t return file', $mainFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$tempFile = $fs->getFile($mainFile['image']['file']);
|
||||
|
||||
$res = $tempFile->copy($replacementFile);
|
||||
|
||||
if (true === $res)
|
||||
{
|
||||
$this->newFile = $replacementFile;
|
||||
$tempFile->delete();
|
||||
|
||||
$params = array('success' => true);
|
||||
$this->updateMetaData($params);
|
||||
|
||||
$result = true;
|
||||
|
||||
// if (true === $args['runReplacer'])
|
||||
// {
|
||||
$result = $this->replacer->replace();
|
||||
// }
|
||||
|
||||
// Conversion done, but backup results.
|
||||
$this->imageModel->conversionSuccess(array('omit_backup' => false));
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // class
|
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Replacer\Replacer as Replacer;
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Model\File\DirectoryModel as DirectoryModel;
|
||||
use ShortPixel\Model\File\FileModel as FileModel;
|
||||
use ShortPixel\Controller\ResponseController as ResponseController;
|
||||
|
||||
/* ShortPixel Image Optimiser Converters. Unified interface for handling conversion between file types */
|
||||
abstract class Converter
|
||||
{
|
||||
const CONVERTABLE_EXTENSIONS = array('png', 'heic');
|
||||
|
||||
const ERROR_LIBRARY = -1; /// PNG Library error
|
||||
const ERROR_PATHFAIL = -2; // Couldn't create replacement path
|
||||
const ERROR_RESULTLARGER = -3; // Converted file is bigger than the original
|
||||
const ERROR_WRITEERROR = -4; // Result file could not be written
|
||||
const ERROR_BACKUPERROR = -5; // Backup didn't work.
|
||||
const ERROR_TRANSPARENT = -6; // Transparency when it is not allowed.
|
||||
|
||||
protected $imageModel; // The current ImageModel from SPIO
|
||||
|
||||
// Method specific
|
||||
abstract public function convert($args = array());
|
||||
abstract public function isConvertable();
|
||||
abstract public function restore();
|
||||
abstract public function getCheckSum();
|
||||
|
||||
// Media Library specific
|
||||
abstract protected function updateMetaData($params);
|
||||
abstract public function getUpdatedMeta();
|
||||
abstract protected function setupReplacer();
|
||||
abstract protected function setTarget($file);
|
||||
|
||||
public function __construct($imageModel)
|
||||
{
|
||||
$this->imageModel = $imageModel;
|
||||
$this->imageModel->getMeta()->convertMeta()->setFileFormat($imageModel->getExtension());
|
||||
}
|
||||
|
||||
public function isConverterFor($extension)
|
||||
{
|
||||
if ($extension === $this->imageModel->getMeta()->convertMeta()->getFileFormat())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ForConversion: Return empty if file can't be converted or is already converrted
|
||||
public static function getConverter($imageModel, $forConversion = false)
|
||||
{
|
||||
$extension = $imageModel->getExtension();
|
||||
|
||||
$converter = false;
|
||||
|
||||
$converter = self::getConverterByExt($extension, $imageModel);
|
||||
|
||||
// No Support (yet)
|
||||
if ($imageModel->get('type') == 'custom')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second option for conversion is image who have been placeholdered.
|
||||
if (true === $imageModel->getMeta()->convertMeta()->hasPlaceHolder() && false === $imageModel->getMeta()->convertMeta()->isConverted() && ! is_null($imageModel->getMeta()->convertMeta()->getFileFormat()))
|
||||
{
|
||||
$converter = self::getConverterByExt($imageModel->getMeta()->convertMeta()->getFileFormat(), $imageModel);
|
||||
}
|
||||
|
||||
if (true === $forConversion) // don't check more.
|
||||
{
|
||||
return $converter;
|
||||
}
|
||||
|
||||
if (false === $converter)
|
||||
{
|
||||
if ($imageModel->getMeta()->convertMeta()->isConverted() && ! is_null($imageModel->getMeta()->convertMeta()->getFileFormat()) )
|
||||
{
|
||||
$converter = self::getConverterByExt($imageModel->getMeta()->convertMeta()->getFileFormat(), $imageModel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $converter;
|
||||
}
|
||||
|
||||
/** Own function to get a unique filename since the WordPress wp_unique_filename seems to not function properly w/ thumbnails */
|
||||
protected function unique_file(DirectoryModel $dir, FileModel $file, $number = 0)
|
||||
{
|
||||
if (! $file->exists())
|
||||
return $file;
|
||||
|
||||
if ($file->is_virtual())
|
||||
{
|
||||
return $file;
|
||||
}
|
||||
|
||||
$number = 0;
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$base = $file->getFileBase();
|
||||
$ext = $file->getExtension();
|
||||
|
||||
while($file->exists())
|
||||
{
|
||||
$number++;
|
||||
$numberbase = $base . '-' . $number;
|
||||
Log::addDebug('check for unique file -- ' . $dir->getPath() . $numberbase . '.' . $ext);
|
||||
$file = $fs->getFile($dir->getPath() . $numberbase . '.' . $ext);
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
protected function getReplacementPath()
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$filename = $this->imageModel->getFileName();
|
||||
$newFileName = $this->imageModel->getFileBase() . '.jpg'; // convert extension to .jpg
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$uniqueFile = $this->unique_file( $this->imageModel->getFileDir(), $fsNewFile);
|
||||
$newPath = $uniqueFile->getFullPath(); //(string) $fsFile->getFileDir() . $uniquepath;
|
||||
|
||||
if (! $this->imageModel->getFileDir()->is_writable())
|
||||
{
|
||||
Log::addWarn('Replacement path for PNG not writable ' . $this->imageModel->getFileDir()->getPath());
|
||||
$msg = __('Replacement path for PNG not writable', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->setTarget($uniqueFile);
|
||||
|
||||
return $newPath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static function getConverterByExt($ext, $imageModel)
|
||||
{
|
||||
$converter = false;
|
||||
switch($ext)
|
||||
{
|
||||
case 'png':
|
||||
$converter = new PNGConverter($imageModel);
|
||||
break;
|
||||
case 'heic':
|
||||
$converter = new ApiConverter($imageModel);
|
||||
break;
|
||||
|
||||
}
|
||||
return $converter;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\Replacer\Replacer as Replacer;
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
|
||||
/* Abstract base to use for image converters. Handles media library related functions ( replacing ) */
|
||||
abstract class MediaLibraryConverter extends Converter
|
||||
{
|
||||
protected $source_url;
|
||||
protected $replacer; // Replacer class Object.
|
||||
protected $newFile; // The newFile Object.
|
||||
|
||||
public function getUpdatedMeta()
|
||||
{
|
||||
$id = $this->imageModel->get('id');
|
||||
$meta = wp_get_attachment_metadata($id); // reset the metadata because we are on the hook.
|
||||
return $meta;
|
||||
}
|
||||
|
||||
protected function setupReplacer()
|
||||
{
|
||||
$this->replacer = new Replacer();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$url = $fs->pathToUrl($this->imageModel);
|
||||
|
||||
if ($this->imageModel->isScaled()) // @todo Test these assumptions
|
||||
{
|
||||
$url = $fs->pathToUrl($this->imageModel->getOriginalFile());
|
||||
}
|
||||
|
||||
$this->source_url = $url;
|
||||
$this->replacer->setSource($url);
|
||||
|
||||
$this->replacer->setSourceMeta($this->imageModel->getWPMetaData());
|
||||
|
||||
}
|
||||
|
||||
protected function setTarget($newFile)
|
||||
{
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
$this->newFile = $newFile; // set target newFile.
|
||||
|
||||
$url = $fs->pathToUrl($this->imageModel);
|
||||
$newUrl = str_replace($this->imageModel->getFileName(), $newFile->getFileName(), $url);
|
||||
|
||||
$this->replacer->setTarget($newUrl);
|
||||
}
|
||||
|
||||
protected function updateMetaData($params)
|
||||
{
|
||||
$defaults = array(
|
||||
'success' => false,
|
||||
'restore' => false,
|
||||
'generate_metadata' => true,
|
||||
);
|
||||
|
||||
$params = wp_parse_args($params, $defaults);
|
||||
|
||||
$newFile = $this->newFile;
|
||||
$fullPath = $newFile->getFullPath();
|
||||
|
||||
if (! is_object($newFile))
|
||||
{
|
||||
Log::addError('Update metadata failed. NewFile not properly set', $newFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
$attach_id = $this->imageModel->get('id');
|
||||
|
||||
$WPMLduplicates = $this->imageModel->getWPMLDuplicates();
|
||||
|
||||
$attachment = get_post($attach_id);
|
||||
|
||||
$guid = $attachment->guid;
|
||||
|
||||
// This action prevents images from being regenerated on the thumbnail hook.
|
||||
do_action('shortpixel-thumbnails-before-regenerate', $attach_id );
|
||||
do_action('shortpixel/converter/prevent-offload', $attach_id);
|
||||
|
||||
// Update attached_file
|
||||
$bool = update_attached_file($attach_id, $newFile->getFullPath() );
|
||||
if (false === $bool)
|
||||
return false;
|
||||
|
||||
|
||||
// Update post mime on attachment
|
||||
if (isset($params['success']) && true === $params['success'])
|
||||
{
|
||||
$newExt = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
$newGuid = str_replace($guid, $newExt, 'jpg'); // This probable doesn't work bcause doesn't update Guid with this function.
|
||||
$post_ar = array('ID' => $attach_id, 'post_mime_type' => 'image/jpeg', 'guid' => $newGuid);
|
||||
}
|
||||
elseif ( isset($params['restore']) && true === $params['restore'] )
|
||||
{
|
||||
$oldExt = $this->imageModel->getMeta()->convertMeta()->getFileFormat();
|
||||
$newGuid = str_replace($guid, 'jpg', $oldExt);
|
||||
$post_ar = array('ID' => $attach_id, 'post_mime_type' => 'image/png', 'guid' => $newGuid);
|
||||
}
|
||||
|
||||
$result = wp_update_post($post_ar);
|
||||
|
||||
if ($result === 0 || is_wp_error($result))
|
||||
{
|
||||
Log::addError('Issue updating WP Post converter - ' . $attach_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
$metadata = wp_get_attachment_metadata($attach_id);
|
||||
|
||||
if (true === $params['generate_metadata'])
|
||||
{
|
||||
$attachment = get_post( $attach_id );
|
||||
|
||||
$new_metadata = wp_generate_attachment_metadata($attach_id, $newFile->getFullPath());
|
||||
|
||||
}
|
||||
else {
|
||||
$new_metadata = array();
|
||||
}
|
||||
|
||||
// Metadata might not be array when add_attachment is calling this hook via AdminController ( PNG2JPG)
|
||||
if (is_array($metadata))
|
||||
{
|
||||
// Original Image in the new situation can not be there. Don't preserve it.
|
||||
if (isset($metadata['original_image']) && ! isset($new_metadata['original_image']) )
|
||||
{
|
||||
unset($metadata['original_image']);
|
||||
}
|
||||
|
||||
$new_metadata = array_merge($metadata, $new_metadata); // merge to preserve other custom metadata
|
||||
|
||||
}
|
||||
|
||||
if (isset($params['success']) && true === $params['success'])
|
||||
{
|
||||
do_action('shortpixel/converter/prevent-offload-off', $attach_id);
|
||||
}
|
||||
|
||||
if (is_array($new_metadata) && count($new_metadata) > 0)
|
||||
{
|
||||
$bool = wp_update_attachment_metadata($attach_id, $new_metadata);
|
||||
}
|
||||
|
||||
// Restore -sigh- fires off a later signal, because on the succesHandler in MediaLIbraryModel it may copy back backups.
|
||||
if (isset($params['restore']) && true === $params['restore'])
|
||||
{
|
||||
do_action('shortpixel/converter/prevent-offload-off', $attach_id);
|
||||
}
|
||||
|
||||
if (is_array($WPMLduplicates) && count($WPMLduplicates) > 0)
|
||||
{
|
||||
foreach ($WPMLduplicates as $duplicate_id)
|
||||
{
|
||||
update_attached_file($duplicate_id, $newFile->getFullPath() );
|
||||
wp_update_attachment_metadata($duplicate_id, $new_metadata);
|
||||
|
||||
$post_ar["ID"] = $duplicate_id;
|
||||
wp_update_post($post_ar);
|
||||
}
|
||||
}
|
||||
|
||||
$this->replacer->setTargetMeta($new_metadata);
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // class
|
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
namespace ShortPixel\Model\Converter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||||
use ShortPixel\Model\Image\ImageModel as ImageModel;
|
||||
use ShortPixel\Model\File\DirectoryModel as DirectoryModel;
|
||||
use ShortPixel\Model\File\FileModel as FileModel;
|
||||
use ShortPixel\Notices\NoticeController as Notices;
|
||||
|
||||
use ShortPixel\Controller\ResponseController as ResponseController;
|
||||
|
||||
use ShortPixel\Helper\DownloadHelper as DownloadHelper;
|
||||
|
||||
|
||||
class PNGConverter extends MediaLibraryConverter
|
||||
{
|
||||
protected $instance;
|
||||
|
||||
protected $current_image; // The current PHP image resource in memory
|
||||
protected $virtual_filesize;
|
||||
protected $replacer; // Replacer class Object.
|
||||
|
||||
protected $converterActive = false;
|
||||
protected $forceConvertTransparent = false;
|
||||
|
||||
protected $lastError;
|
||||
|
||||
protected $settingCheckSum;
|
||||
|
||||
|
||||
public function __construct($imageModel)
|
||||
{
|
||||
parent::__construct($imageModel);
|
||||
|
||||
$settings = \wpSPIO()->settings();
|
||||
$env = \wpSPIO()->env();
|
||||
|
||||
|
||||
$this->converterActive = (intval($settings->png2jpg) > 0) ? true : false;
|
||||
|
||||
if ($env->is_gd_installed === false)
|
||||
{
|
||||
$this->converterActive = false;
|
||||
$this->lastError = __('GD library is not active on this installation. Can\'t convert images to PNG', 'shortpixel-image-optimiser');
|
||||
}
|
||||
|
||||
$this->forceConvertTransparent = ($settings->png2jpg == 2) ? true : false;
|
||||
|
||||
// If conversion is tried, but failed somehow, it will never try it again, even after changing settings. This should prevent that switch.
|
||||
$this->settingCheckSum = intval($settings->png2jpg) + intval($settings->backupImages);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function isConvertable()
|
||||
{
|
||||
$imageModel = $this->imageModel;
|
||||
|
||||
// Settings
|
||||
if ($this->converterActive === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extension
|
||||
if ($imageModel->getExtension() !== 'png') // not a png ext. fail silently.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Existence
|
||||
if (! $imageModel->exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === $imageModel->getMeta()->convertMeta()->isConverted() || true === $this->hasTried($imageModel->getMeta()->convertMeta()->didTry()) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasTried($checksum)
|
||||
{
|
||||
if ( intval($checksum) === $this->getCheckSum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function convert($args = array())
|
||||
{
|
||||
if (! $this->isConvertable($this->imageModel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$defaults = array(
|
||||
'runReplacer' => true, // The replacer doesn't need running when the file is just uploaded and doing in handle upload hook.
|
||||
);
|
||||
|
||||
$conversionArgs = array('checksum' => $this->getCheckSum());
|
||||
|
||||
$this->setupReplacer();
|
||||
$this->raiseMemoryLimit();
|
||||
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('ApiConverter replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
$replaceFile = $fs->getFile($replacementPath);
|
||||
Log::addDebug('Image replacement base : ' . $replaceFile->getFileBase());
|
||||
$this->imageModel->getMeta()->convertMeta()->setReplacementImageBase($replaceFile->getFileBase());
|
||||
|
||||
$prepared = $this->imageModel->conversionPrepare($conversionArgs);
|
||||
if (false === $prepared)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
|
||||
if ($this->forceConvertTransparent === false && $this->isTransparent())
|
||||
{
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_TRANSPARENT);
|
||||
$this->imageModel->conversionFailed($conversionArgs);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::addDebug('Starting PNG conversion of #' . $this->imageModel->get('id'));
|
||||
$bool = $this->run();
|
||||
|
||||
if (true === $bool)
|
||||
{
|
||||
$params = array('success' => true);
|
||||
$this->updateMetaData($params);
|
||||
|
||||
$result = true;
|
||||
if (true === $args['runReplacer'])
|
||||
{
|
||||
$result = $this->replacer->replace();
|
||||
}
|
||||
|
||||
if (is_array($result))
|
||||
{
|
||||
foreach($result as $error)
|
||||
Notices::addError($error);
|
||||
}
|
||||
|
||||
|
||||
$this->imageModel->conversionSuccess($conversionArgs);
|
||||
|
||||
// new hook.
|
||||
do_action('shortpixel/image/convertpng2jpg_success', $this->imageModel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->imageModel->conversionFailed($conversionArgs);
|
||||
|
||||
//legacy. Note at this point metadata has not been updated.
|
||||
do_action('shortpixel/image/convertpng2jpg_after', $this->imageModel, $args);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getCheckSum()
|
||||
{
|
||||
return intval($this->settingCheckSum);
|
||||
}
|
||||
|
||||
|
||||
protected function run()
|
||||
{
|
||||
do_action('shortpixel/image/convertpng2jpg_before', $this->imageModel);
|
||||
|
||||
$img = $this->getPNGImage();
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$width = $this->imageModel->get('width');
|
||||
$height = $this->imageModel->get('height');
|
||||
|
||||
// If imageModel doesn't have proper width / height set. This can happen with remote files.
|
||||
if (! is_int($width) && ! $width > 0)
|
||||
{
|
||||
$width = imagesx($img);
|
||||
}
|
||||
if (! is_int($height) && ! $height > 0)
|
||||
{
|
||||
$height = imagesy($img);
|
||||
}
|
||||
|
||||
Log::addDebug("PNG2JPG doConvert width $width height $height", memory_get_usage());
|
||||
$bg = imagecreatetruecolor($width, $height);
|
||||
|
||||
if(false === $bg || false === $img)
|
||||
{
|
||||
Log::addError('ImageCreateTrueColor failed');
|
||||
if (false === $bg)
|
||||
{
|
||||
$msg = __('Creating an TrueColor Image failed - Possible library error', 'shortpixel-image-optimiser');
|
||||
}
|
||||
elseif (false === $img)
|
||||
{
|
||||
$msg = __('Image source failed - Check if source image is PNG and library is working', 'shortpixel-image-optimiser');
|
||||
}
|
||||
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_LIBRARY);
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 255, 255));
|
||||
imagealphablending($bg, 1);
|
||||
imagecopy($bg, $img, 0, 0, 0, 0, $width, $height);
|
||||
|
||||
// $fsFile = $fs->getFile($image); // the original png file
|
||||
// @todo Add ResponseController support to here and getReplacementPath.
|
||||
$replacementPath = $this->getReplacementPath();
|
||||
if (false === $replacementPath)
|
||||
{
|
||||
Log::addWarn('Png2Jpg replacement path failed');
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_PATHFAIL);
|
||||
|
||||
return false; // @todo Add ResponseController something here.
|
||||
}
|
||||
|
||||
// check old filename, replace with uniqued filename.
|
||||
|
||||
/** Quality is set to 90 and not using WP defaults (or filter) for good reason. Lower settings very quickly degrade the libraries output quality. Better to leave this hardcoded at 90 and let the ShortPixel API handle the optimization **/
|
||||
if ($bool = imagejpeg($bg, $replacementPath, 90)) {
|
||||
Log::addDebug("PNG2JPG doConvert created JPEG at $replacementPath");
|
||||
$newSize = filesize($replacementPath); // This might invoke wrapper but ok
|
||||
|
||||
if (! is_null($this->virtual_filesize))
|
||||
{
|
||||
$origSize = $this->virtual_filesize;
|
||||
}
|
||||
else {
|
||||
$origSize = $this->imageModel->getFileSize();
|
||||
}
|
||||
|
||||
// Reload the file we just wrote.
|
||||
$newFile = $fs->getFile($replacementPath);
|
||||
|
||||
|
||||
if($newSize > $origSize * 0.95 || $newSize == 0) {
|
||||
//if the image is not 5% smaller, don't bother.
|
||||
//if the size is 0, a conversion (or disk write) problem happened, go on with the PNG
|
||||
Log::addDebug("PNG2JPG converted image is larger ($newSize vs. $origSize), keeping the PNG");
|
||||
$msg = __('Converted file is larger. Keeping original file', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
$newFile->delete();
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_RESULTLARGER);
|
||||
|
||||
return false;
|
||||
}
|
||||
elseif (! $newFile->exists())
|
||||
{
|
||||
Log::addWarn('PNG imagejpeg file not written!', $uniqueFile->getFileName() );
|
||||
$msg = __('Error - PNG file not written', 'shortpixel-image-optimiser');
|
||||
ResponseController::addData($this->imageModel->get('id'), 'message', $msg);
|
||||
$this->imageModel->getMeta()->convertMeta()->setError(self::ERROR_WRITEERROR);
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$this->newFile = $newFile;
|
||||
}
|
||||
|
||||
|
||||
Log::addDebug('PNG2jPG Converted');
|
||||
}
|
||||
|
||||
$fs->flushImageCache();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function restore()
|
||||
{
|
||||
$params = array(
|
||||
'restore' => true,
|
||||
);
|
||||
$fs = \wpSPIO()->filesystem();
|
||||
|
||||
$this->setupReplacer();
|
||||
|
||||
$oldFileName = $this->imageModel->getFileName(); // Old File Name, Still .jpg
|
||||
$newFileName = $this->imageModel->getFileBase() . '.png';
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$oldFileName = $this->imageModel->getOriginalFile()->getFileName();
|
||||
$newFileName = $this->imageModel->getOriginalFile()->getFileBase() . '.png';
|
||||
}
|
||||
|
||||
$fsNewFile = $fs->getFile($this->imageModel->getFileDir() . $newFileName);
|
||||
|
||||
$this->newFile = $fsNewFile;
|
||||
$this->setTarget($fsNewFile);
|
||||
|
||||
$this->updateMetaData($params);
|
||||
$result = $this->replacer->replace();
|
||||
|
||||
$fs->flushImageCache();
|
||||
|
||||
}
|
||||
|
||||
protected function isTransparent() {
|
||||
$isTransparent = false;
|
||||
$transparent_pixel = $bg = false;
|
||||
|
||||
$imagePath = $this->imageModel->getFullPath();
|
||||
|
||||
// Check for transparency at the bit path.
|
||||
if(ord(file_get_contents($imagePath, false, null, 25, 1)) & 4) {
|
||||
Log::addDebug("PNG2JPG: 25th byte has third bit 1 - transparency");
|
||||
$isTransparent = true;
|
||||
// return true;
|
||||
} else {
|
||||
|
||||
$contents = file_get_contents($imagePath);
|
||||
if (stripos($contents, 'PLTE') !== false && stripos($contents, 'tRNS') !== false) {
|
||||
$isTransparent = true;
|
||||
}
|
||||
if (false === $isTransparent) {
|
||||
|
||||
$width = $this->imageModel->get('width');
|
||||
$height = $this->imageModel->get('height');
|
||||
Log::addDebug("PNG2JPG Image width: " . $width . " height: " . $height . " aprox. size: " . round($width*$height*5/1024/1024) . "M memory limit: " . ini_get('memory_limit') . " USED: " . memory_get_usage());
|
||||
|
||||
$image = $this->getPNGImage();
|
||||
if (false === $image)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Log::addDebug("PNG2JPG width $width height $height. Now checking pixels.");
|
||||
//run through pixels until transparent pixel is found:
|
||||
for ($i = 0; $i < $width; $i++) {
|
||||
for ($j = 0; $j < $height; $j++) {
|
||||
$rgba = imagecolorat($image, $i, $j);
|
||||
if (($rgba & 0x7F000000) >> 24) {
|
||||
$isTransparent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // non-transparant.
|
||||
|
||||
Log::addDebug("PNG2JPG is " . (false === $isTransparent ? " not" : "") . " transparent");
|
||||
|
||||
return $isTransparent;
|
||||
|
||||
}
|
||||
|
||||
/** Try to load resource and an PNG via library */
|
||||
protected function getPNGImage()
|
||||
{
|
||||
if (is_object($this->current_image))
|
||||
{
|
||||
return $this->current_image;
|
||||
}
|
||||
|
||||
if ($this->imageModel->isScaled())
|
||||
{
|
||||
$imagePath = $this->imageModel->getOriginalFile()->getFullPath();
|
||||
}
|
||||
else {
|
||||
$imagePath = $this->imageModel->getFullPath();
|
||||
}
|
||||
|
||||
if (true === $this->imageModel->is_virtual())
|
||||
{
|
||||
$downloadHelper = DownloadHelper::getInstance();
|
||||
Log::addDebug('PNG converter: Item is remote, attempting to download');
|
||||
|
||||
$tempFile = $downloadHelper->downloadFile($this->imageModel->getURL());
|
||||
if (is_object($tempFile))
|
||||
{
|
||||
$imagePath = $tempFile->getFullPath();
|
||||
$this->virtual_filesize = $tempFile->getFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$image = @imagecreatefrompng($imagePath);
|
||||
if (! $image)
|
||||
{
|
||||
$this->current_image = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->current_image = $image;
|
||||
}
|
||||
|
||||
return $this->current_image;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Try to increase limits when doing heavy processing
|
||||
private function raiseMemoryLimit()
|
||||
{
|
||||
if(function_exists('wp_raise_memory_limit')) {
|
||||
wp_raise_memory_limit( 'image' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // class
|
Reference in New Issue
Block a user