592 lines
15 KiB
PHP
592 lines
15 KiB
PHP
|
<?php
|
||
|
namespace ShortPixel\Model\Image;
|
||
|
|
||
|
use ShortPixel\Helper\DownloadHelper as DownloadHelper;
|
||
|
use ShortPixel\Helper\UtilHelper as UtilHelper;
|
||
|
|
||
|
|
||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
exit; // Exit if accessed directly.
|
||
|
}
|
||
|
|
||
|
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
|
||
|
|
||
|
use \ShortPixel\Model\File\FileModel as FileModel;
|
||
|
|
||
|
// Represent a thumbnail image / limited image in mediaLibrary.
|
||
|
class MediaLibraryThumbnailModel extends \ShortPixel\Model\Image\ImageModel
|
||
|
{
|
||
|
|
||
|
public $name;
|
||
|
|
||
|
/* public $width;
|
||
|
public $height;
|
||
|
public $mime; */
|
||
|
protected $prevent_next_try = false;
|
||
|
protected $is_main_file = false;
|
||
|
protected $is_retina = false; // diffentiate from thumbnail / retina.
|
||
|
protected $id; // this is the parent attachment id
|
||
|
protected $size; // size name of image in WP, if applicable.
|
||
|
protected $sizeDefinition; // size width / height / crop according to WordPress
|
||
|
|
||
|
public function __construct($path, $id, $size)
|
||
|
{
|
||
|
|
||
|
parent::__construct($path);
|
||
|
$this->image_meta = new ImageThumbnailMeta();
|
||
|
$this->id = $id;
|
||
|
$this->imageType = self::IMAGE_TYPE_THUMB;
|
||
|
$this->size = $size;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
protected function loadMeta()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
protected function saveMeta()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public function __debugInfo() {
|
||
|
return array(
|
||
|
'image_meta' => $this->image_meta,
|
||
|
'name' => $this->name,
|
||
|
'path' => $this->getFullPath(),
|
||
|
'size' => $this->size,
|
||
|
'width' => $this->get('width'),
|
||
|
'height' => $this->get('height'),
|
||
|
'exists' => ($this->exists()) ? 'yes' : 'no',
|
||
|
'is_virtual' => ($this->is_virtual()) ? 'yes' : 'no',
|
||
|
'wordpress_size' => $this->sizeDefinition,
|
||
|
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/** Set the meta name of thumbnail. */
|
||
|
public function setName($name)
|
||
|
{
|
||
|
$this->name = $name;
|
||
|
}
|
||
|
|
||
|
public function setSizeDefinition($sizedef)
|
||
|
{
|
||
|
$this->sizeDefinition = $sizedef;
|
||
|
}
|
||
|
|
||
|
public function setImageType($type)
|
||
|
{
|
||
|
$this->imageType = $type;
|
||
|
}
|
||
|
|
||
|
public function getRetina()
|
||
|
{
|
||
|
if ($this->is_virtual())
|
||
|
{
|
||
|
$fs = \wpSPIO()->filesystem();
|
||
|
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||
|
$virtualFile = $fs->getFile($filepath);
|
||
|
|
||
|
$filebase = $virtualFile->getFileBase();
|
||
|
$filepath = (string) $virtualFile->getFileDir();
|
||
|
$extension = $virtualFile->getExtension();
|
||
|
|
||
|
// This function needs an hard check on file exists, which might not be wanted.
|
||
|
if (false === \wpSPIO()->env()->useVirtualHeavyFunctions())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else {
|
||
|
$filebase = $this->getFileBase();
|
||
|
$filepath = (string) $this->getFileDir();
|
||
|
$extension = $this->getExtension();
|
||
|
}
|
||
|
|
||
|
$retina = new MediaLibraryThumbnailModel($filepath . $filebase . '@2x.' . $extension, $this->id, $this->size); // mind the dot in after 2x
|
||
|
$retina->setName($this->size);
|
||
|
$retina->setImageType(self::IMAGE_TYPE_RETINA);
|
||
|
|
||
|
$retina->is_retina = true;
|
||
|
|
||
|
$forceCheck = true;
|
||
|
if ($retina->exists($forceCheck))
|
||
|
return $retina;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public function isFileTypeNeeded($type = 'webp')
|
||
|
{
|
||
|
// pdf extension can be optimized, but don't come with these filetypes
|
||
|
if ($this->getExtension() == 'pdf')
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($type == 'webp')
|
||
|
$file = $this->getWebp();
|
||
|
elseif ($type == 'avif')
|
||
|
$file = $this->getAvif();
|
||
|
|
||
|
if ( ($this->isThumbnailProcessable() || $this->isOptimized()) && $file === false) // if no file, it can be optimized.
|
||
|
return true;
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// @param FileDelete can be false. I.e. multilang duplicates might need removal of metadata, but not images.
|
||
|
public function onDelete($fileDelete = true)
|
||
|
{
|
||
|
if ($fileDelete == true)
|
||
|
$bool = parent::onDelete();
|
||
|
else {
|
||
|
$bool = true;
|
||
|
}
|
||
|
|
||
|
// minimally reset all the metadata.
|
||
|
if ($this->is_main_file)
|
||
|
{
|
||
|
$this->image_meta = new ImageMeta();
|
||
|
}
|
||
|
else {
|
||
|
$this->image_meta = new ImageThumbnailMeta();
|
||
|
}
|
||
|
|
||
|
return $bool;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
protected function setMetaObj($metaObj)
|
||
|
{
|
||
|
$this->image_meta = clone $metaObj;
|
||
|
}
|
||
|
|
||
|
protected function getMetaObj()
|
||
|
{
|
||
|
return $this->image_meta;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// get_path param see MediaLibraryModel
|
||
|
// This should be unused at the moment!
|
||
|
public function getOptimizeUrls()
|
||
|
{
|
||
|
if (! $this->isProcessable() )
|
||
|
return false;
|
||
|
|
||
|
$url = $this->getURL();
|
||
|
|
||
|
if (! $url)
|
||
|
{
|
||
|
return false; //nothing
|
||
|
}
|
||
|
|
||
|
return $url;
|
||
|
}
|
||
|
|
||
|
public function getURL()
|
||
|
{
|
||
|
$fs = \wpSPIO()->filesystem();
|
||
|
|
||
|
if ($this->size == 'original' && ! $this->get('is_retina'))
|
||
|
{
|
||
|
$url = wp_get_original_image_url($this->id);
|
||
|
}
|
||
|
elseif ($this->isUnlisted())
|
||
|
{
|
||
|
$url = $fs->pathToUrl($this);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We can't trust higher lever function, or any WP functions. I.e. Woocommerce messes with the URL's if they like so.
|
||
|
// So get it from intermediate and if that doesn't work, default to pathToUrl - better than nothing.
|
||
|
// https://app.asana.com/0/1200110778640816/1202589533659780
|
||
|
$size_array = image_get_intermediate_size($this->id, $this->size);
|
||
|
|
||
|
if ($size_array === false || ! isset($size_array['url']))
|
||
|
{
|
||
|
$url = $fs->pathToUrl($this);
|
||
|
}
|
||
|
elseif (isset($size_array['url']))
|
||
|
{
|
||
|
$url = $size_array['url'];
|
||
|
// Even this can go wrong :/
|
||
|
if (strpos($url, $this->getFileName() ) === false)
|
||
|
{
|
||
|
// Taken from image_get_intermediate_size if somebody still messes with the filters.
|
||
|
$mainurl = wp_get_attachment_url( $this->id);
|
||
|
$url = path_join( dirname( $mainurl ), $this->getFileName() );
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return $this->fs()->checkURL($url);
|
||
|
}
|
||
|
|
||
|
// Just a placeholder for abstract, shouldn't do anything.
|
||
|
public function getImprovements()
|
||
|
{
|
||
|
return parent::getImprovements();
|
||
|
}
|
||
|
|
||
|
|
||
|
public function getBackupFileName()
|
||
|
{
|
||
|
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||
|
if (false == $mainFile)
|
||
|
{
|
||
|
return parent::getBackupFileName();
|
||
|
}
|
||
|
|
||
|
if ($mainFile->getMeta()->convertMeta()->getReplacementImageBase() !== false)
|
||
|
{
|
||
|
if ($this->is_main_file)
|
||
|
return $mainFile->getMeta()->convertMeta()->getReplacementImageBase() . '.' . $this->getExtension();
|
||
|
else {
|
||
|
// $fileBaseNoSize =
|
||
|
$name = str_replace($mainFile->getFileBase(), $mainFile->getMeta()->convertMeta()->getReplacementImageBase(), $this->getFileName());
|
||
|
|
||
|
return $name;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return parent::getBackupFileName();
|
||
|
}
|
||
|
|
||
|
|
||
|
protected function preventNextTry($reason = '')
|
||
|
{
|
||
|
$this->prevent_next_try = $reason;
|
||
|
}
|
||
|
|
||
|
// Don't ask thumbnails this, only the main image
|
||
|
public function isOptimizePrevented()
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Don't ask thumbnails this, only the main image
|
||
|
public function resetPrevent()
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
protected function isThumbnailProcessable()
|
||
|
{
|
||
|
// if thumbnail processing is off, thumbs are never processable.
|
||
|
// This is also used by main file, so check for that!
|
||
|
|
||
|
if ( $this->excludeThumbnails() && $this->is_main_file === false && $this->get('imageType') !== self::IMAGE_TYPE_ORIGINAL)
|
||
|
{
|
||
|
$this->processable_status = self::P_EXCLUDE_SIZE;
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$bool = parent::isProcessable();
|
||
|
|
||
|
return $bool;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Function to check if said thumbnail is a WP-native or something SPIO added as unlisted
|
||
|
*
|
||
|
*
|
||
|
*/
|
||
|
protected function isUnlisted()
|
||
|
{
|
||
|
if (! is_null($this->getMeta('file')))
|
||
|
return true;
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// !Important . This doubles as checking excluded image sizes.
|
||
|
protected function isSizeExcluded()
|
||
|
{
|
||
|
|
||
|
$excludeSizes = \wpSPIO()->settings()->excludeSizes;
|
||
|
|
||
|
if (is_array($excludeSizes) && in_array($this->name, $excludeSizes))
|
||
|
{
|
||
|
$this->processable_status = self::P_EXCLUDE_SIZE;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$bool = parent::isSizeExcluded();
|
||
|
|
||
|
return $bool;
|
||
|
}
|
||
|
|
||
|
public function isProcessableFileType($type = 'webp')
|
||
|
{
|
||
|
// Prevent webp / avif processing for thumbnails if this is off. Exclude main file
|
||
|
if ($this->excludeThumbnails() === true && $this->is_main_file === false )
|
||
|
return false;
|
||
|
|
||
|
return parent::isProcessableFileType($type);
|
||
|
}
|
||
|
|
||
|
protected function getExcludePatterns()
|
||
|
{
|
||
|
$args = array(
|
||
|
'filter' => true,
|
||
|
'thumbname' => $this->name,
|
||
|
'is_thumbnail' => (true === $this->is_main_file) ? false : true,
|
||
|
);
|
||
|
|
||
|
// @todo Find a way to cache IsProcessable perhaps due to amount of checks being done. Should be release in flushOptimizeCache or elsewhere (?)
|
||
|
|
||
|
// $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
|
||
|
|
||
|
$patterns = UtilHelper::getExclusions($args);
|
||
|
// echo "<PRE>"; print_r($args); print_r($patterns); echo "</PRE>";
|
||
|
return $patterns;
|
||
|
}
|
||
|
|
||
|
protected function excludeThumbnails()
|
||
|
{
|
||
|
return (! \wpSPIO()->settings()->processThumbnails);
|
||
|
}
|
||
|
|
||
|
public function hasBackup($args = array())
|
||
|
{
|
||
|
$defaults = array(
|
||
|
'forceConverted' => false,
|
||
|
'noConversionCheck' => false, // do not check on mainfile, this loops when used in loadMeta / legacyConversion
|
||
|
);
|
||
|
$args = wp_parse_args($args, $defaults);
|
||
|
|
||
|
// @todo This can probably go.
|
||
|
if (true === $args['noConversionCheck'])
|
||
|
{
|
||
|
return parent::hasBackup();
|
||
|
}
|
||
|
|
||
|
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||
|
if (false == $mainFile)
|
||
|
{
|
||
|
return parent::hasBackup();
|
||
|
|
||
|
}
|
||
|
|
||
|
// When main file and converted and omitBackup is true ( only original backup ) and not forced.
|
||
|
$loadRegular= (false === $mainFile->getMeta()->convertMeta()->isConverted() ||
|
||
|
false === $mainFile->getMeta()->convertMeta()->omitBackup()) && false === $args['forceConverted'];
|
||
|
|
||
|
if (true === $loadRegular)
|
||
|
{
|
||
|
return parent::hasBackup();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$directory = $this->getBackupDirectory();
|
||
|
$converted_ext = $mainFile->getMeta()->convertMeta()->getFileFormat();
|
||
|
|
||
|
if (! $directory)
|
||
|
return false;
|
||
|
|
||
|
|
||
|
$backupFile = $directory . $this->getFileBase() . '.' . $converted_ext;
|
||
|
|
||
|
// Issue with PNG not being scaled on the main file.
|
||
|
if (! file_exists($backupFile) && $mainFile->isScaled())
|
||
|
{
|
||
|
$backupFile = $directory . $mainFile->getOriginalFile()->getFileBase() . '.' . $converted_ext;
|
||
|
}
|
||
|
if (file_exists($backupFile) && ! is_dir($backupFile) )
|
||
|
return true;
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function hasDBRecord()
|
||
|
{
|
||
|
global $wpdb;
|
||
|
|
||
|
$sql = 'SELECT id FROM ' . $wpdb->prefix . 'shortpixel_postmeta WHERE attach_id = %d AND size = %s';
|
||
|
$sql = $wpdb->prepare($sql, $this->id, $this->size);
|
||
|
|
||
|
$id = $wpdb->get_var($sql);
|
||
|
|
||
|
if (is_null($id))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
elseif (is_numeric($id)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public function restore()
|
||
|
{
|
||
|
if ($this->is_virtual())
|
||
|
{
|
||
|
$fs = \wpSPIO()->filesystem();
|
||
|
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||
|
|
||
|
$this->setVirtualToReal($filepath);
|
||
|
}
|
||
|
|
||
|
$bool = parent::restore();
|
||
|
|
||
|
if ($bool === true)
|
||
|
{
|
||
|
if ($this->is_main_file)
|
||
|
{
|
||
|
// If item is converted and will not be moved back to original format ( but converted ) , keep the convert metadata
|
||
|
if (true === $this->getMeta()->convertMeta()->isConverted() && false === $this->getMeta()->convertMeta()->omitBackup() )
|
||
|
{
|
||
|
$convertMeta = clone $this->getMeta()->convertMeta();
|
||
|
$imageMeta = new ImageMeta();
|
||
|
$imageMeta->convertMeta()->fromClass($convertMeta);
|
||
|
$bool = false; // Prevent cleanRestore from deleting the metadata.
|
||
|
}
|
||
|
else {
|
||
|
$imageMeta = new ImageMeta();
|
||
|
}
|
||
|
|
||
|
$this->image_meta = $imageMeta;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$this->image_meta = new ImageThumbNailMeta();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $bool;
|
||
|
}
|
||
|
|
||
|
/** Tries to retrieve an *existing* BackupFile. Returns false if not present.
|
||
|
* This file might not be writable.
|
||
|
* To get writable directory reference to backup, use FileSystemController
|
||
|
*/
|
||
|
public function getBackupFile($args = array())
|
||
|
{
|
||
|
|
||
|
$defaults = array(
|
||
|
'forceConverted' => false,
|
||
|
'noConversionCheck' => false, // do not check on mainfile, this loops when used in loadMeta / legacyConversion
|
||
|
);
|
||
|
$args = wp_parse_args($args, $defaults);
|
||
|
|
||
|
if (true === $args['noConversionCheck'])
|
||
|
{
|
||
|
return parent::getBackupFile();
|
||
|
}
|
||
|
|
||
|
$mainFile = ($this->is_main_file) ? $this : $this->getMainFile();
|
||
|
if (false == $mainFile)
|
||
|
{
|
||
|
return parent::getBackupFile();
|
||
|
|
||
|
}
|
||
|
// When main file and converted and omitBackup is true ( only original backup ) and not forced.
|
||
|
$loadRegular= (false === $mainFile->getMeta()->convertMeta()->isConverted() ||
|
||
|
false === $mainFile->getMeta()->convertMeta()->omitBackup()) && false === $args['forceConverted'];
|
||
|
|
||
|
if (true === $loadRegular )
|
||
|
{
|
||
|
return parent::getBackupFile();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ($this->hasBackup($args))
|
||
|
{
|
||
|
|
||
|
$directory = $this->getBackupDirectory();
|
||
|
$converted_ext = $mainFile->getMeta()->convertMeta()->getFileFormat();
|
||
|
|
||
|
$backupFile = $directory . $this->getFileBase() . '.' . $converted_ext;
|
||
|
|
||
|
/* Because WP doesn't support big PNG with scaled for some reason, it's possible it doesn't create them. Which means we end up with a scaled images without backup */
|
||
|
if (! file_exists($backupFile) && $mainFile->isScaled())
|
||
|
{
|
||
|
$backupFile = $directory . $mainFile->getOriginalFile()->getFileBase() . '.' . $converted_ext;
|
||
|
}
|
||
|
|
||
|
return new FileModel($backupFile);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected function createBackup()
|
||
|
{
|
||
|
if ($this->is_virtual()) // download remote file to backup.
|
||
|
{
|
||
|
$fs = \wpSPIO()->filesystem();
|
||
|
$filepath = apply_filters('shortpixel/file/virtual/translate', $this->getFullPath(), $this);
|
||
|
|
||
|
$result = false;
|
||
|
if ($this->virtual_status == self::$VIRTUAL_REMOTE)
|
||
|
{
|
||
|
// filepath is translated. Check if this exists as a local copy, if not remotely download.
|
||
|
if ($filepath !== $this->getFullPath())
|
||
|
{
|
||
|
$fileObj = $fs->getFile($filepath);
|
||
|
$fileExists = $fileObj->exists();
|
||
|
}
|
||
|
else {
|
||
|
$fileExists = false;
|
||
|
}
|
||
|
|
||
|
if (false === $fileExists)
|
||
|
{
|
||
|
$downloadHelper = DownloadHelper::getInstance();
|
||
|
$url = $this->getURL();
|
||
|
$result = $downloadHelper->downloadFile($url, array('destinationPath' => $filepath));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
elseif ($this->virtual_status == self::$VIRTUAL_STATELESS)
|
||
|
{
|
||
|
$result = $filepath;
|
||
|
}
|
||
|
else {
|
||
|
Log::addWarning('Virtual Status not set. Trying to blindly download vv DownloadHelper');
|
||
|
$downloadHelper = DownloadHelper::getInstance();
|
||
|
$url = $this->getURL();
|
||
|
$result = $downloadHelper->downloadFile($url, array('destinationPath' => $filepath));
|
||
|
}
|
||
|
|
||
|
if ($result == false)
|
||
|
{
|
||
|
$this->preventNextTry(__('Fatal Issue: Remote virtual file could not be downloaded for backup', 'shortpixel-image-optimiser'));
|
||
|
Log::addError('Remote file download failed from : ' . $url . ' to: ' . $filepath, $this->getURL());
|
||
|
$this->error_message = __('Remote file could not be downloaded' . $this->getFullPath(), 'shortpixel-image-optimiser');
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$this->setVirtualToReal($filepath);
|
||
|
}
|
||
|
|
||
|
return parent::createBackup();
|
||
|
}
|
||
|
|
||
|
// @todo This is a breach of pattern to realize checking for changes to the main image path on conversion / duplicates.
|
||
|
private function getMainFile()
|
||
|
{
|
||
|
$fs = \wpSPIO()->filesystem();
|
||
|
return $fs->getMediaImage($this->id, true, true);
|
||
|
}
|
||
|
|
||
|
} // class
|