350 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace ShortPixel\Model;
 | |
| 
 | |
| use ShortPixel\Model\Image\ImageModel as ImageModel;
 | |
| 
 | |
| 
 | |
| if ( ! defined( 'ABSPATH' ) ) {
 | |
|  exit; // Exit if accessed directly.
 | |
| }
 | |
| 
 | |
| use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
 | |
| 
 | |
| 
 | |
| class FrontImage
 | |
| {
 | |
| 		protected $raw;
 | |
| 		protected $image_loaded = false;
 | |
| 		protected $is_parsable = false;
 | |
| 		protected $imageBase; // directory path of this image.
 | |
| 
 | |
| 		protected $id; // HTML ID of image
 | |
| 		protected $alt;
 | |
| 		protected $src;  // original src of image
 | |
| 		protected $srcset; // orginal srcset of image
 | |
| 		protected $class;
 | |
| 		protected $width;
 | |
| 		protected $height;
 | |
| 		protected $style;
 | |
| 		protected $sizes;
 | |
| 
 | |
| 		// Array of all other attributes.
 | |
| 		protected $attributes;
 | |
| 
 | |
| 		// Parsed items of src /srcset / sizes
 | |
| 		protected $dataTags = array();
 | |
| 
 | |
| 		public function __construct($raw_html)
 | |
| 		{
 | |
| 				$this->raw = $raw_html;
 | |
| 				$this->loadImageDom();
 | |
| 		}
 | |
| 
 | |
| 		public function loadImageDom()
 | |
|     {
 | |
|         if (function_exists("mb_convert_encoding")) {
 | |
|             $this->raw = mb_encode_numericentity($this->raw, [0x80, 0x10FFFF, 0, ~0], 'UTF-8');
 | |
|         }
 | |
| 
 | |
|         $dom = new \DOMDocument();
 | |
|         libxml_use_internal_errors(true); // disable error emit from libxml
 | |
| 
 | |
|         $result = $dom->loadHTML($this->raw, LIBXML_NOWARNING);
 | |
| 
 | |
|         // HTML failed loading
 | |
|         if (false === $result)
 | |
|         {
 | |
|            return false;
 | |
|         }
 | |
| 
 | |
|         $image = $dom->getElementsByTagName('img')->item(0);
 | |
|         $attributes = array();
 | |
| 
 | |
|         /* This can happen with mismatches, or extremely malformed HTML.
 | |
|         In customer case, a javascript that did  for (i<imgDefer) --- </script> */
 | |
|         if (! is_object($image))
 | |
| 				{
 | |
| 					$this->is_parsable = false;
 | |
|           return false;
 | |
| 				}
 | |
| 
 | |
|         foreach ($image->attributes as $attr) {
 | |
| 						// Skip is no value
 | |
| 					 if (strlen($attr->nodeValue) == 0)
 | |
| 					 	continue;
 | |
| 
 | |
| 					 if (property_exists($this, $attr->nodeName))
 | |
| 					 {
 | |
| 						  $this->{$attr->nodeName} = $attr->nodeValue;
 | |
| 					 }
 | |
| 
 | |
| 					 $this->attributes[$attr->nodeName] = $attr->nodeValue;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         // Parse the directory path and other sources
 | |
| 				$result = $this->setupSources();
 | |
| 
 | |
| 
 | |
| 				if (true === $result)
 | |
| 					$this->image_loaded = true;
 | |
|     }
 | |
| 
 | |
| 		public function hasBackground()
 | |
| 		{
 | |
| 				if (! is_null($this->style) && strpos($this->style, 'background') !== false)
 | |
| 				{
 | |
| 					 return true;
 | |
| 				}
 | |
| 				return false;
 | |
| 		}
 | |
| 
 | |
| 		public function hasPreventClasses()
 | |
| 		{
 | |
| 			// no class, no prevent.
 | |
| 			if (is_null($this->class))
 | |
| 			{
 | |
| 				 return false;
 | |
| 			}
 | |
| 
 | |
| 			$preventArray = apply_filters('shortpixel/front/preventclasses', array('sp-no-webp', 'rev-sildebg') );
 | |
| 
 | |
| 			foreach($preventArray as $classname)
 | |
| 			{
 | |
| 				if (false !== strpos($this->class, $classname) )
 | |
| 				{
 | |
| 					 return true;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		public function hasSource()
 | |
| 		{
 | |
| 			  if (is_null($this->src) && is_null($this->srcset))
 | |
| 				{
 | |
| 					 return false;
 | |
| 				}
 | |
| 				return true;
 | |
| 		}
 | |
| 
 | |
| 		public function isParseable()
 | |
| 		{
 | |
| 			 if (
 | |
| 				 false === $this->hasPreventClasses() &&
 | |
| 				 false === $this->hasBackground()  &&
 | |
| 				 true === $this->hasSource() &&
 | |
| 				 true === $this->image_loaded
 | |
| 				 )
 | |
| 			{
 | |
| 					return true;
 | |
| 			}
 | |
| 
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		public function getImageData()
 | |
| 		{
 | |
| 			 if (! is_null($this->srcset))
 | |
| 			 {
 | |
|  			 	 	$data = $this->getLazyData('srcset');
 | |
| 					$data = explode(',', $data); // srcset is multiple images, split.
 | |
| 			 }
 | |
| 			 else {
 | |
| 				 	$data = $this->getLazyData('src');
 | |
| 					$data = array($data);  // single item, wrap in array
 | |
| 			 }
 | |
| 
 | |
| 			 $this->getLazyData('sizes'); // sets the sizes.
 | |
| 
 | |
| 			 return $data;
 | |
| 		}
 | |
| 
 | |
| 		public function getImageBase()
 | |
| 		{
 | |
| 				 if (! is_null($this->imageBase))
 | |
| 			 		return $this->imageBase->getPath();
 | |
| 
 | |
|         return null;
 | |
| 		}
 | |
| 
 | |
| 		public function parseReplacement($args)
 | |
| 		{
 | |
| 				if (is_null($this->class))
 | |
| 				{
 | |
| 					 $this->class = '';
 | |
| 				}
 | |
| 
 | |
| 				$this->class .= ' sp-no-webp';
 | |
| 
 | |
| 				$output = "<picture>";
 | |
| 
 | |
| 				if (isset($args['avif']) && count($args['avif']) > 0)
 | |
| 				{
 | |
| 						$output .= $this->buildSource($args['avif'], 'avif');
 | |
| 				}
 | |
| 
 | |
| 				if (isset($args['webp']) && count($args['webp']) > 0)
 | |
| 				{
 | |
| 						$output .= $this->buildSource($args['webp'], 'webp');
 | |
| 				}
 | |
| 
 | |
| 				$output .= $this->buildImage();
 | |
| 
 | |
| 				$output .= "</picture>";
 | |
| 
 | |
| 				return $output;
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		protected function setupSources()
 | |
| 		{
 | |
| 			$src = null;
 | |
| 
 | |
| 			if (! is_null($this->src))
 | |
| 			{
 | |
| 				$src = $this->src;
 | |
| 			}
 | |
| 			elseif (! is_null($this->srcset))
 | |
| 			{
 | |
| 				$parts = preg_split('/\s+/', trim($this->srcset));
 | |
| 				$image_url = $parts[0];
 | |
| 				$src = $image_url;
 | |
| 			}
 | |
| 
 | |
| 			if (is_null($src))
 | |
| 			{
 | |
| 				 return false;
 | |
| 			}
 | |
| 
 | |
|       // Filter out extension that are not for us.
 | |
|       if (false === $this->checkExtensionConvertable($src))
 | |
|       {
 | |
|           return false;
 | |
|       }
 | |
| 
 | |
| 
 | |
| 
 | |
| 			$fs = \wpSPIO()->filesystem();
 | |
| 			$fileObj = $fs->getFile($src);
 | |
| 			$fileDir = $fileObj->getFileDir();
 | |
| 			$this->imageBase = $fileObj->getFileDir();
 | |
| 
 | |
| 			return true;
 | |
| 			// If (! is_hnull $srcset)
 | |
| 			// Get first item from srcset ( remove the size ? , then feed it to FS, get directory from it.
 | |
| 		}
 | |
| 
 | |
|     /*** Check if the extension is something we want to check
 | |
|     * @param String The URL source of the image.
 | |
|     **/
 | |
|     private function checkExtensionConvertable($source)
 | |
|     {
 | |
|        $extension = substr($source, strrpos($source, '.') + 1);
 | |
|        if (in_array($extension, ImageModel::PROCESSABLE_EXTENSIONS))
 | |
|        {
 | |
|           return true;
 | |
|        }
 | |
|        return false;
 | |
| 
 | |
|     }
 | |
| 
 | |
| 		protected function buildSource($sources, $fileFormat)
 | |
| 		{
 | |
| 
 | |
| 				$prefix = (isset($this->dataTags['srcset'])) ? $this->dataTags['srcset'] : $this->dataTags['src'];
 | |
| 				$srcset = implode(',', $sources);
 | |
| 
 | |
| 				$sizeOutput = '';
 | |
| 				if (! is_null($this->sizes))
 | |
| 				{
 | |
| 						$sizeOutput = $this->dataTags['sizes'] . 'sizes="' . $this->sizes . '"';
 | |
| 				}
 | |
| 
 | |
| 			  $output = '<source ' . $prefix . 'srcset="' . $srcset . '" ' . $sizeOutput . ' type="image/' . $fileFormat . '">';
 | |
| 
 | |
| 				return $output;
 | |
| 		}
 | |
| 
 | |
| 		protected function buildImage()
 | |
| 		{
 | |
| 			$src = $this->src;
 | |
| 			$output = '<img src="' . $src . '" ';
 | |
| 
 | |
| 			// Get this from set attributes on class.
 | |
| 			$attrs = array('id', 'height', 'width', 'srcset', 'sizes', 'class');
 | |
| 			foreach($attrs as $attr)
 | |
| 			{
 | |
| 				if (! is_null($this->{$attr}))
 | |
|         {
 | |
| 					$output .= $attr . '="' . $this->{$attr} . '" ';
 | |
|         }
 | |
| 			}
 | |
| 
 | |
|       // Always output alt tag, because it's important to screen readers and otherwise.
 | |
|       $output .= 'alt="' . $this->alt . '" ';
 | |
| 
 | |
| 			// Left over attributes that should be harmless, ie extra image data or other custom tags.
 | |
| 			$leftAttrs = $this->getImageAttributes();
 | |
| 			foreach($leftAttrs as $name => $value)
 | |
| 			{
 | |
| 	 				$output .= $name . '="' . $value . '" ';
 | |
| 			}
 | |
| 
 | |
| 			$output .= ' > '; // ending image.
 | |
| 
 | |
| 			return $output;
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		protected function getImageAttributes()
 | |
| 		{
 | |
| 
 | |
| 			$dontuse = array(
 | |
| 					'src', 'data-src', 'data-lazy-src', 'srcset', 'sizes'
 | |
| 
 | |
| 			);
 | |
| 			$dontuse = array_merge($dontuse, array('id', 'alt', 'height', 'width', 'srcset', 'sizes', 'class'));
 | |
| 
 | |
| 			$attributes = $this->attributes;
 | |
| 
 | |
| 			$leftAttrs = array();
 | |
| 			foreach($attributes as $name => $value)
 | |
| 			{
 | |
| 				 if (! in_array($name, $dontuse ))
 | |
| 				 {
 | |
| 					  $leftAttrs[$name] = $value;
 | |
| 				 }
 | |
| 			}
 | |
| 
 | |
| 			return $leftAttrs;
 | |
| 		}
 | |
| 
 | |
| 		protected function getLazyData($type)
 | |
| 		{
 | |
| 				$attributes = $this->attributes;
 | |
| 				$value = $prefix = false;
 | |
| 
 | |
| 				if (isset($attributes['data-lazy-' . $type]) && strlen($attributes['data-lazy-' . $type]) > 0)
 | |
| 				{
 | |
| 						$value = $attributes['data-lazy-' . $type];
 | |
| 						$prefix = 'data-lazy-';
 | |
| 				}
 | |
| 				elseif( isset($attributes['data-' . $type]) && strlen($attributes['data-' . $type]) > 0)
 | |
| 				{
 | |
| 					 $value = $attributes['data-' . $type];
 | |
| 					 $prefix = 'data-';
 | |
| 				}
 | |
| 				elseif(isset($attributes[$type]) && strlen($attributes[$type]) > 0)
 | |
| 				{
 | |
| 					 $value = $attributes[$type];
 | |
| 					 $prefix = '';
 | |
| 				}
 | |
| 
 | |
| 				$this->dataTags[$type] = $prefix;
 | |
| 
 | |
| 				return $value;
 | |
| 		}
 | |
| } // class FrontImage
 |