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
 |