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
|